Home
Code & Snippets
Tutorials
GUI Docs
Projects
Drawings & Sketches
Links
Bio & Resume

Kindly hosted by


Support This Project

 

GUI Tutorial : Part #2

Table of Contents

Welcome to the second tutorial in GUI series. If you haven't read the first tutorial, please do so, otherwise you might
not understand the intinsics of what's happening in this one.

Material & Texture XML formats

As I promised, in this tutorial we will colour and texture our simple quad and also learn how to load it's attributes
I preferred to store my data in the XML format, but that doesn't mean that you have to do that too. Experiment if you
wish with different formats, and use the one that suits you. As I said, I'll be using the XML format, that basically is a structured
language with all I need - attribute/value or property/value simplistic definitions.
If you aren't sure about the general concept of storing/retrieving data in and from XML, search at Google, there are
many good tutorials there on XML language. I use TinyXML to read/write to XML file and t retrieve data from attributes.

First of all, we need some kind of database to store our textures & materials in. I will not go deeply into Material & Texture
managers (it's up to you to take a look - if you want, of course - how they work), I just mention that it's a safe practice to
use them without even knowing about what's happening inside. Let's take a look at following example of XML data file:

<# 
   Texture filename - pretty obvious
   Mask - obvious as well - an RGB value for mask of the texture
#>
<Texture Filename="Data/MouseDown_Button_Texture.bmp" Mask="0,0,0"/>
<#
...
...
#>
<#
  Name: Just a name by which this material can be retrieved from database.
  RefID: Same thing, material ID reference in database
  Ambient, Diffuse, etc - material RGBA attributes
  GL_BLEND - OpenGL blending values
  GL_BLEND(0) - Blending factor: specifying the amount of blending, -1 if no blending.
  GL_BLEND(1) - Source blending parameter (OpenGL Specific)
  GL_BLEND(2) - Destination blending parameter (OpenGL Specific)

  Texture child element - specifies texture filename & additional texture attributes (if any)
#>
<Material Name="DefaultBtn_MouseDown" RefID="24" Ambient="1,1,1" Diffuse="1,1,1" Emissive="1,1,1" Specular="1,1,1" 
Shininess="0" GL_Blend="-1,770,1"> <Texture Filename="Data/MouseDown_Button_Texture.bmp"/> </Material>

Texture database ensures that there are no duplicates of same texture are loaded, and a similar thing exists in material
database. You should not assign same RefID to diferent materials, but if that occurs, the material manager will assign no
ID to the last loaded material with that ID. It's easy to track last ID - just look at last one at the end of the file!

Defining Element' XML data

OK, now let's take a look at how we're going to represent our GUI element attributes in an XML data file.
Let's say that in the future we will have many more controls, such as progress bar, textbox, buttons and others. So, we
are going to give a name to each tag according to element type. So, in our case, we are going to have the following XML code:

<Element Rect="200,300,500,400" Color="255,177,100,0" MatByRefID="1" 
         UV="(0.00,0.00),(1.00,0.00),(1.00,1.00),(0.00,1.00)"/>

Parsing the Element' data from XML

My parsing function for the CGUIElement looks like this:

int CGUIElement::Parse(TiXmlNode *this_node, char *filename)
{
	// Let's check if all passed values are good, and if not, do nothing!
	if(this_node == NULL && filename == NULL)
		return -1;
	
	const char *value = NULL;
	TiXmlElement* element = NULL;
	TiXmlNode *node = NULL;
	TiXmlDocument doc;
	bool parse_again = false;

	// If a user passed a filename, use it as an external link to our data for this element
	if(filename != NULL)
	{
		if(!doc.LoadFile(filename))	// Try to parse data using given filename
			return 0;
		
		node = doc.FirstChild();	// Aquire first data node
		parse_again = true;			// Mark a flag - see below what it is for
	}
	
	if(this_node !=NULL)
	{
		element = this_node->ToElement();
		
		if(element->Attribute("Filename") !=NULL)
		{
			if(!doc.LoadFile(element->Attribute("Filename")))
				return 0;
			
			node = doc.FirstChild();
			parse_again = true;
		}
	}

	// Assign pointer to last node loaded
	m_pXMLNode = this_node;
	if(this_node == NULL)
		m_pXMLNode = node;

	// The idea is simple: let the user to read, say, a template description of an object with default colours,
	// textures and et cetera, but then allow him to specify them AGAIN - e.g the ones he really wants to appear.
	// This idea is resembles an 'overwrite' practice - read something, and then replace the parts you want
	// with newer values or the ones you want/need. Simple on words, but as usual, hard on practice! :)
	if(parse_again)
	{
		if(!Parse(node, NULL))
			return 0;
	}
	else
	{
		// Now, parse the data the user wants with this actual node!

	}
	return 1;
}        

This might be tought for starters, but I can assure you, this is the hardest part of this tutorial - gone! If you read down
here already, it means only a few easy steps left!

(I say again that it's up to you to decide which language/format you are going to store your data in!)

Adding texture, material and color and UV to Element

Now, let's finally add colour and material to our GUI element. To have our quad textured, we need to also store texture
coordinates for each of it's corner, so let's add them as well.

class CGUIElement
{
 public:
 // ...
 // ...

protected:
	tRect			m_Rect, m_DragRect;		// The actual element rectangle structure
	TiXmlNode		*m_pXMLNode;			// This node' XML data reference for saving/reloading
	/************************************** NEW !!!!! ***************************************************************/
	// Here we have three new variables: colour and material/texture and also the mapping coordinates as well.
	tRGBA			m_Color;
	tMaterial		*m_pBackgroundMaterial;
	tVERTEX2f		m_fTexCoord[4];	
	/****************************************************************************************************************/
};

I store my material attributes in the following way:

<Material Name="SomeMaterial" RefID="1">
<Texture Filename="Data/Texture.bmp"/>
</Material>

Yep, it's that easy. Ambient, Diffuse, etc properties of material aren't written here to save space (they are defaulted to 1 (RGBA)).

Parsing Element data with Material, Texture, Color and UV from XML

Let's see what we have listed above: material reference ID in database, rectangle for element quad, UV coordinates and
color! Anything missed? I think not. Now it's time to parse all this data down to GUI element member variables.
So, here's how I am going to do it:

int CGUIElement::Parse(TiXmlNode *this_node, char *filename)
{
  // ...
  // ...
  
  if(parse_again)
  {
   if(!Parse(node, NULL))
  	return 0;
  }
  else
  {
  	// Now, parse the data the user wants with this actual node!
  	element = NULL;
  	value = NULL;
  
  	element = m_pXMLNode->ToElement();
          
          // Parse our element' rectangle data
  	value = element->Attribute("Rect");
  
  	if(value !=NULL)
  	{
  		sscanf(value, "%d,%d,%d,%d", &m_Rect.m_iLeft, &m_Rect.m_iRight, &m_Rect.m_iTop, &m_Rect.m_iBottom);
  		value = NULL; // Nullify pointer - precaution measures
  	}
  	
          // Alternative XML check if user wants to store data this way <Element left="200" right="300" etc.. />
  	if(element->Attribute("left") !=NULL && element->Attribute("right") !=NULL && element->Attribute("top") 
!=
NULL && element->Attribute("bottom") !=NULL) { m_Rect.m_iLeft = atoi(element->Attribute("left")); m_Rect.m_iRight = atoi(element->Attribute("right")); m_Rect.m_iTop = atoi(element->Attribute("top")); m_Rect.m_iBottom = atoi(element->Attribute("bottom")); } value = element->Attribute("Color"); if(value !=NULL) { sscanf(value, "%d,%d,%d", &m_Color.m_fR, &m_Color.m_fG, &m_Color.m_fB); value = NULL; } // Parse UV data from XML value = element->Attribute("UV"); if(value !=NULL) { sscanf(value, "(%f,%f),(%f,%f),(%f,%f),(%f,%f)", &m_fTexCoord[0].x, &m_fTexCoord[0].y, &m_fTexCoord[1].x, &m_fTexCoord[1].y, &m_fTexCoord[2].x, &m_fTexCoord[2].y, &m_fTexCoord[3].x, &m_fTexCoord[3].y); value = NULL; } // Parse material/texture data.. // If user specified reference ID, use it value = element->Attribute("MatByRefID"); if(value !=NULL) { m_pBackgroundMaterial = CMaterialManager::GetSingleton().GetMaterial(-1, atoi(value)); value = NULL; } // Or if user specified the material's name, use it instead! value = element->Attribute("MatByName"); if(value !=NULL) { m_pBackgroundMaterial = CMaterialManager::GetSingleton().GetMaterial(value); value = NULL; } } return 1; }

Yep, its that long. If you don't want the user to be flexible in XML data definitions, remove the code for rectangle, but
I'd reather leave it.

Refining Drawing function

OK, we loaded the data, let's see how the result will look like. But before that, we need to add a little more code to our
OnDraw() function, so that it will handle textures now as well as color. Remember, if there is texture, there is no color.
The coloured texture can only be when we enable materials and lights, but that is going to be not far away. OK, OnDraw() function:

void CGUIElement::OnDraw()
{
	int x1, x2, y1, y2;

	x1 = m_Rect.m_iLeft;
	x2 = m_Rect.m_iRight;
	y1 = m_Rect.m_iTop;
	y2 = m_Rect.m_iBottom;

	if(m_pBackgroundMaterial == NULL)
	{
		glColor3d(m_Color.m_fR, m_Color.m_fG, m_Color.m_fB);
		glBegin(GL_QUADS);
		glVertex3d(x1, y1, 0);
		glVertex3d(x1, y2, 0);
		glVertex3d(x2, y2, 0);
		glVertex3d(x2, y1, 0);
		glEnd();
	}
	else
	{
		ApplyMaterial(m_pBackgroundMaterial, TEX_COLOR, NULL);
		glBegin(GL_QUADS);
		glTexCoord2d(m_fTexCoord[0].x, m_fTexCoord[0].y); glVertex3d(x1, y2, 0);
		glTexCoord2d(m_fTexCoord[1].x, m_fTexCoord[1].y); glVertex3d(x2, y2, 0);
		glTexCoord2d(m_fTexCoord[2].x, m_fTexCoord[2].y); glVertex3d(x2, y1, 0);
		glTexCoord2d(m_fTexCoord[3].x, m_fTexCoord[3].y); glVertex3d(x1, y1, 0);
		glEnd();
	}
}

Putting it all together

Not much of a change? Well, it's obvious. We check for background material pointer, and if there is any, we use it!
Now, we also need to add initialization and loading of material/teture database at the very start of our program, let's say,
in InitGL() or even earlier. I've put it into InitGL() and also added declarations of texture/material manager pointers at the top of the 'lesson1.cpp' file:

int InitGL(GLvoid)										// All Setup For OpenGL Goes Here
{
	glShadeModel(GL_SMOOTH);							// Enable Smooth Shading
	glClearColor(0.0f, 0.0f, 0.0f, 0.5f);				// Black Background
	glClearDepth(1.0f);									// Depth Buffer Setup
	glEnable(GL_DEPTH_TEST);							// Enables Depth Testing
	glDepthFunc(GL_LEQUAL);								// The Type Of Depth Testing To Do
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);	// Really Nice Perspective Calculations

	glEnable(GL_TEXTURE_2D);
	// Create a texture manager singleton instance
	g_TextureManager = new CTextureManager;
	// Create a material manager singleton instance
	g_MaterialManager = new CMaterialManager;

	// Load textures
	g_TextureManager->LoadTextures(NULL, "mat_tex.xml");
	// Load materials
	g_MaterialManager->LoadMaterials(NULL, "mat_tex.xml");

	element.Parse(NULL, "element.xml");
	element1.Parse(NULL, "element.xml");

	// Asiggn new rectangle so those two won't overlap completely
	element1.SetRect(tRect(400,500, 500,400));
	
	return TRUE;										// Initialization Went OK
}

And don't forget to delete material/texture databases at the end of our program, say in KillGLWindow
No more hassle, I promise - it's time to see how our program works in it's full capacity! Run it and be proud of yourself!

Next tutorial we will add the ability to use mouse and move/resize our controls
via a new utility class ( CGUI ) and CGUIBorder. Cyas there!