The code of a typical Paint method is
shown below. It contains special code to fill the X3D buffer (a data structure
used by the X3D renderer), paint the OpenGL points and paint the primitive in the
current Pad.
void TBRIK::Paint(Option_t *option)
{
// Allocate memory for points
SetPoints(points);
[...]
if (!rangeView && gPad->GetView3D()) PaintGLPoints(points);
// Allocate memory for segments
[...]
// Allocate memory for points
[...]
// Allocate memory for polygons
[...]
// Paint in the pad
PaintShape(buff,rangeView);
if (strstr(option, "x3d")) FillX3DBuffer(buff);
[...]
}
This example shows the TBRIK::Paint
method.
TBRIK is part of the old 3D primitives
collection which is now replaced by the
TGeo system. Nevertheless, the
TBRIK case is still valid because
the TGeo primitives are rendered in the
same way by the TGeoPainter class.
The X3d and OpenGL viewers are invoked using a special method of
TPad called
x3d. The method has an option which can
be equal to "opengl" or
"x3d" depending which viewer one wants
to use. This method's code looks like the following:
void TPad::x3d(Option_t *option)
{
if (!strcasecmp(option, "OPENGL")) {
// Disconnect the previous 3D context
if (fPadView3D) fPadView3D->SetPad(0);
fPadView3D = gVirtualGL->CreatePadGLView(this);
if (!fPadView3D) {
gROOT->LoadClass("TGLKernel", "RGL");
fPadView3D = gVirtualGL->CreatePadGLView(this);
if (!fPadView3D) Error("x3d", "OpenGL shared libraries not loaded");
}
if (fPadView3D) {
Modified();
Update();
}
return;
}
TPluginHandler *h;
if ((h = gROOT->GetPluginManager()->FindHandler("TViewX3D"))) {
if (h->LoadPlugin() == -1) return;
h->ExecPlugin(5,this,option,"X3D Viewer",800,600);
}
}
In this code, two different techniques are used to invoke the
OpenGL or X3d
viewers. They both allow to load the graphic libraries
only when needed.
The way it is done in the OpenGL case is now obsolete and we will prefer the technique used in the X3d case, using the ROOT "Plugin Manager".
To use a class in the "Plugin Manager", it has to be declared in the file
$ROOTSYS/etc/system.rootrc. The class
TViewerX3D is declared as "Plugin"
in the file $ROOTSYS/etc/system.rootrc
the following way:
Plugin.TViewerX3D: * TViewerX3D X3d "TViewerX3D( [...] )"
This abstract interface (virtual class) will be named TVirtualViewer3D. Its concrete implementations will be:
The class TVirtualViewer3D will start a
new "3D viewer" in a separated window. Like now, starting a new "3D viewer" will be triggered by
TPad::x3d (a better name name for this
method can be find later on) The new
TPad::x3d
will be much simpler than the current one:
void TPad::x3d(Option_t *option)
{
// Invokes a 3D viewer
fViewer3D = 0;
fViewer3D = TVirtualViewer3D::Viewer3D(option);
if (fViewer3D) {
fViewer3D->CreateScene(option);
} else {
Error("x3d", "Cannot load 3D viewer with option: %s",option);
}
}
fViewer3D belongs to
TPad. It is a pointer to
TVirtualViewer3D.
TVirtualViewer3D::Viewer3D is a static method in
TVirtualViewer3D which returns a pointer to a
TVirtualViewer3D. This method uses the plug-in
manager facility:
TVirtualViewer3D* TVirtualViewer3D::Viewer3D(Option_t *option)
{
// Create a Viewer 3D according to "option"
TVirtualViewer3D *viewer = 0;
TPluginHandler *h;
if ((h = gROOT->GetPluginManager()->FindHandler("TVirtualViewer3D", option))) {
if (h->LoadPlugin() == -1) return 0;
viewer = (TVirtualViewer3D *) h->ExecPlugin(1, gPad);
}
return viewer;
}
The FindHandler method directly pass as
second parameter the TPad::x3d option.
This will allow to pick directly the right concrete implementation of
TVirtualViewer3D in the file
$ROOTSYS/etc/system.rootrc which will be
something like that:
Plugin.TVirtualViewer3D: x3d TViewerX3D X3d "TViewerX3D(TVirtualPad*)"
+Plugin.TVirtualViewer3D: ogl TViewerOpenGL OGL "TViewerOpenGL(TVirtualPad*)"
+Plugin.TVirtualViewer3D: oiv TViewerOIV OIV "TViewerOIV(TVirtualPad*)"
TVirtualViewer3D.h :
//////////////////////////////////////////////////////////////////////////
// //
// TVirtualViewer3D //
// //
// Abstract 3D primitives viewer. The concrete implementations are: //
// //
// TViewerX3D : X3d viewer //
// TViewerOpenGL: OpenGL viewer //
// TViewerOIV : Open Inventor //
// //
//////////////////////////////////////////////////////////////////////////
class TVirtualViewer3D {
protected:
TVirtualPad *fPad; // pad to be displayed in a 3D viewer
public:
TVirtualViewer3D();
TVirtualViewer3D(TVirtualPad *pad);
virtual ~TVirtualViewer3D();
virtual void CreateScene(Option_t *option); // Creates a 3D scene from the TPad
virtual void UpdateScene(Option_t *option); // Called by TBuffer3D::Paint
static TVirtualViewer3D *Viewer3D(Option_t *option);
ClassDef(TVirtualViewer3D,0) // Abstract interface to 3D viewers
};
New data members:
TBuffer3D *fBuffer3D; // Contains the current primitive description
TVirtualViewer3D *fViewer3D; // Current TVirtualViewer3D
New methods:
TBuffer3D *AllocateBuffer3D(Int_t n) // Allocates the needed space in fBuffer3D
TBuffer3D *GetBuffer3D() // Returns a pointer to fBuffer3D
TVirtualViewer3D *GetViewer3D() {return fViewer3D;}
void TVirtualViewer3D::CreateScene(Option_t *option)
{
TObject *obj;
// Loop over all the primitives inheriting from TAtt3D
// Depending on the viewer we might have several loops
// like that with different option values
TObjLink *lnk = fPad->GetListOfPrimitives()->FirstLink();
while (lnk) {
obj = lnk->GetObject();
if (obj->InheritsFrom(TAtt3D::Class())) {
obj->Paint(option);
}
lnk = lnk->Next();
}
}
Because
obj->Paint is recursive we cannot
fill the 3D scene graph or compute the scene size directly in
CreateScene. We need the
UpdateScene called from
gPad:
void TVirtualViewer3D::UpdateScene(option)
{
// Called by TBuffer3D::Paint
if (option == 'bla') [do something]
if (option == 'bli') [do something else]
}
void SHAPE::Paint(Option_t *option)
{
Int_t NbPnts = ...;
Int_t NbSegs = ...;
Int_t NbPols = ...;
// A) Allocate the needed space in the TBuffer3D in gPad
TBuffer3D *buff = gPad->AllocateBuffer3D(3*NbPnts, 3*NbSegs, 6*NbPols);
if (!buff) return;
// In case of option "size" it is enough to fill the buffer sizes
buff->fNbPnts = NbPnts;
buff->fNbSegs = NbSegs;
buff->fNbPols = NbPols;
if (strstr(option,"size")) {
buff->Paint(option);
return;
}
// B) Fill TBuffer3D
buff->fPnts[ 0] = -fDx; buff->fPnts[ 1] = -fDy; buff->fPnts[ 2] = -fDz;
...
... convert the points coordinates into the master reference system
with fGeom->LocalToMaster() is case of TGeo shapes and
with gGeometry->Local2Master() in case of TShapes ...
buff->fSegs[ 0] = c ; buff->fSegs[ 1] = 0 ; buff->fSegs[ 2] = 1 ;
...
buff->fPols[ 0] = c ; buff->fPols[ 1] = 4 ; buff->fPols[ 2] = 0 ;
...
// C) Paint buff
buff->Paint(option);
}
For performance reasons we might need some special cases in
SHAPE::Paint.
For instance
In the X3D case we have to compute the memory size needed by the X3D scene before
filling it (option
size)
so a first pass over all the primitives in the pad is required.
In that case the step B) is not needed.
For performance reasons again, we will not use the
option parameter because
finding the option value with strstr()
takes time. Instead we will use a Int_t
(or bit) data member of TBuffer3D.
enum EBuffer3DOption {kPAD, kRANGE, kSIZE, kX3D, kOGL};
TBuffer3D will have an parameter
called option which will be set
once before looping over all the shapes, and when needed the option value
will be tested that way:
if (buff->fOption == TBuffer3D::kSIZE) {
buff->Paint(option);
return;
}
class TBuffer3D : public TObject {
private:
public:
enum EBuffer3DType {kBRIK, kPGON, kPCON, kSPHE, kTUBE, kTUBS,
kTORUS, kXTRU, kLINE, kCSHAPE, kPARA,
kM3DBOX, kMARKER };
enum EBuffer3DOption {kPAD, kRANGE, kSIZE, kX3D, kOGL};
TBuffer3D();
TBuffer3D(Int_t n1, Int_t n2, Int_t n3);
virtual ~TBuffer3D();
void ReAllocate(Int_t n1, Int_t n2, Int_t n3);
void Paint(Option_t *option);
TObject *fId; // Pointer to he original object
Int_t fOption; // Option (see EBuffer3DOption)
Int_t fType; // Primitive type (see EBuffer3DType)
Int_t fNbPnts; // Number of points describing the shape
Int_t fNbSegs; // Number of segments describing the shape
Int_t fNbPols; // Number of polygons describing the shape
Int_t *fSegs; // c0, p0, q0, c1, p1, q1, ..... ..... ....
Int_t *fPols; // c0, n0, s0, s1, ... sn, c1, n1, s0, ... sn
Int_t fPntsSize; // Current size of fPnts
Int_t fSegsSize; // Current size of fSegs
Int_t fPolsSize; // Current size of fSegs
Double_t *fPnts; // x0, y0, z0, x1, y1, z1, ..... ..... ....
ClassDef(TBuffer3D,0) // 3D primitives description
}
Note that in case of primitives like 3D polylines the number of polygons
will be 0. Only points and segs are useful in that case (this is how it
is currently implemented in
TPolyLine3D::Paint).
Even if the shape description is independent from the shape type, it might be useful to know the shape's type (see EBuffer3DType, fType) to allow special treatment in particular 3D viewer.
TBuffer3D::Paint :
A possible implementation could be the following:
void TBuffer3D::Paint(Option_t *option)
{
Int_t i, i0, i1, i2;
Double_t x0, y0, z0, x1, y1, z1;
TVirtualViewer3D *viewer3D;
TView *view;
// Compute the shape range and update gPad->fView
switch (fOption) {
case kRANGE:
x0 = x1 = fPnts[0];
y0 = y1 = fPnts[1];
z0 = z1 = fPnts[2];
for (i=1; i