From 365db63ddca23505425b6728bf88b4488e9a8bc7 Mon Sep 17 00:00:00 2001
From: Paul McCarthy <pauld.mccarthy@gmail.com>
Date: Tue, 15 Dec 2015 13:54:03 +0000
Subject: [PATCH] Tensor lighting implemented, but not working yet. Can't
 figure out what I'm doing wrong.

---
 fsl/fsleyes/gl/gl21/gltensor_funcs.py  | 31 ++++++++++-----
 fsl/fsleyes/gl/gl21/gltensor_vert.glsl | 54 ++++++++++++++++++++------
 fsl/fsleyes/gl/routines.py             | 49 +++++++++++------------
 3 files changed, 86 insertions(+), 48 deletions(-)

diff --git a/fsl/fsleyes/gl/gl21/gltensor_funcs.py b/fsl/fsleyes/gl/gl21/gltensor_funcs.py
index dbe57a39d..4cc8667f4 100644
--- a/fsl/fsleyes/gl/gl21/gltensor_funcs.py
+++ b/fsl/fsleyes/gl/gl21/gltensor_funcs.py
@@ -104,7 +104,7 @@ def compileShaders(self):
                     'v1ValXform',      'v2ValXform', 'v3ValXform',
                     'l1ValXform',      'l2ValXform', 'l3ValXform',
                     'voxToDisplayMat', 'imageShape', 'resolution',
-                    'lighting']
+                    'lighting',        'lightDir']
 
     vertAtts     = ['voxel', 'vertex']
 
@@ -220,11 +220,21 @@ def preDraw(self):
     """
     gl.glUseProgram(self.shaders)
 
-    if self.displayOpts.lighting:
-        gl.glEnable(gl.GL_LIGHTING)
-        gl.glEnable(gl.GL_LIGHT0)
-    
-        # gl.glLight(gl.GL_LIGHT0, gl.GL_)
+    # Define the light direction in
+    # the world coordinate system
+    lightDir = np.array([1, 1, 1], dtype=np.float32)
+    lightDir[self.zax] = 3
+
+    # Transform it into the display
+    # coordinate system
+    mvMat     = gl.glGetFloatv(gl.GL_MODELVIEW_MATRIX)
+    lightDir  = np.dot(mvMat[:3, :3], lightDir)
+
+    # Normalise to unit length
+    ldLen     = np.sqrt(np.sum(lightDir ** 2))
+    lightDir /= ldLen
+
+    gl.glUniform3fv(self.lightDirPos, 1, lightDir)
     
     self.v1Texture.bindTexture(gl.GL_TEXTURE5)
     self.v2Texture.bindTexture(gl.GL_TEXTURE6)
@@ -236,6 +246,9 @@ def preDraw(self):
     gl.glEnableVertexAttribArray(self.voxelPos)
     gl.glEnableVertexAttribArray(self.vertexPos)
 
+    gl.glEnable(gl.GL_CULL_FACE)
+    gl.glCullFace(gl.GL_BACK)
+    
 
 def draw(self, zpos, xform=None):
 
@@ -296,10 +309,8 @@ def draw(self, zpos, xform=None):
 def postDraw(self):
     gl.glUseProgram(0)
 
-    if self.displayOpts.lighting: 
-        gl.glDisable(gl.GL_LIGHT0)
-        gl.glDisable(gl.GL_LIGHTING)
-        
+    gl.glDisable(gl.GL_CULL_FACE)
+    
     gl.glBindBuffer(gl.GL_ARRAY_BUFFER,         0)
     gl.glBindBuffer(gl.GL_ELEMENT_ARRAY_BUFFER, 0)
     
diff --git a/fsl/fsleyes/gl/gl21/gltensor_vert.glsl b/fsl/fsleyes/gl/gl21/gltensor_vert.glsl
index 258b2f55d..3a7994be5 100644
--- a/fsl/fsleyes/gl/gl21/gltensor_vert.glsl
+++ b/fsl/fsleyes/gl/gl21/gltensor_vert.glsl
@@ -27,6 +27,8 @@ uniform float resolution;
 
 uniform bool lighting;
 
+uniform vec3 lightDir;
+
 attribute vec3  voxel;
 attribute vec3  vertex;
 
@@ -63,36 +65,66 @@ void main(void) {
   // shift it by the voxel coordinates
   // to transform it in to the image
   // voxel coordinate system.
-  vec3 pos  = vertex;
+  //
+  // See these web pages for details
+  // on drawing ellipsoids:
+  //
+  //   - http://archive.gamedev.net/archive/reference/  \
+  //     programming/features/superquadric/index.html
+  //
+  //   - paulbourke.net/geometry/circlesphere/
+  vec3 pos     = vertex;
+  mat3 eigvecs = mat3(v1, v2, v3);
 
   // TODO scale by the range of l1/l2/l3
   pos.x *= l1 * 150;
   pos.y *= l2 * 150;
   pos.z *= l3 * 150;
-  
-  pos  = mat3(v1, v2, v3) * pos;
-  pos += voxel;
+  pos    = eigvecs * pos;
+  pos   += voxel;
 
   if (lighting) {
-    
+
+    // Calculate the normal
+    // vector for this vertex.
     vec3 norm = vertex;
-    norm.x   /= l2;
+    norm.x   /= l1;
     norm.y   /= l2;
     norm.z   /= l3;
-    norm      = mat3(v1, v2, v3) * pos;
-    
-    light     = vec3(1, 1, 1);
+
+    // The matrix made up of the eigenvectors
+    // should be orthonormal, so can be used
+    // to transform the vertex surface normals.
+    //
+    // [ orthonormal -> eigvecs = T(I(eigvecs)) ]
+    norm      = eigvecs * norm;
+
+    // GLSL 1.20 calculates the normal
+    // matrix for us; we will have to
+    // calculate it ourselves if we move
+    // to a more modern version of GLSL.
+    norm     = gl_Normal * norm;
+
+    norm = normalize(norm);
+
+    // Calculate the diffuse and
+    // ambient light components
+    float ambient = 0.1;
+    float diffuse = clamp(dot(norm, -lightDir), 0, 1);
+
+    diffuse = (diffuse + ambient);
+    light   = vec3(diffuse, diffuse, diffuse);
   }
   
   else
-    light     = vec3(1, 1, 1);
+    light = vec3(1, 1, 1);
 
   // Transform from voxels into display
   gl_Position = gl_ModelViewProjectionMatrix *
                 voxToDisplayMat              *
                 vec4(pos, 1);
 
-  fragVoxCoord     = voxel;
+  fragVoxCoord     = floor(voxel + 0.5);
   fragTexCoord     = texCoord;
   fragColourFactor = vec4(light, 1);
 }
diff --git a/fsl/fsleyes/gl/routines.py b/fsl/fsleyes/gl/routines.py
index 2ec6374e9..72ed37a4b 100644
--- a/fsl/fsleyes/gl/routines.py
+++ b/fsl/fsleyes/gl/routines.py
@@ -83,9 +83,6 @@ def show2D(xax, yax, width, height, lo, hi):
     elif zax == 1:
         gl.glRotatef(270, 1, 0, 0)
 
-    gl.glDepthMask(gl.GL_FALSE)
-    gl.glDisable(gl.GL_DEPTH_TEST)
-
 
 def calculateSamplePoints(shape, resolution, xform, xax, yax, origin='centre'):
     """Calculates a uniform grid of points, in the display coordinate system
@@ -650,22 +647,23 @@ def unitSphere(res):
 
     :returns: A tuple comprising:
     
-              - a ``numpy.float32`` array of size ``((res + 1)**2, 3)``
+              - a ``numpy.float32`` array of size ``(res**2, 3)``
                 containing a set of ``(x, y, z)`` vertices which define
                 the ellipsoid surface.
      
-              - A ``numpy.uint32`` array of size ``(4 * res * res)``
+              - A ``numpy.uint32`` array of size ``(4 * (res - 1)**2)``
                 containing a list of indices into the vertex array,
                 defining a vertex ordering that can be used to draw
                 the ellipsoid using the OpenGL ``GL_QUAD`` primitive type.
-    """
 
-    ustep = np.pi       / res
-    vstep = np.pi * 2.0 / res
+
+    .. todo:: Generate indices to use with ``GL_TRIANGLES`` instead of
+              ``GL_QUADS``.
+    """
 
     # All angles to be sampled
-    u = np.linspace(-np.pi / 2, np.pi / 2 + ustep, res + 1, dtype=np.float32)
-    v = np.linspace(-np.pi,     np.pi     + vstep, res + 1, dtype=np.float32)
+    u = np.linspace(-np.pi / 2, np.pi / 2, res, dtype=np.float32)
+    v = np.linspace(-np.pi,     np.pi,     res, dtype=np.float32)
 
     cosu = np.cos(u)
     cosv = np.cos(v)
@@ -675,14 +673,14 @@ def unitSphere(res):
     cucv = np.outer(cosu, cosv)
     cusv = np.outer(cosu, sinv)
 
-    vertices = np.zeros(((res + 1) ** 2, 3), dtype=np.float32)
+    vertices = np.zeros((res ** 2, 3), dtype=np.float32)
 
     # All x coordinates are of the form cos(u) * cos(v),
     # y coordinates are of the form cos(u) * sin(v),
     # and z coordinates of the form sin(u).
     vertices[:, 0] = cucv.flatten()
     vertices[:, 1] = cusv.flatten()
-    vertices[:, 2] = sinu.repeat(res + 1)
+    vertices[:, 2] = sinu.repeat(res)
 
     # Generate a list of indices which join the
     # vertices so they can be used to draw the
@@ -695,12 +693,12 @@ def unitSphere(res):
     #  2. (u + ustep, v)
     #  3. (u + ustep, v + vstep)
     #  4. (u,         v + vstep)
-    nquads   = res * res
-    quadIdxs = np.array([0, res + 1, res + 2, 1], dtype=np.uint32)
+    nquads   = (res - 1) ** 2
+    quadIdxs = np.array([0, res, res + 1, 1], dtype=np.uint32)
 
     indices  = np.tile(quadIdxs, nquads)
-    indices += np.arange(nquads, dtype=np.uint32).repeat(4)
-    indices += np.arange(res,    dtype=np.uint32).repeat(4 * res)
+    indices += np.arange(nquads,  dtype=np.uint32).repeat(4)
+    indices += np.arange(res - 1, dtype=np.uint32).repeat(4 * (res - 1))
     
     return vertices, indices
 
@@ -711,23 +709,20 @@ def fullUnitSphere(res):
 
     :arg res: Resolution - the number of angles to sample.
 
-    :returns: A ``numpy.float32`` array of size ``(4 * res**2, 3)`` containing
-              the ``(x, y, z)`` vertices which can be used to draw a unit
-              sphere (using the ``GL_QUAD`` primitive type).
+    :returns: A ``numpy.float32`` array of size ``(4 * (res - 1)**2, 3)``
+              containing the ``(x, y, z)`` vertices which can be used to draw
+              a unit sphere (using the ``GL_QUADS`` primitive type).
     """
 
-    ustep = np.pi       / res
-    vstep = np.pi * 2.0 / res
-
-    u = np.linspace(-np.pi / 2, np.pi / 2 + ustep, res + 1, dtype=np.float32)
-    v = np.linspace(-np.pi,     np.pi     + vstep, res + 1, dtype=np.float32)
+    u = np.linspace(-np.pi / 2, np.pi / 2, res, dtype=np.float32)
+    v = np.linspace(-np.pi,     np.pi,     res, dtype=np.float32)
 
     cosu = np.cos(u)
     cosv = np.cos(v)
     sinu = np.sin(u)
     sinv = np.sin(v) 
 
-    vertices = np.zeros((res * res * 4, 3), dtype=np.float32)
+    vertices = np.zeros(((res - 1) * (res - 1) * 4, 3), dtype=np.float32)
 
     cucv   = np.outer(cosu[:-1], cosv[:-1]).flatten()
     cusv   = np.outer(cosu[:-1], sinv[:-1]).flatten()
@@ -738,8 +733,8 @@ def fullUnitSphere(res):
     cucv1  = np.outer(cosu[:-1], cosv[1:]) .flatten()
     cusv1  = np.outer(cosu[:-1], sinv[1:]) .flatten()
     
-    su     = np.repeat(sinu[:-1], res)
-    s1u    = np.repeat(sinu[1:],  res)
+    su     = np.repeat(sinu[:-1], res - 1)
+    s1u    = np.repeat(sinu[1:],  res - 1)
 
     vertices.T[:,  ::4] = [cucv,   cusv,   su]
     vertices.T[:, 1::4] = [cu1cv,  cu1sv,  s1u]
-- 
GitLab