[OpenGL]2D Texture Mapping

Reference:Nehe's OpenGL - Lesson 17

目前工作僅需要將BMP圖片顯示在GL Scene裡面,所以就僅擷取出貼圖片材質的部分。並附上原網頁的註解,已方便對照跟查詢。由於是原文貼上,所以有些用語會有有前後不接的狀況,所以想瞭解原始的意義,請至Hehe的網頁觀看。

0.在開始進行前,先建立存放Texture的全域變數。
 ....  
 GLuint m_glTextureList[DS_GL_MAX_TEXTURE];  
 ....  

1.建立讀取BMP檔的函式。由於此函式有用到auxDIBImageLoad,所以要在*.h引用gl\glaux.h。
CDSFileIO為自訂的類別,目的是要檢查檔案是否存在,不存在的話,讀了也沒有意義。原文有提到圖片大小的建議,目前因為要配合TreeList顯示的問題,所以是讀取16*16的大小的圖片,於Windows XP SP2測試是正常。
 //----------- L.6 ----------------  
 //Now immediately after the above code, and before ReSizeGLScene(), we want to add the following section of code.   
 //    The job of this code is to load in a bitmap file. If the file doesn't exist NULL is sent back meaning the texture couldn't be loaded.   
 //    Before I start explaining the code there are a few VERY important things you need to know about the images you plan to use as textures.   
 //    The image height and width MUST be a power of 2.   
 //    The width and height must be at least 64 pixels, and for compatability reasons, shouldn't be more than 256 pixels.   
 //    If the image you want to use is not 64, 128 or 256 pixels on the width or height, resize it in an art program.   
 //    There are ways around this limitation, but for now we'll just stick to standard texture sizes.   
 //First thing we do is create a file handle. A handle is a value used to identify a resource so that our program can access it.   
 //    We set the handle to NULL to start off.  
 //----------- L.6 ----------------  
 AUX_RGBImageRec *COpenGLDrawer::LoadBMP(const char *szFilePath)  
 {  
     if ( CDSFileIO::IsFileExisted(szFilePath) ) {  
         return auxDIBImageLoad(szFilePath);                // Load The Bitmap And Return A Pointer  
     }    //End of if ( CDSFileIO::IsFileExisted(szFilePath) )  
     return NULL;                                        // If Load Failed Return NULL  
 }  

2.建立設定Texture的函式。DS_GL_MAX_TEXTURE為自訂常數,用來定義讀取BMP的Texture buffer大小。所以就看需求定義即可。由於是非動態宣告buffer大小,所以要小心邊界的問題。
 //The follwing code has also changed very little from the code used in previous tutorials.   
 //    If you're not sure what each of the following lines do, go back and review.   
 //Note that TextureImage[ ] is going to hold 2 rgb image records.   
 //    It's very important to double check code that deals with loading or storing our textures.   
 //    One wrong number could result in a memory leak or crash!  
 // Load Bitmaps And Convert To Textures  
 int COpenGLDrawer::LoadGLTextures(std::vector<std::string> vstrFilePaths)                                  
 {  
     int Status = TRUE;  
     AUX_RGBImageRec *TextureImage[DS_GL_MAX_TEXTURE];        // Create Storage Space For The Textures  
           
 //The next line is the most important line to watch. If you were to replace the 2 with any other number, major problems will happen.   
 //    Double check! This number should match the number you used when you set up TextureImages[ ].   
 //The two textures we're going to load are font.bmp (our font), and bumps.bmp.   
 //    The second texture can be replaced with any texture you want.   
 //    I wasn't feeling very creative, so the texture I decided to use may be a little drab.              
 /*    memset(TextureImage,0,sizeof(void *)*2);                // Set The Pointer To NULL    */  
     memset(TextureImage,0,sizeof(void *)*DS_GL_MAX_TEXTURE);    // Set The Pointer To NULL  
   
     for ( unsigned int i = 0 ; i < vstrFilePaths.size() ; i++ )  
     {  
         if ( !(TextureImage[i] = this->LoadBMP(vstrFilePaths[i].c_str())) )        //有一個讀不到就不繼續執行,不然就是要改塞白色。  
         {  
             Status = FALSE;  
         }  
     }    //End of for ( unsigned int i = 0 ; i < vstrFilePaths.size() ; i++ )          
   
     if ( !Status ) {  
         return Status; };  
   
 //Another important line to double check.   
 //    I can't begin to tell you how many emails I've received from people asking "why am I only seeing one texture,   
 //        or why are my textures all white!?!".   
 //    Usually this line is the problem.   
 //    If you were to replace the 2 with a 1, only one texture would be created and the second texture would appear all white.   
 //    If you replaced the 2 with a 3 you're program may crash!   
 //You should only have to call glGenTextures() once. After glGenTextures() you should generate all your textures.   
 //    I've seen people put a glGenTextures() line before each texture they create.   
 //    Usually they causes the new texture to overwrite any textures you've already created.   
 //    It's a good idea to decide how many textures you need to build, call glGenTextures() once, and then build all the textures.   
 //    It's not wise to put glGenTextures() inside a loop unless you have a reason to.          
 //--------- L.6 ------------  
 //Now that we've loaded the image data into TextureImage[0], we will build a texture using this data.   
 //    The first line glGenTextures(1, &texture[0]) tells OpenGL we want to generate one texture name   
 //        (increase the number if you load more than one texture).   
 //    Remember at the very beginning of this tutorial we created room for one texture with the line GLuint texture[1].   
 //    Although you'd think the first texture would be stored at &texture[1] instead of &texture[0], it's not.   
 //    The first actual storage area is 0.   
 //    If we wanted two textures we would use GLuint texture[2] and the second texture would be stored at texture[1].   
 //The second line glBindTexture(GL_TEXTURE_2D, texture[0]) tells OpenGL to bind the named texture texture[0] to a texture target.   
 //    2D textures have both height (on the Y axes) and width (on the X axes).   
 //    The main function of glBindTexture is to assign a texture name to texture data.  
 //    In this case we're telling OpenGL there is memory available at &texture[0].   
 //    When we create the texture, it will be stored in the memory that &texture[0] references.  
 //--------- L.6 ------------  
     glGenTextures((GLsizei)DS_GL_MAX_TEXTURE, &this->m_glTextureList[0]);        //Create n Texture  
   
     for ( unsigned int i = 0 ; i < this->m_vstrTextureFilePath.size() ; i++ )    // Loop Through All The Textures  
     {  
         //Build All The Textures  
         glBindTexture(GL_TEXTURE_2D, this->m_glTextureList[i]);  
   
 //----------- L.6 ---------------  
 //The next two lines tell OpenGL what type of filtering to use when the image is larger (GL_TEXTURE_MAG_FILTER) or   
 //        stretched on the screen than the original texture, or when it's smaller (GL_TEXTURE_MIN_FILTER) on the screen than the actual texture.   
 //    I usually use GL_LINEAR for both. This makes the texture look smooth way in the distance, and when it's up close to the screen.   
 //    Using GL_LINEAR requires alot of work from the processor/video card, so if your system is slow, you might want to use GL_NEAREST.   
 //    A texture that's filtered with GL_NEAREST will appear blocky when it's stretched. You can also try a combination of both.   
 //    Make it filter things up close, but not things in the distance.  
 //----------- L.6 ---------------  
         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);  
         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);  
   
 //----------- L.6 ---------------  
 //Next we create the actual texture. The following line tells OpenGL the texture will be a 2D texture (GL_TEXTURE_2D).   
 //    Zero represents the images level of detail, this is usually left at zero. Three is the number of data components.   
 //    Because the image is made up of red data, green data and blue data, there are three components.   
 //    TextureImage[0]->sizeX is the width of the texture. If you know the width, you can put it here,   
 //        but it's easier to let the computer figure it out for you.   
 //    TextureImage[0]->sizey is the height of the texture. zero is the border.   
 //    It's usually left at zero. GL_RGB tells OpenGL the image data we are using is made up of red, green and blue data in that order.   
 //    GL_UNSIGNED_BYTE means the data that makes up the image is made up of unsigned bytes, and finally...   
 //        TextureImage[0]->data tells OpenGL where to get the texture data from.   
 //    In this case it points to the data stored in the TextureImage[0] record.  
 //----------- L.6 ---------------  
         glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[i]->sizeX, TextureImage[i]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[i]->data);  
     }    //End of for ( int i = 0 ; i < DS_GL_MAX_TEXTURE ; i++ )  
   
 /*        }    */  
   
 //The following lines of code check to see if the bitmap data we loaded to build our textures is using up ram.   
 //    If it is, the ram is freed. Notice we check and free both rgb image records.   
 //    If we used 3 different images to build our textures, we'd check and free 3 rgb image records.  
 //---------- L.6 ---------------  
 //Now we free up any ram that we may have used to store the bitmap data. We check to see if the bitmap data was stored in TextureImage[0].   
 //    If it was we check to see if the data has been stored. If data was stored, we erase it.   
 //    Then we free the image structure making sure any used memory is freed up.  
 //---------- L.6 ---------------  
     for ( int i = 0 ; i < this->m_vstrTextureFilePath.size() ; i++ )  
     {  
         if ( TextureImage[i] )                            // If Texture Exists  
         {  
             if ( TextureImage[i]->data ) {                //If Texture Image Exists  
                 free(TextureImage[i]->data); };            //Free the Texture Image Memory  
   
             free(TextureImage[i]);                        //Free The Image Structure  
         }    //End of if ( TextureImage[i] )  
     }    //End of for ( int i = 0 ; i < DS_GL_MAX_TEXTURE ; i++ )  
   
     return Status;                                // Return The Status  
 }  

3.上述兩個步驟完成後,則在GL做初始化的時候,載入並設定Texture。這邊所需的圖檔路徑皆在物件產生時就先指定好。
 void COpenGLDrawer::glInitialize(void)  
 {  
 ....  
     if ( !this->LoadGLTextures(this->m_vstrTextureFilePath) ) {  
         return;    };  
 ....  
 }  

4.於GL Scene繪製方式如下,關鍵的部分為,要進行Texture Mapping時,要先開啟GL_TEXTURE。繪製完成後,可設定關閉,以釋放記憶體。
 void CDSGLDrawer::glDrawScene(void)  
 {  
 //Show Texture  
 //The next line of code selects which texture we want to use.   
 //    If there was more than one texture you wanted to use in your scene,   
 //        you would select the texture using glBindTexture(GL_TEXTURE_2D, texture[number of texture to use]).   
 //    If you wanted to change textures, you would bind to the new texture.   
 //    One thing to note is that you can NOT bind a texture inside glBegin() and glEnd(), you have to do it before or after glBegin().   
 //    Notice how we use glBindTextures to specify which texture to create and to select a specific texture.      
     glBindTexture(GL_TEXTURE_2D, this->m_glTextureList[0]);                // Select Our Texture  
           
 //To properly map a texture onto a quad, you have to make sure the top right of the texture is mapped to the top right of the quad.   
 //    The top left of the texture is mapped to the top left of the quad,   
 //        the bottom right of the texture is mapped to the bottom right of the quad, and finally,   
 //        the bottom left of the texture is mapped to the bottom left of the quad.   
 //    If the corners of the texture do not match the same corners of the quad, the image may appear upside down, sideways, or not at all.   
 //The first value of glTexCoord2f is the X coordinate.   
 //    0.0f is the left side of the texture. 0.5f is the middle of the texture, and 1.0f is the right side of the texture.   
 //    The second value of glTexCoord2f is the Y coordinate. 0.0f is the bottom of the texture.   
 //    0.5f is the middle of the texture, and 1.0f is the top of the texture.   
 //So now we know the top left coordinate of a texture is 0.0f on X and 1.0f on Y, and the top left vertex of a quad is -1.0f on X,   
 //        and 1.0f on Y.   
 //    Now all you have to do is match the other three texture coordinates up with the remaining three corners of the quad.  
 //Try playing around with the x and y values of glTexCoord2f.   
 //    Changing 1.0f to 0.5f will only draw the left half of a texture from 0.0f (left) to 0.5f (middle of the texture).   
 //    Changing 0.0f to 0.5f will only draw the right half of a texture from 0.5f (middle) to 1.0f (right).          
 //--------------- L.6 -------------------  
 //If everything went OK, and the texture was created, we enable 2D texture mapping.   
 //    If you forget to enable texture mapping your object will usually appear solid white, which is definitely not good.  
 //--------------- L.6 -------------------  
     glEnable(GL_TEXTURE_2D);                            // Enable Texture Mapping  
     glPolygonMode(GL_FRONT, GL_FILL);                    //Set polygon rasterization mode  
     glColor3f(1.0f, 1.0f, 1.0f);                        //用來重設顏色,如果前面這個位置有被繪製圖形,就必須做此動作。反之則無所謂。  
     glBegin(GL_QUADS);  
         glTexCoord2f(0.0f, 0.0f);  
         glVertex2f(this->m_vcdsGrid[0].LB.x, this->m_vcdsGrid[0].LB.y);    // Bottom Left Of The Texture and Quad  
         glTexCoord2f(1.0f, 0.0f);  
         glVertex2f(this->m_vcdsGrid[0].RB.x, this->m_vcdsGrid[0].RB.y);    // Bottom Right Of The Texture and Quad  
         glTexCoord2f(1.0f, 1.0f);  
         glVertex2f(this->m_vcdsGrid[0].RT.x, this->m_vcdsGrid[0].RT.y);    // Top Right Of The Texture and Quad  
         glTexCoord2f(0.0f, 1.0f);  
         glVertex2f(this->m_vcdsGrid[0].LT.x, this->m_vcdsGrid[0].LT.y);    // Top Left Of The Texture and Quad  
     glEnd();  
     glDisable(GL_TEXTURE_2D);                            //Disable texture mapping  
 }  

這樣就可以把讀進來的圖,貼到GL Scene裡面了。

[CTreeCtrl]在Tree Item前顯示載入的BMP圖片

1.使用LoadImage()載入BMP檔。
2.使用CBitMap::Attach()載入HBITMAP。
3.將產生的CBitMap物件透過CImageList::Add()設定給CImageList。
4.使用CTreeCtrl::SetImageList()將CImageList設定給CTreeCtrl。
5.在需要塞入圖片的Item使用SetItemImage()指定圖片即可。
6.由於這邊使用LoadImage()時,是讀新的圖片進來,所以不用到圖片時,則需在使用DeleteObject()來釋放記憶體。

 HBITMAP hBMP= (HBITMAP)LoadImage(NULL,"c:\\temp\\noname.bmp",IMAGE_BITMAP,0,0,LR_LOADFROMFILE);  
 if ( !hBMP ) {  
     CDSMsgShower::ShowMsgByErrorBox(this->m_hWnd, "ERROR", MB_OK, "Fail to load bmp file. Error =%d", ::GetLastError());  
 }  

 CBitmap *pBMP = new CBitmap;  
 pBMP->Attach(hBMP);  
   
 if (!m_cImageList.Create(16,16, ILC_COLOR16|ILC_MASK, 6, 6))  
     AfxMessageBox("Problem in creating ImageList");  
 this->m_cImageList.Add(pBMP, RGB(255, 255, 255));  //Mask部分如沒有其他顏色要過濾掉的話,就設成白色

[Menu]Disable item of title menu

當有將menu設為Dialog的Menu時,僅需用下列步驟就可以操作sub menu的Item的停用或啟用。

 this->GetMenu()->GetSubMenu(nSubMenuIndex)->EnableMenuItem(ID_SUB_MENU_ITEM, MF_GRAYED);

如果是要針對menu本身的Item,僅需用下列的作法:
 this->GetMenu()->EnableMenuItem(1, MF_GRAYED|MF_BYPOSITION);  

因為menu的item並沒有ID,所以在設定的時候要多給MF_BYPOSITION,這樣就可以用Index來指定Item了。

[CTreeCtrl]取得Item的Handle

1.GetRootItem():取得目前TreeCtrl上的根項目。

使用此函式,第一個會取得的為Mountain。












2.GetNextSiblingItem():取得下一個同階層的項目。
  在此函式前呼叫GetRootItem()的話,則在呼叫此函式時會得到River、Houses、Road、Plant。

3.把GetRootItem()取得到的HTREEITEM丟給GetChildItem(),則會得到Fold。之後再使用GetNextSiblingItem(),則會得到Fault-Block。當GetNextSiblingItem()回傳為NULL時,則代表已無可用的child item存在。

4.GetSelectedItem():取得目前左鍵點選之項目。(回應之事件為OnClick時,才算有選中)

5.GetFirstVisibleItem():取得目前CTreeCtrl第一個看的到的項目。

使用此函式第一個會得到Mountain。
(目前測試,在進行item新增後,再使用此函式,有時候會得到Fault-Block。目前原因不明,需再多進行測試。)









6.GetNextVisibleItem():取得下一個看的到的項目。
即Mountain、River、Houses、Plant及其底下項目。唯獨Road只會得到根項目而已。

[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(),目前的測試是,沒放也沒關係。所以看起來他們只是要整並更新方式而已。

[MFC]COLORREF給glColor_xx()使用

目前在網路上找到得資料,都沒有可以直接轉換的函式。
目前有找到個算是偷吃步的作法,就是重新把COLORREF內部的R、G、B重新取出。
使用的介面名稱為GetRValue()GetGValue()GetBValue()

不過取出的R、G、B值皆為BYTE型態,因此直接塞給GL是不能使用的。
這邊有測試過glColor3b()glColor3i(),結果都是不可行的。

所以變成只能使用glColor3f()來設定顏色。

但由於glColor3f()只能輸入float,所以要再把取得到的R、G、B再除於255轉換成float。
不過一次要做三個除法,所以當繪製的圖形一多,或許會有效能的問題,這邊還需在想要如何處理會比較適合。

範例如下:
 ....  
     glColor3f(GetRValue(clrSelectedColor*1.0f/255,   
              GetGValue(clrSelectedColor)*1.0f/255,  
              GetBValue(clrSelectedColor)*1.0f/255);  
 ....  

[OpenGL]轉換Windows座標成Viewport座標

Ref:http://nehe.gamedev.net/data/articles/article.asp?article=13

Code部分還蠻簡單的,就直接貼上去做memo。
 CDSVector3d COpenGLDrawer::ConvertWinPos2OGLPos(int x, int y)  
 {  
 //Reference:http://nehe.gamedev.net/data/articles/article.asp?article=13  
     //1. Viewport Origin And Extent   
     //    We need to grab the current viewport.   
     //    The information we need is the starting X and Y position of our GL viewport along with the viewport width and height.   
     //    Once we get this information using glGetIntegerv(GL_VIEWPORT, viewport), viewport will hold the following information:   
     //    viewport[0]=x  
     //    viewport[1]=y  
     //    viewport[2]=width  
     //    viewport[3]=height  
     GLint glnViewPort[4];                                    //Where the Viewport will be stored  
     ::glGetIntegerv(GL_VIEWPORT, glnViewPort);                //Retrieves the Viewport values (X, Y, Width, Height)  
   
   
     //2. The Modelview Matrix   
     //    Once we have the viewport information, we can grab the Modelview information.   
     //    The Modelview Matrix determines how the vertices of OpenGL primitives are transformed to eye coordinates.      
     GLdouble gldModelView[16];                                //Where the 16 doubles of the Modelview matrix are to be stored  
     ::glGetDoublev(GL_MODELVIEW_MATRIX, gldModelView);        //Retrieve the Modelview Matrix  
   
   
     //3. The Projection Matrix   
     //    After that, we need to get the Projection Matrix. The Projection Matrix transforms vertices in eye coordinates to clip coordinates.  
     GLdouble gldProjection[16];                                //Where the 16 doubles of the Projection Matrix are to be strored  
     ::glGetDoublev(GL_PROJECTION_MATRIX, gldProjection);    //Retrieve the Projetion Matrix  
   
   
     //4. The Windows Screen Coordinates   
     //    After we have done all of that, we can grab the Windows screen coordinates. We are interested in the current mouse position.  
     GLfloat glfWinX = 0.0f, glfWinY = 0.0f, glfWinZ = 0.0f;        //Holds Our X, y and Z coordinates  
   
     glfWinX = (float)x;        //Holds the mouse X Coordinate  
     glfWinY = (float)y;        //Holds the mouse Y Corrdinate  
   
     //Now Windows coordinates start with (0, 0) being at the top left whereas OpenGL coords start at the lower left. To convert to OpenGL coordinates we do the following:  
     glfWinY = (float)(glnViewPort[3] - glfWinY);        //Subtract the current Mouse Y Corrdinate from the screen height.  
   
     //Get z corrdinate  
     ::glReadPixels(glfWinX, glfWinY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &glfWinZ);  
   
     //5. Variables Where The Output OpenGL Coords Will Be Stored   
     //    All that is left to do is calculate our final OpenGL coordinates.  
     GLdouble gldPosX, gldPosY, gldPosZ;        //Hold the Final value  
     ::gluUnProject( glfWinX, glfWinY, glfWinZ, gldModelView, gldProjection, glnViewPort, &gldPosX, &gldPosY, &gldPosZ);  
   
     return CDSVector3d(gldPosX, gldPosY, gldPosZ);  
 }  

這邊用到的CDSVector3d是自訂的座標物件。

[TreeCtrl]CTreeLis於Item前塞入圖示

這邊找到的方法是使用CImageList來實作。要注意的是,這邊宣告的CImageList要為Data member,否則會無法顯示圖示。

這邊使用的Icon為BitMap,建立的圖形長度為16bits。
因此在CImageList::Create()要輸入的cx值為16。

建立好CImageList後,再把把他透過CTreeList::SetItemImage()塞給所要使用的CTreeList即可。

程式碼如下:
header
 ....  
 CImageList m_cImageList;  
 ....  

CPP:
     if ( !m_cImageList.Create(IDB_BMP_ICON, 16, 2, RGB(0, 0, 0)) ) {  
         CDSMsgShower::ShowMsgByErrorBox(this->m_hWnd, "ACCERT", MB_OK, "Fail to create image list."); };  
   
     this->m_treEnvItems.SetImageList(&m_cImageList, TVSIL_NORMAL);  
   
     ....  
     this->m_treEnvItems.SetItemImage(hEnvItemsTree, 0, 1);  

如圖形無法顯示,有可能是輸入的bitmap長度有錯。

[TreeCtrl]Drag and Drop

Ref:Drag and drop

這邊需使用到CImageList來進行,D&D的圖示繪製。
由於CTreeCtrl有提供接收D&D的訊息事件,TVN_BEGINDRAG
新增的方式為,選擇於類別檢視內的CTreeCtrl衍伸類別,之後選擇屬性視窗,點選訊息,之後新增TVN_GEGINDRAG就可以了。

這邊會需要用到幾個data member,所以先宣告好。
Header:
 .....  
     protected:  
         CImageList *m_pDragImage;  
         HTREEITEM m_hDragItem;  
         HTREEITEM m_hDropItem;  
         BOOL m_IsLDragging;  
 .....  

在建立好OnTvnBegindrag()後,就可以在裡面實作按下滑鼠左鍵的行為:
 void CDSTreeCtrl::OnTvnBegindrag(NMHDR *pNMHDR, LRESULT *pResult)  
 {  
     LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);  
     // TODO: 在此加入控制項告知處理常式程式碼  
     *pResult = 0;  
   
     this->m_hDragItem = pNMTreeView->itemNew.hItem;  
     this->m_pDragImage = this->CreateDragImage(this->m_hDragItem);  
   
     if ( !this->m_pDragImage ) {  
         return; };  
   
     this->m_IsLDragging = TRUE;  
     this->m_pDragImage->BeginDrag(0, CPoint(-15, -15));        //0 = current used image. Because of this item just has one image.  
                                                             //Under cursor, (-15, -15).  
     POINT pt = pNMTreeView->ptDrag;  
     ClientToScreen(&pt);  
     this->m_pDragImage->DragEnter(NULL, pt);  
     this->SetCapture();  
 }  

此時,僅有設定拖曳的時候所要顯示的圖示,所以還要處理WM_MOUSEMOVE的事件。這個訊息一樣可以透過上述方式來新增。
 void CDSTreeCtrl::OnMouseMove(UINT nFlags, CPoint point)  
 {  
     // TODO: 在此加入您的訊息處理常式程式碼和 (或) 呼叫預設值  
     HTREEITEM hItem = NULL;  
     UINT uFlag = 0;  
   
     if ( this->m_IsLDragging )  
     {  
         POINT pt = point;  
         ClientToScreen(&pt);  
         CImageList::DragMove(pt);  
   
         if ( NULL != (hItem = this->HitTest(point, &uFlag)) )  
         {  
             CImageList::DragShowNolock(FALSE);  
             this->SelectDropTarget(hItem);  
             this->m_hDropItem = hItem;  
             CImageList::DragShowNolock(TRUE);  
         }    //End of if ( NULL != (hItem = this->HitTest(point, &flags)) )  
     }    //End of if ( this->m_IsLDragging )  
   
     CTreeCtrl::OnMouseMove(nFlags, point);  
 }  

最後就是當滑鼠左鍵釋放時,要把選擇的item移到新的位置去。這邊的作法目前僅適用於同一個Tree的item移動。
如有要移到其他位置,那就要配合SendMessage()來處理了。
 void CDSTreeCtrl::OnLButtonUp(UINT nFlags, CPoint point)  
 {  
     // TODO: 在此加入您的訊息處理常式程式碼和 (或) 呼叫預設值  
     if ( this->m_IsLDragging )  
     {  
         this->m_IsLDragging = FALSE;  
         CImageList::DragLeave(this);  
         CImageList::EndDrag();  
         ::ReleaseCapture();  
   
         delete this->m_pDragImage;    //MUST TO DO.  
   
         this->SelectDropTarget(NULL);    //Remove drop target highlighting  
   
         if ( this->m_hDragItem == this->m_hDropItem ) {  
             return; };  
   
         //If Drag item is an ancestor of drop item then return  
         HTREEITEM hItemParent = this->m_hDropItem;  
         while ( NULL != (hItemParent = this->GetParentItem(hItemParent)) )  
         {  
             if ( hItemParent == this->m_hDragItem ) {  
                 return; };  
         }    //End of while ( NULL != (hItemParent = this->GetParentItem(hItemParent)) )  
   
         this->Expand(this->m_hDropItem, TVE_EXPAND);  
   
         //HTREEITEM hItemNew = NULL;  
         //hItemNew = CopyBranch(this->m_hDragItem, this->m_hDropItem, TVI_LAST);  
             //REF:http://www.vckbase.com/english/code/treeview/copy_item.shtml.htm  
         //this->DeleteItem(this->m_hDragItem);  
         //this->SelectItem(hItemNew);  
   
     }    //End of if ( this->m_IsLDragging )  
   
     CTreeCtrl::OnLButtonUp(nFlags, point);  
 }  

NOTE:由於TreeCtrl收到的座標原點在TreeCtrl內,所以當LButtonUp時,所得到的座標資訊,是無法用在其他的物件內。 故必須重新抓新的滑鼠座標。
處理方式有以下兩種:
1.直接用GetCursorPos()並配合ScreenToClient()來取得被當作Drop目的地的座標。
     pLDragOnGLView->pTree->ClientToScreen(&pLDragOnGLView->ptMousePos);  
     this->m_glWindow.ScreenToClient(&pLDragOnGLView->ptMousePos);  

2.將CTreeCtrl提供的point先用ClientToScreen()轉成螢幕座標,再用被當作Drop目的物件的ScreenToClient()來取得實際的座標位置。
     CPoint pt;  
     ::GetCursorPos(&pt);  
     this->m_glWindow.ScreenToClient(&pt);  
     this->m_glWindow.OnLButtonUp(pLDragOnGLView->unMouseMovementFlags, pLDragOnGLView->ptMousePos);  
   

然後由於在OnTvnBeginDrag()時,有設定SetCapture(),所以LButtonUp的訊息會先進到CTreeCtrl內,因此如有要把拖曳的物件放到別的控制項的話,必須在另外呼叫控制項的LButtonUp。

[TreeList]CTreeCtrl更新選定之Root項目的分支

在取得到目前所要更新的Root Item後,先呼叫CTreeCtrl::GetChildItem()取得到第一個Leaf的HTREEITEM。
之後僅需使用CTreeCtrl::GetNextSiblingItem()來取得下一個相鄰的Leaf。

程式碼如下:
 HTREEITEM hCurSelItem = this->m_TreeCtrl.GetSelectedItem();  
   
 //Update to UI  
 HTREEITEM hLeafItem = NULL;  
   
 hLeafItem = this->m_treRolesSetting.GetChildItem(hCurSelItem);  
 this->m_treRolesSetting.SetItemText(hLeafItem, "1st Leaf");  
   
 hLeafItem = this->m_treRolesSetting.GetNextSiblingItem(hLeafItem);  
 this->m_treRolesSetting.SetItemText(hLeafItem, "2nd Leaf");  

[TreeCtrl]CTreeCtrl取得目前滑鼠右鍵點選項目

這邊需使用到HitTest()來取得滑鼠點選的項目。

步驟如下:
1.使用GetCursorPos()來取得目前滑鼠座標。
2.使用CTreeCtrl::ScreenToClient()轉換滑鼠座標位置到CTreeCtrl的相對位置。
3.使用CTreeCtrl::HitTest()來取得此座標位置是否有Item存在。
4.判斷HitTest()回傳的Flag狀態是否為TVHT_ONITEM
5.如上述步驟都成立,則HitTest()回傳之HTREEITEM則為所要之Item。如此值為NULL,則代表此座標位置並無Tree Item存在。

程式碼如下:
 CPoint pt;  
 UINT unFlag = 0;  
 ::GetCursorPos(&pt);  
 pTree->ScreenToClient(&pt);  
   
 //When this function is called, the pt parameter specifies the coordinates of the point to test.   
 //The function returns the handle of the item at the specified point or NULL if no item occupies the point.   
 //In addition, the pFlags parameter contains a value that indicates the location of the specified point.  
 HTREEITEM htreeHitItem = pTree->HitTest(pt, &unFlag);  
   
 if ( htreeHitItem == NULL || !(unFlag&TVHT_ONITEM) )        //No hit item, and not on item  
 {  
     //Just show Create  
     return false;   
 }else if ( htreeHitItem && (unFlag & TVHT_ONITEM) ) {  
     //Just show edit and delete  
     pTree->Select(htreeHitItem, TVGN_CARET);        //Set this item be selected  
     //pTree->SelectItem(htreeHitItem);              //用這個也可以
 }else{        //Not allow  
     return false;  
 }    //End of if ( htreeHitItem == NULL || !(unFlag&TVHT_ONITEM) )  

如需再指定此Item被選擇,僅需使用CTreeCtrl::Select()或是CTreeCtrl::SelectItem()即可。

Build docker image from multiple build contexts

Build docker image from multiple build contexts ...