3.6.  Visualisation

This Chapter is intended to be read after Chapter Section 2.12 on Visualisation object oriented design in Part II. Many of the concepts used here are defined there, and it strongly recommended that a writer of a new visualisation driver or trajectory drawer reads Chapter Section 2.12 first. The class structure described there is summarised in Figure 3.9.

Geant Visualisation System Class Diagram

Figure 3.9.  Geant Visualisation System Class Diagram


3.6.1.  Creating a new graphics driver

To create a new graphics driver for Geant4, it is necessary to implement a new set of three classes derived from the three base classes, G4VGraphicsSystem, G4VSceneHandler and G4VViewer.

3.6.1.1.  A useful place to start

A skeleton set of classes is included in the code distribution in the visualisation category under subdirectory visualisation/XXX (but they are not default-registered graphics systems [2]

There are several sets of classes, described in more detail below. A recommended approach is to copy the files that best match your graphics system to a new subdirectory with a name that suits your graphics system .

Then

  1. Change the name of the files (change the code -- XXX or XXXFile, etc., as chosen -- to something that suits your graphics system ).

  2. Change XXX similarly in all files.

  3. Change XXX similarly in name := G4XXX in GNUmakefile.

  4. Add your new subdirectory to SUBDIRS and SUBLIBS in visualisation/GNUmakefile.

  5. Look at the code and use it to build your visualisation driver. You might also find it useful to look at ASCIITree (and VTree) as an example of a minimal graphics driver . Look at FukuiRenderer as an example of a driver which implements AddSolid methods for some solids. Look at OpenGL as an example of a driver which implements a graphical database (display lists) and the machinery to decide when to rebuild. (OpenGL is complicated by the proliferation of combinations of the use or not of display lists for three window systems, X-windows, X with motif (interactive), Microsoft Windows (Win32), a total of six combinations, and much use is made of inheritance to avoid code duplication.)

  6. If it requires external libraries, introduce two new environment variables G4VIS_BUILD_XXX_DRIVER and G4VIS_USE_XXX (where XXX is your choice as above) and make the modifications to:

    • source/visualization/management/include/G4VisExecutive.icc

    • config/G4VIS_BUILD.gmk

    • config/G4VIS_USE.gmk

3.6.1.1.1.  Graphics driver templates in the XXX sub-category

You may use the following templates to help you get started writing a graphics driver . (The word ``template'' is used in the ordinary sense of the word; they are not C++ templates.)

  • G4XXX, G4XXXSceneHandler, G4XXXViewer Templates for the simplest possible graphics driver . These would be suitable for an ``immediate'' driver, i.e., one which renders each object immediately to a screen. Of course, if the view needs re-drawing, as, for example, after a change of viewpoint, the viewer requests a re-issue of drawn objects.

  • G4XXXFile, G4XXXFileSceneHandler, G4XXXFileViewer Templates for a file-writing graphics driver. The particular features are: delayed opening of the file on receipt of the first item; rewinding file on ClearView (to simulate the clearing of views and prevent the duplication of material in the file); closing of the file on ShowView, which may also trigger the launch of a browser. There are various degrees of sophistication in, for example, the allocation of filenames -- see FukuiRenderer or HepRepFile.

    These templates also show the use of a specific AddSolid function whereby the specific parameters, for example, the dimensions of a G4Box, can be accessed.

  • G4XXXStored, G4XXXStoredSceneHandler, G4XXXStoredViewer Templates for a graphics driver with a store/database. The advantage of a store is that the view can be refreshed, for example, from a different viewpoint, without a need to recompute. It is up to the viewer to decide when a re-computation is necessary. They also show how to distinguish between permanent and transient objects -- see also Section Section 3.6.1.6.

  • G4XXXSG, G4XXXSGSceneHandler, G4XXXSGViewer Templates for a sophisticated graphics driver with a scene graph. The scene graph, following Open Inventor parlance, is a tree of objects that dictates the order in which the objects are rendered. It obviously lends itself to the rendering of the Geant4 geometry hierarchy. For example, the Open Inventor driver draws only the top level volumes unless made invisible by picking. Thus the user can unwrap a branch of the geometry level by level. This has performance benefits and gives the user significant and useful control over the view. These classes show how to make a scene graph of drawn volumes, i.e., the set of volumes that have not been culled. (Normally, volumes marked invisible are culled, i.e., not drawn. Also, the user may wish to limit the number of drawn volumes for performance reasons.) The drivers also have to process non-geometry items and distinguish between transient and permanent objects as above.

3.6.1.2.  Important Command Actions

To help understand how the Geant4 Visualization System works, here are a few important function invocation sequences that follow user commands. For an explanation of the commands themselves, see command guidance or the Control section of the Application Developers Guide. For a fuller explanation of the functions, see appropriate base class head files or Software Reference Manual.

  • /vis/viewer/clear
        viewer->ClearView();   // Clears buffer or rewinds file.
        viewer->FinishView();  // Swaps buffer (double buffer systems).
        
  • /vis/viewer/flush
        /vis/viewer/refresh
        /vis/viewer/update
        
  • /vis/viewer/rebuild
        viewer->SetNeedKernelVisit(true);
        
  • /vis/viewer/refresh

    If the view is ``auto-refresh'', this command is also invoked after /vis/viewer/create, /vis/viewer/rebuild or a change of view parameters (/vis/viewer/set/..., etc.).

        viewer->SetView();    // Sets camera position, etc.
        viewer->ClearView();  // Clears buffer or rewinds file.
        viewer->DrawView();   // Draws to screen or writes to 
                              // file/socket.
        

  • /vis/viewer/update
        viewer->ShowView();   // Activates interactive windows or 
                              // closes file and/or triggers 
                              // post-processing.
        
  • /vis/scene/notifyHandlers

    For each viewer of the current scene, the equivalent of

        /vis/viewer/refresh
        

    If ``flush'' is specified on the command line, the equivalent of

        /vis/viewer/update
        

    /vis/scene/notifyHandlers is also invoked after a change of scene (/vis/scene/add/..., etc.).

3.6.1.3.  What happens in DrawView?

This depends on the viewer. Those with their own graphical database, for example, OpenGL's display lists or Open Inventor's scene graph, do not need to re-traverse the scene unless there has been a significant change of view parameters. For example, a mere change of viewpoint requires only a change of model-view matrix whilst a change of rendering mode from wireframe to surface might require a rebuild of the graphical database. A rebuild of the run-duration (persistent) objects in the scene is called a ``kernel visit''; the viewer prints ``Traversing scene data...''.

Note that end-of-event (transient) objects are only rebuilt at the end of an event or run, under control of the visualisation manager. Smart scene handlers keep them in separate display lists so that they can be rebuilt separately from the run-duration objects - see Section 3.6.1.6.

  • Integrated viewers with no graphical database For example, G4OpenGLImmediateXViewer::DrawView().

        NeedKernelVisit();  // Always need to visit G4 kernel.
        ProcessView();
        FinishView();
        

  • Integrated viewers with graphical database For example, G4OpenGLStoredXViewer::DrawView().

        KernelVisitDecision();  // Private function containing...
          if significant change of view parameters...
            NeedKernelVisit();
        ProcessView();
        FinishView();
        

  • File-writing viewers For example, G4DAWNFILEViewer::DrawView().

        NeedKernelVisit();
        ProcessView();
        

    Note that viewers needing to invoke FinishView must do it in DrawView.

3.6.1.4.  What happens in ProcessView?

ProcessView is inherited from G4VViewer:

void G4VViewer::ProcessView() {
  // If ClearStore has been requested, e.g., if the scene has changed,
  // of if the concrete viewer has decided that it necessary to visit
  // the kernel, perhaps because the view parameters have changed
  // drastically (this should be done in the concrete viewer's
  // DrawView)...
  if (fNeedKernelVisit) {
    fSceneHandler.ProcessScene(*this);
    fNeedKernelVisit = false;
  }
}

3.6.1.5.  What happens in ProcessScene?

ProcessScene is inherited from G4VSceneHandler}. It causes a traversal of the run-duration models in the scene. For drivers with graphical databases, it causes a rebuild (ClearStore). Then for the run-duration models:

    fReadyForTransients = false;
    BeginModeling();
    for each run-duration model...
      pModel -> DescribeYourselfTo(*this);
    EndModeling();
    fReadyForTransients = true;

(A second pass is made on request -- see G4VSceneHandler::ProcessScene.) The use of fReadyForTransients is described in Section 3.6.1.6.

What happens then depends on the type of model:

  • G4AxesModel G4AxesModel::DescribeYourselfTo simply calls sceneHandler.AddPrimitive methods directly.

        sceneHandler.BeginPrimitives();
        sceneHandler.AddPrimitive(x_axis);  // etc.
        sceneHandler.EndPrimitives();
        

    Most other models are like this, except for the following...

  • G4PhysicalVolumeModel The geometry is descended recursively, culling policy is enacted, and for each accepted (and possibly, clipped) solid:

        sceneHandler.PreAddSolid(theAT, *pVisAttribs);
        pSol->DescribeYourselfTo(sceneHandler);
        // For example, if pSol points to a G4Box...
        |-->G4Box::DescribeYourselfTo(G4VGraphicsScene& scene){
             scene.AddSolid(*this);
            }
        sceneHandler.PostAddSolid();
        

    The scene handler may implement the virtual function { AddSolid(const G4Box&)}, or inherit:

        void G4VSceneHandler::AddSolid(const G4Box& box) {
          RequestPrimitives(box);
        }
        

    RequestPrimitives converts the solid into primitives (G4Polyhedron) and invokes AddPrimitive:

        BeginPrimitives(*fpObjectTransformation);
        pPolyhedron = solid.GetPolyhedron();
        AddPrimitive(*pPolyhedron);
        EndPrimitives();
        

    The resulting default sequence for a G4PhysicalVolumeModel is shown in Figure 3.10.

        DrawView();
        |-->ProcessView();
            |-->ProcessScene();
                |-->BeginModeling();
                |-->pModel -> DescribeYourselfTo(*this);
                |   |-->sceneHandler.PreAddSolid(theAT, *pVisAttribs);
                |   |-->pSol->DescribeYourselfTo(sceneHandler);
                |   |   |-->sceneHandler.AddSolid(*this);
                |   |       |-->RequestPrimitives(solid);
                |   |           |-->BeginPrimitives (*fpObjectTransformation);
                |   |           |-->pPolyhedron = solid.GetPolyhedron();
                |   |           |-->AddPrimitive(*pPolyhedron);
                |   |           |-->EndPrimitives();
                |   |-->sceneHandler.PostAddSolid();
                |-->EndModeling();
        

    Figure 3.10.  The default sequence for a G4PhysicalVolumeModel}


    Note the sequence of calls at the core:

        sceneHandler.PreAddSolid(theAT, *pVisAttribs);
        pSol->DescribeYourselfTo(sceneHandler);
        |-->sceneHandler.AddSolid(*this);
           |-->RequestPrimitives(solid);
              |-->BeginPrimitives (*fpObjectTransformation);
              |-->pPolyhedron = solid.GetPolyhedron();
              |-->AddPrimitive(*pPolyhedron);
              |-->EndPrimitives();
        sceneHandler.PostAddSolid();
        

    is reduced to

        sceneHandler.PreAddSolid(theAT, *pVisAttribs);
        pSol->DescribeYourselfTo(sceneHandler);
        |-->sceneHandler.AddSolid(*this);  
        sceneHandler.PostAddSolid();
        

    if the scene handler implements its own AddSolid. Moreover, the sequence

        BeginPrimitives (*fpObjectTransformation); 
        AddPrimitive(*pPolyhedron);
        EndPrimitives();
        

    can be invoked without a prior PreAddSolid, etc. The flag fProcessingSolid will be false for the last case. The possibility of any or all of these three scenarios occurring, for both permanent and transient objects, affects the implementation of a scene handler if there is any attempt to build a graphical database. This is reflected in the templates XXXStored and XXXSG described in Section 3.6.1.1.1. Transients are discussed in Section 3.6.1.6.

  • G4TrajectoriesModel At end of event, the trajectory container is unpacked and, for each trajectory, sceneHandler.AddCompound called. The scene handler may implement this virtual function or inherit:

        void G4VSceneHandler::AddCompound (const G4VTrajectory& traj) {
          traj.DrawTrajectory(((G4TrajectoriesModel*)fpModel)->GetDrawingMode());
        }
        

    Similarly, the user may implement DrawTrajectory or inherit:

        void G4VTrajectory::DrawTrajectory(G4int i_mode) const {
          G4VVisManager* pVVisManager = G4VVisManager::GetConcreteInstance();
          if (0 != pVVisManager) {
            pVVisManager->DispatchToModel(*this, i_mode);
          }
        }
        

    Thence, the Draw method of the current trajectory model is invoked (see Section 3.6.2 for details on trajectory models), which in turn, invokes Draw methods of the visualisation manager. The resulting default sequence for a G4TrajectoriesModel is shown in Figure 3.11.

        DrawView();
        |-->ProcessView();
            |-->ProcessScene();
                |-->BeginModeling();
                |-->pModel -> DescribeYourselfTo(*this);
                |   |-->AddCompound(trajectory);
                |       |-->trajectory.DrawTrajectory(...);
                |           |-->DispatchToModel(...);
                |               |-->model->Draw(...);
                |                   |-->G4VisManager::Draw(...);
                |                       |-->BeginPrimitives(objectTransform);
                |                       |-->AddPrimitive(...);
                |                       |-->EndPrimitives();
                |-->EndModeling();
        

    Figure 3.11.  The default sequence for a G4PhysicalVolumeModel}


3.6.1.6.  Dealing with transient objects

Any visualisable object not defined in the run-duration part of a scene is treated as ``transient''. This includes trajectories, hits or anything drawn by the user through the G4VVisManager user-level interface (unless as part of a run-duration model implementation). A flag, fReadyForTransients}, is maintained by the scene handler. In fact, its normal state is true, and only temporarily, during handling of the run-duration part of the scene, is it set to false -- see description of ProcessScene, Section 3.6.1.5.

If the driver supports a graphical database, it is smart to distinguish transient and permanent objects. In this case, every Add method of the scene handler must be transient-aware. In some cases, it is enough to open a graphical data base component in BeginPrimitives, fill it in AddPrimitive and close it appropriately in EndPrimitives. In others, initialisation is done in BeginModeling and consolidation in EndModeling -- see G4OpenGLStoredSceneHandler. If any AddSolid method is implemented, then the graphical data base component should be opened in PreAddSolid, protecting against double opening, for example,

void G4XXXStoredSceneHandler::BeginPrimitives
(const G4Transform3D& objectTransformation) {
  G4VSceneHandler::BeginPrimitives(objectTransformation);
  // If thread of control has already passed through PreAddSolid,
  // avoid opening a graphical data base component again.
  if (!fProcessingSolid) {

for other solids.

The reason for this distinction is that at end of run the user typically wants to display trajectories on a view of the detector, then, at the end of the next event [3] , erase the old and see new trajectories. The visualisation manager messages the scene handler with ClearTransientStore just before drawing the trajectories to achieve this.

If the driver does not have a graphical database or does not distinguish between transient and persistent objects, it must emulate ClearTransientStore. Typically, it must erase everything, including the detector, and re-draw the detector and other run-duration objects, ready for the transients to be added. File-writing drivers must rewind the output file. Typically:

void G4HepRepFileSceneHandler::ClearTransientStore() {
  G4VSceneHandler::ClearTransientStore();
  // This is typically called after an update and before drawing hits
  // of the next event.  To simulate the clearing of "transients"
  // (hits, etc.) the detector is redrawn...
  if (fpViewer) {
    fpViewer -> SetView();
    fpViewer -> ClearView();
    fpViewer -> DrawView();
  }
}

ClearView rewinds the output file and DrawView re-draws the detector, etc. (For smart drivers, DrawView is smart enough to know not to redraw the detector, etc., unless the view parameters have changed significantly -- see Section 3.6.1.3)

3.6.1.7.  More about scene models

Scene models conform to the G4VModel abstract interface. Available models are listed and described there in varying detail. Section 3.6.1.5 describes their use in some common command actions.

In the design of a new model, care should be taken to handle the possibility that the G4ModelingParameters pointer is zero. Currently the only use of the modeling parameters is to communicate the culling policy. Most models, therefore, have no need for modeling parameters.

3.6.2.  Enhanced Trajectory Drawing

3.6.2.1.  Creating a new trajectory model

New trajectory models must inherit from G4VTrajectoryModel and implement these pure virtual functions:

    virtual void Draw(const G4VTrajectory&, G4int i_mode = 0, 
                      const G4bool& visible = true) const = 0;
    virtual void Print(std::ostream& ostr) const = 0;

To use the new model directly in compiled code, simply register it with the G4VisManager, eg:

  G4VisManager* visManager = new G4VisExecutive;
  visManager->Initialise();

  // Create custom model
  MyCustomTrajectoryModel* myModel = 
             new MyCustomTrajectoryModel("custom");

  // Configure it if necessary then register with G4VisManager
  ...
  visManager->RegisterModel(myModel);

3.6.2.2.  Adding interactive functionality

Additional classes need to be written if the new model is to be created and configured interactively:

  • Messenger classes

    Messengers to configure the model should inherit from G4VModelCommand. The concrete trajectory model type should be used for the template parameter, eg:

        class G4MyCustomModelCommand 
              : public G4VModelCommand<G4TrajectoryDrawByParticleID> {
        ...
        };
        

    A number of general use templated commands are available in G4ModelCommandsT.hh.

  • Factory class

    A factory class responsible for the model and associated messenger creation must also be written. The factory should inherit from G4VModelFactory. The abstract model type should be used for the template parameter, eg:

        class G4TrajectoryDrawByChargeFactory 
           : public G4VModelFactory<G4VTrajectoryModel> {
         ...
        };
        

    The model and associated messengers should be constructed in the Create method. Optionally, a context object can also be created, with its own associated messengers. For example:

        ModelAndMessengers
        G4TrajectoryDrawByParticleIDFactory::
            Create(const G4String& placement, const G4String& name)
        {  
          // Create default context and model
          G4VisTrajContext* context = new G4VisTrajContext("default");
          G4TrajectoryDrawByParticleID* model = 
                      new G4TrajectoryDrawByParticleID(name, context);
    
          // Create messengers for default context configuration
          AddContextMsgrs(context, messengers, placement+"/"+name);
    
          // Create messengers for drawer
          messengers.push_back(new 
            G4ModelCmdSetStringColour<G4TrajectoryDrawByParticleID>
                                                   (model, placement));
          messengers.push_back(new 
            G4ModelCmdSetDefaultColour<G4TrajectoryDrawByParticleID>
                                                   (model, placement));
          messengers.push_back(new 
            G4ModelCmdVerbose<G4TrajectoryDrawByParticleID>
                                                   (model, placement));
    
          return ModelAndMessengers(model, messengers);
        }
        

The new factory must then be registered with the visualisation manager. This should be done by overriding the G4VisManager::RegisterModelFactory method in a subclass. See, for example, the G4VisManager implementation:

G4VisExecutive::RegisterModelFactories()
{
   ...
   RegisterModelFactory(new G4TrajectoryDrawByParticleIDFactory());
}

3.6.3.  Trajectory Filtering

3.6.3.1.  Creating a new trajectory filter model

New trajectory filters must inherit at least from G4VFilter. The models supplied with the Geant4 distribution inherit from G4SmartFilter, which implements some specialisations on top of G4VFilter. The models implement these pure virtual functions:

  // Evaluate method implemented in subclass
  virtual G4bool Evaluate(const T&) = 0;

  // Print subclass configuration
  virtual void Print(std::ostream& ostr) const = 0;

To use the new filter model directly in compiled code, simply register it with the G4VisManager, eg:

  G4VisManager* visManager = new G4VisExecutive;
  visManager->Initialise();

  // Create custom model
  MyCustomTrajectoryFilterModel* myModel = 
        new MyCustomTrajectoryFilterModel("custom");

  // Configure it if necessary then register with G4VisManager
  ...
  visManager->RegisterModel(myModel);

3.6.3.2.  Adding interactive functionality

Additional classes need to be written if the new model is to be created and configured interactively. The mechanism is exactly the same as that used to create enchanced trajectory drawing models and associated messengers. See the filter factories in G4TrajectoryFilterFactories for example implementations.

3.6.4.  Other Resources

The following sections contain various information for extending other class functionalities of Geant4 visualisation:

  • User's Guide for Application Developers, Chapter 8 - Visualization
  • User's Guide for Toolkit Developers, Object-oriented Analysis and Design of Geant4 Classes, Section 2.12.

[Status of this chapter]

03.12.05 ``Enhanced Trajectory Drawing'' added by Jane Tinsley.
03.12.05 ``Creating a new visualisation driver'' (from Part II) by John Allison.
09.01.06 ``Creating a new visualisation driver'' considerably expanded by John Allison.
20.06.06 Some sections improved or added from draft vis paper. John Allison.
Dec. 2006 Conversion from latex to Docbook verson by K. Amako



[2] To do this,simply instantiate and register, for example: visManager->RegisterGraphicsSystem(new G4XXX) before visManager->Initialise().

[3] There is an option to accumulate trajectories across events and runs -- see commands /vis/scene/endOfEventAction and /vis/scene/endOfRunAction.