電腦遊戲製作開發設計論壇 首頁 電腦遊戲製作開發設計論壇
任何可以在PC上跑的遊戲都可以討論,主要以遊戲之製作開發為主軸,希望讓台灣的遊戲人有個討論、交流、教學、經驗傳承的園地
 
 常見問題常見問題   搜尋搜尋   會員列表會員列表   會員群組會員群組   會員註冊會員註冊 
 個人資料個人資料   登入檢查您的私人訊息登入檢查您的私人訊息   登入登入 

Google
[轉貼自程式設計俱樂部]glut 教學 - 點選立體模型

 
發表新主題   回覆主題    電腦遊戲製作開發設計論壇 首頁 -> 遊戲程式高級班:DirectX、OpenGL及各種圖型函式庫
上一篇主題 :: 下一篇主題  
發表人 內容
satanupup
喜歡上這裡的冒險者


註冊時間: 2007-05-29
文章: 80

68.10 果凍幣

發表發表於: 2007-6-28, PM 2:17 星期四    文章主題: [轉貼自程式設計俱樂部]glut 教學 - 點選立體模型 引言回覆

作者 : ma_hty(白老鼠(Gary))
使用立體模型去作媒體, 比起的平面貼圖, 除了圖像變化更細緻, 更重要的, 就是 立體模型 和 人手控制 之間的互動性, 在三維空間中選取三維空間的東西, 更能直覺地讓用家使用.

這次, 我要介紹的就是如何使用滑鼠選取立體模型.

網友們, 如果你曾經嘗試自己做 平面貼圖 的 滑鼠點選, 相信也應該明白當中的難處. 三維空間的立體模型, 呈現在平面顯示時, 是多麼複雜的不規則圖形, 這麼... 點選立體模型豈不是更複雜嗎? 但... 很諷刺地, OpenGL 為我們提供了 Selection Mode 的機制, 有效地把這些想來複雜的步驟, 簡單的完成了.

好, 讓我們去寫程式點選立體模型, 並且順道觀摩一下 OpenGL 設計者的心思.

------------------------------
/////////////////////////
// glutTest11.cpp
//
// Created by Gary Ho, ma_hty@hotmail.com, 2006
//


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#include "glut.h"
#include "glm.h"

void display();
void keyboard( unsigned char key, int x, int y );
void mouse( int button, int state, int x, int y );
void motion( int x, int y );
void timer( int value );

void process_pick( float x, float y );
void pick_func( float x, float y );
void pick_change( GLMtriangle *t0, GLMtriangle *t1 );

GLuint list_plane;
GLuint list_ground;

GLMmodel *glm_ground;
GLMtriangle *pathface[1024];
int n_pathface = 0;

int drag_state = -1;
int flying = false;
float plane_pos;
float plane_vec[3] = { 0, 0, 0 };



void main()
{
glutInitDisplayMode( GLUT_DOUBLE | GLUT_DEPTH | GLUT_RGB );
glutInitWindowSize( 640, 640 );
glutCreateWindow( "glutTest11" );

glutDisplayFunc(display);
glutMouseFunc( mouse );
glutMotionFunc( motion );

{
glm_ground = glmReadOBJ( "world_curved.obj" );
glmUnitize( glm_ground );
glmScale( glm_ground, 1.2 );
list_ground = glmList( glm_ground, GLM_SMOOTH );
}

{
GLMmodel *glm_model;
glm_model= glmReadOBJ( "soccerball.obj" );
glmUnitize( glm_model );
glmScale( glm_model, .05 );
list_plane = glmList( glm_model, GLM_MATERIAL | GLM_SMOOTH );
glmDelete( glm_model );
}

glEnable( GL_LIGHTING );
glEnable( GL_LIGHT0 );
glEnable( GL_DEPTH_TEST );

glutMainLoop();
}

void display()
{
glClearColor( .2, .3, .4, 1 );
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

GLint viewport[4];
glGetIntegerv( GL_VIEWPORT, viewport );

glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective( 45, double(viewport[2])/viewport[3], 0.1, 100 );

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt( 0,0,3, 0,0,0, 0,1,0 );

glDisable( GL_LIGHTING );

int i;

if( drag_state!=-1 )
for( i=0; i<n_pathface; i++ )
{
glColor3f( 0, (i+1.0)/n_pathface, 0 );
glBegin(GL_TRIANGLES);
glVertex3fv(&glm_ground->vertices[3 * pathface[i]->vindices[0]]);
glVertex3fv(&glm_ground->vertices[3 * pathface[i]->vindices[1]]);
glVertex3fv(&glm_ground->vertices[3 * pathface[i]->vindices[2]]);
glEnd();
}

glEnable( GL_LIGHTING );
glCallList( list_ground );

if( flying )
{
flying = false;
float *v0, *v1;
float d, cd, r;

cd = 0;
for( i=0; i<n_pathface-1; i++ )
{
v0 = &glm_ground->vertices[3 * pathface[i]->vindices[0]];
v1 = &glm_ground->vertices[3 * pathface[i+1]->vindices[0]];

d = sqrtf(
(v0[0]-v1[0])*(v0[0]-v1[0]) +
(v0[1]-v1[1])*(v0[1]-v1[1]) +
(v0[2]-v1[2])*(v0[2]-v1[2]) );

if( cd+d > plane_pos )
{
r = (plane_pos-cd) / d;
plane_vec[0] = v0[0]*(1-r)+v1[0]*r;
plane_vec[1] = v0[1]*(1-r)+v1[1]*r;
plane_vec[2] = v0[2]*(1-r)+v1[2]*r;
plane_pos += .02;
flying = true;
glutTimerFunc( 30, timer, 0 );
break;
}
cd += d;
}
}

glTranslatef( plane_vec[0], plane_vec[1], plane_vec[2] );
glCallList( list_plane );

glutSwapBuffers();
}

void timer( int value )
{
glutPostRedisplay();
}

void pick_change( GLMtriangle *t0, GLMtriangle *t1 )
{
if( t1 )
{
pathface[ n_pathface ] = t1;
n_pathface++;
}
}
#define BUFSIZE 1024
typedef struct _GSelect
{
unsigned int n_name;
unsigned int d0, d1;
unsigned int name[1];
}GSelect;
GLMtriangle *prev_face = NULL;
GLMtriangle *active_face = NULL;

void process_pick( float x, float y )
{
GLuint selectBuf[BUFSIZE];
unsigned int hit;
unsigned int i;

glSelectBuffer( BUFSIZE, selectBuf );
glInitNames();
glRenderMode( GL_SELECT );
pick_func( x, y );
hit = glRenderMode( GL_RENDER );

active_face = NULL;
if( hit )
{
GLuint *ptr;
GLuint dmin = -1;

ptr = selectBuf;
for( i=0; i<hit; i++ )
{
GSelect &sel = *((GSelect*)ptr);
if( sel.d0 < dmin )
{
dmin = sel.d0;
active_face = (GLMtriangle*)sel.name[0];
}
ptr += 3 + sel.n_name;
}
}

if( active_face != prev_face )
{
pick_change( prev_face, active_face );
prev_face = active_face;
}
}
void pick_func( float x, float y )
{
unsigned int i;
GLMmodel* model;
GLMgroup* group;
GLMtriangle* triangle;

GLint viewport[4];
glGetIntegerv( GL_VIEWPORT, viewport );

glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPickMatrix( x*viewport[2], y*viewport[3], .1,.1, viewport );
gluPerspective( 45, double(viewport[2])/viewport[3], 0.1, 100 );

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt( 0,0,3, 0,0,0, 0,1,0 );

model = glm_ground;
group = model->groups;
while( group )
{
for( i=0; i<group->numtriangles; i++ )
{
triangle = &model->triangles[group->triangles[i]];
glPushName( (unsigned int) triangle );
glBegin(GL_TRIANGLES);
glVertex3fv(&model->vertices[3 * triangle->vindices[0]]);
glVertex3fv(&model->vertices[3 * triangle->vindices[1]]);
glVertex3fv(&model->vertices[3 * triangle->vindices[2]]);
glEnd();
glPopName();
}
group = group->next;
}
}

void mouse( int button, int state, int x, int y )
{
if( flying )
return;

int w,h;
w = glutGet( GLUT_WINDOW_WIDTH );
h = glutGet( GLUT_WINDOW_HEIGHT );

if( button == GLUT_LEFT_BUTTON )
{
if( state == GLUT_DOWN )
{
drag_state = GLUT_LEFT_BUTTON;
n_pathface = 0;
process_pick(float(x)/w,float(h-y)/h);
glutPostRedisplay();
}

if( state == GLUT_UP && drag_state == GLUT_LEFT_BUTTON )
{
drag_state = -1;
process_pick(float(x)/w,float(h-y)/h);

plane_pos = 0;
flying = true;

glutPostRedisplay();
}
}
}

void motion( int x, int y )
{
if( flying )
return;

int w,h;
w = glutGet( GLUT_WINDOW_WIDTH );
h = glutGet( GLUT_WINDOW_HEIGHT );

if( drag_state == GLUT_LEFT_BUTTON )
{
process_pick(float(x)/w,float(h-y)/h);
glutPostRedisplay();
}
}


----------------------------------
這一個範例, 會用到 "glut 教學 - 使用 OBJ 立體模型" 的 glm.h 和 glm.cpp, 請依照下列連結指示, 下載它們.

http://www.programmer-club.com/pc2020v5/forum/ShowSameTitleN.asp?URL=N&board_pc2020=opengl&id=1144
-----------------------------
這個程式, 會由你的專案資料夾讀取兩個 OBJ 檔, 即是
world_curved.obj world_curved.mtl 和
soccerball.obj soccerball.mtl,
這些檔案, 請依照 "glut 教學 - 使用 OBJ 立體模型" 下列連結指示, 下載它們.
http://www.programmer-club.com/pc2020v5/forum/ShowSameTitleN.asp?URL=N&board_pc2020=opengl&id=1144

這個範例, 有兩件立體模型, 一個是地面 和 一個足球. 當你使用滑鼠右鍵在地面上拖曳, 地面上就會出現你的拖曳路徑, 當你完成拖曳 並放開右鍵, 足球就會依據你的拖曳路徑在地面上移動. 注意, 這裡的地面, 是一個任意的立體模型, 它可以是一個平面, 也可以是一個地球, 甚至... 是一隻牛 ^^".

以一個範例來說, 這個程式實在有點長, 因為, 這一課範例除了要示範點選立體模型之外, 也希望一併展示如何 整合好些小技巧 成為較實質的功能.
----------------------
要點選在畫面上的立體模型, 即是說, 你要先把滑鼠座標轉換成於立體模型的位置, 但是, 這個是有執行上的困難的, 或是說, 一個平面你可以用兩個變數把它參數化, 一個球體 你也可以把它參數化, 但是, 任意的立體模型, 並沒有簡單的幾何關係去讓你把它參數化, 這麼... 我們應該如何去索引 立體模型的位置 呢?

這個問題, 其實有一個很簡單直接的答案, 就是, 直接用立體模型的三角形當成作索引值. 這麼, 我們的目標, 就換化成 把滑鼠座標轉換成於立體模型的三角形索引, 只要把所有三角形也轉換成視窗座標, 然後與滑鼠座標作比較, 就可以得出需要的三角形索引 ... ...

因著類同的需求, OpenGL 早已準備好一個 Selection Mode 的機制, 在 OpenGL, 繪圖模式有三種, 分別是 Render Mode, Selection Mode 和 Feedback Mode. 一般繪圖時, 我們會用 Render Mode, 當我們想要取得某位置的三角形時, 我們就會用 Selection Mode.

在 Selection Mode 下, 我們也好像一般情況的繪畫立體模型, 主要的分別, 就是 view frustum 要盡量縮小成一點, 還有, 繪畫時要一併指示索引值 ( 即 name ). 當你在 Selection Mode 完成繪畫立體模型之後, 你的 select buffer 就會存放著所有掉進 view frustum 的索引值.

void process_pick( float x, float y )
{
//...

// 指是 select buffer 為 selectBuf
glSelectBuffer( BUFSIZE, selectBuf );

// 初始化 name stack
glInitNames();

// 進入 Selection Mode
glRenderMode( GL_SELECT );

// 繪畫立體模型
pick_func( x, y );

// 還原成 Render Mode, 並擷取總共有多是 name 掉進了 view frustum
hit = glRenderMode( GL_RENDER );

//...
}
-------------------------------------------------
void pick_func( float x, float y )
{
//...

glMatrixMode(GL_PROJECTION);
glLoadIdentity();

// 把 view frustum 盡量限制成一小點
gluPickMatrix( x*viewport[2], y*viewport[3], .1,.1, viewport );

gluPerspective( 45, double(viewport[2])/viewport[3], 0.1, 100 );

// 對於每三角形
{
triangle = &model->triangles[group->triangles[i]];
// 推 某三角形相應的索引值 進入 name stack
glPushName( (unsigned int) triangle );

// 繪畫三角形
glBegin(GL_TRIANGLES);
//...
glEnd();

// 由 name stack 移除 索引值
glPopName();
}
}
---------------------------------
經過上述步驟, 你的 select buffer 就會記錄著所有掉進 view frustum 的 hit, 請註意!!! hit 並不包含 深度測試 (depth test), 即是說, 不論你的物件遠近, 只要掉進 view frustum, 也包括在 select buffer 之內. 就是這個原因, 每個 hit 也會同時提供 最近 和 最遠 的深度值, 用來辨別深度的. 除此之外, hit 也提供了階層索引架構, 當然, 這個範例用了一層 階層 (即 三角形索引 ).

hit 的資料結構如下:
typedef struct _GSelect
{
unsigned int n_name; // 索引值的階層總數
unsigned int d0, d1; // 最近 和 最遠 的值
unsigned int name[1]; // 索引值資料陣列
}GSelect;

說到這部份, 實質上已經離開了 OpenGL 的範圍, 如下的部份, 只是 解釋 和 應用 資料.

void process_pick( float x, float y )
{
//...

// 對於每個 hit
for( i=0; i<hit; i++ )
{
GSelect &sel = *((GSelect*)ptr);
// 如果最近視點
if( sel.d0 < dmin )
{
// 更新被選取三角形
dmin = sel.d0;
active_face = (GLMtriangle*)sel.name[0];
}
ptr += 3 + sel.n_name;
}
}

// 如果被選取三角形有改變, 呼叫 pick_change
if( active_face != prev_face )
{
pick_change( prev_face, active_face );
prev_face = active_face;
}
}
-----------------------------
當我們把 process_pick 和 pick_func 都寫好, 尚差的工作就是當接數到 滑鼠輸入 時, 呼叫
process_pick(). 如果 被選取三角形有改變, pick_change() 就會再被呼叫. 對於這個範例, 被選取三角形有改變時, 新三角形索引值會被順序記錄, 而成為路徑. 繪畫時, 再按需要使用路徑... ...

就這樣... 範例完成了.
--------------------------
順帶一提, 這範例應用了 glutTimerFunc 來做出 鎖定更新率 的功能, 用意是做出 動畫效果 的, 並且 鎖定更新率 為一秒三十次, 如果只是 重複呼叫 display() 或是 重複呼叫glutPostRedisplay() 來達到 動畫效果, 會出現 因更新率過高 所致的 CPU 使用率偏高情況. 反正動畫一秒三十幅已很足夠, 鎖定更新率 為一秒三十次, 皆大歡喜...

void display()
{
// ...

// 30 微秒之後呼叫 timer()
glutTimerFunc( 30, timer, 0 );

// ...
}

void timer( int value )
{
// 重畫視窗
glutPostRedisplay();
}
----------------------------
經過了漫長的講述之後... ...

白老鼠環顧四周, 然後說道 : "甚麼!!! 你還支撐著啊, 好~ 好~ 好~, 能撐到尾的, 都有獎品"

網友睡眼惺忪的說道 : "唉... 白老鼠 的獎品, 會有什麼好東西呢... 又不過是功課了吧... "

白老鼠汗顏中接著說下去 :

"這一次, 也做一點功課吧, 地面的立體模型 ( 即 glm_ground ), 現在是一個立體世界地圖, 但是, 這個範例並沒限制你只能使用它, 自行換上其它 立體模型 試試看呀.

最後... 如果你完成了這個功課, 就在這簽個名吧, 謝謝."
回頂端
檢視會員個人資料 發送私人訊息
從之前的文章開始顯示:   
發表新主題   回覆主題    電腦遊戲製作開發設計論壇 首頁 -> 遊戲程式高級班:DirectX、OpenGL及各種圖型函式庫 所有的時間均為 台灣時間 (GMT + 8 小時)
1頁(共1頁)

 
前往:  
無法 在這個版面發表文章
無法 在這個版面回覆文章
無法 在這個版面編輯文章
無法 在這個版面刪除文章
無法 在這個版面進行投票
可以 在這個版面附加檔案
可以 在這個版面下載檔案


Powered by phpBB © 2001, 2005 phpBB Group
正體中文語系由 phpbb-tw 維護製作