=== added file 'data/shaders/bump-height.frag'
--- data/shaders/bump-height.frag	1970-01-01 00:00:00 +0000
+++ data/shaders/bump-height.frag	2011-11-15 10:15:41 +0000
@@ -0,0 +1,56 @@
+#ifdef GL_ES
+precision mediump float;
+#endif
+
+uniform sampler2D HeightMap;
+
+varying vec2 TextureCoord;
+varying vec3 NormalEye;
+varying vec3 TangentEye;
+varying vec3 BitangentEye;
+
+void main(void)
+{
+    const vec4 LightSourceAmbient = vec4(0.1, 0.1, 0.1, 1.0);
+    const vec4 LightSourceDiffuse = vec4(0.8, 0.8, 0.8, 1.0);
+    const vec4 LightSourceSpecular = vec4(0.8, 0.8, 0.8, 1.0);
+    const vec4 MaterialAmbient = vec4(1.0, 1.0, 1.0, 1.0);
+    const vec4 MaterialDiffuse = vec4(1.0, 1.0, 1.0, 1.0);
+    const vec4 MaterialSpecular = vec4(0.2, 0.2, 0.2, 1.0);
+    const float MaterialShininess = 100.0;
+    const float height_factor = 13.0;
+
+    // Get the data from the height map
+    float height0 = texture2D(HeightMap, TextureCoord).x;
+    float heightX = texture2D(HeightMap, TextureCoord + vec2(TextureStepX, 0.0)).x;
+    float heightY = texture2D(HeightMap, TextureCoord + vec2(0.0, TextureStepY)).x;
+    vec2 dh = vec2(heightX - height0, heightY - height0);
+
+    // Adjust the normal based on the height map data
+    vec3 N = NormalEye - height_factor * dh.x * TangentEye -
+                         height_factor * dh.y * BitangentEye;
+    N = normalize(N);
+
+    // In the lighting model we are using here (Blinn-Phong with light at
+    // infinity, viewer at infinity), the light position/direction and the
+    // half vector is constant for the all the fragments.
+    vec3 L = normalize(LightSourcePosition.xyz);
+    vec3 H = normalize(LightSourceHalfVector);
+
+    // Calculate the diffuse color according to Lambertian reflectance
+    vec4 diffuse = MaterialDiffuse * LightSourceDiffuse * max(dot(N, L), 0.0);
+
+    // Calculate the ambient color
+    vec4 ambient = MaterialAmbient * LightSourceAmbient;
+
+    // Calculate the specular color according to the Blinn-Phong model
+    vec4 specular = MaterialSpecular * LightSourceSpecular *
+                    pow(max(dot(N,H), 0.0), MaterialShininess);
+
+    // Calculate the final color
+    gl_FragColor = ambient + specular + diffuse;
+
+    //gl_FragColor = vec4(height_diff_raw.xy, 0.0, 1.0);
+    //gl_FragColor = vec4(height_diff_scaled.xy, 0.0, 1.0);
+    //gl_FragColor = vec4(Tangent, 1.0);
+}

=== added file 'data/shaders/bump-height.vert'
--- data/shaders/bump-height.vert	1970-01-01 00:00:00 +0000
+++ data/shaders/bump-height.vert	2011-11-14 11:36:26 +0000
@@ -0,0 +1,28 @@
+attribute vec3 position;
+attribute vec2 texcoord;
+attribute vec3 normal;
+attribute vec3 tangent;
+
+uniform mat4 ModelViewProjectionMatrix;
+uniform mat4 NormalMatrix;
+
+varying vec2 TextureCoord;
+varying vec3 NormalEye;
+varying vec3 TangentEye;
+varying vec3 BitangentEye;
+
+void main(void)
+{
+    TextureCoord = texcoord;
+
+    // Transform normal, tangent and bitangent to eye space, keeping
+    // all of them perpendicular to the Normal. That is why we use
+    // NormalMatrix, instead of ModelView, to transform the tangent and
+    // bitangent.
+    NormalEye = normalize(vec3(NormalMatrix * vec4(normal, 1.0)));
+    TangentEye = normalize(vec3(NormalMatrix * vec4(tangent, 1.0)));
+    BitangentEye = normalize(vec3(NormalMatrix * vec4(cross(normal, tangent), 1.0)));
+
+    // Transform the position to clip coordinates
+    gl_Position = ModelViewProjectionMatrix * vec4(position, 1.0);
+}

=== added file 'data/shaders/bump-normals-tangent.frag'
--- data/shaders/bump-normals-tangent.frag	1970-01-01 00:00:00 +0000
+++ data/shaders/bump-normals-tangent.frag	2011-11-11 14:40:29 +0000
@@ -0,0 +1,49 @@
+#ifdef GL_ES
+precision mediump float;
+#endif
+
+uniform sampler2D NormalMap;
+
+varying vec2 TextureCoord;
+varying mat4 TangentToEyeMatrix;
+
+void main(void)
+{
+    const vec4 LightSourceAmbient = vec4(0.1, 0.1, 0.1, 1.0);
+    const vec4 LightSourceDiffuse = vec4(0.8, 0.8, 0.8, 1.0);
+    const vec4 LightSourceSpecular = vec4(0.8, 0.8, 0.8, 1.0);
+    const vec4 MaterialAmbient = vec4(1.0, 1.0, 1.0, 1.0);
+    const vec4 MaterialDiffuse = vec4(1.0, 1.0, 1.0, 1.0);
+    const vec4 MaterialSpecular = vec4(0.2, 0.2, 0.2, 1.0);
+    const float MaterialShininess = 100.0;
+
+    // Get the raw normal XYZ data from the normal map
+    vec3 normal_raw = texture2D(NormalMap, TextureCoord).xyz;
+
+    // Map "color" range [0, 1.0] to normal range [-1.0, 1.0]
+    vec3 normal_scaled = normal_raw * 2.0 - 1.0;
+
+    // The normal data is in tangent space, convert it to eye space so that
+    // lighting calculations can work (light information is in eye space).
+    vec3 N = normalize(vec3(TangentToEyeMatrix * vec4(normal_scaled, 1.0)));
+
+    // In the lighting model we are using here (Blinn-Phong with light at
+    // infinity, viewer at infinity), the light position/direction and the
+    // half vector is constant for the all the fragments.
+    vec3 L = normalize(LightSourcePosition.xyz);
+    vec3 H = normalize(LightSourceHalfVector);
+
+    // Calculate the diffuse color according to Lambertian reflectance
+    vec4 diffuse = MaterialDiffuse * LightSourceDiffuse * max(dot(N, L), 0.0);
+
+    // Calculate the ambient color
+    vec4 ambient = MaterialAmbient * LightSourceAmbient;
+
+    // Calculate the specular color according to the Blinn-Phong model
+    vec4 specular = MaterialSpecular * LightSourceSpecular *
+                    pow(max(dot(N,H), 0.0), MaterialShininess);
+
+    // Calculate the final color
+    gl_FragColor = ambient + specular + diffuse;
+    //gl_FragColor = vec4(N, 1.0);
+}

=== added file 'data/shaders/bump-normals-tangent.vert'
--- data/shaders/bump-normals-tangent.vert	1970-01-01 00:00:00 +0000
+++ data/shaders/bump-normals-tangent.vert	2011-11-11 14:40:29 +0000
@@ -0,0 +1,39 @@
+attribute vec3 position;
+attribute vec2 texcoord;
+attribute vec3 normal;
+attribute vec3 tangent;
+
+uniform mat4 ModelViewProjectionMatrix;
+uniform mat4 NormalMatrix;
+
+varying vec2 TextureCoord;
+varying mat4 TangentToEyeMatrix;
+
+void main(void)
+{
+    TextureCoord = texcoord;
+
+    // Calculate bitangent
+    vec3 bitangent = normalize(cross(normal, tangent));
+
+    // Calculate tangent space to eye space transformation matrix.
+    // We need this matrix in the fragment shader to transform the data
+    // from the normal map, which is in tangent space, to eye space,
+    // so that we can perform meaningful lighting calculations. Alternatively,
+    // we could have used an EyeToTangentMatrix, to convert light direction etc
+    // to tangent space.
+
+    // First calculate a tangent space to object space transformation matrix.
+    mat4 TangentToObject = mat4(tangent.x, tangent.y, tangent.z, 0.0,
+                                bitangent.x, bitangent.y, bitangent.z, 0.0,
+                                normal.x, normal.y, normal.z, 0.0,
+                                0.0, 0.0, 0.0, 1.0);
+
+    // Multiply with the NormalMatrix to further transform from object
+    // space to eye space (we are manipulating normals, so we can't use
+    // the ModelView matrix for this step!).
+    TangentToEyeMatrix = NormalMatrix * TangentToObject;
+
+    // Transform the position to clip coordinates
+    gl_Position = ModelViewProjectionMatrix * vec4(position, 1.0);
+}

=== added file 'data/textures/asteroid-height-map.png'
Binary files data/textures/asteroid-height-map.png	1970-01-01 00:00:00 +0000 and data/textures/asteroid-height-map.png	2011-11-14 18:01:44 +0000 differ
=== added file 'data/textures/asteroid-normal-map-tangent.png'
Binary files data/textures/asteroid-normal-map-tangent.png	1970-01-01 00:00:00 +0000 and data/textures/asteroid-normal-map-tangent.png	2011-11-11 14:40:29 +0000 differ
=== modified file 'src/default-benchmarks.h'
--- src/default-benchmarks.h	2011-11-08 20:20:02 +0000
+++ src/default-benchmarks.h	2011-11-14 12:39:08 +0000
@@ -51,6 +51,7 @@
         benchmarks.push_back("shading:shading=phong");
         benchmarks.push_back("bump:bump-render=high-poly");
         benchmarks.push_back("bump:bump-render=normals");
+        benchmarks.push_back("bump:bump-render=height");
         benchmarks.push_back("effect2d:kernel=0,1,0;1,-4,1;0,1,0;");
         benchmarks.push_back("effect2d:kernel=1,1,1,1,1;1,1,1,1,1;1,1,1,1,1;");
         benchmarks.push_back("pulsar:quads=5:texture=false:light=false");

=== modified file 'src/model.cpp'
--- src/model.cpp	2011-11-09 10:41:52 +0000
+++ src/model.cpp	2011-11-11 14:38:08 +0000
@@ -105,7 +105,8 @@
  */
 void
 Model::append_object_to_mesh(const Object &object, Mesh &mesh,
-                             int p_pos, int n_pos, int t_pos)
+                             int p_pos, int n_pos, int t_pos,
+                             int nt_pos, int nb_pos)
 {
     size_t face_count = object.faces.size();
 
@@ -123,6 +124,10 @@
             mesh.set_attrib(n_pos, a.n);
         if (t_pos >= 0)
             mesh.set_attrib(t_pos, a.t);
+        if (nt_pos >= 0)
+            mesh.set_attrib(nt_pos, a.nt);
+        if (nb_pos >= 0)
+            mesh.set_attrib(nb_pos, a.nb);
 
         mesh.next_vertex();
         if (p_pos >= 0)
@@ -131,6 +136,10 @@
             mesh.set_attrib(n_pos, b.n);
         if (t_pos >= 0)
             mesh.set_attrib(t_pos, b.t);
+        if (nt_pos >= 0)
+            mesh.set_attrib(nt_pos, b.nt);
+        if (nb_pos >= 0)
+            mesh.set_attrib(nb_pos, b.nb);
 
         mesh.next_vertex();
         if (p_pos >= 0)
@@ -139,8 +148,11 @@
             mesh.set_attrib(n_pos, c.n);
         if (t_pos >= 0)
             mesh.set_attrib(t_pos, c.t);
+        if (nt_pos >= 0)
+            mesh.set_attrib(nt_pos, c.nt);
+        if (nb_pos >= 0)
+            mesh.set_attrib(nb_pos, c.nb);
     }
-
 }
 
 /** 
@@ -178,6 +190,8 @@
     int p_pos = -1;
     int n_pos = -1;
     int t_pos = -1;
+    int nt_pos = -1;
+    int nb_pos = -1;
 
     mesh.reset();
 
@@ -192,6 +206,10 @@
             n_pos = ai - attribs.begin();
         else if (ai->first == AttribTypeTexcoord)
             t_pos = ai - attribs.begin();
+        else if (ai->first == AttribTypeTangent)
+            nt_pos = ai - attribs.begin();
+        else if (ai->first == AttribTypeBitangent)
+            nb_pos = ai - attribs.begin();
     }
 
     mesh.set_vertex_format(format);
@@ -200,7 +218,7 @@
          iter != objects_.end();
          iter++)
     {
-        append_object_to_mesh(*iter, mesh, p_pos, n_pos, t_pos);
+        append_object_to_mesh(*iter, mesh, p_pos, n_pos, t_pos, nt_pos, nb_pos);
     }
 }
 
@@ -217,25 +235,62 @@
          iter++)
     {
         Object &object = *iter;
-        size_t face_count = object.faces.size();
-        size_t vertex_count = object.vertices.size();
 
-        for(unsigned i = 0; i < face_count; i++)
+        for (vector<Face>::const_iterator f_iter = object.faces.begin();
+             f_iter != object.faces.end();
+             f_iter++)
         {
-            const Face &face = object.faces[i];
+            const Face &face = *f_iter;
             Vertex &a = object.vertices[face.a];
             Vertex &b = object.vertices[face.b];
             Vertex &c = object.vertices[face.c];
 
+            /* Calculate normal */
             n = LibMatrix::vec3::cross(b.v - a.v, c.v - a.v);
             n.normalize();
             a.n += n;
             b.n += n;
             c.n += n;
-        }
-
-        for(unsigned i = 0; i < vertex_count; i++)
-            object.vertices[i].n.normalize();
+
+            LibMatrix::vec3 q1(b.v - a.v);
+            LibMatrix::vec3 q2(c.v - a.v);
+            LibMatrix::vec2 u1(b.t - a.t);
+            LibMatrix::vec2 u2(c.t - a.t);
+            float det = (u1.x() * u2.y() - u2.x() * u1.y());
+
+            /* Calculate tangent */
+            LibMatrix::vec3 nt;
+            nt.x(det * (u2.y() * q1.x() - u1.y() * q2.x()));
+            nt.y(det * (u2.y() * q1.y() - u1.y() * q2.y()));
+            nt.z(det * (u2.y() * q1.z() - u1.y() * q2.z()));
+            nt.normalize();
+            a.nt += nt;
+            b.nt += nt;
+            c.nt += nt;
+
+            /* Calculate bitangent */
+            LibMatrix::vec3 nb;
+            nb.x(det * (u1.x() * q2.x() - u2.x() * q1.x()));
+            nb.y(det * (u1.x() * q2.y() - u2.x() * q1.y()));
+            nb.z(det * (u1.x() * q2.z() - u2.x() * q1.z()));
+            nb.normalize();
+            a.nb += nb;
+            b.nb += nb;
+            c.nb += nb;
+        }
+
+        for (vector<Vertex>::iterator v_iter = object.vertices.begin();
+             v_iter != object.vertices.end();
+             v_iter++)
+        {
+            Vertex &v = *v_iter;
+            /* Orthogonalize */
+            v.nt = (v.nt - v.n * LibMatrix::vec3::dot(v.nt, v.n));
+            v.n.normalize();
+            v.nt.normalize();
+            v.nb.normalize();
+        }
+
     }
 }
 

=== modified file 'src/model.h'
--- src/model.h	2011-11-09 10:34:35 +0000
+++ src/model.h	2011-11-11 14:38:08 +0000
@@ -70,9 +70,11 @@
 
     typedef enum {
         AttribTypePosition = 1,
-        AttribTypeNormal = 2,
-        AttribTypeTexcoord = 4,
-        AttribTypeCustom = 8
+        AttribTypeNormal,
+        AttribTypeTexcoord,
+        AttribTypeTangent,
+        AttribTypeBitangent,
+        AttribTypeCustom
     } AttribType;
 
     Model() {}
@@ -97,6 +99,8 @@
         LibMatrix::vec3 v;
         LibMatrix::vec3 n;
         LibMatrix::vec2 t;
+        LibMatrix::vec3 nt;
+        LibMatrix::vec3 nb;
     };
 
     struct Object {
@@ -107,7 +111,8 @@
     };
 
     void append_object_to_mesh(const Object &object, Mesh &mesh,
-                               int p_pos, int n_pos, int t_pos);
+                               int p_pos, int n_pos, int t_pos,
+                               int nt_pos, int nb_pos);
     bool load_3ds(const std::string &filename);
     bool load_obj(const std::string &filename);
 

=== modified file 'src/scene-bump.cpp'
--- src/scene-bump.cpp	2011-11-11 11:07:15 +0000
+++ src/scene-bump.cpp	2011-11-15 10:15:41 +0000
@@ -35,7 +35,7 @@
     texture_(0), rotation_(0.0f), rotationSpeed_(0.0f)
 {
     options_["bump-render"] = Scene::Option("bump-render", "off",
-                                            "How to render bumps [off, normals, high-poly]");
+                                            "How to render bumps [off, normals, normals-tangent, height, high-poly]");
 }
 
 SceneBump::~SceneBump()
@@ -159,6 +159,112 @@
 }
 
 void
+SceneBump::setup_model_normals_tangent()
+{
+    static const std::string vtx_shader_filename(GLMARK_DATA_PATH"/shaders/bump-normals-tangent.vert");
+    static const std::string frg_shader_filename(GLMARK_DATA_PATH"/shaders/bump-normals-tangent.frag");
+    static const LibMatrix::vec4 lightPosition(20.0f, 20.0f, 10.0f, 1.0f);
+    Model model;
+
+    if(!model.load("asteroid-low"))
+        return;
+
+    model.calculate_normals();
+
+    /* Calculate the half vector */
+    LibMatrix::vec3 halfVector(lightPosition.x(), lightPosition.y(), lightPosition.z());
+    halfVector.normalize();
+    halfVector += LibMatrix::vec3(0.0, 0.0, 1.0);
+    halfVector.normalize();
+
+    std::vector<std::pair<Model::AttribType, int> > attribs;
+    attribs.push_back(std::pair<Model::AttribType, int>(Model::AttribTypePosition, 3));
+    attribs.push_back(std::pair<Model::AttribType, int>(Model::AttribTypeNormal, 3));
+    attribs.push_back(std::pair<Model::AttribType, int>(Model::AttribTypeTexcoord, 2));
+    attribs.push_back(std::pair<Model::AttribType, int>(Model::AttribTypeTangent, 3));
+
+    model.convert_to_mesh(mesh_, attribs);
+
+    /* Load shaders */
+    ShaderSource vtx_source(vtx_shader_filename);
+    ShaderSource frg_source(frg_shader_filename);
+
+    /* Add constants to shaders */
+    frg_source.add_const("LightSourcePosition", lightPosition);
+    frg_source.add_const("LightSourceHalfVector", halfVector);
+
+    if (!Scene::load_shaders_from_strings(program_, vtx_source.str(),
+                                          frg_source.str()))
+    {
+        return;
+    }
+
+    std::vector<GLint> attrib_locations;
+    attrib_locations.push_back(program_["position"].location());
+    attrib_locations.push_back(program_["normal"].location());
+    attrib_locations.push_back(program_["texcoord"].location());
+    attrib_locations.push_back(program_["tangent"].location());
+    mesh_.set_attrib_locations(attrib_locations);
+
+    Texture::load(GLMARK_DATA_PATH"/textures/asteroid-normal-map-tangent.png", &texture_,
+                  GL_NEAREST, GL_NEAREST, 0);
+}
+
+void
+SceneBump::setup_model_height()
+{
+    static const std::string vtx_shader_filename(GLMARK_DATA_PATH"/shaders/bump-height.vert");
+    static const std::string frg_shader_filename(GLMARK_DATA_PATH"/shaders/bump-height.frag");
+    static const LibMatrix::vec4 lightPosition(20.0f, 20.0f, 10.0f, 1.0f);
+    Model model;
+
+    if(!model.load("asteroid-low"))
+        return;
+
+    model.calculate_normals();
+
+    /* Calculate the half vector */
+    LibMatrix::vec3 halfVector(lightPosition.x(), lightPosition.y(), lightPosition.z());
+    halfVector.normalize();
+    halfVector += LibMatrix::vec3(0.0, 0.0, 1.0);
+    halfVector.normalize();
+
+    std::vector<std::pair<Model::AttribType, int> > attribs;
+    attribs.push_back(std::pair<Model::AttribType, int>(Model::AttribTypePosition, 3));
+    attribs.push_back(std::pair<Model::AttribType, int>(Model::AttribTypeNormal, 3));
+    attribs.push_back(std::pair<Model::AttribType, int>(Model::AttribTypeTexcoord, 2));
+    attribs.push_back(std::pair<Model::AttribType, int>(Model::AttribTypeTangent, 3));
+
+    model.convert_to_mesh(mesh_, attribs);
+
+    /* Load shaders */
+    ShaderSource vtx_source(vtx_shader_filename);
+    ShaderSource frg_source(frg_shader_filename);
+
+    /* Add constants to shaders */
+    frg_source.add_const("LightSourcePosition", lightPosition);
+    frg_source.add_const("LightSourceHalfVector", halfVector);
+    frg_source.add_const("TextureStepX", 1.0 / 1024.0);
+    frg_source.add_const("TextureStepY", 1.0 / 1024.0);
+
+    if (!Scene::load_shaders_from_strings(program_, vtx_source.str(),
+                                          frg_source.str()))
+    {
+        return;
+    }
+
+    std::vector<GLint> attrib_locations;
+    attrib_locations.push_back(program_["position"].location());
+    attrib_locations.push_back(program_["normal"].location());
+    attrib_locations.push_back(program_["texcoord"].location());
+    attrib_locations.push_back(program_["tangent"].location());
+    mesh_.set_attrib_locations(attrib_locations);
+
+    Texture::load(GLMARK_DATA_PATH"/textures/asteroid-height-map.png", &texture_,
+                  GL_NEAREST, GL_NEAREST, 0);
+}
+
+void
 SceneBump::setup()
 {
     Scene::setup();
@@ -168,6 +274,10 @@
     Model::find_models();
     if (bump_render == "normals")
         setup_model_normals();
+    else if (bump_render == "normals-tangent")
+        setup_model_normals_tangent();
+    else if (bump_render == "height")
+        setup_model_height();
     else if (bump_render == "off" || bump_render == "high-poly")
         setup_model_plain(bump_render);
 
@@ -178,6 +288,7 @@
 
     // Load texture sampler value
     program_["NormalMap"] = 0;
+    program_["HeightMap"] = 0;
 
     currentFrame_ = 0;
     rotation_ = 0.0;
@@ -239,6 +350,9 @@
     normal_matrix.inverse().transpose();
     program_["NormalMatrix"] = normal_matrix;
 
+    glActiveTexture(GL_TEXTURE0);
+    glBindTexture(GL_TEXTURE_2D, texture_);
+
     mesh_.render_vbo();
 }
 
@@ -263,6 +377,10 @@
         ref = Canvas::Pixel(0x9c, 0x9c, 0x9c, 0xff);
     else if (bump_render == "normals")
         ref = Canvas::Pixel(0xa4, 0xa4, 0xa4, 0xff);
+    else if (bump_render == "normals-tangent")
+        ref = Canvas::Pixel(0x99, 0x99, 0x99, 0xff);
+    else if (bump_render == "height")
+        ref = Canvas::Pixel(0x9d, 0x9d, 0x9d, 0xff);
     else
         return Scene::ValidationUnknown;
 

=== modified file 'src/scene.h'
--- src/scene.h	2011-11-11 11:07:15 +0000
+++ src/scene.h	2011-11-14 11:36:26 +0000
@@ -380,6 +380,8 @@
 private:
     void setup_model_plain(const std::string &type);
     void setup_model_normals();
+    void setup_model_normals_tangent();
+    void setup_model_height();
 };
 
 class SceneEffect2D : public Scene

