Visualisation¶
This Chapter is intended to be read after Design of Visualisation Category under the object oriented design description 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 Design of Visualisation Category first. The class structure described there is summarised in Fig. 45 :
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
.
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.).
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 Dealing with transient objects
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 inDrawView
.
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;
}
}
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 Dealing with transient objects.
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 invokesAddPrimitive
:BeginPrimitives(*fpObjectTransformation); pPolyhedron = solid.GetPolyhedron(); AddPrimitive(*pPolyhedron); EndPrimitives();
The resulting default sequence for a
G4PhysicalVolumeModel
is shown here: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();
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 sequenceBeginPrimitives (*fpObjectTransformation); AddPrimitive(*pPolyhedron); EndPrimitives();
can be invoked without a prior
PreAddSolid
, etc. The flagfProcessingSolid
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. Transients are discussed in Dealing with transient objects.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 Adding a new view parameter for details on trajectory models), which in turn, invokesDraw
methods of the visualisation manager. The resulting default sequence for aG4TrajectoriesModel
is shown here:DrawView(); |-->ProcessView(); |-->ProcessScene(); |-->BeginModeling(); |-->pModel -> DescribeYourselfTo(\*this); | |-->AddCompound(trajectory); | |-->trajectory.DrawTrajectory(...); | |-->DispatchToModel(...); | |-->model->Draw(...); | |-->G4VisManager::Draw(...); | |-->BeginPrimitives(objectTransform); | |-->AddPrimitive(...); | |-->EndPrimitives(); |-->EndModeling();
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, What happens in ProcessScene?.
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
.
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 1 , 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 What happens in DrawView?.)
More about scene models¶
Scene models conform to the G4VModel
abstract interface. Available
models are listed and described there in varying detail.
What happens in ProcessScene? 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 modelling parameters is to communicate the culling
policy. Most models, therefore, have no need for modelling parameters.
Adding a new view parameter¶
There are quite a few steps involved in adding a new view parameter.
G4ViewParameters.hh
:add new data member.
add new access function declarations (
Get
orIs
andSet
).
G4ViewParameters.icc
:add new access function implementations.
G4ViewParameters.cc
:initialise parameter in constructor.
add code for writing view with
/vis/viewer/save
, e.g., inG4ViewParameters::SceneModifyingCommands
or similar function.augment the following functions appropriately:
G4ViewParameters::PrintDifferences
std::ostream& operator << (std::ostream& os, const G4ViewParameters& v)
G4ViewParameters::operator!=
If the parameter needs to be copied to modelling parameters:
Add the same parameter to
G4ModelingParameters
. Follow the same instructions as above.Implement the actual copying in
G4VSceneHandler::CreateModelingParameters
.
When the view parameters change in any way, it may be necessary to “re-visit the kernel”, i.e., consult the geometry, trajectories, etc. For a parameter that is used in modelling, a kernel re-visit is obviously necessary. For graphics systems that have their own database, it’s not always necessary. For example, for “stored” OpenGL (OGLSX), a change of viewpoint or zoom factor does not require a kernel visit but a change from wireframe to surface rendering does. These sort of decisions are made in the following functions:
G4OpenGLStoredViewer::CompareForKernelVisit
G4OpenGLStoredQtViewer::CompareForKernelVisit
G4OpenInventorViewer::CompareForKernelVisit
Next you can implement a command to set the parameter. This will usually be in
G4VisCommandsViewer.cc
orG4VisCommandsViewerSet.cc
. Please preserve alphabetical order of commands.If this requires a new messenger class, this will have to be instantiated in
G4VisManager::RegisterMessengers
.
Then you can actually implement code that uses the parameter.
Enhanced Trajectory Drawing¶
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);
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, e.g.
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());
}
Trajectory Filtering¶
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, e.g.
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);
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 enhanced trajectory drawing models and associated messengers. See the filter factories in G4TrajectoryFilterFactories for example implementations.
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, .
- 1
There is an option to accumulate trajectories across events and runs – see commands
/vis/scene/endOfEventAction
and/vis/scene/endOfRunAction
.