PDA

Visualizza la versione completa : [C++] Gestione del "picking" in OpenGL


cerza
03-02-2011, 15:21
Salve,
spero di essere nella sezione giusta! avrei bisogno di capire meglio la gestione del picking in opengl solo che non riesco a trovare un esempio molto chiaro; nel mio caso dovrei poter selezionare un oggetto, tra vari oggetti presenti e cambiargli colore...
grazie a tutti, anche solo a quelli che si sono fermati a leggere...

maria

Paulin
03-02-2011, 19:38
Il picking in OpenGL si aggira tutto attorno al selection buffer che si ottiene tramite la funzione glSelectButter, ad esempio in questo modo:

#define MAX_BUFFER_SIZE 512
GLuint selectionBuffer[MAX_BUFFER_SIZE];
glSelectBuffer(MAX_BUFFER_SIZE, selectionBuffer);

Quando la rasterizzazione è in modalità selezione, allora OpenGL raccoglie informazini sulle primitive disegnate sulla vista, per ottenere la modalità selezione si usa:

glRenderMode(GL_SELECT);

Una volta che la modalità di rasterizzazione sarà commutata da GL_SELECT ad un'altra delle tre modalità possibili, il selection buffer avrà ricevuto le informazioni degli oggetti in vista.

GLint hits = glRenderMode(GL_RENDER);
// hits holds the number of diplayed objects.

Per vedere cosa c'é ora dentro al selection buffer si può fare così:



#define MAXFLT 3.402823466e+38F // Maximum value for the float type.

GLint nearest = -1;
GLfloat zmin;
GLfloat zmax;
GLfloat min_depth = MAXFLT;
GLuint * ptr = selectionBuffer

for(i = 0 ; i < hits ; i++)
{
names = *ptr++; // Number of names in this hit.
zmin = *ptr++; // Minimum depth of the current hit.
zmax = *ptr++; // maximum depth of the current hit.
// Now ptr points to the first name.
for (j = 0; j < names; j++) // Loop through each name.
{
if(zmin < min_depth) // Selecting the nearest.
{
min_depth = zmin;
nearest = *ptr; // Nearest object in the selection.
}
ptr++; // Go to the next name.
}
}

Questo codice controlla quanti oggetti sono "caduti" dentro il selecion buffer e seleziona tra tutti quello più vicino (i commenti li ho scritti tutti in inglese perché quando commento il codice scrivo in inglese). A questo punto sai quale oggetto è stato selezionato con il mouse e puoi cambiargli il colore.

Per restringere la regione di selezione ad una piccola regione sotto il puntatore del mouse si usa gluPickMatrix. ad esempio:

GLint viewport[4];
glGetIntegerv( GL_VIEWPORT, viewport ); // Get viewport coordinates.
gluPickMatrix( x, (viewport[3] - y ), nPixel_x, nPixels_y, viewport );

I termini nPixel_x e nPixels_y corrispondono al numero di pixel che si vuole delimitino la regione di schermo da campionare, io di solito utilizzo 3x3 pixels o anche più.

Il nome è un identificativo numerico che va assegnato all'oggetto quando viene disegnato:

#define SPHERE 0
glPushName( SPHERE ); // Identify object to be drawn.
DrawSphere();
glPopName();

Ovviamente prima di adoperare i nomi in modalità selezione occorre inizializzare lo stack dei nomi di OpenGL con:
glInitNames();

Non è tutto, ma è un inizio.

cerza
04-02-2011, 10:02
ciao e grazie mille per aver risposto,
allora ho per grandi linee capito il codice che mi hai postato solo che mentre tu assegni il codice numerico all'oggetto in maniera statica io dovrei farlo in modo dinamico in quanto dovrei prima creare una serie di oggetti e poi selezionarli per farli cambiare colore, quindi ho messo la creazione degli oggetti in un ciclo for ed assegno l'etichetta agli oggetti facendo:

glPushMatrix();

glTranslated((double)listObject.objj[i].positionX, (double)listObject.objj[i].positionY, (double)listObject.objj[i].positionZ);
if(mode == GL_SELECT){glLoadName(i);
glColor3f(red, green, blue);
glutSolidSphere(0.5,20,10);}
glPopMatrix();

però se faccio in questo modo non mi disegna gli oggetti se invece tolgo le graffe dopo l'if mi fa vedere gli oggetti ma anche selezionandoli non mi dice che sono stati selezionati.

buona giornata
maria

Paulin
04-02-2011, 12:43
Bene, allora siamo un passo più avanti. La funzione glLoadName(id); andrà comunque caricarla nella tua funzione dove organizzi il rendering della scena OpenGL, più o meno così:


for(i = 0 ; i < nObj; i++)
{
glPushMatrix();
glTranslated(obj[i].pos.x, obj[i].pos.y, obj[i].pos.z);
glPushName(obj[i].id); // Nota A*
obj[i].RenderObj(&obj[i]); // Nota B*
glPopName();
glPopMatrix();
}
Nota A: Non confonderei l'indice dell'oggetto con il suo identificativo.
Nota B: Userei un puntatore a funzione per renderizzare l'oggetto (anche non necessario).


typedef struct
{
GLdouble x,y,z;
} Posit;

typedef struct
{
GLint id;
Posit pos;
void (*RenderObj)(GLint, void*); // Rendering procedure.
} Obj;

Obj objects[nObj];
Nel codice di renderizzazione dell'oggetto inserirei la gestione della selezione:


void RenderSphere(void *pobj)
{
if (pobj->id == selected) glColor3f(1.0, 1.0, 0.0);
else glColor3f(0.2, 0.0, 0.8);
auxSolidSphere(1.0);
}
Per assegnare la funzione di renderizzazione alla procedura, all'inizializzazione della vista:

obj[0].RenderObj = RenderSphere;

All'evento del mouse:

GLint selected;
selected = HitTest(pt.x, pt.y);

Fai alcune prove poi facci sapere ...

cerza
04-02-2011, 16:11
grazie grazie per la riposta,
solo che ho porvato un pò con il tuo codice ma ad un certo punto mi sono persa....ricomincio dall'inizio.
ho le segneti strutture



struct oggetto{
GLfloat positionX;
GLfloat positionY;
GLfloat positionZ;
GLint tipo;
GLint colore;
};


struct listObj{
int counter;
oggetto objj[];
};

struct listObj listObject;


succesivamente quando disegno i vari oggetti faccio così, assegando in automatico il valore che identificherà quell'oggetto con glLoadName mentre tu mi dici di di fare una glpushname ed una glpopName, domanda:ma sn due cose diverse oppire fanno la stessa cosa?



if(listObject.objj[i].tipo == 5){
glPushMatrix();
float cuboX = listObject.objj[i].positionX;
float cuboY = listObject.objj[i].positionY;
float cuboZ = listObject.objj[i].positionZ;
if(mode == GL_SELECT)
glLoadName(i);
glTranslatef(cuboX, cuboY, cuboZ);
//glColor3f(red,green,blue);
glutSolidCube(1.0);
glPopMatrix();
}
}




tra le varie cose ho anche definito :



void processHits (GLint hits, GLuint buffer[])
{
unsigned int i, j;
GLuint names, *ptr;

printf ("hits = %d\n", hits);
ptr = (GLuint *) buffer;
for (i = 0; i < hits; i++)
{ /* for each hit */
names = *ptr;
ptr+=3;
for (j = 0; j < names; j++)
{ /* for each name */

if(*ptr==1) printf ("oggetto %d", listObject.objj[j].tipo);
if(*ptr==2) printf ("oggetto\n", listObject.objj[j].tipo);
ptr++;
}
printf ("\n");
}
}


anche se gli utlimi due if non vanno bene ma non sapevo come farli.....
questo è quanto, mi scuso se sono insistente....

maria

giuseppe500
04-02-2011, 20:24
secondo me, ma è una mia interpretazione è meglio affidarsi a bounding box semplici se hai bisogno di prestazioni tipo sfere o box, e testare l'intersezione con questi.
Chiaramente ogni mesh dovra avere un proprio bounding box.
associata alla mesh e avrai anche un effetto ,uno shader o materiale in termini terra terra che renderizzano quella mesh(geometria) in un modo definito dal programma shader.

Basta cambiare al volo lo shader quando hai selezionato la mesh con un qualcosa che renderizzi in modo "selezionato"(dipende tutto da te come presentare la geometria , cambiando lo shader)
e il gioco è fatto.

Ti consiglio se devi iniziare a lavorare con opengl o anche directx di affidarti a un engine gia fatto all inizio tipo wild magic della geometric tools , ogre , openscenegraph o .... be ce ne sono davvero tanti cerca con google questi tre per vedere poi scegli.
per capire , poi passerai se vorrai ad un tuo engine 3d(spero in c++).
non trovi?

ps. e parti dalla documentazione, wild magic ha addirittura tre libri di documentazione, anche se si pagano!?

ciao :ciauz:

Paulin
05-02-2011, 15:48
Ho messo assieme un piccolo esempio per realizzare il picking su tre cubi in modo da cambiare colore al cubo cliccato, è un file unico compilabile come applicativo per Windows.


#include <windows.h>
#include <math.h>

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

#define MAXFLT 3.402823466e+38F // Maximum value for the float type.


HDC ghDC;
HGLRC ghRC;

CHAR szAppName[] = "Picking";

static LRESULT CALLBACK MainWndProc (HWND, UINT, WPARAM, LPARAM);


typedef struct
{
GLdouble x,y,z;
} Posit; // New type for XYZ position.

typedef struct
{
GLdouble R,G,B;
} Color; // New type for RGB color.

struct
{
GLdouble fovy, ratio, zNear, zFar;
} Cam; // Camera properties.

struct
{
GLdouble x,y,z;
} Rot; // Rotation

typedef struct
{
GLint id;
Posit pos;
Color clr;
void (*RenderObj)(GLint, void*); // Rendering procedure.
} Obj; // New tuype for graphic obj in the scene.

#define nObjs 3

Obj obj[nObjs];

GLdouble deepz; // Deep in the scene.
GLint selected; // Currently selected object id.


static void RenderAxes(void)
{
glColor3f(1,0,0);
glBegin(GL_LINES);
glVertex3i(0,0,0);
glVertex3i(2,0,0);
glEnd();
glColor3f(0,1,0);
glBegin(GL_LINES);
glVertex3i(0,0,0);
glVertex3i(0,2,0);
glEnd();
glColor3f(0,0,1);
glBegin(GL_LINES);
glVertex3i(0,0,0);
glVertex3i(0,0,2);
glEnd();
}


static void Cube(GLint mode, void* pobj)
{
if(mode == GL_RENDER)
{
glColor3f(1,1,1);
auxWireCube(1.01);
}

Obj *p = pobj;
if (p->id == selected) glColor3f(1.0, 1.0, 0.0);
else glColor3f(p->clr.R, p->clr.G, p->clr.B);
auxSolidCube(1.0);
}


static void DrawScene(GLint mode)
{
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();

glTranslated(0, 0, deepz);
glRotated(Rot.x, 1,0,0);
glRotated(Rot.y, 0,1,0);
glRotated(Rot.z, 0,0,1);

RenderAxes();

for(int i=0; i<nObjs; i++)
{
glPushMatrix();
glTranslated(obj[i].pos.x,obj[i].pos.y,obj[i].pos.z);

if(mode == GL_SELECT)
glPushName(obj[i].id);

obj[i].RenderObj(mode, &obj[i]);

if(mode == GL_SELECT)
glPopName();
glPopMatrix();
}
}


void SizeView(int w, int h)
{
glViewport(0, 0, w, h);
Cam.ratio = (GLfloat)w/(GLfloat)h;

glMatrixMode(GL_PROJECTION);
glLoadIdentity();

gluPerspective(Cam.fovy, Cam.ratio, Cam.zNear, Cam.zFar);
DrawScene(GL_RENDER);
}


void Display(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
DrawScene(GL_RENDER);
SwapBuffers(ghDC);
}


GLint ProcessHits(GLint hits, GLuint selBuf[])
{
GLuint names;
GLint nearest = -1;
GLfloat minz;
GLfloat depth = MAXFLT;
GLuint * ptr = selBuf;

for (int i=0; i<hits; i++)
{
names = *ptr++;
minz = *ptr++;
ptr++;

for (int j=0; j<names; j++)
{
if (minz < depth)
{
depth = minz;
nearest = *ptr;
}
ptr++;
}
}

return nearest;
}


#define BUFSIZE 512
GLuint HitTest(int x, int y)
{
GLint hits;
GLint viewport[4];
GLuint nearest = -1;
GLuint selectBuf[BUFSIZE];

glGetIntegerv(GL_VIEWPORT, viewport);
glSelectBuffer(BUFSIZE, selectBuf);
glRenderMode(GL_SELECT);

glInitNames();

glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
gluPickMatrix((GLdouble)x, (GLdouble)(viewport[3] - y), 3.0, 3.0, viewport);
gluPerspective(Cam.fovy, Cam.ratio, Cam.zNear, Cam.zFar);

DrawScene(GL_SELECT);

glMatrixMode(GL_PROJECTION);
glPopMatrix();

glFlush();
hits = glRenderMode(GL_RENDER);

return ProcessHits(hits, selectBuf);
}


void InitView(void)
{
Cam.fovy = 45;
Cam.ratio = 1.5;
Cam.zNear = 1;
Cam.zFar = 1000;

Rot.x = 15.0;
Rot.y = -15.0;

deepz = -10.0;
selected = -1;

for(int i = 0 ; i < nObjs ; i++)
{
obj[i].id = i;
}

obj[0].pos.x = 1.0;
obj[0].pos.y = 1.5;
obj[0].pos.z = -1.0;

obj[0].clr.R = 0.0;
obj[0].clr.G = 0.6;
obj[0].clr.B = 0.0;

obj[0].RenderObj = Cube;


obj[1].pos.x = 1.5;
obj[1].pos.y = 1.0;
obj[1].pos.z = 1.5;

obj[1].clr.R = 0.8;
obj[1].clr.G = 0.0;
obj[1].clr.B = 0.2;

obj[1].RenderObj = Cube;


obj[2].pos.x = -1.0;
obj[2].pos.y = 0.5;
obj[2].pos.z = 0.0;

obj[2].clr.R = 0.2;
obj[2].clr.G = 0.0;
obj[2].clr.B = 0.8;

obj[2].RenderObj = Cube;


glClearColor(0.0, 0.0, 0.0, 0.0);

glEnable(GL_DEPTH_TEST);
}

BOOL bSetupPixelFormat(HDC hdc)
{
PIXELFORMATDESCRIPTOR pfd =
{
sizeof(PIXELFORMATDESCRIPTOR),
1,
PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
PFD_TYPE_RGBA,
24,
0, 0, 0, 0, 0, 0,
0, 0, 0,
0, 0, 0, 0,
32, 0, 0,
PFD_MAIN_PLANE,
0, 0, 0, 0
};

GLint pixelformat = ChoosePixelFormat(hdc, &pfd);

if ( (pixelformat = ChoosePixelFormat(hdc, &pfd)) == 0 )
return FALSE;

if (SetPixelFormat(hdc, pixelformat, &pfd) == FALSE)
return FALSE;

return TRUE;
}


int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
MSG msg;
HWND hWnd;
WNDCLASS wndclass;

wndclass.style = 0;
wndclass.lpfnWndProc = (WNDPROC)MainWndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon (hInstance, szAppName);
wndclass.hCursor = LoadCursor (NULL,IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wndclass.lpszMenuName = szAppName;
wndclass.lpszClassName = szAppName;
if (!RegisterClass (&wndclass) )
return FALSE;

hWnd = CreateWindow (szAppName,
szAppName,
WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL);

if (!hWnd)
return FALSE;

ShowWindow (hWnd, nCmdShow);
UpdateWindow (hWnd);

while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

return msg.wParam;
}


static LRESULT CALLBACK MainWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
LONG lRet = 1;

switch (uMsg)
{

case WM_CREATE:
ghDC = GetDC(hWnd);
if (!bSetupPixelFormat(ghDC))
PostQuitMessage (0);
ghRC = wglCreateContext(ghDC);
wglMakeCurrent(ghDC, ghRC);
InitView();
break;

case WM_SIZE:
SizeView(LOWORD(lParam),HIWORD(lParam));
InvalidateRect(hWnd, NULL, FALSE);
break;

case WM_PAINT:
Display();
break;

case WM_ERASEBKGND:
InvalidateRect(hWnd,NULL,FALSE);
break;

case WM_CLOSE:
DestroyWindow(hWnd);
break;

case WM_DESTROY:
if (ghRC) wglDeleteContext(ghRC);
if (ghDC) ReleaseDC(hWnd, ghDC);
PostQuitMessage (0);
break;

case WM_LBUTTONDOWN:
selected = HitTest(LOWORD(lParam), HIWORD(lParam));
InvalidateRect(hWnd, NULL, FALSE);
break;

case WM_LBUTTONUP:
InvalidateRect(hWnd, NULL, FALSE);
break;

case WM_KEYDOWN:

switch (wParam)
{

case VK_UP:
Rot.x -= 1%360;
InvalidateRect(hWnd,NULL,FALSE);
break;

case VK_DOWN:
Rot.x += 1%360;
InvalidateRect(hWnd,NULL,FALSE);
break;

case VK_LEFT:
Rot.y -= 1%360;
InvalidateRect(hWnd,NULL,FALSE);
break;

case VK_RIGHT:
Rot.y += 1%360;
InvalidateRect(hWnd,NULL,FALSE);
break;
}

default:
lRet = DefWindowProc (hWnd, uMsg, wParam, lParam);
break;
}

return lRet;
}

Come vedi dal codice, con i tasti "freccia" si ruota la vista.
Dovrebbe compilare senza problemi anche su un compilatore C.

cerza
06-02-2011, 10:35
grazie mille sei un tesoro!!!!
adesso lo provo ti farò sapere.... comunque avevo provato ad utilizzare una funzione per il picking di un esempio 2D e quindi va alla grande per il 2D ma se la inserisco nel mio codice per il 3D non funziona, mi rileva 0 hits ma com'è possibile ??

grazie ancora buona domenica

maria

cerza
06-02-2011, 10:48
ciao scusami ancora ma non avendo il glaux.h inizialmente non mi compilava, poi anche inserendo tale file .h in c:\program files\microsof sdks\windows\v6.1\include\gl ancora non va!!! come faccio? so che stò approfittando della tua disponibilità.....

maria

Paulin
07-02-2011, 13:34
ciao scusami ancora ma non avendo il glaux.h inizialmente non mi compilava, poi anche inserendo tale file .h in c:\program files\microsof sdks\windows\v6.1\include\gl ancora non va!!! come faccio?
Hai aggiunto anche il file glaux.lib nella cartella delle librerie?
Hai aggiunto glaux.lib nella lista del linker nella proprietà del progetto?


comunque avevo provato ad utilizzare una funzione per il picking di un esempio 2D e quindi va alla grande per il 2D ma se la inserisco nel mio codice per il 3D non funziona, mi rileva 0 hits ma com'è possibile ??
É piuttosto facile far funzionare il picking in 2D (x,y) perché hai una proiezione ortogonale sullo schermo (w,h), nel 3D invece il selection buffer deve fare riferimento ad una matrice trasformata dove il mondo 3D (x,y,z) è correttamente proiettato sullo schermo (w,h).
Se guardi la funzione HitTest(int x, int y) contiene questo codice:


glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
gluPickMatrix((GLdouble)x, (GLdouble)(viewport[3] - y), 3.0, 3.0, viewport);
gluPerspective(Cam.fovy, Cam.ratio, Cam.zNear, Cam.zFar);

DrawScene(GL_SELECT);

glMatrixMode(GL_PROJECTION);
glPopMatrix();
che è quello che gli permette di svolgere una simile operazione. Si legge che nel ridefinire la matrice in modalità selezione occorre ridefinire la prospettiva alla stesso modo in cui è stata definita all'apertura della finestra e disegnarvi gli oggetti nello stesso modo in cui sono stati disegnati in modalità rendering.


per quanto riguarda l'utilizzo di glPushName o glLoadName:
http://www.opengl.org/sdk/docs/man/xhtml/glPushName.xml
http://www.opengl.org/sdk/docs/man/xhtml/glLoadName.xml

si legge:
glLoadName causes name to replace the value on the top of the name stack.
glPushName causes name to be pushed onto the name stack.

Il che significa che possono essere adoperati entrambi ma in modi differenti (non sono la stessa cosa).

Loading