He tenido problemas similares al hacer un juego OpenGL también a través de qglwidget, y la solución que se me ocurrió es el uso de proyección ortográfica para generar controles creados con scratch. Al final de la función de dibujo principal, llamo a una función separada que configura la vista y luego dibuja los 2d bits. En cuanto a los eventos del mouse, capturo los eventos enviados al widget principal y luego uso una combinación de pruebas de aciertos, compensaciones de coordenadas recursivas y pseudo-devoluciones de llamada que permiten anidar mis controles. En mi implementación, reenvío comandos a los controles de nivel superior (principalmente cuadros de desplazamiento) de a uno por vez, pero puede agregar fácilmente una estructura de listas enlazadas si necesita agregar controles dinámicamente. Actualmente solo uso cuadrantes para representar las piezas como paneles de colores, pero debería ser simple agregar texturas o incluso hacer que sean componentes en 3D que responden a la iluminación dinámica. Hay una gran cantidad de código, así que sólo voy a tirar los bits correspondientes en las piezas:
void GLWidget::paintGL() {
if (isPaused) return;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix();
glLightfv(GL_LIGHT0, GL_POSITION, FV0001);
gluLookAt(...);
// Typical 3d stuff
glCallList(Body::glGrid);
glPopMatrix();
//non3d bits
drawOverlay(); // <---call the 2d rendering
fpsCount++;
}
Aquí está el código que configura la matriz modelview/proyección para la representación 2d:
void GLWidget::drawOverlay() {
glClear(GL_DEPTH_BUFFER_BIT);
glPushMatrix(); //reset
glLoadIdentity(); //modelview
glMatrixMode(GL_PROJECTION);//set ortho camera
glPushMatrix();
glLoadIdentity();
gluOrtho2D(0,width()-2*padX,height()-2*padY,0); // <--critical; padx/y is just the letterboxing I use
glMatrixMode(GL_MODELVIEW);
glDisable(GL_DEPTH_TEST); // <-- necessary if you want to draw things in the order you call them
glDisable(GL_LIGHTING); // <-- lighting can cause weird artifacts
drawHUD(); //<-- Actually does the drawing
glEnable(GL_LIGHTING);
glEnable(GL_DEPTH_TEST);
glMatrixMode(GL_PROJECTION); glPopMatrix();
glMatrixMode(GL_MODELVIEW); glPopMatrix();
}
Este es la función principal de dibujo que maneja las cosas hud; la mayoría de las funciones de dibujo se parecen al gran trozo de 'panel de fondo'. Es, básicamente, empuja la matriz modelview, utiliza traducir/rotación/escala como lo haría en 3D, pintar los controles, a continuación, popMatrix:
void GLWidget::drawHUD() {
float gridcol[] = { 0.5, 0.1, 0.5, 0.9 };
float gridcolB[] = { 0.3, 0.0, 0.3, 0.9 };
//string caption
QString tmpStr = QString::number(FPS);
drawText(tmpStr.toAscii(),120+padX,20+padY);
//Markers
tGroup* tmpGroup = curGroup->firstChild;
while (tmpGroup != 0) {
drawMarker(tmpGroup->scrXYZ);
tmpGroup = tmpGroup->nextItem;
}
//Background panel
glEnable(GL_BLEND);
glTranslatef(0,height()*0.8,0);
glBegin(GL_QUADS);
glColor4fv(gridcol);
glVertex2f(0.0f, 0.0);
glVertex2f(width(), 0);
glColor4fv(gridcolB);
glVertex2f(width(), height()*0.2);
glVertex2f(0.0f, height()*0.2);
glEnd();
glDisable(GL_BLEND);
glLoadIdentity(); //nec b/c of translate ^^
glTranslatef(-padX,-padY,0);
//Controls
cargoBox->Draw();
sideBox->Draw();
econBox->Draw();
}
ya por los propios controles. Todos mis controles, como botones, controles deslizantes, etiquetas, etc., pertenecen a una clase base común con funciones virtuales vacías que les permiten interactuar de forma independiente entre sí. La base tiene elementos típicos como la altura, las coordenadas, etc., así como algunas sugerencias para gestionar las devoluciones de llamada y la representación del texto. También se incluye la función de hittest genérico (x, y) que indica si se ha hecho clic en ella. Podrías hacer esto virtual si quieres controles elípticos. Esta es la clase base, la hittest, y el código para un simple botón .:
class glBase {
public:
glBase(float tx, float ty, float tw, float th);
virtual void Draw() {return;}
virtual glBase* mouseDown(float xIn, float yIn) {return this;}
virtual void mouseUp(float xIn, float yIn) {return;}
virtual void mouseMove(float xIn, float yIn) {return;}
virtual void childCallBack(float vIn, void* caller) {return;}
bool HitTest(float xIn, float yIn);
float xOff, yOff;
float width, height;
glBase* parent;
static GLWidget* renderer;
protected:
glBase* callFwd;
};
bool glBase::HitTest(float xIn, float yIn) { //input should be relative to parents space
xIn -= xOff; yIn -= yOff;
if (yIn >= 0 && xIn >= 0) {
if (xIn <= width && yIn <= height) return true;
}
return false;
}
class glButton : public glBase {
public:
glButton(float tx, float ty, float tw, float th, char rChar);
void Draw();
glBase* mouseDown(float xIn, float yIn);
void mouseUp(float xIn, float yIn);
private:
bool isPressed;
char renderChar;
};
glButton::glButton(float tx, float ty, float tw, float th, char rChar) :
glBase(tx, ty, tw, th), isPressed(false), renderChar(rChar) {}
void glButton::Draw() {
float gridcolA[] = { 0.5, 0.6, 0.5, 1.0 };//up
float gridcolB[] = { 0.2, 0.3, 0.2, 1.0 };
float gridcolC[] = { 1.0, 0.2, 0.1, 1.0 };//dn
float gridcolD[] = { 1.0, 0.5, 0.3, 1.0 };
float* upState;
float* upStateB;
if (isPressed) {
upState = gridcolC;
upStateB = gridcolD;
} else {
upState = gridcolA;
upStateB = gridcolB;
}
glPushMatrix();
glTranslatef(xOff,yOff,0);
glBegin(GL_QUADS);
glColor4fv(upState);
glVertex2f(0, 0);
glVertex2f(width, 0);
glColor4fv(upStateB);
glVertex2f(width, height);
glVertex2f(0, height);
glEnd();
if (renderChar != 0) //center
renderer->drawChar(renderChar, (width - 12)/2, (height - 16)/2);
glPopMatrix();
}
glBase* glButton::mouseDown(float xIn, float yIn) {
isPressed = true;
return this;
}
void glButton::mouseUp(float xIn, float yIn) {
isPressed = false;
if (parent != 0) {
if (HitTest(xIn, yIn)) parent->childCallBack(0, this);
}
}
Dado que los controles compuestos como reguladores de tener varios botones, y scrollboxes tienen azulejos y deslizadores, todo tiene que ser recursivo para el dibujo/eventos de mouse. Cada control también tiene una función de devolución de llamada genérica que pasa un valor y su propia dirección; esto le permite al padre probar cada uno de sus hijos para ver quién llama y responder apropiadamente (por ejemplo, botones arriba/abajo). Tenga en cuenta que los controles secundarios se agregan a través de new/delete en los c/dtors. Además, se llama a las funciones draw() para los controles secundarios durante la propia llamada draw() de los padres, que permite que todo sea independiente. Aquí está el código relevante de una barra de desplazamiento de nivel medio que contiene 3 botones sub:
glBase* glSlider::mouseDown(float xIn, float yIn) {
xIn -= xOff; yIn -= yOff;//move from parent to local coords
if (slider->HitTest(xIn,yIn-width)) { //clicked slider
yIn -= width; //localize to field area
callFwd = slider->mouseDown(xIn, yIn);
dragMode = true;
dragY = yIn - slider->yOff;
} else if (upButton->HitTest(xIn,yIn)) {
callFwd = upButton->mouseDown(xIn, yIn);
} else if (downButton->HitTest(xIn,yIn)) {
callFwd = downButton->mouseDown(xIn, yIn);
} else {
//clicked in field, but not on slider
}
return this;
}//TO BE CHECKED (esp slider hit)
void glSlider::mouseUp(float xIn, float yIn) {
xIn -= xOff; yIn -= yOff;
if (callFwd != 0) {
callFwd->mouseUp(xIn, yIn); //ok to not translate b/c slider doesn't callback
callFwd = 0;
dragMode = false;
} else {
//clicked in field, not any of the 3 buttons
}
}
void glSlider::childCallBack(float vIn, void* caller) { //can use value blending for smooth
float tDelta = (maxVal - minVal)/(10);
if (caller == upButton) {//only gets callbacks from ud buttons
setVal(Value - tDelta);
} else {
setVal(Value + tDelta);
}
}
Ahora que tenemos los controles independientes que hacen en un contexto OpenGL, lo único que se necesita es la interfaz del control de nivel superior , por ejemplo, eventos de mouse desde el glwidget.
void GLWidget::mousePressEvent(QMouseEvent* event) {
if (cargoBox->HitTest(event->x()-padX, event->y()-padY)) { //if clicked first scrollbox
callFwd = cargoBox->mouseDown(event->x()-padX, event->y()-padY); //forward the click, noting which last got
return;
} else if (sideBox->HitTest(event->x()-padX, event->y()-padY)) {
callFwd = sideBox->mouseDown(event->x()-padX, event->y()-padY);
return;
} else if (econBox->HitTest(event->x()-padX, event->y()-padY)) {
callFwd = econBox->mouseDown(event->x()-padX, event->y()-padY);
return;
}
lastPos = event->pos(); //for dragging
}
void GLWidget::mouseReleaseEvent(QMouseEvent *event) {
if (callFwd != 0) { //Did user mousedown on something?
callFwd->mouseUp(event->x()-padX, event->y()-padY);
callFwd = 0; //^^^ Allows you to drag off a button before releasing w/o triggering its callback
return;
} else { //local
tBase* tmpPick = pickObject(event->x(), event->y());
//normal clicking, game stuff...
}
}
En general este sistema es un poco hacky, y no he añadido algunas características como la respuesta del teclado o el cambio de tamaño, pero éstos deben ser fáciles de añadir, tal vez requiera un 'tipo de evento' en la devolución de llamada (es decir, del ratón o teclado). Incluso podría subclase el glBase en el widget superior, e incluso le permitirá recibir el parent-> childcallback. Aunque es mucho trabajo, este sistema solo requiere vainilla C++/opengl y usa muchos menos recursos de CPU/memoria que las cosas nativas de Qt, probablemente en un orden de magnitud o dos. Si necesita más información, solo pregunte.
Me acabo de dar cuenta de que puedo simplemente configurar el modo de procesamiento en ortonormal en lugar de perspectiva. Esto hace innecesario el sombreado GUI. Pero aún así, creo que tiene que haber una forma incorporada de hacer overpainting en Qt3D (ya que hay un método en el viejo y viejo QGLWidget (que no quiero usar ya que está en el módulo de widgets Qt5 y no puedo) render usando QGLPainter)). – leemes
Esto parece ser una posibilidad (suena muy bien): http://comments.gmane.org/gmane.comp.lib.qt.3d/264 – leemes