目前因為需要把建立在TreeICtrl的物件,丟到OpenGL上做顯示,因此需要可以抓到目前滑鼠指標放置TreeCtrl物件的位置跟對象。
目前已知的作法是透過Frustum Culling可以得知目前滑鼠所選擇的區域。但以目前的能力來講,這難度比較高。
因此這邊選用OpenGL提供的Selection Mode來達到需求。
目前要放置的對象為2D Grid,所以目前要抓的是,滑鼠指標是在那個Cell上。
程式參考Nehe的範例,但由於還有些項目跟數值不清楚,所以還是以Nehe提供的數值跟說明為主。
目前這邊是當作筆記保留,所以不加其他說明,避免誤導社會大眾。
底下為實際運作流程:
1.建立滑鼠事件觸發後的函式:
void COpenGLDrawer::OnObjectSelection(CPoint point, COLORREF clrPickColor)
{
GLuint buffer[512]; // Set Up A Selection Buffer
GLint hits = 0; // The Number Of Objects That We Selected
//Now we set up a viewport. viewport[] will hold the current x, y, length and width of the current viewport (OpenGL Window).
//glGetIntegerv(GL_VIEWPORT, viewport) gets the current viewport boundries and stores them in viewport[].
// Initially, the boundries are equal the the OpenGL window dimensions.
// glSelectBuffer(512, buffer) tells OpenGL to use buffer for it's selection buffer.
// The Size Of The Viewport. [0] Is <x>, [1] Is <y>, [2] Is <length>, [3] Is <width>
GLint viewport[4];
// This Sets The Array <viewport> To The Size And Location Of The Screen Relative To The Window
glGetIntegerv(GL_VIEWPORT, viewport);
glSelectBuffer(512, buffer); // Tell OpenGL To Use Our Array For Selection
//All of the code below is very important. The first line puts OpenGL in selection mode.
// In selection mode, nothing is drawn to the screen.
// Instead, information about objects rendered while in selection mode will be stored in the selection buffer.
//Next we initialize the name stack by calling glInitNames() and glPushName(0).
// It's important to note that if the program is not in selection mode, a call to glPushName() will be ignored.
// Of course we are in selection mode, but it's something to keep in mind.
// Puts OpenGL In Selection Mode. Nothing Will Be Drawn. Object ID's and Extents Are Stored In The Buffer.
(void) glRenderMode(GL_SELECT);
glInitNames(); // Initializes The Name Stack
glPushName(0); // Push 0 (At Least One Entry) Onto The Stack
//After preparing the name stack, we have to to restrict drawing to the area just under our crosshair.
// In order to do this we have to select the projection matrix. After selecting the projection matrix we push it onto the stack.
// We then reset the projection matrix using glLoadIdentity().
//We restrict drawing using gluPickMatrix().
// The first parameter is our current mouse position on the x-axis, the second parameter is the current mouse position on the y-axis,
// then the width and height of the picking region. Finally the current viewport[].
// The viewport[] indicates the current viewport boundaries. mouse_x and mouse_y will be the center of the picking region.
glMatrixMode(GL_PROJECTION); // Selects The Projection Matrix
glPushMatrix(); // Push The Projection Matrix
glLoadIdentity(); // Resets The Matrix
// This Creates A Matrix That Will Zoom Up To A Small Portion Of The Screen, Where The Mouse Is.
/*gluPickMatrix((GLdouble) mouse_x, (GLdouble) (viewport[3]-mouse_y), 1.0f, 1.0f, viewport);*/
gluPickMatrix((GLdouble) point.x, (GLdouble) (viewport[3]-point.y), 1.0f, 1.0f, viewport); //輸入為Windows Coordinate of DrawScene
//Calling gluPerspective() multiplies the perspective matrix by the pick matrix which restricts the drawing to the area requested by
// gluPickMatrix().
//We then switch to the modelview matrix and draw our targets by calling DrawTargets().
// We draw the targets in DrawTargets() and not in Draw() because we only want selection to check for hits with objects (targets) and
// not the sky, ground or crosshair.
//After drawing our targets, we switch back to the projection matrix and pop the stored matrix off the stack.
// We then switch back to the modelview matrix.
//The last line of code below switches back to render mode so that objects we draw actually appear on the screen.
// hits will hold the number of objects that were rendered in the viewing area requested by gluPickMatrix().
// Apply The Perspective Matrix
/* gluPerspective(45.0f, (GLfloat) (viewport[2]-viewport[0])/(GLfloat) (viewport[3]-viewport[1]), 0.1f, 100.0f); */
gluPerspective(45.0f, (GLfloat) (viewport[2]-viewport[0])/(GLfloat) (viewport[3]-viewport[1]), 0.1f, 100.0f);
glMatrixMode(GL_MODELVIEW); // Select The Modelview Matrix
DrawTargets(); // Render The Targets To The Selection Buffer
glMatrixMode(GL_PROJECTION); // Select The Projection Matrix
glPopMatrix(); // Pop The Projection Matrix
glMatrixMode(GL_MODELVIEW); // Select The Modelview Matrix
hits=glRenderMode(GL_RENDER); // Switch To Render Mode, Find Out How Many
//Now we check to see if there were more than 0 hits recorded.
// If so, we set choose to equal the name of the first object drawn into the picking area.
// depth holds how deep into the screen, the object is.
//Each hit takes 4 items in the buffer. The first item is the number of names on the name stack when the hit occured.
// The second item is the minimum z value of all the verticies that intersected the viewing area at the time of the hit.
// The third item is the maximum z value of all the vertices that intersected the viewing area at the time of the hit and the last item
// is the content of the name stack at the time of the hit (name of the object).
// We are only interested in the minimum z value and the object name in this tutorial.
if (hits > 0) // If There Were More Than 0 Hits
{
int choose = buffer[3]; // Make Our Selection The First Object
int depth = buffer[1]; // Store How Far Away It Is
//We then loop through all of the hits to make sure none of the objects are closer than the first object hit.
// If we didn't do this, and two objects were overlapping, the first object hit might behind another object,
// and clicking the mouse would take away the first object, even though it was behind another object. When you shoot at something,
// the closest object should be the object that gets hit.
//So, we check through all of the hits.
// Remember that each object takes 4 items in the buffer, so to search through each hit we have to multiply the current loop value by 4.
// We add 1 to get the depth of each object hit.
// If the depth is less than the the current selected objects depth,
// we store the name of the closer object in choose and we store the depth of the closer object in depth.
// After we have looped through all of our hits, choose will hold the name of the closest object hit,
// and depth will hold the depth of the closest object hit.
/*
for (int loop = 1; loop < hits; loop++) // Loop Through All The Detected Hits
{
// If This Object Is Closer To Us Than The One We Have Selected
if (buffer[loop*4+1] < GLuint(depth))
{
choose = buffer[loop*4+3]; // Select The Closer Object, sample use the offset 3.
depth = buffer[loop*4+1]; // Store How Far Away It Is
}
} //End of for (int loop = 1; loop < hits; loop++)
*/
//All we have to do is mark the object as being hit. We check to make sure the object has not already been hit.
// If it has not been hit, we mark it as being hit by setting hit to TRUE.
// We increase the players score by 1 point, and we increase the kills counter by 1.
/*
if (!object[choose].hit) // If The Object Hasn't Already Been Hit
{
object[choose].hit=TRUE; // Mark The Object As Being Hit
score+=1; // Increase Score
kills+=1; // Increase Level Kills
*/
this->m_vcdsGrid4AStar[choose].IsWarkable = !this->m_vcdsGrid4AStar[choose].IsWarkable;
this->m_vcdsGrid4AStar[choose].clrSelectedColor = clrPickColor;
//I use kills to keep track of how many objects have been destroyed on each level.
// I wanted each level to have more objects (making it harder to get through the level).
// So I check to see if the players kills is greater than the current level multiplied by 5.
// On level 1, the player only has to kill 5 objects (1*5).
// On level 2 the player has to kill 10 objects (2*5), progressively getting harder each level.
//So, the first line of code checks to see if kills is higher than the level multiplied by 5. If so, we set miss to 0.
// This sets the player morale back to 10 out of 10 (the morale is 10-miss).
// We then set kills to 0 (which starts the counting process over again).
//Finally, we increase the value of level by 1 and check to see if we've hit the last level.
// I have set the maximum level to 30 for the following two reasons... Level 30 is insanely difficult.
// I am pretty sure no one will ever have that good of a game. The second reason... At the top of the code, we only set up 30 objects.
// If you want more objects, you have to increase the value accordingly.
//It is VERY important to note that you can have a maximum of 64 objects on the screen (0-63).
// If you try to render 65 or more objects, picking becomes confused, and odd things start to happen.
// Everything from objects randomly exploding to your computer crashing. It's a physical limit in OpenGL (just like the 8 lights limit).
//If by some chance you are a god, and you finish level 30, the level will no longer increase, but your score will.
// Your morale will also reset to 10 every time you finish the 30th level.
/*
if (kills>level*5) // New Level Yet?
{
miss=0; // Misses Reset Back To Zero
kills=0; // Reset Level Kills
level+=1; // Increase Level
if (level>30) // Higher Than 30?
level=30; // Set Level To 30 (Are You A God?)
}
} //End of if (!object[choose].hit)
*/
} //End of if (hits > 0)
}
2.建立當select mode選用後,用來搜尋滑鼠指標點選的圖形。由於這邊是用線條構成的grid,所以點選時,其實是點不到繪製的物件。因此在這邊搜尋用的圖形,全部改用填滿的grid來作為搜尋的依據。
裡面有個很關鍵的一行,就是在繪製搜尋用的圖像前,要先把要繪製的view port座標移動到目前看到已經畫出來的model相同對應的位置。否則可能會造成選擇的區域不正確
void COpenGLDrawer::DrawTargets(void)
{
glLoadIdentity(); // Reset The Modelview Matrix
/* glTranslatef(0.0f,0.0f, -10.0f); // Move Into The Screen 20 Units //NOTE:This is the KEY for Picking!!! */
glTranslatef( this->m_fPosX, this->m_fPosY, -20);
/*for (int loop=0; loop<level; loop++) // Loop Through 9 Objects*/
for ( unsigned int loop = 0 ; loop < this->m_vcdsGrid.size() ; loop++ )
{
//The first line of code is the secret to picking individual objects.
// What it does is assigns a name (number) to each object. The first object drawn will be 0.
// The second object will be 1, etc... If the loop was to hit 29, the last object drawn would be given the name 29.
// After assigning a name to the object, we push the modelview matrix onto the stack.
// It's important to note the calls to glLoadName() are ignored if the program is not in selection mode.
//We then move to the location on the screen where we want our object to be drawn.
// We use object[loop].x to position on the x-axis, object[loop].y to position on the y-axis and object[loop].distance to position
// the object on the z-axis (depth into the screen).
// We have already translated 10 units into the screen, so the actual distance at which the object will be drawn is going to
// be object[loop].distance-10.0f.
glLoadName(loop); // Assign Object A Name (ID)
glPushMatrix(); // Push The Modelview Matrix
/*glTranslatef(object[loop].x,object[loop].y,object[loop].distance); // Position The Object (x,y)*/
//Before we draw the object, we have to check if it's been hit or not.
// We do this by checking to see if object[loop].hit is TRUE.
// If it is, we jump to Explosion(loop) which will draw the explosion animation instead of the actual object.
// If the object was not hit, we spin the object on it's z-axis by object[loop].spin degrees before we call Object().
//Object takes 3 parameters. The first one is the width, the second one is the height and the third one is the number of the texture to use.
// To get the width and height, we use the array size[object[loop].texid].w and size[object[loop].texid].h.
// This looks up the width and height from our predefined object size array at the beginning of this program.
// The reason we use object[loop].texid is because it represents the type of object we are drawing.
// A texid of 0 is always the blueface... a texid of 3 is always the coke can, etc.
//After drawing an object, we pop the matrix resetting the view, so our next object is drawn at the proper location on the screen.
/* if (object[loop].hit) // If Object Has Been Hit
{
Explosion(loop); // Draw An Explosion
}
else // Otherwise
{
glRotatef(object[loop].spin,0.0f,0.0f,1.0f); // Rotate The Object
Object(size[object[loop].texid].w,size[object[loop].texid].h,object[loop].texid); // Draw The Object
}
*/
//if ( !this->m_vcdsGrid4AStar[loop].IsWarkable )
//{
// glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
// glBegin(GL_QUADS);
// glColor3f(0.48f, 0.44f, 1.0f); //Light Slate Blue
// glVertex2f(this->m_vcdsGrid4AStar[loop].cdsGridInfo.LB.x, this->m_vcdsGrid4AStar[loop].cdsGridInfo.LB.y);
// glVertex2f(this->m_vcdsGrid4AStar[loop].cdsGridInfo.RB.x, this->m_vcdsGrid4AStar[loop].cdsGridInfo.RB.y);
// glVertex2f(this->m_vcdsGrid4AStar[loop].cdsGridInfo.RT.x, this->m_vcdsGrid4AStar[loop].cdsGridInfo.RT.y);
// glVertex2f(this->m_vcdsGrid4AStar[loop].cdsGridInfo.LT.x, this->m_vcdsGrid4AStar[loop].cdsGridInfo.LT.y);
// glEnd();
//} //End of if ( !this->m_vcdsGrid4AStar[loop].IsWarkable )
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); //Set polygon rasterization mode
//Must fill it and the
glBegin(GL_QUADS); //Start to draw squares
glColor3f(0.0f, 0.0f, 0.0f); //Black
for ( unsigned int i = 0 ; i < this->m_vcdsGrid.size() ; i++ )
{
//3D
//glVertex3f(this->m_vcdsGrid[i].LB.x, this->m_vcdsGrid[i].LB.y, this->m_vcdsGrid[i].LB.z);
//glVertex3f(this->m_vcdsGrid[i].RB.x, this->m_vcdsGrid[i].RB.y, this->m_vcdsGrid[i].RB.z);
//glVertex3f(this->m_vcdsGrid[i].RT.x, this->m_vcdsGrid[i].RT.y, this->m_vcdsGrid[i].RT.z);
//glVertex3f(this->m_vcdsGrid[i].LT.x, this->m_vcdsGrid[i].LT.y, this->m_vcdsGrid[i].LT.z);
//2D
//glVertex2f(this->m_vcdsGrid[i].LB.x, this->m_vcdsGrid[i].LB.y);
//glVertex2f(this->m_vcdsGrid[i].RB.x, this->m_vcdsGrid[i].RB.y);
//glVertex2f(this->m_vcdsGrid[i].RT.x, this->m_vcdsGrid[i].RT.y);
//glVertex2f(this->m_vcdsGrid[i].LT.x, this->m_vcdsGrid[i].LT.y);
glVertex2f(this->m_vcdsGrid4AStar[loop].cdsGridInfo.LB.x, this->m_vcdsGrid4AStar[loop].cdsGridInfo.LB.y);
glVertex2f(this->m_vcdsGrid4AStar[loop].cdsGridInfo.RB.x, this->m_vcdsGrid4AStar[loop].cdsGridInfo.RB.y);
glVertex2f(this->m_vcdsGrid4AStar[loop].cdsGridInfo.RT.x, this->m_vcdsGrid4AStar[loop].cdsGridInfo.RT.y);
glVertex2f(this->m_vcdsGrid4AStar[loop].cdsGridInfo.LT.x, this->m_vcdsGrid4AStar[loop].cdsGridInfo.LT.y);
} //End of for ( unsigned int i = 0 ; i < this->m_vcpGrid.size() ; i++ )
glEnd();
glPopMatrix(); // Pop The Modelview Matrix
} //End of for (int loop=0; loop<level; loop++)
}
主要在做select動作,是透過這兩個函式,使用到的關鍵function為glInitNames()、glLoadName()、glPushName()。
至於其他網路上的範例會在用來更新畫面的Draw()裡面放glLoadName(),目前的測試是,沒放也沒關係。所以看起來他們只是要整並更新方式而已。
No comments:
Post a Comment