From 68822c8b9020830d5e983f29f2eb6f2654e1e016 Mon Sep 17 00:00:00 2001
From: Paul McCarthy <pauld.mccarthy@gmail.com>
Date: Wed, 14 Oct 2015 10:04:44 +0100
Subject: [PATCH] Documentation for GLModel

---
 fsl/fsleyes/gl/gl14/edge2D.prog       |   2 +-
 fsl/fsleyes/gl/gl14/glmodel_frag.prog |   8 +-
 fsl/fsleyes/gl/gl14/glmodel_funcs.py  |  18 +++-
 fsl/fsleyes/gl/gl14/glmodel_vert.prog |   9 +-
 fsl/fsleyes/gl/gl21/glmodel_frag.glsl |   9 ++
 fsl/fsleyes/gl/gl21/glmodel_funcs.py  |  17 +++-
 fsl/fsleyes/gl/gl21/glmodel_vert.glsl |   2 +-
 fsl/fsleyes/gl/glmodel.py             | 113 ++++++++++++++++++++++++--
 8 files changed, 158 insertions(+), 20 deletions(-)

diff --git a/fsl/fsleyes/gl/gl14/edge2D.prog b/fsl/fsleyes/gl/gl14/edge2D.prog
index d1a3d55ef..9f0dbe52b 100644
--- a/fsl/fsleyes/gl/gl14/edge2D.prog
+++ b/fsl/fsleyes/gl/gl14/edge2D.prog
@@ -1,4 +1,4 @@
-# EDge detection routine for 2D textures - see edge3D.prog.
+# Edge detection routine for 2D textures - see also edge3D.prog.
 #
 # Author: Paul McCarthy <pauldmccarthy@gmail.com>
 #
diff --git a/fsl/fsleyes/gl/gl14/glmodel_frag.prog b/fsl/fsleyes/gl/gl14/glmodel_frag.prog
index ced6c8ffc..295911073 100644
--- a/fsl/fsleyes/gl/gl14/glmodel_frag.prog
+++ b/fsl/fsleyes/gl/gl14/glmodel_frag.prog
@@ -3,9 +3,13 @@
 # Fragment program used for rendering GLModel instances.
 #
 # Inputs:
-#   fragment.texcoord[0]
+#   fragment.texcoord[0] - Texture coordinates
+#   program.local[0]     - Offsets (used as edge widths - see edge2D.prog)
 #
-#   program.local[0] - Offsets
+# Outputs:
+#   result.color         - Fragment colour.
+#
+# Author: Paul McCarthy <pauldmccarthy@gmail.com> 
 #
 
 TEMP val;
diff --git a/fsl/fsleyes/gl/gl14/glmodel_funcs.py b/fsl/fsleyes/gl/gl14/glmodel_funcs.py
index fce0a4ea0..cc1fea8aa 100644
--- a/fsl/fsleyes/gl/gl14/glmodel_funcs.py
+++ b/fsl/fsleyes/gl/gl14/glmodel_funcs.py
@@ -1,9 +1,12 @@
 #!/usr/bin/env python
 #
-# glmodel_funcs.py -
+# glmodel_funcs.py - OpenGL 1.4 functions used by the GLModel class.
 #
 # Author: Paul McCarthy <pauldmccarthy@gmail.com>
 #
+"""This module provides functions which are used by the :class:`.GLModel`
+class to render :class:`.Model` overlays in an OpenGL 1.4 compatible manner.
+"""
 
 
 import OpenGL.GL                      as gl
@@ -15,6 +18,10 @@ import fsl.fsleyes.gl.shaders         as shaders
 
 
 def compileShaders(self):
+    """Compiles vertex and fragment shader programs for the given
+    :class:`.GLModel` instance. The shaders are added as attributes of the
+    instance.
+    """
     
     vertShaderSrc = shaders.getVertexShader(  self)
     fragShaderSrc = shaders.getFragmentShader(self)
@@ -27,12 +34,17 @@ def compileShaders(self):
 
 
 def destroy(self):
+    """Deletes the vertex/fragment shader programs that were compiled by
+    :func:`compileShaders`.
+    """    
     arbvp.glDeleteProgramsARB(1, gltypes.GLuint(self.vertexProgram))
     arbfp.glDeleteProgramsARB(1, gltypes.GLuint(self.fragmentProgram)) 
 
 
 def updateShaders(self):
-
+    """Updates the state of the vertex/fragment shaders. This involves
+    setting the parameter values used by the shaders.
+    """ 
     offsets = self.getOutlineOffsets()
     
     loadShaders(self)
@@ -41,6 +53,7 @@ def updateShaders(self):
 
 
 def loadShaders(self):
+    """Loads the :class:`.GLModel` vertex/fragment shader programs. """
     gl.glEnable(arbvp.GL_VERTEX_PROGRAM_ARB) 
     gl.glEnable(arbfp.GL_FRAGMENT_PROGRAM_ARB)
 
@@ -51,5 +64,6 @@ def loadShaders(self):
 
 
 def unloadShaders(self):
+    """Un-loads the :class:`.GLModel` vertex/fragment shader programs. """
     gl.glDisable(arbvp.GL_VERTEX_PROGRAM_ARB) 
     gl.glDisable(arbfp.GL_FRAGMENT_PROGRAM_ARB)     
diff --git a/fsl/fsleyes/gl/gl14/glmodel_vert.prog b/fsl/fsleyes/gl/gl14/glmodel_vert.prog
index c1ff267a1..0b38e8949 100644
--- a/fsl/fsleyes/gl/gl14/glmodel_vert.prog
+++ b/fsl/fsleyes/gl/gl14/glmodel_vert.prog
@@ -3,13 +3,14 @@
 # Vertex program for rendering GLModel instances.
 #
 # Inputs:
-#    state.matrix.mvp
-#    vertex.position
-#    vertex.texcoord[0]
+#    vertex.texcoord[0] - Texture coordinates
 #
 # Outputs:
 #    result.position
-#    result.texcoord[0]
+#    result.texcoord[0] - Texture coordinates (passed through to fragment
+#                         shader).
+#
+# Author: Paul McCarthy <pauldmccarthy@gmail.com> 
 #
 
 # Transform the vertex position into display coordinates
diff --git a/fsl/fsleyes/gl/gl21/glmodel_frag.glsl b/fsl/fsleyes/gl/gl21/glmodel_frag.glsl
index 7c13880b0..e3d17c87f 100644
--- a/fsl/fsleyes/gl/gl21/glmodel_frag.glsl
+++ b/fsl/fsleyes/gl/gl21/glmodel_frag.glsl
@@ -1,3 +1,8 @@
+/*
+ * Fragment shader used for drawing GLModel instances.
+ *
+ * Author: Paul McCarthy <pauldmccarthy@gmail.com> 
+ */
 #version 120
 
 #pragma include edge.glsl
@@ -11,6 +16,10 @@ void main(void) {
   vec4 colour = texture2D(tex, texCoord);
   vec4 tol    = 1.0 / vec4(255, 255, 255, 255);
 
+  /*
+   * If the fragment lies on an edge
+   * colour it, otherwise clear it.
+   */
   if (edge2D(tex, texCoord, colour, tol, offsets)) gl_FragColor   = colour;
   else                                             gl_FragColor.a = 0.0;
 }
diff --git a/fsl/fsleyes/gl/gl21/glmodel_funcs.py b/fsl/fsleyes/gl/gl21/glmodel_funcs.py
index d78b7c20c..2092f73c3 100644
--- a/fsl/fsleyes/gl/gl21/glmodel_funcs.py
+++ b/fsl/fsleyes/gl/gl21/glmodel_funcs.py
@@ -1,9 +1,12 @@
 #!/usr/bin/env python
 #
-# glmodel_funcs.py -
+# glmodel_funcs.py - OpenGL 2.1 functions used by the GLModel class.
 #
 # Author: Paul McCarthy <pauldmccarthy@gmail.com>
 #
+"""This module provides functions which are used by the :class:`.GLModel`
+class to render :class:`.Model` overlays in an OpenGL 2.1 compatible manner.
+"""
 
 import numpy                  as np
 import OpenGL.GL              as gl
@@ -12,6 +15,10 @@ import fsl.fsleyes.gl.shaders as shaders
 
 
 def compileShaders(self):
+    """Compiles vertex and fragment shaders for the given :class:`.GLModel`
+    instance. The shaders, and locations of uniform variables, are added
+    as attributes of the instance.
+    """
     vertShaderSrc = shaders.getVertexShader(  self)
     fragShaderSrc = shaders.getFragmentShader(self)
     self.shaders  = shaders.compileShaders(vertShaderSrc, fragShaderSrc)
@@ -21,10 +28,16 @@ def compileShaders(self):
 
 
 def destroy(self):
+    """Deletes the vertex/fragment shaders that were compiled by
+    :func:`compileShaders`.
+    """
     gl.glDeleteProgram(self.shaders)
 
 
 def updateShaders(self):
+    """Updates the state of the vertex/fragment shaders. This involves
+    setting the uniform variable values used by the shaders.
+    """
 
     width, height = self._renderTexture.getSize()
     outlineWidth  = self.opts.outlineWidth
@@ -44,8 +57,10 @@ def updateShaders(self):
 
 
 def loadShaders(self):
+    """Loads the :class:`.GLModel` vertex/fragment shaders. """
     gl.glUseProgram(self.shaders)
 
 
 def unloadShaders(self):
+    """Un-loads the :class:`.GLModel` vertex/fragment shaders. """
     gl.glUseProgram(0)
diff --git a/fsl/fsleyes/gl/gl21/glmodel_vert.glsl b/fsl/fsleyes/gl/gl21/glmodel_vert.glsl
index 2efb49a27..6b98518a0 100644
--- a/fsl/fsleyes/gl/gl21/glmodel_vert.glsl
+++ b/fsl/fsleyes/gl/gl21/glmodel_vert.glsl
@@ -1,5 +1,5 @@
 /*
- * Vertex shader for GLModel instances.
+ * Vertex shader used for drawing GLModel instances.
  *
  * Author: Paul McCarthy <pauldmccarthy@gmail.com> 
  */
diff --git a/fsl/fsleyes/gl/glmodel.py b/fsl/fsleyes/gl/glmodel.py
index 841d80699..96c091d10 100644
--- a/fsl/fsleyes/gl/glmodel.py
+++ b/fsl/fsleyes/gl/glmodel.py
@@ -1,9 +1,12 @@
 #!/usr/bin/env python
 #
-# glmodel.py -
+# glmodel.py - The GLModel class.
 #
 # Author: Paul McCarthy <pauldmccarthy@gmail.com>
 #
+"""This module provides the :class:`GLModel` class, a :class:`.GLObject` used
+to render :class:`.Model` overlays.
+"""
 
 
 import numpy     as np
@@ -18,8 +21,55 @@ import fsl.fsleyes.colourmaps  as fslcmaps
 
 
 class GLModel(globject.GLObject):
+    """The ``GLModel`` class is a :class:`.GLObject` which encapsulates the
+    logic required to render 2D slices of a :class:`.Model` overlay.
 
+    
+    The ``GLModel`` renders cross-sections of a ``Model`` overlay using a
+    three-pass technique, roughly following that described at 
+    http://glbook.gamedev.net/GLBOOK/glbook.gamedev.net/moglgp/advclip.html:
+
+    
+    1. The front face of the model is rendered to the stencil buffer.
+
+    2. The back face is rendered to the stencil buffer,  and subtracted
+       from the front face.
+
+    3. This intersection (of the model with a plane at the slice Z location)
+       is then rendered to the canvas.
+
+
+    The ``GLModel`` class has the ability to draw the model with a fill
+    colour, or to draw only the model outline. To accomplish this, in step 3
+    above, the intersection mask is actually drawn to an off-screen
+    :class:`.GLObjectRenderTexture`. If outline mode (controlled by the
+    :attr:`.ModelOpts.outline` property), an edge-detection shader program is
+    run on this off-screen texture. Then, the texture is rendered to the
+    canvas. If outline mode is disabled, the shader programs are not executed.
+
+
+    The ``GLModel`` class makes use of two OpenGL version-specific modules,
+    :mod:`.gl14.glmodel_funcs`, and :mod:`.gl21.glmodel_funcs`. These version
+    specific modules must provide the following functions:
+
+    =========================== ======================================
+    ``destroy(GLModel)``        Performs any necessary clean up.
+    ``compileShaders(GLModel)`` Compiles vertex/fragment shaders.
+    ``updateShaders(GLModel)``  Updates vertex/fragment shader states.
+    ``loadShaders(GLModel)``    Loads vertex/fragment shaders.
+    ``unloadShaders(GLModel)``  Unloads vertex/fragment shaders.
+    =========================== ======================================
+    """
+
+    
     def __init__(self, overlay, display):
+        """Create a ``GLModel``.
+
+        :arg overlay: A :class:`.Model` overlay.
+
+        :arg display: A :class:`.Display` instance defining how the
+                      ``overlay`` is to be displayed.
+        """
 
         globject.GLObject.__init__(self)
 
@@ -39,6 +89,10 @@ class GLModel(globject.GLObject):
 
         
     def destroy(self):
+        """Must be called when this ``GLModel`` is no longer needed. Removes
+        some property listeners and destroys the off-screen
+        :class:`.GLObjectRenderTexture`.
+        """
         self._renderTexture.destroy()
         fslgl.glmodel_funcs.destroy(self)
         self.removeListeners()
@@ -49,6 +103,10 @@ class GLModel(globject.GLObject):
 
         
     def addListeners(self):
+        """Called by :meth:`__init__`. Adds some property listeners to the
+        :class:`.Display` and :class:`.ModelOpts` instances so the OpenGL
+        representation can be updated when the display properties are changed..
+        """
 
         name    = self.name
         display = self.display
@@ -74,6 +132,9 @@ class GLModel(globject.GLObject):
 
         
     def removeListeners(self):
+        """Called by :meth:`destroy`. Removes all of the listeners added by
+        the :meth:`addListeners` method.
+        """
         self.opts   .removeListener('refImage',     self.name)
         self.opts   .removeListener('coordSpace',   self.name)
         self.opts   .removeListener('transform',    self.name)
@@ -84,8 +145,21 @@ class GLModel(globject.GLObject):
         self.display.removeListener('contrast',     self.name)
         self.display.removeListener('alpha',        self.name)
 
+ 
+    def setAxes(self, xax, yax):
+        """Overrides :meth:`.GLObject.setAxes`. Calls the base class
+        implementation, and calls :meth:`.GLObjectRenderTexture.setAxes`
+        on the off-screen texture.
+        """
+        globject.GLObject.setAxes(self, xax, yax)
+        self._renderTexture.setAxes(xax, yax)
+
 
     def _updateVertices(self, *a):
+        """Called by :meth:`__init__`, and when certain display properties
+        change. (Re-)generates the model vertices and indices. They are stored
+        as attributes called ``vertices`` and ``indices`` respectively.
+        """
 
         vertices = self.overlay.vertices
         indices  = self.overlay.indices
@@ -101,11 +175,18 @@ class GLModel(globject.GLObject):
 
         
     def getDisplayBounds(self):
+        """Overrides :meth:`.GLObject.getDisplayBounds`. Returns a bounding
+        box which contains the model vertices. 
+        """
         return (self.opts.bounds.getLo(),
                 self.opts.bounds.getHi()) 
 
     
     def getDataResolution(self, xax, yax):
+        """Overrides :meth:`.GLObject.getDataResolution`. Returns a 
+        resolution (in pixels), along each display coordinate system axis,
+        suitable for drawing this ``GLModel``.
+        """
 
         # TODO How can I have this resolution tied
         # to the rendering target resolution (i.e.
@@ -142,14 +223,14 @@ class GLModel(globject.GLObject):
         
         return resolution
         
-    
-    def setAxes(self, xax, yax):
-        globject.GLObject.setAxes(self, xax, yax)
-        self._renderTexture.setAxes(xax, yax)
-
         
     def getOutlineOffsets(self):
-        """Used by the :mod:`glmodel_funcs` modules.
+        """Returns an array containing two values, which are to be used as the
+        outline widths along the horizontal/vertical screen axes (if outline
+        mode is being used). The values are in display coordinate system units.
+
+        .. note:: This method is used by the :mod:`.gl14.glmodel_funcs` and
+                  :mod:`.gl21.glmodel_funcs` modules.
         """
         width, height = self._renderTexture.getSize()
         outlineWidth  = self.opts.outlineWidth
@@ -165,10 +246,14 @@ class GLModel(globject.GLObject):
  
 
     def preDraw(self):
+        """Overrides :meth:`.GLObject.preDraw`. This method does nothing. """
         pass
 
     
     def draw(self, zpos, xform=None):
+        """Overrids :meth:`.GLObject.draw`. Draws a 2D slice of the
+        :class:`.Model`, at the specified Z location.
+        """
 
         display = self.display 
         opts    = self.opts
@@ -192,6 +277,11 @@ class GLModel(globject.GLObject):
         if zpos < zmin or zpos > zmax:
             return
 
+        # Figure out the equation of a plane
+        # perpendicular to the Z axis, and
+        # located at the z position. This is
+        # used as a clipping plane to draw 
+        # the model intersection.
         clipPlaneVerts                = np.zeros((4, 3), dtype=np.float32)
         clipPlaneVerts[0, [xax, yax]] = [xmin, ymin]
         clipPlaneVerts[1, [xax, yax]] = [xmin, ymax]
@@ -251,9 +341,9 @@ class GLModel(globject.GLObject):
                               gl.GL_UNSIGNED_INT,
                               indices)
 
-        # third pass - render the intersection
+        # Third pass - render the intersection
         # of the front and back faces from the
-        # stencil buffer to the render texture
+        # stencil buffer to the render texture.
         gl.glColorMask(gl.GL_TRUE, gl.GL_TRUE, gl.GL_TRUE, gl.GL_TRUE)
 
         gl.glDisable(gl.GL_CLIP_PLANE0)
@@ -283,6 +373,10 @@ class GLModel(globject.GLObject):
         self._renderTexture.unbindAsRenderTarget()
         self._renderTexture.restoreViewport()
 
+        # If drawing the model outline, run the
+        # render texture through the shader
+        # programs. Otherwise, the render texture
+        # is just drawn directly to the canvas.
         if opts.outline:
             fslgl.glmodel_funcs.loadShaders(self)
 
@@ -294,4 +388,5 @@ class GLModel(globject.GLObject):
 
     
     def postDraw(self):
+        """Overrides :meth:`.GLObject.postDraw`. This method does nothing. """
         pass
-- 
GitLab