[OpenGL]選取顯示的物件-2D, Pick

Ref:Nehe's Openg - Lesson 32

目前因為需要把建立在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

Build docker image from multiple build contexts

Build docker image from multiple build contexts ...