Me gustaría compartir con ustedes una clase que he llamado TglCanva
para tener la funcionalidad de OpenGL en un Panel de C++ Builder.
He visto que existen otros códigos al respecto, pero me parecen muy
complicados con muchas funciones innecesarias.
La filosofía es mantener un panel (en negro o en otro color) que me
permita utilizar, en dos eventos básicos, los comandos propios de
OpenGL. Me gustaría escuchar sugerencias sobre todo en algunos
detalles que aún no tengo muy claros sobre esta implementación.
A mi este codigo me ha funcionado bien, en mi maquina, hasta ahora.
Por favor, me pueden escribir las obervaciones al correo
fbenavm@...
Una última observación. En anteriores versiones de Borland, debía
invocar el comando implib para acceder la funcionalidad de OpenGL en
Borland. Pero en esta ultima version, ya eso no es necesario.
Esto es algo que realmente todavía no entiendo.
Saludos !!!
************* TglCanva.h *********************
#ifndef TglCanvaH
#define TglCanvaH
//--------------------------------------------------------------------
-------
#include <SysUtils.hpp>
#include <Classes.hpp>
#include <Controls.hpp>
#include <ExtCtrls.hpp>
//--------------------------------------------------------------------
-------
#include <math.h>
#include <GL/glu.h>
#include <GL/gl.h>
#include "Col3.h"
#include "TransColor.h"
/*
No es necesario definir un nuevo tipo de evento para esto tan
simple
He incorporado el PanelNumber para informar el número de
panel que está
siendo renderizado al momento de llamar el evento
*/
typedef void __fastcall (__closure *TglEvent)(TObject* Sender, int
PanelNumber);
/*
Quizás sea mas conveniente heredar la clase TglCanva de una
clase más simple.
La clase TPanel fue la que menos problemas me dió en este
caso.
*/
class PACKAGE TglCanva : public TPanel
{
private:
/*
Estos handles son los que aparecen en casi todas las ayudas
para crear una
ventana de OpenGL usando Win32.
*/
HDC hdc;
HGLRC hrc;
int pixelFormat;
/*
Col3 es una estructura de tres componentes double
para crear los colores
*/
Col3 backColor;
/*
El renderizado se hace con dos eventos: p y r.
p es el evento que se dispara en OnSetParameters.
r OnRender
*/
TglEvent p;
TglEvent r;
/*
Decidí dar dos booleanos para inicializar los
gráficos y para renderizar
Sólo con el objeto de agregar un mecanismo de control
adicional
*/
bool init;
bool render;
/*
Debo sobreescribir el evento OnResize de Panel.
La idea es evitar el flickering durante el cambio de
tamaños.
*/
TNotifyEvent resizeEvent;
/*
Es posible desplegar varios paneles con la
funcionalidad de OpenGL
El panelnumbre mantiene esta informacion.
*/
unsigned int panelNumber;
/*
Para tener la posibilidad de usar varios paneles creo
una seccion critica
para hacer los renderizados de cada uno de ellos, sin
que Windows "confunda"
los distintos Handles o el refrescamiento sea llamado
de forma asincrónica
*/
static unsigned int panelCounter;
static CRITICAL_SECTION criticalSection;
/*
Los metodos setPixelFormatDescriptor, setParams y
setHandlers aparecen
en la ayuda de Borland con OpenGL y son bien
conocidos. Establecen
los handles para las areas en donde se despliegan los
openGL
*/
void setPixelFormatDescriptor();
void setParams();
void setHandlers();
/*
Se sobreescriben estos metodos en el objeto Panel.
No se ocupa el refrescamiento de la VCL sino el que
refresca de acuerdo
al renderizado de OpenGL
*/
void __fastcall CreateWnd();
void __fastcall Paint();
/*
El manejo de los colores en el IDE me decido hacerlo
de manera un poco
diferente, para que aparezca el color de fondo que me
decido a usar
en la imagen.
Aparece el color usado en el comando glClearColor(**);
*/
TColor __fastcall getBackColor();
void __fastcall setBackColor(TColor color);
/*
Para evitar el "flicker" desagradable producido por
el refrescamiento
de Windows. Este Mesage_Map se encarga del mensaje
EraseBackground
de Windows, para que refresque de acuerdo a OpenGL
evitando ese
flickering.
*/
MESSAGE void __fastcall EraseBackground(TMessage& message);
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER
(WM_ERASEBKGND,TMessage,EraseBackground);
END_MESSAGE_MAP(TPanel);
protected:
void __fastcall ResizeGraphics(TObject* sender);
public:
__fastcall TglCanva(TComponent* Owner);
__fastcall ~TglCanva();
void __fastcall parameters(TglEvent p);
/*
Se sobreescribe el método Refresh
*/
void __fastcall Refresh();
/*
Este método llama los eventos SetParameters y Render
en ese orden
*/
void __fastcall Reset();
/*
Inicializa el Panel con la funcionalidad de OpenGL
*/
void initGraphics();
/*
Cada vez que se llama a este proceso, el panel se
pone en modo de
renderizado o no renderizado, según sea el caso.
*/
void switchRender();
/*
Total de paneles con la funcionalidad de OpenGL
*/
static unsigned int totalPanels();
__published:
__property TColor Color = {read = getBackColor , write =
setBackColor};
/*
Se sobreescribe este metodo sobre el Panel
*/
__property TNotifyEvent OnResize = {read = resizeEvent, write
= resizeEvent};
/*
Eventos que aparecen en el IDE para imprimir.
La idea es usar OnSetParams para definir el tipo de
proyección, luces u
otros aspectos que no cambian con el refrescado
*/
__property TglEvent OnSetParameters = {read = p , write =
parameters};
/*
En OnRender se despliega la escena
*/
__property TglEvent OnRender = {read = r, write = r};
};
#endif
************* TglCanva.cpp *********************
#include <vcl.h>
#include <float.h>
#pragma hdrstop
#pragma package(smart_init)
#include "TglCanva.h"
#include "TransColor.h"
//--------------------------------------------------------------------
-------
// ValidCtrCheck is used to assure that the components created do not
have
// any pure virtual functions.
//
static inline void ValidCtrCheck(TglCanva *)
{
new TglCanva(NULL);
}
unsigned int TglCanva::panelCounter = 0;
CRITICAL_SECTION TglCanva::criticalSection;
//--------------------------------------------------------------------
-------
__fastcall TglCanva::TglCanva(TComponent* Owner)
: TPanel(Owner)
{
this->init = false;
this->render = false;
this->hdc = 0;
this->hrc = 0;
this->p = 0;
this->r = 0;
this->resizeEvent = 0;
this->TPanel::OnResize = this->ResizeGraphics;
/*
Se inicializa la seccion critica. Se utiliza cada vez
que se hace
un refrescamiento en alguno de los paneles
*/
if (TglCanva::panelCounter == 0)
{
InitializeCriticalSection(&TglCanva::criticalSection);
}
/*
Para evitar errores de redondeo en Borland
*/
_control87(MCW_EM, MCW_EM);
/*
Contador de paneles. Se usa para liberar, al final,
la seccion critica.
*/
TglCanva::panelCounter++;
this->panelNumber = TglCanva::panelCounter;
}
void TglCanva::setHandlers()
{
/*
Este handle los solicita OpenGL
*/
this->hdc = this->GetDeviceContext(this->WindowHandle);
this->setPixelFormatDescriptor();
this->hrc = wglCreateContext(hdc);
this->setParams();
}
void TglCanva::initGraphics()
{
this->init = true;
this->setHandlers();
this->Refresh();
}
__fastcall TglCanva::~TglCanva()
{
if (this->hdc)
{
wglMakeCurrent(hdc,0);
ReleaseDC(this->hdc,this->hrc);
wglDeleteContext(this->hrc);
}
TglCanva::panelCounter--;
if (TglCanva::panelCounter == 0)
{
DeleteCriticalSection(&TglCanva::criticalSection);
}
}
void TglCanva::setPixelFormatDescriptor()
{
PIXELFORMATDESCRIPTOR pfd = {
sizeof(PIXELFORMATDESCRIPTOR),
1,
PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL |
PFD_DOUBLEBUFFER,
PFD_TYPE_RGBA | PFD_SUPPORT_COMPOSITION ,
24,
0,0,0,0,0,0,
0,0,
0,0,0,0,0,
32,
0,
0,
PFD_MAIN_PLANE,
0,
0,0,0
};
this->pixelFormat = ChoosePixelFormat(hdc, &pfd);
SetPixelFormat(this->hdc, this->pixelFormat, &pfd);
}
void __fastcall TglCanva::CreateWnd()
{
this->TPanel::CreateWnd();
if (this->init)
{
this->setHandlers();
}
}
void TglCanva::setParams()
{
/*
Cada vez que se hacen llamadas a OpenGL se entra en
la seccion critica.
En set Parameterse se establece el color de fondo,
asi como otras llamadas
que no se hace necesario repetir durante el
refrescamiento.
*/
EnterCriticalSection(&TglCanva::criticalSection);
wglMakeCurrent(this->hdc,this->hrc);
glClearColor(this->backColor.R,this->backColor.G,this-
>backColor.B, 1);
if (this->p != 0)
{
this->p(this, this->panelNumber);
}
LeaveCriticalSection(&TglCanva::criticalSection);
}
void __fastcall TglCanva::Paint()
{
if (this->init)
{
/*
Aquí sí se hace el renderizado. Por esta
razón se entra en la
sección crítica, se establece el ViewPort y
el contexto.
Naturalmente, se limpia la pantalla
*/
EnterCriticalSection(&TglCanva::criticalSection);
wglMakeCurrent(this->hdc,this->hrc);
glViewport(0,0,this->Width,this->Height);
glClear(GL_COLOR_BUFFER_BIT);
if ((this->r != 0)&&(this->render))
{
this->r(this, this->panelNumber);
}
glFlush();
SwapBuffers(hdc);
LeaveCriticalSection(&TglCanva::criticalSection);
}
else
{
this->TPanel::Paint();
}
}
void TglCanva::switchRender()
{
/*
Se enciende o se apaga el renderizado
*/
if ((this->hdc)&&(!render))
{
this->render = true;
}
else
{
this->render = false;
}
this->Paint();
}
void __fastcall TglCanva::parameters(TglEvent p)
{
this->p = p;
}
TColor __fastcall TglCanva::getBackColor()
{
return(Col3ToColor(this->backColor));
}
void __fastcall TglCanva::setBackColor(TColor color)
{
this->backColor = ColorToCol3(color);
if (this->init)
{
this->setParams();
}
else
{
this->TPanel::Color = Col3ToColor(this->backColor);
this->Refresh();
}
}
/*
El reset, refresca los parametros. El Refresh, solo el
renderizado
*/
void __fastcall TglCanva::Reset()
{
this->setParams();
this->Paint();
}
void __fastcall TglCanva::Refresh()
{
this->Paint();
}
void __fastcall TglCanva::ResizeGraphics(TObject* sender)
{
if (this->init)
{
this->Reset();
if (this->resizeEvent)
{
this->resizeEvent(this);
}
}
else
{
this->TPanel::Paint();
}
}
/*
El mensaje de borrar el fondo, no se procesa
*/
MESSAGE void __fastcall TglCanva::EraseBackground(TMessage& message)
{
if (this->init)
{
message.Result = 1;
}
}
unsigned int TglCanva::totalPanels()
{
return(TglCanva::panelCounter);
}
//--------------------------------------------------------------------
-------
namespace Tglcanva
{
void __fastcall PACKAGE Register()
{
TComponentClass classes[1] = {__classid(TglCanva)};
RegisterComponents("glPanels", classes, 0);
}
}
//--------------------------------------------------------------------
-------
************* TCol3.h *********************
//--------------------------------------------------------------------
-------
#ifndef Col3H
#define Col3H
//--------------------------------------------------------------------
-------
__declspec (dllexport) struct Col3
{
long double R;
long double G;
long double B;
long double alpha;
Col3();
Col3(long double, long double, long double);
};
#endif
************* TCol3.cpp *********************
//--------------------------------------------------------------------
-------
#pragma hdrstop
#include "Col3.h"
//--------------------------------------------------------------------
-------
#pragma package(smart_init)
Col3::Col3()
{
this->R = 0;
this->G = 0;
this->B = 0;
this->alpha = 1;
}
Col3::Col3(long double R, long double G, long double B)
{
this->R = R;
this->G = G;
this->B = B;
this->alpha = 1;
}
//--------------------------------------------------------------------
-------
************************ TransColor.h **************************
#ifndef TransColorH
#define TransColorH
//--------------------------------------------------------------------
-------
#include <vcl.h>
#include "Col3.h"
const int bconst = 65536;
const int gconst = 256;
Col3 ColorToCol3(TColor color)
{
Col3 ret;
int R,G,B;
int y = abs(color);
if (y > 16777216)
{
int u = (int)((double)y / 16777216);
y = y - u * 16777216;
}
B = (int)((double)y / (double)bconst);
y = y - B*bconst;
G = (int)((double)y / (double)gconst);
y = y - G*gconst;
R = (int)(y);
ret.R = (double)(R)/256;
ret.G = (double)(G)/256;
ret.B = (double)(B)/256;
return(ret);
}
TColor Col3ToColor(Col3 color)
{
TColor c;
c = (TColor)((int)(color.B * (double)gconst * (double)bconst)
+ (int)(color.G * (double)bconst)
+ (int)(color.R * (double)gconst));
return(c);
}
#endif