Archived

This topic is now archived and is closed to further replies.

BungoMan85

conflict between rendering text and selecting objects

Recommended Posts

Using code from www.gametutorials.com I tried to make a simple program that will draw objects and let the user select them while at the same time rendering some text on the screen. Seemed like a simple enough task. But when I added in the code to render the text it effectively broke the code that checks to see if an object has been picked. Those of you familiar with the tutorials at www.gametutorials.com should recognize most of this code. I changed a few things although the majority of it is the same. Here is the RenderScene() function that is called every frame.
void RenderScene() 
{
	glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();

	//Display the name of the current selected file object

	glDrawText(30,30,CurrentFile.c_str());

	//This is the Camera object, it works, this is NOT the problem

	g_Camera.Look();

	glColor3ub(0,255,0);

	//Draw a nice green grid so we have a way to reference just where we are

	for (float i=-50;i<=50;i+=1)
	{
		glBegin(GL_LINES);

		glVertex3f(-50,0,i);
		glVertex3f(50,0,i);

		glVertex3f(i,0,-50);
		glVertex3f(i,0,50);

		glEnd();
	}

	//This is where we draw the file objects

	for (int x=0;x<g_Files.size();x++)
	{
		//Create a new quadratic object

		GLUquadricObj* pObj=gluNewQuadric();

		//Push the objects symbol name, this is what lets us select the object

		glPushName(g_Files[x].dwFileID);
		
		glPushMatrix();

		//We want a nice wire-frame style

		gluQuadricDrawStyle(pObj,GLU_LINE);

		//Draw the sphere at the desired coordinates

		glTranslatef(g_Files[x].x,g_Files[x].y,g_Files[x].z);

		//If it is selected make it a blue sphere rather than a green sphere

		if (g_Files[x].bSelected)
		{
			glColor3ub(0,0,255);
		}

		//Draw the sphere

		gluSphere(pObj,0.5f,20,20);

		glColor3ub(0,255,0);

		glPopMatrix();

		//This makes us stop associating things with the current symbol name

		glPopName();

		//Free the memory used by the quadratic

		gluDeleteQuadric(pObj);
	}

	SwapBuffers(g_hDC);
}
Here is the message handling procedure, this gets the click events.
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMessage, WPARAM wParam, LPARAM lParam)
{
	LONG lResult=0; 
	PAINTSTRUCT ps;
	DWORD dwObjectID=0;

	switch (uMessage)
	{ 
		case WM_PAINT:		BeginPaint(hWnd,&ps);
					EndPaint(hWnd,&ps);
					break;
		case WM_KEYDOWN:	switch(wParam)
					{
						case VK_ESCAPE:	PostQuitMessage(0);
								break;
					}
					break;
					//Get the object ID at the location the x,y coordinates (if one exists)

		case WM_LBUTTONDOWN:	dwObjectID=RetrieveObjectID(LOWORD(lParam),HIWORD(lParam));

					//If an object was selected

					if (dwObjectID)
					{
						//Clear all the previously selected objects

						for (int x=0;x<g_Files.size();x++)
						{
							g_Files[x].bSelected=FALSE;
						}

						//Make sure the selected object will turn blue (starting ID was 100 so we need to subtract 100)

						g_Files[dwObjectID-100].bSelected=TRUE;

						//Set the current file name (to be displayed)

						CurrentFile=g_Files[dwObjectID-100].FileName;
					}
					break;
		case WM_CLOSE:		PostQuitMessage(0);
					break; 
		default:		lResult=DefWindowProc(hWnd,uMessage,wParam,lParam); 
					break; 
	} 
 
	return lResult;
}
Here is the function that gets the ID of the selected object if one exists.
DWORD RetrieveObjectID(int x, int y)
{
	//This will hold the amount of objects clicked

	int objectsFound=0;
	//We need an array to hold our view port coordinates

	int viewportCoords[4]={0};
	//This will hold the ID''s of the objects we click on.

	//We make it an arbitrary number of 32 because openGL also stores other information

	//that we don''t care about.  There is about 4 slots of info for every object ID taken up.

	unsigned int selectBuffer[32]={0};

	//glSelectBuffer is what we register our selection buffer with.  The first parameter

	//is the size of our array.  The next parameter is the buffer to store the information found.

	//More information on the information that will be stored in selectBuffer is further below.

	glSelectBuffer(32,selectBuffer);

	//This function returns information about many things in OpenGL.  We pass in GL_VIEWPORT

	//to get the view port coordinates.  It saves it like a RECT with {top, left, bottom, right}

	glGetIntegerv(GL_VIEWPORT,viewportCoords);

	//Now we want to get out of our GL_MODELVIEW matrix and start effecting our

	//GL_PROJECTION matrix.  This allows us to check our X and Y coords against 3D space.

	glMatrixMode(GL_PROJECTION);

	//We push on a new matrix so we don''t effect our 3D projection

	glPushMatrix();

	//This makes it so it doesn''t change the frame buffer if we render into it, instead, 

	//a record of the names of primitives that would have been drawn if the render mode was

	//GL_RENDER are now stored in the selection array (selectBuffer).

	glRenderMode(GL_SELECT);

	//Reset our projection matrix

	glLoadIdentity();

	//gluPickMatrix allows us to create a projection matrix that is around our

	//cursor.  This basically only allows rendering in the region that we specify.

	//If an object is rendered into that region, then it saves that objects ID for us (The magic).

	//The first 2 parameters are the X and Y position to start from, then the next 2

	//are the width and height of the region from the starting point.  The last parameter is

	//of course our view port coordinates.  You will notice we subtract "y" from the

	//BOTTOM view port coordinate.  We do this to flip the Y coordinates around.  The 0 y

	//coordinate starts from the bottom, which is opposite to window''s coordinates.

	//We also give a 2 by 2 region to look for an object in.  This can be changed to preference.

	gluPickMatrix(x,viewportCoords[3]-y,2,2,viewportCoords);

	//Next, we just call our normal gluPerspective() function, exactly as we did on startup.

	//This is to multiply the perspective matrix by the pick matrix we created up above. 

	gluPerspective(45.0f,(float)g_rRect.right/(float)g_rRect.bottom,0.1f,150.0f);

	//Go back into our model view matrix

	glMatrixMode(GL_MODELVIEW);

	//Now we render into our selective mode to pinpoint clicked objects

	RenderScene();

	//If we return to our normal render mode from select mode, glRenderMode returns

	//the number of objects that were found in our specified region (specified in gluPickMatrix())

	//Return to render mode and get the number of objects found

	objectsFound=glRenderMode(GL_RENDER);

	//Put our projection matrix back to normal.

	glMatrixMode(GL_PROJECTION);

	//Stop effecting our projection matrix

	glPopMatrix();
	
	//Go back to our normal model view matrix

	glMatrixMode(GL_MODELVIEW);

	if (objectsFound>0)
	{
		//If we found more than one object, we need to check the depth values

		//of all the objects found.  The object with the LEAST depth value is

		//the closest object that we clicked on.  Depending on what you are doing,

		//you might want ALL the objects that you clicked on (if some objects were

		//behind the closest one), but for this tutorial we just care about the one

		//in front.  So, how do we get the depth value?  Well, The selectionBuffer

		//holds it.  For every object there is 4 values.  The first value is

		//"the number of names in the name stack at the time of the event, followed 

		//by the minimum and maximum depth values of all vertices that hit since the 

		//previous event, then followed by the name stack contents, bottom name first." - MSDN

		//The only ones we care about are the minimum depth value (the second value) and

		//the object ID that was passed into glLoadName() (This is the fourth value).

		//So, [0 - 3] is the first object''s data, [4 - 7] is the second object''s data, etc...

		//Be carefull though, because if you are displaying 2D text in front, it will

		//always find that as the lowest object.  So make sure you disable text when

		//rendering the screen for the object test.  I use a flag for RenderScene().

		//So, lets get the object with the lowest depth!		


		//Set the lowest depth to the first object to start it off.

		//1 is the first object''s minimum Z value.

		//We use an unsigned int so we don''t get a warning with selectBuffer below.

		unsigned int lowestDepth=selectBuffer[1];

		//Set the selected object to the first object to start it off.

		//3 is the first object''s object ID we passed into glLoadName().

		int selectedObject=selectBuffer[3];

		//Go through all of the objects found, but start at the second one

		for (int i=1;i<objectsFound;i++)
		{
			//Check if the current objects depth is lower than the current lowest

			//Notice we times i by 4 (4 values for each object) and add 1 for the depth.

			if(selectBuffer[(i*4)+1]<lowestDepth)
			{
				//Set the current lowest depth

				lowestDepth=selectBuffer[(i*4)+1];

				//Set the current object ID

				selectedObject=selectBuffer[(i*4)+3];
			}
		}

		//Return the selected object

		return selectedObject;
	}

	//We didn''t click on any objects so return 0

	return 0;
}
By itself all that worked just beautifully. In fact I would go as far as saying it was perfect for what I''m trying to do. Now for the part where we display text. This is where I ran into some problems. Namely it did not allow me to select an object. I think it has to do with the fact that each seperate part does stuff to the matrix mode which I could see causing conflicts. The problem is I''m rather new at all this so I''m not sure what can be done to fix it. Here is the code that creates the font (no problem here).
//Global variable holding the font list ID

UINT g_FontListID=0;
//Global handle to save the old font, this is for preventing memory leaks and other nasty things

HFONT g_hOldFont;
//This is called in WinMain

g_FontListID=CreateOpenGLFont("Arial",32);

//This creates the font

UINT CreateOpenGLFont(LPSTR lpszFontName, int iHeight)
{
	//This will hold the base ID for our display list

	UINT fontListID=0;
	//This will store the handle to our font

	HFONT hFont;

	//Here we generate the lists for each character we want to use.

	//This function then returns the base pointer, which will be 1 because

	//we haven''t created any other lists.  If we generated another list after

	//this, the base pointer would be at 257 since the last one used was 256 (which is MAX_CHARS)

	fontListID=glGenLists(256);

	//Now we actually need to create the font.  We use a windows function called:

	//CreateFont() that returns a handle to a font (HFONT).  Our CreateOpenGLFont()

	//function allows us to pass in a name and height.  For simplistic reasons, I left

	//other options out, but feel free to add them to your function (like bold, italic, width..)

	hFont=CreateFont(iHeight,0,0,0,FW_BOLD,FALSE,FALSE,FALSE,ANSI_CHARSET,OUT_TT_PRECIS,CLIP_DEFAULT_PRECIS,ANTIALIASED_QUALITY,FF_DONTCARE|DEFAULT_PITCH,lpszFontName);

	//Now that we have created a new font, we need to select that font into our global HDC.

	//We store the old font so we can select it back in when we are done to avoid memory leaks.

	hOldFont=(HFONT)SelectObject(g_hDC,hFont);

	//This function does the magic.  It takes the current font selected in

	//the hdc and makes bitmaps out of each character.  These are called glyphs.

	//The first parameter is the HDC that holds the font to be used.

	//The second parameters is the ASCII value to start from, which is zero in our case.

	//The third parameters is the ASCII value to end on (255 is the last of the ASCII values so we minus 1 from MAX_CHARS)

	//The last parameter is the base pointer for the display lists being used.  This should be 1.

	wglUseFontBitmaps(g_hDC,0,255,fontListID);

	//Return the ID to the display list to use later

	return fontListID;
}
Here is the function that sets the position of the text, I think THIS is what is messing things up, since the RetrieveObjectID function calls RenderScene and RenderScene calls glDrawText which calls this function it seems to me that everytime we want to get the selected object everything gets messed up.
void PositionText(int x, int y)
{
	//If you are to use this font code for your applications,

	//you must be aware that you cannot position the font in 3D,

	//which means you can''t rotate and scale it.  That will be covered in

	//the next font tutorial.  BUT, though that might be a drag, this code

	//is useful because when you display the text, it will always be on top

	//of everything else.  This is good if the camera is moving around, and you

	//don''t want the text to move.  If the text was positioned in 3D you would have

	//to come up with a tricky way of making it always render in front of the camera.

	//To do this, we need to set the Raster Position.  That is the position that OpenGL

	//starts drawing at.  Since it''s in floating point, it''s not very intuitive, so what

	//we do is create a new view port, and then always draw the text at (0, 0, 0) in that

	//view port.  The weird part is that the Y is flipped, so (0, 0) is the bottom left corner.

	//Below we do some simple math to flip it back to normal.


	//Before we create a new view port, we need to save the current one we have.

	//This saves our transform (matrix) information and our current viewport information.

	//At the end of this function we POP it back.

	glPushAttrib(GL_TRANSFORM_BIT|GL_VIEWPORT_BIT);

	//Here we use a new projection and modelview matrix to work with.

	//Set our matrix to our projection matrix

	glMatrixMode(GL_PROJECTION);
	//Push on a new matrix to work with

	glPushMatrix();
	//reset the matrix

	glLoadIdentity();
	//Set our matrix to our model view matrix

	glMatrixMode(GL_MODELVIEW);
	//Push on a new matrix to work with

	glPushMatrix();
	//Reset that matrix

	glLoadIdentity();

	//Because the Y is flipped, we want 0 to be at the top, not bottom.

	//If we subtract the font height from the screen height, that should display the

	//font at the top of the screen (if they passed in 0 for Y), but then we subtract

	//the Y from that to get the desired position.  Since the font''s drawing point is

	//at the base line of the font, we needed to subtract the font height to make sure

	//if they passed in (0, 0) it wouldn''t be off screen.  If you view this in window mode,

	//the top of the window will cut off part of the font, but in full screen it works fine.

	//You just need to add about 25 to the Y to fix that for window mode.

	//Calculate the weird screen position

	y=GetSystemMetrics(SM_CYSCREEN)-32-y;

	//Now we create another view port (that is why we saved the old one above).

	//Since glViewPort takes the lower LEFT corner, we needed to change the Y

	//to make it more intuitive when using PositionText().  We minus 1 from the X and Y

	//because 0 is taken into account with the position.  The next 2 parameters are set

	//to 0 for the width and height so it will always draw in the middle of that position.

	//glRasterPos4f() takes (0, 0, 0) as the middle of the viewport, so if we give it a small

	//width/height it will draw at the X and Y given.  Sounds strange, to test this, try

	//using glRasterPos4f(0, 0, 0, 1) instead of PositionText() and you will see, everything

	//will be drawn from the middle.

	//Create a new viewport to draw into

	glViewport( x - 1, y - 1, 0, 0 );

	//This is the most important function in here.  This actually positions the text.

	//The parameters are (x, y, z, w).  w should always be 1 , it''s a clip coordinate.

	//don''t worry about that though.  Because we set the projection and modelview matrix

	//back to the beginning (through LoadIdentity()), the view port is looking at (0, 0, 0).

	//This is the middle, so if we set the drawing position to the middle, it will draw at our

	//X and Y because the width/height of the viewport is 0, starting at X and Y.

	//You can actually call this function (or glRasterPos2f(0, 0)) instead of PositionText(),

	//but it is in floating point and doesn''t work as nicely.  You will see why if you try.

	//Set the drawing position

	glRasterPos4f(0,0,0,1);

	//Now that we positioned the raster position, any text we draw afterwards will start

	//from that position.  Now we just have to put everything else back to normal.

	//Pop the current modelview matrix off the stack

	glPopMatrix();
	//Go back into projection mode

	glMatrixMode(GL_PROJECTION);
	//Pop the projection matrix off the stack

	glPopMatrix();

	//This restores our TRANSFORM and VIEWPORT attributes

	glPopAttrib();
}
Now finally the function that does the drawing, this is probably not the problem...
void glDrawText(int x, int y, const char *lpszString, ...)
{
	//This will hold our text to display

	char strText[256];
	//This will hold the pointer to the argument list

	va_list argumentPtr;

	//If you have never used a va_list, listen up.  Remember printf()?

	//or sprintf()?  Well, you can add unlimited arguments into the text like:

	//printf("My name is %s and I am %d years old!", strName, age);

	//Well, that is what va_list''s do.  


	// First we need to check if there was even a string given

	if (lpszString==NULL)
	{
		//Don''t render anything then

		return;
	}

	//First we need to parse the string for arguments given

	//To do this we pass in a va_list variable that is a pointer to the list of arguments.

	//Then we pass in the string that holds all of those arguments.

	va_start(argumentPtr,lpszString);

	//Then we use a special version of sprintf() that takes a pointer to the argument list.

	//This then does the normal sprintf() functionality.

	vsprintf(strText,lpszString,argumentPtr);

	//This resets and frees the pointer to the argument list.

	va_end(argumentPtr);

	// Before we draw the text, we need to position it with our own function.

	PositionText(x,y);

	//Now, before we set the list base, we need to save off the current one.

	glPushAttrib(GL_LIST_BIT);

	//Then we want to set the list base to the font''s list base, which should be 1 in our case.

	//That way when we call our display list it will start from the font''s lists''.

	glListBase(g_FontListID);

	//Now comes the actually rendering.  We pass in the length of the string,

	//then the data types (which are characters so its a UINT), then the actually char array.

	//This will then take the ASCII value of each character and associate it with a bitmap.

	glCallLists(strlen(strText),GL_UNSIGNED_BYTE,strText);

	glPopAttrib();
}


Just some code that will destroy the font, etc.

[source]
void DestroyFont()
{
	glDeleteLists(g_FontListID,256);
	SelectObject(g_hDC,g_hOldFont);
}
If anyone knows another method of doing either thing that would not conflict with each other that would be good. But if anyone knows how to fix the conflict in the existing code that would be awesome. Bungo!

Share this post


Link to post
Share on other sites
Probably not a solution, but just a few suggestions.

First, don''t render the same stuff for selection as for normal drawing. When you''re rendering in selection mode, only draw that which you wish to be selectable. If you don''t need text to be selectable, so don''t draw it. Also, if you ever have a high-poly model, don''t render it in selection mode. It''ll be very slow and you probably won''t ever need that kind of selection precision. If you want it selectable, render a low-poly version or a collision hull in its place when in selection mode.

Share this post


Link to post
Share on other sites
ah!!! thank you! that DID work. i just go through all the motions of RenderScene() MINUS the text stuff and it works PERFECTLY. i didnt quite understand the rest of your post. but thank you so much for your help. as far as making it all faster im more worried about getting it all working first, THEN ill optimize the hell out of it. thank you very much though =)

Bungo!

Share this post


Link to post
Share on other sites
Good to hear!

Anyway, the problem was probably related to the text using a different projection matrix and viewport. If you want to eventually have selection for your 2D GUI items, I suggest detecting selection separately from the rest of the scene. There''s some really easy ways to do this, such as using an ortho matrix that corresponds exactly to viewport dimensions. This allows one to convert 2D mouse positions simply by fliping the Y-axis.

Share this post


Link to post
Share on other sites