Showing posts with label Coding Standards. Show all posts
Showing posts with label Coding Standards. Show all posts

Monday, 20 February 2012

Operator Overloading in C++ Part 3

In the previous post we looked at the mandated member operators, for this part I'm going to look at the different types of operators we can generate and provide examples of each.

For ease I will split this section into relational operators and arithmetic operators. We can represent the relational operators using the following diagram


These operators will always return a boolean value which will be the result of some form of comparison. 


Arithmetic operators will return an object of some type which depending upon the context may be either the same object or a new type of object.  Later we will also add the += style operators which will mutate the current object rather than returning a value.
Equality Operators
In the case of the Vec3 class there are only two equality operator which make sense to implement, these are the == and != operators. Also we must ensure that we are doing correct comparison as we are storing floating point values. ( see here for an in depth discussion of floating point comparison ). For this example I'm going to generate a very simple FCompare macro which will be used to do a comparison via a simple epsilon error value.


//----------------------------------------------------------------------------------------------------------------------
/// @brief define EPSILON for floating point comparison
//----------------------------------------------------------------------------------------------------------------------
#ifndef EPSILON
  const float EPSILON = 0.001f;
#endif

//----------------------------------------------------------------------------------------------------------------------
/// @brief FCompare macro used for floating point comparison functions
//----------------------------------------------------------------------------------------------------------------------
  #define FCompare(a,b) \
      ( ((a)-EPSILON)<(b) && ((a)+EPSILON)>(b) )
In the first example I will produce a "Member operator" for the equality operator, first in the .h file we declare the operator
bool Vec3::operator==(const Vec3 &_rhs)
{
  return FCompare(m_x,_rhs.m_x) &&
         FCompare(m_y,_rhs.m_y) &&
         FCompare(m_z,_rhs.m_z);
}
As you can see in this case we do a component wise FCompare with the current object and the rhs value. We can test this using the following code from the previous examples
if(b==c)
 std::cout<<"b==c is true\n";
else
 std::cout<<"b!=c \n";
if(a==fromFloat)
 std::cout<<"a==fromFloat\n";
else
 std::cout<<"a!=fromFloat\n";
Which gives the following output
testing equality operators 
b==c is true
a!=fromFloat
To generate the != method we do the following

/// in .h
/// @brief != operator
bool operator!=(const Vec3 &_rhs);
/// in .cpp
bool Vec3::operator!=(const Vec3 &_rhs)
{
  return !(*this==_rhs);
}
For the free function operator we have to write some additional code again to allow the free function access to the attributes. In this case we are going to write a method called isEqual ( note that my coding standard says this kind of method should be formed as a question as it returns a bool)
/// @brief function to test equality
bool isEqual(const Vec3 &_rhs) const;
The actual code for this class method will use the same FCompare macro as above and looks like this
bool Vec3::isEqual(const Vec3 &_rhs) const
{
  return FCompare(m_x,_rhs.m_x) &&
         FCompare(m_y,_rhs.m_y) &&
         FCompare(m_z,_rhs.m_z);
}
The free function definitions of the == and != methods are now declared outside of the class but still in the .h file as follows
/// @brief free operator for equality testing
bool operator ==(const Vec3 &_lhs, const Vec3 &_rhs);
/// @brief free operator for equality testing
bool operator !=(const Vec3 &_lhs, const Vec3 &_rhs);
Finally we write the main function code, not that as they are no longer class methods it is no longer valid to mark the methods as const (in fact you get the following error with g++ error: non-member function 'bool operator==(const Vec3&, const Vec3&)' cannot have cv-qualifier )
bool operator ==(const Vec3 &_lhs, const Vec3 &_rhs) const
{
  return _lhs.isEqual(_rhs);
}

bool operator !=(const Vec3 &_lhs, const Vec3 &_rhs)
{
  return !_lhs.isEqual(_rhs);
}

Tuesday, 9 November 2010

refactoring fun (not)

Finally re-factor all the NGL code to match the Qt method standard using lower case first word then Camel case for each following word.

First time round it went really wrong as I mended a couple of const bugs at the same time breaking most of the code.

So thanks to bzr I managed to revert the code and do one thing at a time correctly. The new version seems to work well and the coding standard has been updated to reflect the changes. Not bad for a day's work

Here is a simple python NGL program to draw a teapot using the new style

#!/usr/bin/python
import math
import pdb
from OpenGL.GL import *
from OpenGL.GLU import *
from PyQt4 import QtGui
from PyQt4.QtOpenGL import *
from PyQt4.Qt import Qt
from PyQt4 import QtCore
from PyNGL import *
import sys
import random

class GLWindow(QGLWidget):

 def __init__(self, parent):
   QGLWidget.__init__(self, parent)
   self.setMinimumSize(1024, 720)
   self.m_spinYFace = 0
   self.m_spinXFace = 0
   self.m_origX = 0
   self.m_origY = 0
   self.m_transformStack=TransformStack()


 def mousePressEvent (self,  _event) :

  # this method is called when the mouse button is pressed in this case we
  # store the value where the maouse was clicked (x,y) and set the Rotate flag to true
  if(_event.button() == Qt.LeftButton) :
   self.m_origX = _event.x();
   self.m_origY = _event.y();
   self.m_rotate =True;



 def mouseMoveEvent ( self,_event ) :
  # note the method buttons() is the button state when event was called
  # this is different from button() which is used to check which button was
  # pressed when the mousePress/Release event is generated
  if(self.m_rotate and _event.buttons() == Qt.LeftButton) :
   self.m_spinYFace = ( self.m_spinYFace + (_event.x() - self.m_origX) ) % 360
   self.m_spinXFace = ( self.m_spinXFace + (_event.y() - self.m_origY) ) % 360
   self.m_origX = _event.x();
   self.m_origY = _event.y();
   # re-draw GL
   self.updateGL();

 def mouseReleaseEvent (self,  _event) :

  # this event is called when the mouse button is released
  # we then set Rotate to false
  if (_event.button() == Qt.LeftButton) :
   self.m_rotate=False


 def paintGL(self):
   '''
   Drawing routine
   '''
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)


   trans=Transformation()
   # set the mouse rotation
   trans.setRotation(self.m_spinXFace,self.m_spinYFace,0)
   # set this in the TX stack
   self.m_transformStack.setGlobal(trans);

   shader=ShaderManager.instance();
   shader.setShaderParamFromMatrix("gl3xTest","ModelMatrix",self.m_transformStack.getCurrAndGlobal().getTransposeMatrix());

   vbo=VBOPrimitives.instance()
   vbo.draw("teapot")


   glFlush()



 def resizeGL(self, w, h):
   '''
   Resize the GL window
   '''
   glViewport(0, 0, w, h)
   self.m_cam.setShape(100,float(w/h),0.5,10,CAMERAPROJECTION.PERSPECTIVE)





 def initializeGL(self):
   '''
   Initialize GL
   '''
   # set viewing projection
   ngl=NGLInit.instance()
   ngl.initGlew()
   glClearColor(0.4, 0.4, 0.4, 1.0)
   glClearDepth(1.0)
   From=Vector(0,1,1)
   To=Vector(0,0,0)
   Up=Vector(0,1,0)
   self.m_cam=Camera(From,To,Up,CAMERAPROJECTION.PERSPECTIVE)
   self.m_cam.setShape(100,1024.0/720.0,0.5,10,CAMERAPROJECTION.PERSPECTIVE);
   glEnable(GL_LIGHTING)
   glShadeModel(GL_SMOOTH)
   glEnable(GL_DEPTH_TEST)
   glEnable(GL_NORMALIZE)
   glClearColor(0.5,0.5,0.5,1.0)

   self.shader=ShaderManager.instance()
   # load a frag and vert shaders
   self.shader.loadShader("gl3xTest","Vertex.vs","Fragment.fs","")
   # set this as the active shader
   self.shader.useShader("gl3xTest");
   # now pass the modelView and projection values to the shader
   self.shader.setShaderParamFromMatrix("gl3xTest","ViewMatrix",self.m_cam.getModelView())
   self.shader.setShaderParamFromMatrix("gl3xTest","projectionMatrix",self.m_cam.getProjection())

   m=Material(STDMAT.GOLD)
   m.use()
   self.Light0 = Light(Vector(5,12,0,1),Colour(1,1,1),Colour(1,1,1),LIGHTMODES.LIGHTLOCAL)
   self.Light0.enable()



#
# You don't need anything below this
class NGLOpenGLDemo(QtGui.QMainWindow):

 def __init__(self):
  QtGui.QMainWindow.__init__(self)
  self.widget = GLWindow(self)
  self.setCentralWidget(self.widget)


 def keyPressEvent(self ,_event) :
  INC=0.1

  if _event.key() == Qt.Key_Q :
   sys.exit()
  elif _event.key() == Qt.Key_W :
   glPolygonMode(GL_FRONT_AND_BACK,GL_LINE)
  elif _event.key() == Qt.Key_S :
   glPolygonMode(GL_FRONT_AND_BACK,GL_FILL)
  elif _event.key() == Qt.Key_Up :
   self.widget.m_cam.Move(0.0,-INC,0.0)
  elif _event.key() == Qt.Key_Down :
   self.widget.m_cam.Move(0.0,INC,0.0)
  elif _event.key() == Qt.Key_Left :
   self.widget.m_cam.Move(INC,0.0,0.0)
  elif _event.key() == Qt.Key_Right :
   self.widget.m_cam.Move(-INC,0.0,0.0)

  elif _event.key() == Qt.Key_I :
   self.widget.m_cam.Move(0.0,0.0,INC)
  elif _event.key() == Qt.Key_O :
   self.widget.m_cam.Move(0.0,0.0,-INC)

  self.widget.updateGL()


if __name__ == '__main__':
 app = QtGui.QApplication(['Simple NGL Python Demo'])
 window = NGLOpenGLDemo()
 window.setWindowTitle("PyNGL Demo")
 window.show()
 app.exec_()

This program relies on two shaders to do basic transformations and blinn style shading. The Vertex shader is as follows

uniform mat4 projectionMatrix;
uniform mat4 ViewMatrix;
uniform mat4 ModelMatrix;


varying vec3 fragmentNormal;

void main(void)
{
  fragmentNormal = (ViewMatrix*ModelMatrix*vec4(gl_Normal, 0.0)).xyz;
  gl_Position = projectionMatrix*ViewMatrix*ModelMatrix*gl_Vertex;
}


This shader is passed the values calculated for the projection (from the NGL::Camera class) as well as the Model and View transformations.
It will calculate the position of the vertex as well as the fragment normal (based on the normal passed for shading from the teapot model in this case)

The actual shading is done in the fragment shader below

/// @brief[in] the vertex normal
varying vec3 fragmentNormal;


void main ()
{
  // set the output colour to black
  vec4 colour= vec4(0.0);
  // normalize the vertex normal
  vec3 N = normalize(fragmentNormal);
  // The Light source vector
  vec3 L;
  // the Halfway vector (used for speed)
  vec3 H;
  // pre declare the colour contribution values
  vec4 ambient;
  vec4 diffuse;
  vec4 specular;


  // get the Light vector
  L = normalize(gl_LightSource[0].position.xyz);
  // get the halfway vector
  H = normalize(gl_LightSource[0].halfVector.xyz);
  // ambient just added
  ambient = gl_FrontMaterial.ambient *gl_LightSource[0].ambient;
  // calculate diffuse based on Lambert's law (L.N)
  diffuse = gl_FrontMaterial.diffuse  *gl_LightSource[0].diffuse * max(dot(L, N), 0.0);
  // calculate specular based on H.N^Shininess
  specular = gl_FrontMaterial.specular *gl_LightSource[0].specular * pow(max(dot(H, N), 0.0), gl_FrontMaterial.shininess);
  // combine contribution for the light
  colour+=ambient+diffuse+specular;
  // finally set the colour clamping between 0 and 1
  gl_FragColor = clamp(vec4(colour),0.0,1.0);

}


This shader calculates the shading values based on the 1st GL light in the scene. It calculates ambient diffuse and specular contributions based on the current openGL material passed from the NGL::Material class.

We use a basic diffuse / lambert shading model for the diffuse contribution and the half way vector for the specular shading to mimic Phong / Blinn reflections.

And finally a golden teapot

Saturday, 6 November 2010

To re-factor or not to re-factor

That is the Question,

For the last few years I've been making the students work to a coding standard (by giving them a small amount of marks for doing so). This has a number of advantages

  1. All the code looks the same and makes it easier to integrate into a larger code base.
  2. Makes spotting plagiarism easier (especially with the citation rules)
  3. Prepares the students for real life working situations (our coding standard is based on two real world examples)
When the original standard was published the code base for ngl was based on OpenGL and SDL however this year it has all been ported to Qt as this seems to be the new standard for most animation apps.

The problem I now face comes from this section in the standard

Function Names

Function names have the first letter of each world capitalised, with no underscores between words.
For Example:
AddEntry
     

Where possible, functions/methods that return a bool result should have a name that looks like a question, eg
IsValid, HasProtocol, AreParallel...
     
Each function should be preceeded by a comment containing a brief description about the function (how it works, what it does) above the function body in the .cpp file.

All of the Methods  in ngl follow this rule, however when using Qt the following is the standard syntax
ui->m_initEdit->setAutoIndent(true);
ui->m_initEdit->setAutoCompletionThreshold(2);
ui->m_initEdit->setAutoCompletionSource(QsciScintilla::AcsAll);
ui->m_initEdit->setBraceMatching(QsciScintilla::SloppyBraceMatch);
ui->m_initEdit->setMatchedBraceForegroundColor(Qt::blue);
ui->m_initEdit->setMatchedBraceBackgroundColor(Qt::yellow);

This means we have a mixed standard in most programs, I could re-factor the ngl: lib but this would take a while and break quite a lot of legacy code (which is still in the process of being re-written anyway).

At the present my thoughts are

Positives :

  1. Makes all the code look the same and matches Qt style
  2. Will not confuse people when writing code as it will always be the same 
  3. quite like the looks of the Qt syntax
  4. Not issued much code to the students yet, and start the main NGL teaching in a couple of weeks
Negatives :
  1. I will take quite a while to convert things
  2. Could break quite a lot of old code
  3. I'm actually used to using the current method and would have to re-learn how I write things
  4. The actual Qt guidelines are not that good so can't use them wholsale 
Think I will wait until Monday and decide then.