// @configure_input@
/**************************************************************************\
*
* This file is part of the Coin 3D visualization library.
* Copyright (C) by Kongsberg Oil & Gas Technologies.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* ("GPL") version 2 as published by the Free Software Foundation.
* See the file LICENSE.GPL at the root directory of this source
* distribution for additional information about the GNU GPL.
*
* For using Coin with software that can not be combined with the GNU
* GPL, and for taking advantage of the additional benefits of our
* support services, please contact Kongsberg Oil & Gas Technologies
* about acquiring a Coin Professional Edition License.
*
* See http://www.coin3d.org/ for more information.
*
* Kongsberg Oil & Gas Technologies, Bygdoy Alle 5, 0257 Oslo, NORWAY.
* http://www.sim.no/ sales@sim.no coin-support@coin3d.org
*
\**************************************************************************/
// NOTE: The So@Gui@Viewer.cpp sourcecode file is completely
// autogenerated from "templatized" source code.
// *************************************************************************
/*!
\class So@Gui@Viewer Inventor/@Gui@/viewers/So@Gui@Viewer.h
\brief The So@Gui@Viewer class is the top level base viewer class.
\ingroup components viewers
This is an abstract class, which adds the following features to it's
So@Gui@RenderArea superclass: convenient methods for camera
handling, an automatic headlight configuration.
As for the camera handling: when setting a new scenegraph for the
viewer, the scenegraph will automatically be scanned for a node
derived from SoCamera. If not found, the viewer will itself set up a
camera for the scene. The camera can then be conveniently controlled
by the application programmers in many aspects:
- camera type: toggle between using an orthographic camera and a
perspective camera with So@Gui@Viewer::toggleCameraType()
- zoom out to exactly encompass all scene geometry within the view
by using So@Gui@Viewer::viewAll()
- tag a specific position and orientation for the camera as the
"home" position with So@Gui@Viewer::saveHomePosition(), which one
can then return to by using
So@Gui@Viewer::resetToHomePosition()
- automatically fit the near and far clipping planes of the camera
around the scene's geometry by using
So@Gui@Viewer::setAutoClipping()
- control stereo viewing parameters
Note that there is no dragger or manipulator attached to the scene
camera. The camera transform manipulation is calculated in a more
direct manner in the non-abstract viewer classes inheriting
So@Gui@Viewer by reading mouse and keyboard events and interpreting
how these should influence the camera. The calculations results in
new values for SoCamera::position, SoCamera::orientation, and the
other SoCamera field values for the camera designated to be the
viewer viewpoint camera. These values are then inserted directly
into the viewer's SoCamera node.
See e.g. the source code for So@Gui@ExaminerViewer::processSoEvent()
for the details.
The So@Gui@Viewer class automatically adds a headlight to the scene,
which will always point in the approximate same direction as the
current viewer camera, thereby securing that the scene geometry is
always lighted and visible. (If you don't want the constant
headlight, but rather want to light the scene on your own, this
behavior can be turned off with So@Gui@Viewer::setHeadlight()).
So@Gui@Viewer-derived viewers all inherit the following keyboard
controls from this class (but only when the viewer is in "examine
mode", ie So@Gui@Viewer::isViewing() returns \c TRUE):
- "s": put the viewer in "seek mode", where the end user may click
anywhere on scene geometry to trigger an animation which moves the
camera towards the point clicked
- "Home": hit this key to move camera back to last saved "home
position"
- arrow keys: moves camera slightly left, right, up or down
- "q": exit application
*/
// *************************************************************************
/*!
\enum So@Gui@Viewer::AutoClippingStrategy
Enum for auto clipping strategy.
\sa setAutoClippingStrategy()
*/
/*!
\var So@Gui@Viewer::AutoClippingStrategy So@Gui@Viewer::CONSTANT_NEAR_PLANE
Constant near plane auto clipping strategy. Explained in detail in
the documentation for the So@Gui@Viewer::setAutoClippingStrategy()
method.
*/
/*!
\var So@Gui@Viewer::AutoClippingStrategy So@Gui@Viewer::VARIABLE_NEAR_PLANE
Variable near plane auto clipping strategy. Explained in detail in
the documentation for the So@Gui@Viewer::setAutoClippingStrategy()
method.
*/
// *************************************************************************
#ifdef HAVE_CONFIG_H
#include
#endif // HAVE_CONFIG_H
#include
#include
#include
#include // FLT_MAX
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef HAVE_SOPOLYGONOFFSET
#include
#endif // HAVE_SOPOLYGONOFFSET
#include
#include
#include
#include
#include
#include
#include
#include
// *************************************************************************
// (note: this *must* be a #define, not a static variable -- to avoid
// initialization race conditions with the static variables being set
// to the value of this)
#define UNINITIALIZED_ENVVAR -1 // value of envvars before tested
// Environment variable for debugging purpose: display a running
// frames-per-second counter. See code comments above
// So@Gui@ViewerP::recordFPS() function below for more information.
static int COIN_SHOW_FPS_COUNTER = UNINITIALIZED_ENVVAR;
// *************************************************************************
#define PRIVATE(ptr) (ptr->pimpl)
#define PUBLIC(ptr) (ptr->pub)
// *************************************************************************
So@Gui@ViewerP::So@Gui@ViewerP(So@Gui@Viewer * publ)
{
PUBLIC(this) = publ;
this->searchaction = new SoSearchAction;
this->matrixaction = new SoGetMatrixAction(SbViewportRegion(100,100));
this->superimpositions = NULL;
this->storedcamera = NULL;
// initialize auto clipping parameters
this->autoclipstrategy = So@Gui@Viewer::VARIABLE_NEAR_PLANE;
this->autoclipvalue = 0.6f;
this->autoclipcb = NULL;
this->stereotype = So@Gui@Viewer::STEREO_NONE;
this->stereotypesetexplicit = FALSE;
this->stereostencilmaskvp = SbViewportRegion(0, 0);
this->stereostencilmask = NULL;
this->stereostenciltype = So@Gui@Viewer::STEREO_NONE;
this->stereoanaglyphmask[0][0] = TRUE;
this->stereoanaglyphmask[0][1] = this->stereoanaglyphmask[0][2] = FALSE;
this->stereoanaglyphmask[1][0] = FALSE;
this->stereoanaglyphmask[1][1] = this->stereoanaglyphmask[1][2] = TRUE;
}
So@Gui@ViewerP::~So@Gui@ViewerP()
{
// This impossible to miss reminder was inserted so we don't
// accidentally let an So* v2 slip out the door without fixing this
// API design flaw. 20030625 mortene.
#if (SO@GUI@_MAJOR_VERSION == 2)
#error This is a reminder: when jumping to version 2 of an So* toolkit, the viewer destructors (at least, possibly also further up in the inheritance hierarchy) should be made virtual.
#endif // version = 2
delete[] this->stereostencilmask;
if ( this->superimpositions != NULL ) delete this->superimpositions;
delete this->searchaction;
delete this->matrixaction;
if (this->storedcamera) { this->storedcamera->unref(); }
}
SoSeparator *
So@Gui@ViewerP::createSuperScene(void)
{
static const char * superSceneGraph[] =
{
"#Inventor V2.1 ascii",
"",
"Separator {",
" renderCaching OFF",
" renderCulling OFF",
" pickCulling OFF",
" boundingBoxCaching OFF",
// Headlight. By inserting this before any scenegraph camera, the
// light will always be pointing in the correct direction.
" DEF so@gui@->headlight DirectionalLight {",
" direction 1 -1 -10",
" }",
" DEF so@gui@->drawstyleroot Switch {",
" whichChild -1",
" DEF so@gui@->lightmodel LightModel {",
" model BASE_COLOR",
" }",
" DEF so@gui@->drawstyle DrawStyle {",
" pointSize ~",
" lineWidth ~",
" linePattern ~",
" }",
" DEF so@gui@->complexity Complexity {",
" textureQuality 0.0",
" value 0.1",
" }",
" }",
" DEF so@gui@->hiddenlineroot Switch {",
" whichChild -1",
" DEF so@gui@->basecolor BaseColor { }",
" DEF so@gui@->materialbinding MaterialBinding {",
" value OVERALL",
" }",
" DEF so@gui@->polygonoffsetparent Switch {",
" whichChild -1",
#ifdef HAVE_SOPOLYGONOFFSET
" DEF so@gui@->polygonoffset PolygonOffset { }",
#endif // HAVE_SOPOLYGONOFFSET
" }",
" }",
" DEF so@gui@->userscenegraphroot Separator {",
// turn off caching to make it possible for users to disable
// caching in their scene graphs.
" renderCaching OFF\n",
" }",
"}",
NULL
};
int i, bufsize;
for (i = bufsize = 0; superSceneGraph[i]; i++)
bufsize += strlen(superSceneGraph[i]) + 1;
char * buf = new char [bufsize + 1];
for (i = bufsize = 0; superSceneGraph[i]; i++) {
strcpy(buf + bufsize, superSceneGraph[i]);
bufsize += strlen(superSceneGraph[i]);
buf[bufsize] = '\n';
bufsize++;
}
SoInput * input = new SoInput;
input->setBuffer(buf, bufsize);
SoNode * root = NULL;
SbBool ok = SoDB::read(input, root);
delete input;
delete [] buf;
if (!ok) {
// FIXME: this looks unnecessary robust, and I believe it should
// be replaced by an assert()..? 20030430 mortene.
SoDebugError::post("So@Gui@ViewerP::createSuperScene",
"couldn't create viewer superscene");
return NULL;
}
assert(root->isOfType(SoSeparator::getClassTypeId()));
root->ref();
this->searchaction->reset();
this->searchaction->setSearchingAll(TRUE);
this->searchaction->setInterest(SoSearchAction::FIRST);
#define LOCATE_NODE(member, type, name) \
do { \
member = NULL; \
this->searchaction->setName(SbName(name)); \
this->searchaction->apply(root); \
if (this->searchaction->getPath() != NULL) { \
SoNode * node = this->searchaction->getPath()->getTail(); \
assert(node != NULL); \
if (node->isOfType(type::getClassTypeId())) \
member = (type *) node; \
} else { \
SoDebugError::post("So@Gui@ViewerP::createSuperScene", \
"didn't locate node \"%s\"", name); \
} \
} while (FALSE)
LOCATE_NODE(this->headlight, SoDirectionalLight, "so@gui@->headlight");
LOCATE_NODE(this->drawstyleroot, SoSwitch, "so@gui@->drawstyleroot");
LOCATE_NODE(this->hiddenlineroot, SoSwitch, "so@gui@->hiddenlineroot");
LOCATE_NODE(this->polygonoffsetparent, SoSwitch,
"so@gui@->polygonoffsetparent");
LOCATE_NODE(this->usersceneroot, SoSeparator, "so@gui@->userscenegraphroot");
LOCATE_NODE(this->sobasecolor, SoBaseColor, "so@gui@->basecolor");
LOCATE_NODE(this->socomplexity, SoComplexity, "so@gui@->complexity");
LOCATE_NODE(this->sodrawstyle, SoDrawStyle, "so@gui@->drawstyle");
LOCATE_NODE(this->solightmodel, SoLightModel, "so@gui@->lightmodel");
LOCATE_NODE(this->somaterialbinding, SoMaterialBinding, "so@gui@->materialbinding");
if (this->sobasecolor) this->sobasecolor->setOverride(TRUE);
if (this->socomplexity) this->socomplexity->setOverride(TRUE);
if (this->sodrawstyle) this->sodrawstyle->setOverride(TRUE);
if (this->solightmodel) this->solightmodel->setOverride(TRUE);
if (this->somaterialbinding) this->somaterialbinding->setOverride(TRUE);
#ifdef HAVE_SOPOLYGONOFFSET
LOCATE_NODE(this->sopolygonoffset, SoPolygonOffset, "so@gui@->polygonoffset");
if (this->sopolygonoffset) this->sopolygonoffset->setOverride(TRUE);
#endif // HAVE_SOPOLYGONOFFSET
#undef LOCATE_NODE
this->searchaction->reset();
root->unrefNoDelete();
return (SoSeparator *) root;
}
// Returns the coordinate system the current camera is located in. If
// there are transformations before the camera in the scene graph,
// this must be considered before doing certain operations. \a matrix
// and \a inverse will not contain the transformations caused by the
// camera fields, only the transformations traversed before the camera
// in the scene graph.
void
So@Gui@ViewerP::getCameraCoordinateSystem(SoCamera * cameraarg,
SoNode * root,
SbMatrix & matrix,
SbMatrix & inverse)
{
this->searchaction->reset();
this->searchaction->setSearchingAll(TRUE);
this->searchaction->setInterest(SoSearchAction::FIRST);
this->searchaction->setNode(cameraarg);
this->searchaction->apply(root);
matrix = inverse = SbMatrix::identity();
if (this->searchaction->getPath()) {
this->matrixaction->apply(this->searchaction->getPath());
matrix = this->matrixaction->getMatrix();
inverse = this->matrixaction->getInverse();
}
this->searchaction->reset();
}
// These functions do this:
//
// * when going from orthocam -> perspectivecam: set the
// heightAngle field to its default value (45°), and move
// camera to a position where the scene/model would fill about
// the same screenspace as it did in the orthocam
//
// * when going from perspectivecam -> orthocam: keep the
// current position, but tune the view-volume height so the
// scene/model takes up about the same screenspace
//
// 20020522 mortene.
void
So@Gui@ViewerP::convertOrtho2Perspective(const SoOrthographicCamera * in,
SoPerspectiveCamera * out)
{
out->aspectRatio.setValue(in->aspectRatio.getValue());
out->focalDistance.setValue(in->focalDistance.getValue());
out->orientation.setValue(in->orientation.getValue());
out->position.setValue(in->position.getValue());
out->viewportMapping.setValue(in->viewportMapping.getValue());
SbRotation camrot = in->orientation.getValue();
float focaldist = in->height.getValue() / (2.0*tan(M_PI / 8.0));
SbVec3f offset(0,0,focaldist-in->focalDistance.getValue());
camrot.multVec(offset,offset);
out->position.setValue(offset+in->position.getValue());
out->focalDistance.setValue(focaldist);
// 45° is the default value of this field in SoPerspectiveCamera.
out->heightAngle = (float)(M_PI / 4.0);
#if SO@GUI@_DEBUG && 0 // debug
SoDebugError::postInfo("So@Gui@ViewerP::convertOrtho2Perspective",
"perspective heightAngle==%f",
180.0f * out->heightAngle.getValue() / M_PI);
#endif // debug
}
void
So@Gui@ViewerP::convertPerspective2Ortho(const SoPerspectiveCamera * in,
SoOrthographicCamera * out)
{
out->aspectRatio.setValue(in->aspectRatio.getValue());
out->focalDistance.setValue(in->focalDistance.getValue());
out->orientation.setValue(in->orientation.getValue());
out->position.setValue(in->position.getValue());
out->viewportMapping.setValue(in->viewportMapping.getValue());
float focaldist = in->focalDistance.getValue();
out->height = 2.0f * focaldist * (float)tan(in->heightAngle.getValue() / 2.0);
#if SO@GUI@_DEBUG && 0 // debug
SoDebugError::postInfo("So@Gui@ViewerP::convertOrtho2Perspective",
"ortho height==%f",
out->height.getValue());
#endif // debug
}
void
So@Gui@ViewerP::reallyRedraw(const SbBool clearcol, const SbBool clearz)
{
// Recalculate near/far planes. Must be done in reallyRedraw() --
// not actualRedraw() -- so the clipping planes are correct even
// when rendering multiple times with different camera settings.
if (this->camera && PUBLIC(this)->isAutoClipping()) {
// Temporarily turn off notification when changing near and far
// clipping planes, to avoid latency.
const SbBool notif = this->camera->isNotifyEnabled();
this->camera->enableNotify(FALSE);
this->setClippingPlanes();
this->camera->enableNotify(notif);
}
if (this->drawAsHiddenLine()) {
// First pass: render as filled, but with the background color.
this->solightmodel->model.setIgnored(FALSE); // override as SoLightModel::BASE
this->sodrawstyle->style.setIgnored(TRUE); // draw as-is filled/lines/points
this->socomplexity->type.setIgnored(TRUE); // as-is rendering space
this->socomplexity->value.setIgnored(TRUE); // as-is complexity on non-simple shapes
// textureQuality field of socomplexity node is always 0.0
this->sobasecolor->rgb.setValue(PUBLIC(this)->getBackgroundColor());
this->sobasecolor->rgb.setIgnored(FALSE);
this->somaterialbinding->value.setIgnored(FALSE); // override with OVERALL
this->polygonoffsetparent->whichChild = SO_SWITCH_ALL;
PUBLIC(this)->getSceneManager()->render(clearcol, clearz);
// Second pass, render wireframe on top.
this->sodrawstyle->style = SoDrawStyle::LINES;
this->sodrawstyle->style.setIgnored(FALSE); // force lines
this->sobasecolor->rgb.setIgnored(TRUE); // use as-is line colors
this->somaterialbinding->value.setIgnored(TRUE); // as-is
this->polygonoffsetparent->whichChild = SO_SWITCH_NONE;
PUBLIC(this)->getSceneManager()->render(FALSE, FALSE);
return;
}
if (this->drawAsWireframeOverlay()) {
// First pass: render as-is, with polygon offset
this->solightmodel->model.setIgnored(TRUE);
this->somaterialbinding->value.setIgnored(TRUE);
this->sobasecolor->rgb.setIgnored(TRUE);
this->sodrawstyle->style.setIgnored(TRUE); // draw as-is filled/lines/points
this->socomplexity->type.setIgnored(TRUE); // as-is rendering space
this->socomplexity->value.setIgnored(TRUE); // as-is complexity on non-simple shapes
this->socomplexity->textureQuality.setIgnored(TRUE);
this->somaterialbinding->value.setIgnored(TRUE); // override with OVERALL
this->polygonoffsetparent->whichChild = SO_SWITCH_ALL;
PUBLIC(this)->getSceneManager()->render(clearcol, clearz);
// Second pass, render wireframe on top.
this->sobasecolor->rgb.setValue(this->wireframeoverlaycolor);
this->sobasecolor->rgb.setIgnored(FALSE);
this->somaterialbinding->value.setIgnored(FALSE); // override with OVERALL
this->solightmodel->model.setIgnored(FALSE); // override as SoLightModel::BASE
this->sodrawstyle->style = SoDrawStyle::LINES;
this->sodrawstyle->style.setIgnored(FALSE); // force lines
this->polygonoffsetparent->whichChild = SO_SWITCH_NONE;
this->socomplexity->textureQuality.setIgnored(FALSE);
PUBLIC(this)->getSceneManager()->render(FALSE, FALSE);
// disable override nodes
(void) this->sobasecolor->rgb.enableNotify(FALSE);
this->sobasecolor->rgb.setIgnored(TRUE);
(void) this->sobasecolor->rgb.enableNotify(TRUE);
(void) this->somaterialbinding->value.enableNotify(FALSE);
this->somaterialbinding->value.setIgnored(TRUE);
(void) this->somaterialbinding->value.enableNotify(TRUE);
(void) this->solightmodel->model.enableNotify(FALSE);
this->solightmodel->model.setIgnored(TRUE);
(void) this->solightmodel->model.enableNotify(TRUE);
(void) this->socomplexity->textureQuality.enableNotify(FALSE);
this->socomplexity->textureQuality.setIgnored(TRUE);
(void) this->socomplexity->textureQuality.enableNotify(TRUE);
(void) this->sodrawstyle->style.enableNotify(FALSE);
this->sodrawstyle->style.setIgnored(TRUE);
(void) this->sodrawstyle->style.enableNotify(TRUE);
return;
}
SbBool clearzbuffer = TRUE;
So@Gui@Viewer::DrawStyle style = this->currentDrawStyle();
switch (style) {
case So@Gui@Viewer::VIEW_LOW_RES_LINE:
case So@Gui@Viewer::VIEW_LOW_RES_POINT:
case So@Gui@Viewer::VIEW_BBOX:
clearzbuffer = FALSE;
default:
break; // Include "default:" case to avoid compiler warning.
}
PUBLIC(this)->getSceneManager()->render(clearcol, clearzbuffer && clearz);
}
// *************************************************************************
// Returns a boolean to indicate if the dynamic drawstyle equals
// the static drawstyle.
SbBool
So@Gui@ViewerP::drawInteractiveAsStill(void) const
{
SbBool moveasstill = this->drawstyles[So@Gui@Viewer::INTERACTIVE] == So@Gui@Viewer::VIEW_SAME_AS_STILL;
if (! moveasstill)
moveasstill = this->drawstyles[So@Gui@Viewer::INTERACTIVE] == this->drawstyles[So@Gui@Viewer::STILL];
if (! moveasstill)
moveasstill =
this->drawstyles[So@Gui@Viewer::INTERACTIVE] == So@Gui@Viewer::VIEW_NO_TEXTURE &&
this->drawstyles[So@Gui@Viewer::STILL] != So@Gui@Viewer::VIEW_AS_IS;
return moveasstill;
}
// Returns the current drawing style.
So@Gui@Viewer::DrawStyle
So@Gui@ViewerP::currentDrawStyle(void) const
{
SbBool interactivemode = PUBLIC(this)->getInteractiveCount() > 0 ? TRUE : FALSE;
if (!interactivemode || this->drawInteractiveAsStill())
return this->drawstyles[So@Gui@Viewer::STILL];
else
return this->drawstyles[So@Gui@Viewer::INTERACTIVE];
}
// Returns a boolean to indicate if the current drawstyle settings implies
// hidden line rendering.
SbBool
So@Gui@ViewerP::drawAsHiddenLine(void) const
{
return ((this->currentDrawStyle() == So@Gui@Viewer::VIEW_HIDDEN_LINE) ? TRUE : FALSE);
}
// Returns a boolean to indicate if the current drawstyle settings
// implies wirefram overlay rendering.
SbBool
So@Gui@ViewerP::drawAsWireframeOverlay(void) const
{
return ((this->currentDrawStyle() == So@Gui@Viewer::VIEW_WIREFRAME_OVERLAY) ? TRUE : FALSE);
}
// Use the given style setting to set the correct states in the
// rendering control nodes. This will affect the way the scene is
// currently rendered.
void
So@Gui@ViewerP::changeDrawStyle(So@Gui@Viewer::DrawStyle style)
{
// Turn on/off Z-buffering based on the style setting.
switch (style) {
case So@Gui@Viewer::VIEW_LOW_RES_LINE:
case So@Gui@Viewer::VIEW_LOW_RES_POINT:
case So@Gui@Viewer::VIEW_BBOX:
PUBLIC(this)->glLockNormal();
// FIXME: shouldn't this be done "lazy", i.e. before we do any
// actual rendering? 20001126 mortene.
glDisable(GL_DEPTH_TEST);
PUBLIC(this)->glUnlockNormal();
break;
default:
PUBLIC(this)->glLockNormal();
// FIXME: shouldn't this be done "lazy", i.e. before we do any
// actual rendering? 20001126 mortene.
glEnable(GL_DEPTH_TEST);
PUBLIC(this)->glUnlockNormal();
break;
}
// Render everything as its supposed to be done, don't override
// any of the settings in the ``real'' graph.
if (style == So@Gui@Viewer::VIEW_AS_IS) {
this->drawstyleroot->whichChild = SO_SWITCH_NONE;
return;
}
this->drawstyleroot->whichChild = SO_SWITCH_ALL;
if ((style == So@Gui@Viewer::VIEW_HIDDEN_LINE) ||
(style == So@Gui@Viewer::VIEW_WIREFRAME_OVERLAY)) {
this->hiddenlineroot->whichChild = SO_SWITCH_ALL;
return;
} else {
this->hiddenlineroot->whichChild = SO_SWITCH_NONE;
}
// Set or unset lightmodel override.
switch (style) {
case So@Gui@Viewer::VIEW_NO_TEXTURE:
case So@Gui@Viewer::VIEW_LOW_COMPLEXITY:
this->solightmodel->model.setIgnored(TRUE); // as-is BASE or PHONG
break;
case So@Gui@Viewer::VIEW_LINE:
case So@Gui@Viewer::VIEW_POINT:
case So@Gui@Viewer::VIEW_BBOX:
case So@Gui@Viewer::VIEW_LOW_RES_LINE:
case So@Gui@Viewer::VIEW_LOW_RES_POINT:
this->solightmodel->model.setIgnored(FALSE); // force BASE lighting
break;
default:
assert(FALSE); break;
}
// Set or unset drawstyle override.
switch (style) {
case So@Gui@Viewer::VIEW_NO_TEXTURE:
case So@Gui@Viewer::VIEW_LOW_COMPLEXITY:
this->sodrawstyle->style.setIgnored(TRUE); // as-is drawing style filled/lines/points
break;
case So@Gui@Viewer::VIEW_LINE:
case So@Gui@Viewer::VIEW_LOW_RES_LINE:
case So@Gui@Viewer::VIEW_BBOX:
this->sodrawstyle->style = SoDrawStyle::LINES;
this->sodrawstyle->style.setIgnored(FALSE); // force line rendering
break;
case So@Gui@Viewer::VIEW_POINT:
case So@Gui@Viewer::VIEW_LOW_RES_POINT:
this->sodrawstyle->style = SoDrawStyle::POINTS;
this->sodrawstyle->style.setIgnored(FALSE); // force point rendering
break;
default:
assert(FALSE); break;
}
// Set or unset complexity value override.
switch (style) {
case So@Gui@Viewer::VIEW_NO_TEXTURE:
case So@Gui@Viewer::VIEW_LINE:
case So@Gui@Viewer::VIEW_POINT:
case So@Gui@Viewer::VIEW_BBOX:
this->socomplexity->value.setIgnored(TRUE); // as-is complexity
break;
case So@Gui@Viewer::VIEW_LOW_COMPLEXITY:
case So@Gui@Viewer::VIEW_LOW_RES_LINE:
case So@Gui@Viewer::VIEW_LOW_RES_POINT:
this->socomplexity->value.setIgnored(FALSE); // force complexity setting of 0.1
break;
default:
assert(FALSE); break;
}
// Set or unset complexity textureQuality override (the value of the
// override-field is always 0.0, ie signalling "textures off").
switch (style) {
case So@Gui@Viewer::VIEW_HIDDEN_LINE:
case So@Gui@Viewer::VIEW_NO_TEXTURE:
case So@Gui@Viewer::VIEW_LINE:
case So@Gui@Viewer::VIEW_POINT:
case So@Gui@Viewer::VIEW_BBOX:
case So@Gui@Viewer::VIEW_LOW_RES_LINE:
case So@Gui@Viewer::VIEW_LOW_RES_POINT:
this->socomplexity->textureQuality.setIgnored(FALSE); // textures off
break;
default:
this->socomplexity->textureQuality.setIgnored(TRUE); // don't override
break;
}
// Set or unset complexity type override.
switch (style) {
case So@Gui@Viewer::VIEW_NO_TEXTURE:
case So@Gui@Viewer::VIEW_LOW_COMPLEXITY:
case So@Gui@Viewer::VIEW_LINE:
case So@Gui@Viewer::VIEW_POINT:
case So@Gui@Viewer::VIEW_LOW_RES_LINE:
case So@Gui@Viewer::VIEW_LOW_RES_POINT:
this->socomplexity->type.setIgnored(TRUE); // as-is
break;
case So@Gui@Viewer::VIEW_BBOX:
this->socomplexity->type = SoComplexity::BOUNDING_BOX;
this->socomplexity->type.setIgnored(FALSE); // force bounding box rendering
break;
default:
assert(FALSE); break;
}
#if 0 // debug
SoDebugError::postInfo("So@Gui@Viewer::changeDrawStyle",
"\n"
"\tdrawstyle style: 0x%02x (isIgnored() == %s)\n"
"\tlightmodel model: 0x%02x, (isIgnored() == %s)\n"
"\tcomplexity type: 0x%02x, (isIgnored() == %s)\n"
"\tcomplexity value: %f, (isIgnored() == %s)\n"
"",
this->sodrawstyle->style.getValue(),
this->sodrawstyle->style.isIgnored() ? "T" : "F",
this->solightmodel->model.getValue(),
this->solightmodel->model.isIgnored() ? "T" : "F",
this->socomplexity->type.getValue(),
this->socomplexity->type.isIgnored() ? "T" : "F",
this->socomplexity->value.getValue(),
this->socomplexity->value.isIgnored() ? "T" : "F");
#endif // debug
}
// Position the near and far clipping planes just in front of and
// behind the scene's bounding box. This will give us the optimal
// utilization of the z buffer resolution by shrinking it to its
// minimum depth.
//
// Near and far clipping planes are specified in the camera fields
// nearDistance and farDistance.
void
So@Gui@ViewerP::setClippingPlanes(void)
{
// This is necessary to avoid a crash in case there is no scene
// graph specified by the user.
if (this->camera == NULL) return;
if (this->autoclipbboxaction == NULL)
this->autoclipbboxaction =
new SoGetBoundingBoxAction(PUBLIC(this)->getViewportRegion());
else
this->autoclipbboxaction->setViewportRegion(PUBLIC(this)->getViewportRegion());
this->autoclipbboxaction->apply(this->sceneroot);
SbXfBox3f xbox = this->autoclipbboxaction->getXfBoundingBox();
SbMatrix cammat;
SbMatrix inverse;
this->getCameraCoordinateSystem(this->camera, this->sceneroot, cammat, inverse);
xbox.transform(inverse);
SbMatrix mat;
mat.setTranslate(- this->camera->position.getValue());
xbox.transform(mat);
mat = this->camera->orientation.getValue().inverse();
xbox.transform(mat);
SbBox3f box = xbox.project();
// Bounding box was calculated in camera space, so we need to "flip"
// the box (because camera is pointing in the (0,0,-1) direction
// from origo.
float nearval = -box.getMax()[2];
float farval = -box.getMin()[2];
// FIXME: what if we have a weird scale transform in the scenegraph?
// Could we end up with nearval > farval then? Investigate, then
// either use an assert() (if it can't happen) or an So@Gui@Swap()
// (to handle it). 20020116 mortene.
// Check if scene is completely behind us.
if (farval <= 0.0f) { return; }
if (this->camera->isOfType(SoPerspectiveCamera::getClassTypeId())) {
// Disallow negative and small near clipping plane distance.
float nearlimit; // the smallest value allowed for nearval
if (this->autoclipstrategy == So@Gui@Viewer::CONSTANT_NEAR_PLANE) {
nearlimit = this->autoclipvalue;
}
else {
assert(this->autoclipstrategy == So@Gui@Viewer::VARIABLE_NEAR_PLANE);
// From glFrustum() documentation: Depth-buffer precision is
// affected by the values specified for znear and zfar. The
// greater the ratio of zfar to znear is, the less effective the
// depth buffer will be at distinguishing between surfaces that
// are near each other. If r = far/near, roughly log (2) r bits
// of depth buffer precision are lost. Because r approaches
// infinity as znear approaches zero, you should never set znear
// to zero.
GLint depthbits[1];
glGetIntegerv(GL_DEPTH_BITS, depthbits);
int use_bits = (int) (float(depthbits[0]) * (1.0f-this->autoclipvalue));
float r = (float) pow(2.0, (double) use_bits);
nearlimit = farval / r;
}
if (nearlimit >= farval) {
// (The "5000" magic constant was found by fiddling around a bit
// on an OpenGL implementation with a 16-bit depth-buffer
// resolution, adjusting to find something that would work well
// with both a very "stretched" / deep scene and a more compact
// single-model one.)
nearlimit = farval / 5000.0f;
}
// adjust the near plane if the the value is too small.
if (nearval < nearlimit) { nearval = nearlimit; }
}
// Some slack around the bounding box, in case the scene fits
// exactly inside it. This is done to minimize the chance of
// artifacts caused by the limitation of the z-buffer
// resolution. One common artifact if this is not done is that the
// near clipping plane cuts into the corners of the model as it's
// rotated.
const float SLACK = 0.001f;
// FrustumCamera can be found in the SmallChange CVS module. We
// should not change the nearDistance for this camera, as this will
// modify the frustum.
//
// FIXME: quite the hack that So@Gui@ needs to know about the
// FrustumCamera class. Wouldn't it be better if FrustumCamera
// instead registered a callback with setAutoClippingStrategy() and
// handled this itself? 20040908 mortene.
if (this->camera->getTypeId().getName() == "FrustumCamera") {
nearval = this->camera->nearDistance.getValue();
farval *= (1.0f + SLACK);
if (farval <= nearval) {
// nothing is visible, so just set farval to som value > nearval.
farval = nearval + 10.0f;
}
}
else {
nearval *= (1.0f - SLACK);
farval *= (1.0f + SLACK);
}
if (this->autoclipcb) {
SbVec2f nearfar(nearval, farval);
nearfar = this->autoclipcb(this->autoclipuserdata, nearfar);
nearval = nearfar[0];
farval = nearfar[1];
}
if (nearval != this->camera->nearDistance.getValue()) {
this->camera->nearDistance = nearval;
}
if (farval != this->camera->farDistance.getValue()) {
this->camera->farDistance = farval;
}
// FIXME: there's a possible optimization to take advantage of here,
// since we are able to sometimes know for sure that all geometry is
// completely inside the view volume. I quote from the "OpenGL FAQ
// and Troubleshooting Guide":
//
// "10.050 I know my geometry is inside the view volume. How can I
// turn off OpenGL's view-volume clipping to maximize performance?
//
// Standard OpenGL doesn't provide a mechanism to disable the
// view-volume clipping test; thus, it will occur for every
// primitive you send.
//
// Some implementations of OpenGL support the
// GL_EXT_clip_volume_hint extension. If the extension is
// available, a call to
// glHint(GL_CLIP_VOLUME_CLIPPING_HINT_EXT,GL_FASTEST) will inform
// OpenGL that the geometry is entirely within the view volume and
// that view-volume clipping is unnecessary. Normal clipping can
// be resumed by setting this hint to GL_DONT_CARE. When clipping
// is disabled with this hint, results are undefined if geometry
// actually falls outside the view volume."
//
// 20020117 mortene.
// Debug assistance, can be turned on without recompilation (just
// set the environment variable SO@GUI@_DEBUG_CLIPPLANES):
#if SO@GUI@_DEBUG
static int debugoutputnearfar = -1;
if (debugoutputnearfar == -1) {
const char * env = SoAny::si()->getenv("SO@GUI@_DEBUG_CLIPPLANES");
debugoutputnearfar = (env && atoi(env) > 0) ? 1 : 0;
}
if (debugoutputnearfar == 1) { // debug
SoDebugError::postInfo("So@Gui@Viewer::setClippingPlanes",
"near, far: %f (%f), %f (%f)",
nearval, this->camera->nearDistance.getValue(),
farval, this->camera->farDistance.getValue());
}
#endif // debug
}
// Translate camera a distance equal to the difference in projected,
// normalized screen coordinates given by the argument.
void
So@Gui@ViewerP::moveCameraScreen(const SbVec2f & screenpos)
{
SoCamera * cam = PUBLIC(this)->getCamera();
assert(cam);
if (SO@GUI@_DEBUG && 0) { // debug
SoDebugError::postInfo("So@Gui@Viewer::moveCameraScreen",
"screenpos: <%f, %f>, campos: <%f, %f, %f>",
screenpos[0], screenpos[1],
cam->position.getValue()[0],
cam->position.getValue()[1],
cam->position.getValue()[2]);
}
SbViewVolume vv = cam->getViewVolume(PUBLIC(this)->getGLAspectRatio());
SbPlane panplane = vv.getPlane(cam->focalDistance.getValue());
SbLine line;
vv.projectPointToLine(screenpos + SbVec2f(0.5, 0.5f), line);
SbVec3f current_planept;
panplane.intersect(line, current_planept);
vv.projectPointToLine(SbVec2f(0.5f, 0.5f), line);
SbVec3f old_planept;
panplane.intersect(line, old_planept);
// Reposition camera according to the vector difference between the
// projected points.
cam->position = cam->position.getValue() - (current_planept - old_planept);
if (SO@GUI@_DEBUG && 0) { // debug
SoDebugError::postInfo("So@Gui@Viewer::moveCameraScreen",
"newcampos: <%f, %f, %f>",
cam->position.getValue()[0],
cam->position.getValue()[1],
cam->position.getValue()[2]);
}
}
// Called when viewer enters interactive mode (animation, drag, ...).
void
So@Gui@ViewerP::interactivestartCB(void *, So@Gui@Viewer * thisp)
{
// In interactive buffer mode, doublebuffering is used during interaction.
if (PRIVATE(thisp)->buffertype == So@Gui@Viewer::BUFFER_INTERACTIVE) {
PRIVATE(thisp)->localsetbuffertype = TRUE;
thisp->So@Gui@RenderArea::setDoubleBuffer(TRUE);
PRIVATE(thisp)->localsetbuffertype = FALSE;
}
// Use the dynamic drawstyle.
if (!PRIVATE(thisp)->drawInteractiveAsStill())
PRIVATE(thisp)->changeDrawStyle(PRIVATE(thisp)->drawstyles[So@Gui@Viewer::INTERACTIVE]);
}
// Called when viewer goes out of interactive mode and into "frozen"
// mode.
void
So@Gui@ViewerP::interactiveendCB(void *, So@Gui@Viewer * thisp)
{
// In interactive buffer mode, doublebuffering is used during
// interaction, singelbuffering while the camera is static.
if (PRIVATE(thisp)->buffertype == So@Gui@Viewer::BUFFER_INTERACTIVE) {
PRIVATE(thisp)->localsetbuffertype = TRUE;
thisp->So@Gui@RenderArea::setDoubleBuffer(FALSE);
PRIVATE(thisp)->localsetbuffertype = FALSE;
}
// Back to static drawstyle.
if (!PRIVATE(thisp)->drawInteractiveAsStill())
PRIVATE(thisp)->changeDrawStyle(PRIVATE(thisp)->drawstyles[So@Gui@Viewer::STILL]);
}
// Called repeatedly during the seek animation.
void
So@Gui@ViewerP::seeksensorCB(void * data, SoSensor * s)
{
SbTime currenttime = SbTime::getTimeOfDay();
So@Gui@Viewer * thisp = (So@Gui@Viewer *)data;
SoTimerSensor * sensor = (SoTimerSensor *)s;
float t =
float((currenttime - sensor->getBaseTime()).getValue()) / PRIVATE(thisp)->seekperiod;
if ((t > 1.0f) || (t + sensor->getInterval().getValue() > 1.0f)) t = 1.0f;
SbBool end = (t == 1.0f);
t = (float) ((1.0 - cos(M_PI*t)) * 0.5);
PRIVATE(thisp)->camera->position = PRIVATE(thisp)->camerastartposition +
(PRIVATE(thisp)->cameraendposition - PRIVATE(thisp)->camerastartposition) * t;
PRIVATE(thisp)->camera->orientation =
SbRotation::slerp(PRIVATE(thisp)->camerastartorient,
PRIVATE(thisp)->cameraendorient,
t);
if (end) thisp->setSeekMode(FALSE);
}
// Reset the frames-per-second counter upon window resize events,
// abnormal delays, etc.
//
// The methods for recording FPS values are Coin extensions, not
// available in the original Open Inventor API.
//
// \sa addFrametime(), recordFPS()
void
So@Gui@ViewerP::resetFrameCounter(void)
{
this->framecount = 0;
for (int i = 0; i < So@Gui@ViewerP::FRAMESARRAY_SIZE; i++)
this->frames[i] = SbVec2f(0.0f, 0.0f);
this->totalcoin = 0.0f;
this->totaldraw = 0.0f;
this->lastgettimeofday = SbTime::getTimeOfDay().getValue();
}
// Adds the time spent drawing the last frame to the array of past
// frame times. Returns the current averaged fps-value.
//
// The methods for recording FPS values are Coin extensions, not
// available in the original Open Inventor API.
//
// \sa resetFrameCounter(), recordFPS()
SbVec2f
So@Gui@ViewerP::addFrametime(const double ft)
{
this->framecount++;
int arrayptr = (this->framecount - 1) % FRAMESARRAY_SIZE;
this->totalcoin += (float(ft) - this->frames[arrayptr][0]);
float coinfps =
this->totalcoin / So@Gui@Min(this->framecount, (int) FRAMESARRAY_SIZE);
double timeofday = SbTime::getTimeOfDay().getValue();
double ct = timeofday - this->lastgettimeofday;
this->totaldraw += (float(ct) - this->frames[arrayptr][1]);
float drawfps =
this->totaldraw / So@Gui@Min(this->framecount, (int) FRAMESARRAY_SIZE);
this->frames[arrayptr] = SbVec2f((float)ft, (float)ct);
this->lastgettimeofday = timeofday;
return SbVec2f(1.0f / coinfps, 1.0f / drawfps);
}
static unsigned char fps2dfont[][12] = {
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, //
{ 0, 0, 12, 12, 0, 8, 12, 12, 12, 12, 12, 0 }, // !
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 20, 20 }, // \"
{ 0, 0, 18, 18, 18, 63, 18, 18, 63, 18, 18, 0 }, // #
{ 0, 8, 28, 42, 10, 10, 12, 24, 40, 42, 28, 8 }, // $
{ 0, 0, 6, 73, 41, 22, 8, 52, 74, 73, 48, 0 }, // %
{ 0, 12, 18, 18, 12, 25, 37, 34, 34, 29, 0, 0 }, // &
{ 12, 12, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // '
{ 0, 6, 8, 8, 16, 16, 16, 16, 16, 8, 8, 6 }, // (
{ 0, 48, 8, 8, 4, 4, 4, 4, 4, 8, 8, 48 }, //)
{ 0, 0, 0, 0, 0, 0, 8, 42, 20, 42, 8, 0 }, // *
{ 0, 0, 0, 8, 8, 8,127, 8, 8, 8, 0, 0 }, // +
{ 0, 24, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0 }, // ,
{ 0, 0, 0, 0, 0, 0,127, 0, 0, 0, 0, 0 }, // -
{ 0, 0, 24, 24, 0, 0, 0, 0, 0, 0, 0, 0 }, // .
{ 0, 32, 32, 16, 16, 8, 8, 8, 4, 4, 2, 2 }, // /
{ 0, 0, 28, 34, 34, 34, 34, 34, 34, 34, 28, 0 }, // 0
{ 0, 0, 8, 8, 8, 8, 8, 8, 40, 24, 8, 0 }, // 1
{ 0, 0, 62, 32, 16, 8, 4, 2, 2, 34, 28, 0 }, // 2
{ 0, 0, 28, 34, 2, 2, 12, 2, 2, 34, 28, 0 }, // 3
{ 0, 0, 4, 4, 4,126, 68, 36, 20, 12, 4, 0 }, // 4
{ 0, 0, 28, 34, 2, 2, 2, 60, 32, 32, 62, 0 }, // 5
{ 0, 0, 28, 34, 34, 34, 60, 32, 32, 34, 28, 0 }, // 6
{ 0, 0, 16, 16, 16, 8, 8, 4, 2, 2, 62, 0 }, // 7
{ 0, 0, 28, 34, 34, 34, 28, 34, 34, 34, 28, 0 }, // 8
{ 0, 0, 28, 34, 2, 2, 30, 34, 34, 34, 28, 0 }, // 9
{ 0, 0, 24, 24, 0, 0, 0, 24, 24, 0, 0, 0 }, // :
{ 0, 48, 24, 24, 0, 0, 0, 24, 24, 0, 0, 0 }, // ;
{ 0, 0, 0, 2, 4, 8, 16, 8, 4, 2, 0, 0 }, // <
{ 0, 0, 0, 0, 0,127, 0,127, 0, 0, 0, 0 }, // =
{ 0, 0, 0, 16, 8, 4, 2, 4, 8, 16, 0, 0 }, // >
{ 0, 0, 16, 16, 0, 16, 28, 2, 2, 2, 60, 0 }, // ?
{ 0, 0, 28, 32, 73, 86, 82, 82, 78, 34, 28, 0 }, // @
{ 0, 0, 33, 33, 33, 63, 18, 18, 18, 12, 12, 0 }, // A
{ 0, 0, 60, 34, 34, 34, 60, 34, 34, 34, 60, 0 }, // B
{ 0, 0, 14, 16, 32, 32, 32, 32, 32, 18, 14, 0 }, // C
{ 0, 0, 56, 36, 34, 34, 34, 34, 34, 36, 56, 0 }, // D
{ 0, 0, 62, 32, 32, 32, 60, 32, 32, 32, 62, 0 }, // E
{ 0, 0, 16, 16, 16, 16, 30, 16, 16, 16, 30, 0 }, // F
{ 0, 0, 14, 18, 34, 34, 32, 32, 32, 18, 14, 0 }, // G
{ 0, 0, 34, 34, 34, 34, 62, 34, 34, 34, 34, 0 }, // H
{ 0, 0, 62, 8, 8, 8, 8, 8, 8, 8, 62, 0 }, // I
{ 0, 0,112, 8, 8, 8, 8, 8, 8, 8, 62, 0 }, // J
{ 0, 0, 33, 33, 34, 36, 56, 40, 36, 34, 33, 0 }, // K
{ 0, 0, 30, 16, 16, 16, 16, 16, 16, 16, 16, 0 }, // L
{ 0, 0, 33, 33, 33, 45, 45, 45, 51, 51, 33, 0 }, // M
{ 0, 0, 34, 34, 38, 38, 42, 42, 50, 50, 34, 0 }, // N
{ 0, 0, 12, 18, 33, 33, 33, 33, 33, 18, 12, 0 }, // O
{ 0, 0, 32, 32, 32, 60, 34, 34, 34, 34, 60, 0 }, // P
{ 3, 6, 12, 18, 33, 33, 33, 33, 33, 18, 12, 0 }, // Q
{ 0, 0, 34, 34, 34, 36, 60, 34, 34, 34, 60, 0 }, // R
{ 0, 0, 60, 2, 2, 6, 28, 48, 32, 32, 30, 0 }, // S
{ 0, 0, 8, 8, 8, 8, 8, 8, 8, 8,127, 0 }, // T
{ 0, 0, 28, 34, 34, 34, 34, 34, 34, 34, 34, 0 }, // U
{ 0, 0, 12, 12, 18, 18, 18, 33, 33, 33, 33, 0 }, // V
{ 0, 0, 34, 34, 34, 54, 85, 73, 73, 73, 65, 0 }, // W
{ 0, 0, 34, 34, 20, 20, 8, 20, 20, 34, 34, 0 }, // X
{ 0, 0, 8, 8, 8, 8, 20, 20, 34, 34, 34, 0 }, // Y
{ 0, 0, 62, 32, 16, 16, 8, 4, 4, 2, 62, 0 }, // Z
{ 0, 14, 8, 8, 8, 8, 8, 8, 8, 8, 8, 14 }, // [
{ 0, 2, 2, 4, 4, 8, 8, 8, 16, 16, 32, 32 }, // [backslash]
{ 0, 56, 8, 8, 8, 8, 8, 8, 8, 8, 8, 56 }, // ]
{ 0, 0, 0, 0, 0, 34, 34, 20, 20, 8, 8, 0 }, // ^
{ 0,127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // _
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 24, 12 }, // `
{ 0, 0, 29, 34, 34, 30, 2, 34, 28, 0, 0, 0 }, // a
{ 0, 0, 60, 34, 34, 34, 34, 50, 44, 32, 32, 32 }, // b
{ 0, 0, 14, 16, 32, 32, 32, 16, 14, 0, 0, 0 }, // c
{ 0, 0, 26, 38, 34, 34, 34, 34, 30, 2, 2, 2 }, // d
{ 0, 0, 28, 34, 32, 62, 34, 34, 28, 0, 0, 0 }, // e
{ 0, 0, 16, 16, 16, 16, 16, 16, 62, 16, 16, 14 }, // f
{ 28, 2, 2, 26, 38, 34, 34, 34, 30, 0, 0, 0 }, // g
{ 0, 0, 34, 34, 34, 34, 34, 50, 44, 32, 32, 32 }, // h
{ 0, 0, 8, 8, 8, 8, 8, 8, 56, 0, 8, 8 }, // i
{ 56, 4, 4, 4, 4, 4, 4, 4, 60, 0, 4, 4 }, // j
{ 0, 0, 33, 34, 36, 56, 40, 36, 34, 32, 32, 32 }, // k
{ 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, 8, 56 }, // l
{ 0, 0, 73, 73, 73, 73, 73,109, 82, 0, 0, 0 }, // m
{ 0, 0, 34, 34, 34, 34, 34, 50, 44, 0, 0, 0 }, // n
{ 0, 0, 28, 34, 34, 34, 34, 34, 28, 0, 0, 0 }, // o
{ 32, 32, 60, 34, 34, 34, 34, 50, 44, 0, 0, 0 }, // p
{ 2, 2, 26, 38, 34, 34, 34, 34, 30, 0, 0, 0 }, // q
{ 0, 0, 16, 16, 16, 16, 16, 24, 22, 0, 0, 0 }, // r
{ 0, 0, 60, 2, 2, 28, 32, 32, 30, 0, 0, 0 }, // s
{ 0, 0, 14, 16, 16, 16, 16, 16, 62, 16, 16, 0 }, // t
{ 0, 0, 26, 38, 34, 34, 34, 34, 34, 0, 0, 0 }, // u
{ 0, 0, 8, 8, 20, 20, 34, 34, 34, 0, 0, 0 }, // v
{ 0, 0, 34, 34, 34, 85, 73, 73, 65, 0, 0, 0 }, // w
{ 0, 0, 34, 34, 20, 8, 20, 34, 34, 0, 0, 0 }, // x
{ 48, 16, 8, 8, 20, 20, 34, 34, 34, 0, 0, 0 }, // y
{ 0, 0, 62, 32, 16, 8, 4, 2, 62, 0, 0, 0 }, // z
{ 0, 6, 8, 8, 8, 4, 24, 4, 8, 8, 8, 6 }, // {
{ 0, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 }, // |
{ 0, 48, 8, 8, 8, 16, 12, 16, 8, 8, 8, 48 }, // }
{ 0, 0, 0, 0, 0, 0, 78, 57, 0, 0, 0, 0 } // ~
};
static void
printString(const char * s)
{
int i,n;
n = strlen(s);
for (i = 0; i < n; i++)
glBitmap(8, 12, 0.0, 2.0, 10.0, 0.0, fps2dfont[s[i] - 32]);
}
static void
Draw2DString(const char * str, SbVec2s glsize, SbVec2f position)
{
// Store GL state.
glPushAttrib(GL_ENABLE_BIT|GL_CURRENT_BIT);
glDisable(GL_LIGHTING);
glDisable(GL_DEPTH_TEST);
glDisable(GL_TEXTURE_2D);
glDisable(GL_BLEND);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glOrtho(0.0, glsize[0], 0.0, glsize[1], -1, 1);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glColor3f(0.0, 0.0, 0.0);
glRasterPos2f(position[0] + 1, position[1]);
printString(str);
glRasterPos2f(position[0] - 1, position[1]);
printString(str);
glRasterPos2f(position[0], position[1] + 1);
printString(str);
glRasterPos2f(position[0], position[1] - 1);
printString(str);
glColor3f(1.0, 1.0, 0.0);
glRasterPos2f(position[0], position[1]);
printString(str);
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // restore default value
glPopAttrib();
}
// FIXME: the following is just a temporary hack to enable the FPS
// counter. We should really write a proper interface against it, so
// applications can set up feedback loops to control scene complexity
// and get a nice and steady maximum framerate, for instance.
//
// For anyone who want to execute that task, check what TGS has done
// first. If their API is fine, use the same approach.
//
// 20001124 mortene.
// Draw a text string showing the current frame-per-seconds value in
// the lower left corner of the OpenGL canvas (after recording
// information needed to calculate the fps).
//
// The methods for recording FPS values are Coin extensions, not
// available in the original Open Inventor API.
//
// The two displayed values can be explained as follows:
//
// The first number is the time it takes for the SoGLRenderAction to
// traverse the scene graph (displayed as Hz / FPS). The second is the
// interval between each time SoGLRenderAction::apply() is invoked
// (i.e. the "actual" rendering rate, as experienced by the user).
//
// The first number is mainly useful just for internal debugging
// purposes.
//
// The second number will always be <= to the first, because it will
// also include the time stalling on glFlush() upon releasing the
// OpenGL context after traversal (glFlush() stalls until the GPU
// completes all OpenGL commands), and any application-code processing
// inbetween rendering.
//
// There is by the way a useful "trick" to improve application
// performance implied in that last paragraph above: if
// application-code processing is interleaved between the completion
// of the SoGLRenderAction traversal and the release of the OpenGL
// context, application-code is likely to run on the CPU in parallel
// with GPU processing OpenGL commands.
//
// FIXME: the above optimization trick should be documented in the
// visible API docs somewhere, with code to show how to do it. The
// example code would probably involve making an application-specific
// viewer, and overriding actualRedraw()? Or can it be done by using a
// callback mechanism somewhere? Ask pederb. 20031009 mortene.
//
// \sa resetFrameCounter(), addFrametime()
void
So@Gui@ViewerP::recordFPS(const double rendertime)
{
const char * env = SoAny::si()->getenv("COIN_SHOW_FPS_COUNTER");
if ( !env ) {
COIN_SHOW_FPS_COUNTER = UNINITIALIZED_ENVVAR;
} else {
COIN_SHOW_FPS_COUNTER = atoi(env);
}
#if 0
// disabled to make fps-couter dynamically adjustable
if (COIN_SHOW_FPS_COUNTER == UNINITIALIZED_ENVVAR) {
const char * env = SoAny::si()->getenv("COIN_SHOW_FPS_COUNTER");
COIN_SHOW_FPS_COUNTER = env ? atoi(env) : 0;
}
#endif
if (COIN_SHOW_FPS_COUNTER > 0) {
SbVec2f fps = this->addFrametime(rendertime);
char buffer[64];
int nr = sprintf(buffer, "%.1f/%.1f fps", fps[0], fps[1]);
assert(nr < 64);
Draw2DString(buffer, PUBLIC(this)->getGLSize(), SbVec2f(10, 10));
}
}
// *************************************************************************
SO@GUI@_OBJECT_ABSTRACT_SOURCE(So@Gui@Viewer);
// *************************************************************************
/*!
\enum So@Gui@Viewer::Type
Hints about what context the viewer will be used in. Usually not
very interesting for the application programmer, it doesn't matter
much which value is used for the viewer type. This "feature" of the
viewer is included just to be compatible with the old SGI Inventor
API.
*/
/*!
\var So@Gui@Viewer::Type So@Gui@Viewer::BROWSER
If a user-supplied scenegraph passed into the setSceneGraph()
function does not contain a camera, setting the viewer type to
BROWSER will make the viewer in that case automatically set up a
camera outside the scene, as part of the viewer's private and hidden
"supergraph".
*/
/*!
\var So@Gui@Viewer::Type So@Gui@Viewer::EDITOR
If a user-supplied scenegraph passed into the setSceneGraph()
function does not contain a camera, setting the viewer type to
EDITOR will make the viewer in that case automatically set up a
camera \e in the user-supplied scene.
So if you want to avoid having the So@Gui@Viewer class muck about
with your supplied scenegraph, set the type-flag to
So@Gui@Viewer::BROWSER instead, which makes an inserted camera node
go into the viewer's own "wrapper" scene graph instead.
*/
/*!
\enum So@Gui@Viewer::DrawType
Contains valid values for the first argument to the
So@Gui@Viewer::setDrawStyle() call. Decides the effect of the second
argument.
\sa So@Gui@Viewer::setDrawStyle(), So@Gui@Viewer::DrawStyle
*/
/*!
\var So@Gui@Viewer::DrawType So@Gui@Viewer::STILL
If this value is passed as the first argument of
So@Gui@Viewer::setDrawStyle(), the second argument decides which
draw style to use when the viewer camera is standing still in the
same position with the same orientation -- i.e. when the end user is
\e not interacting with the scene camera.
*/
/*!
\var So@Gui@Viewer::DrawType So@Gui@Viewer::INTERACTIVE
If this value is passed as the first argument of
So@Gui@Viewer::setDrawStyle(), the second argument decides which
draw style to use when the end user is interacting with the scene
camera, causing continuous animation redraws.
*/
/*!
\enum So@Gui@Viewer::DrawStyle
Decides drawstyle for a scene with either a still camera or an
animating camera.
\sa So@Gui@Viewer::setDrawStyle(), So@Gui@Viewer::DrawType
*/
/*!
\var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_AS_IS
Normal rendering, draws all scene geometry in it's original style.
*/
/*!
\var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_HIDDEN_LINE
Draw scene in "hidden line" mode: that is, as wireframe with no
"see-through".
Note that this is actually an expensive way to render, as the scene
must be rendered twice to achieve the effect of hiding lines behind
the invisible geometry.
*/
/*!
\var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_WIREFRAME_OVERLAY
Render the scene as normal, but overlay a set of lines showing the
contours of all polygons.
*/
/*!
\var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_NO_TEXTURE
Render scene without textures.
*/
/*!
\var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_LOW_COMPLEXITY
Render all "complex" shape types with low complexity to improve
rendering performance.
"Complex shapes" in this context includes spheres, cones, cylinder,
NURBS surfaces, and others which are tesselated to polygons before
being rendered.
*/
/*!
\var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_LINE
View all polygon geometry in wireframe mode.
*/
/*!
\var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_POINT
Render only the vertex positions of the geometry.
*/
/*!
\var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_BBOX
View the scene's bounding boxes, instead of rendering the full
geometry.
A very efficient way of optimizing rendering performance for scenes
with high primitive counts while moving the camera about is to set
this mode for the So@Gui@Viewer::INTERACTIVE DrawType.
*/
/*!
\var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_LOW_RES_LINE
Render as wireframe and don't bother with getting them rendered
correctly in depth.
*/
/*!
\var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_LOW_RES_POINT
Render as vertex points and don't bother with getting them rendered
correctly in depth.
*/
/*!
\var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_SAME_AS_STILL
Always render a scene with an animating camera (ie
So@Gui@Viewer::INTERACTIVE DrawType) in the same manner as scene
with a still camera.
*/
/*!
\enum So@Gui@Viewer::BufferType
Set of valid values for So@Gui@Viewer::setBufferingType().
*/
/*!
\var So@Gui@Viewer::BufferType So@Gui@Viewer::BUFFER_SINGLE
Change underlying OpenGL canvas to be single-buffered.
*/
/*!
\var So@Gui@Viewer::BufferType So@Gui@Viewer::BUFFER_DOUBLE
Change underlying OpenGL canvas to be double-buffered.
*/
/*!
\var So@Gui@Viewer::BufferType So@Gui@Viewer::BUFFER_INTERACTIVE
Set up so animation rendering is done in a double-buffered OpenGL
canvas, but ordinary rendering happens directly in the front-buffer.
This mode can be useful with absurdly large scenes, as the rendering
will \e visibly progress, and one will avoid having the end user
wonder why nothing is happening while the scene is rendered to the
back buffer in the default So@Gui@Viewer::BUFFER_DOUBLE mode.
*/
// *************************************************************************
/*
Return the parent node in the scene graph of the given \a node.
NB: this is just a quick'n'dirty thing for often executed code,
and doesn't cover cases where nodes have multiple parents.
*/
SoGroup *
So@Gui@ViewerP::getParentOfNode(SoNode * root, SoNode * node) const
{
assert(node && root && "called with null argument");
const SbBool oldsearch = SoBaseKit::isSearchingChildren();
SoBaseKit::setSearchingChildren(TRUE);
this->searchaction->reset();
this->searchaction->setSearchingAll(TRUE);
this->searchaction->setNode(node);
this->searchaction->apply(root);
SoPath * p = this->searchaction->getPath();
SoGroup * parent = NULL;
if (p) {
parent = (SoGroup *) ((SoFullPath *)p)->getNodeFromTail(1);
assert(parent && "couldn't find parent");
}
this->searchaction->reset();
SoBaseKit::setSearchingChildren(oldsearch);
return parent;
}
// *************************************************************************
/*!
Constructor. \a parent, \a name and \a embed are passed on to
So@Gui@RenderArea, so see the documentation for our parent
constructor for for more information on those.
The \a t type setting hints about what context the viewer will be
used in. Usually not very interesting for the application
programmer, but if you want to make sure the So@Gui@Viewer class
doesn't muck about with your supplied scenegraph, set the type-flag
to So@Gui@Viewer::BROWSER. (This "feature" of the viewer is
included just to be compatible with the old SGI Inventor API.)
The \a build flag decides whether or not to delay building the
widgets / window which is going to make up the components of the
viewer.
*/
So@Gui@Viewer::So@Gui@Viewer(@WIDGET@ parent,
const char * name,
SbBool embed,
So@Gui@Viewer::Type t,
SbBool build)
: inherited(parent, name, embed, TRUE, TRUE, FALSE)
{
PRIVATE(this) = new So@Gui@ViewerP(this);
// initialization of protected data
PRIVATE(this)->type = t;
PRIVATE(this)->viewingflag = TRUE;
PRIVATE(this)->altdown = FALSE;
PRIVATE(this)->camera = NULL;
PRIVATE(this)->scenegraph = NULL;
// initialization of internal data
PRIVATE(this)->cursoron = TRUE;
PRIVATE(this)->localsetbuffertype = FALSE;
PRIVATE(this)->cameratype = SoPerspectiveCamera::getClassTypeId();
PRIVATE(this)->buffertype = this->isDoubleBuffer() ? BUFFER_DOUBLE : BUFFER_SINGLE;
PRIVATE(this)->interactionstartCallbacks = new SoCallbackList;
PRIVATE(this)->interactionendCallbacks = new SoCallbackList;
PRIVATE(this)->interactionnesting = 0;
PRIVATE(this)->seekdistance = 50.0f;
PRIVATE(this)->seekdistanceabs = FALSE;
PRIVATE(this)->seektopoint = TRUE;
PRIVATE(this)->seekperiod = 2.0f;
PRIVATE(this)->inseekmode = FALSE;
PRIVATE(this)->seeksensor = new SoTimerSensor(So@Gui@ViewerP::seeksensorCB, this);
PRIVATE(this)->sceneroot = PRIVATE(this)->createSuperScene();
PRIVATE(this)->sceneroot->ref();
PRIVATE(this)->drawstyles[STILL] = VIEW_AS_IS;
PRIVATE(this)->drawstyles[INTERACTIVE] = VIEW_SAME_AS_STILL;
this->addStartCallback(So@Gui@ViewerP::interactivestartCB);
this->addFinishCallback(So@Gui@ViewerP::interactiveendCB);
PRIVATE(this)->adjustclipplanes = TRUE;
PRIVATE(this)->autoclipbboxaction = NULL;
PRIVATE(this)->stereoviewing = FALSE;
PRIVATE(this)->stereooffset = 0.1f;
PRIVATE(this)->wireframeoverlaycolor = SbColor(1.0f, 0.0f, 0.0f);
if (build) {
this->setClassName("So@Gui@Viewer");
@WIDGET@ widget = this->buildWidget(this->getParentWidget());
this->setBaseWidget(widget);
}
PRIVATE(this)->resetFrameCounter();
}
// *************************************************************************
/*!
Destructor.
*/
So@Gui@Viewer::~So@Gui@Viewer()
{
delete PRIVATE(this)->autoclipbboxaction;
delete PRIVATE(this)->interactionstartCallbacks;
delete PRIVATE(this)->interactionendCallbacks;
delete PRIVATE(this)->seeksensor;
if (PRIVATE(this)->scenegraph) this->setSceneGraph(NULL);
if (PRIVATE(this)->superimpositions != NULL) {
while ( PRIVATE(this)->superimpositions->getLength() > 0 ) {
SoNode * node = (SoNode *) (*PRIVATE(this)->superimpositions)[0];
this->removeSuperimposition(node);
}
}
PRIVATE(this)->sceneroot->unref();
delete PRIVATE(this);
}
// *************************************************************************
// Note: the following function documentation block will also be used
// for all the miscellaneous viewer subclasses, so keep it general.
/*!
Set the camera we want the viewer to manipulate when interacting with
the viewer controls.
The camera passed in as an argument to this method \e must already
be part of the viewer's scenegraph. You do \e not inject viewpoint
cameras to the viewer with this method.
You should rather insert a camera into the scene graph first (if
necessary, often one will be present already), then register it as
the camera used by the viewer controls with this method.
If the application code doesn't explicitly set up a camera through
this method, the viewer will automatically scan through the
scenegraph to find a camera to use. If no camera is available in the
scenegraph at all, it will set up it's own camera.
\sa getCamera()
*/
void
So@Gui@Viewer::setCamera(SoCamera * cam)
{
if (PRIVATE(this)->camera) {
// remove the camera from the super scene graph if we inserted a camera there
int idx = PRIVATE(this)->sceneroot->findChild(PRIVATE(this)->camera);
if (idx >= 0) {
PRIVATE(this)->sceneroot->removeChild(idx);
}
PRIVATE(this)->camera->unref();
}
if (cam) {
cam->ref();
PRIVATE(this)->cameratype = cam->getTypeId();
}
PRIVATE(this)->camera = cam;
this->saveHomePosition();
}
// *************************************************************************
/*!
Returns the camera currently used by the viewer for the user's main
viewpoint.
It \e is possible that this function returns \c NULL, for instance
if there's no scenegraph present in the viewer. (This is mostly
meant as a note for developers extending the So@Gui@ library, as
application programmers usually controls if and when a viewer
contains a scenegraph, and therefore know in advance if this method
will return a valid camera pointer.)
\sa setCamera()
*/
SoCamera *
So@Gui@Viewer::getCamera(void) const
{
// This impossible to miss reminder was inserted so we don't
// accidentally let an So* v2 slip out the door without fixing this
// API design flaw. 20030903 mortene.
#if (SO@GUI@_MAJOR_VERSION == 2)
#error This is a reminder: when jumping to version 2 of an So* toolkit, the So@Gui@Viewer::getCamera() method should be made virtual.
#endif // version = 2
return PRIVATE(this)->camera;
}
// *************************************************************************
/*!
When the viewer has to make its own camera as a result of the graph
passed to setSceneGraph() not containing any camera nodes, this call
can be made in advance to decide which type the camera will be of.
Default is to use an SoPerspectiveCamera.
If this method is called when there is a scene graph and a camera
already set up, it will delete the old camera and set up a camera
with the new type if the \a t type is different from that of the
current camera.
\sa getCameraType()
*/
void
So@Gui@Viewer::setCameraType(SoType t)
{
if (PRIVATE(this)->camera &&
!PRIVATE(this)->camera->isOfType(SoPerspectiveCamera::getClassTypeId()) &&
!PRIVATE(this)->camera->isOfType(SoOrthographicCamera::getClassTypeId())) {
#if SO@GUI@_DEBUG
SoDebugError::postWarning("So@Gui@Viewer::setCameraType",
"Only SoPerspectiveCamera and SoOrthographicCamera is supported.");
#endif // SO@GUI_DEBUG
return;
}
SoType perspectivetype = SoPerspectiveCamera::getClassTypeId();
SoType orthotype = SoOrthographicCamera::getClassTypeId();
SbBool oldisperspective = PRIVATE(this)->cameratype.isDerivedFrom(perspectivetype);
SbBool newisperspective = t.isDerivedFrom(perspectivetype);
if ((oldisperspective && newisperspective) ||
(!oldisperspective && !newisperspective)) // Same old, same old..
return;
if (SO@GUI@_DEBUG) {
SbBool valid = TRUE;
if (t == SoType::badType()) valid = FALSE;
if (valid) {
valid = FALSE;
if (newisperspective) valid = TRUE;
if (t.isDerivedFrom(orthotype)) valid = TRUE;
}
if (!valid) {
SoDebugError::post("So@Gui@Viewer::setCameraType",
"not a valid camera type: '%s'",
t == SoType::badType() ?
"badType" : t.getName().getString());
return;
}
}
SoCamera * currentcam = PRIVATE(this)->camera;
if (currentcam == NULL) {
// A camera has not been set up for the scene yet, so just store
// the type and short-cut the rest of this function.
PRIVATE(this)->cameratype = t;
return;
}
SoCamera * newcamera = (SoCamera *)t.createInstance();
// Transfer and convert values from one camera type to the other.
if (newisperspective) {
So@Gui@ViewerP::convertOrtho2Perspective((SoOrthographicCamera *)currentcam,
(SoPerspectiveCamera *)newcamera);
}
else {
So@Gui@ViewerP::convertPerspective2Ortho((SoPerspectiveCamera *)currentcam,
(SoOrthographicCamera *)newcamera);
}
SoGroup * cameraparent =
PRIVATE(this)->getParentOfNode(PRIVATE(this)->sceneroot, currentcam);
if (cameraparent) { cameraparent->replaceChild(currentcam, newcamera); }
else {
// camera not actually present in the scene graph, so just NULL
// and void.
newcamera->ref();
newcamera->unref();
newcamera = NULL;
// Yes, this can "legally" happen, if e.g. the camera is taken out
// of the scene graph by the app programmer, and no new camera was
// set with setCamera() -- but this is a quite odd thing to do, so
// we warn about this for now.
SoDebugError::postWarning("So@Gui@Viewer::setCameraType",
"Could not find the current camera in the "
"scene graph, for some odd reason.");
}
// The setCamera() invokation below will set the saved "home"
// position of the camera to the current camera position. We make
// no attempt to avoid this, as it would involve nasty hacks, and
// it shouldn't really matter.
this->setCamera(newcamera); // This will set PRIVATE(this)->cameratype.
}
// *************************************************************************
/*!
Returns camera type which will be used when the viewer has to make its
own camera.
Note that this call does \e not return the current cameratype, as one
might expect. Use getCamera() and SoType::getTypeId() for that inquiry.
\sa setCameraType()
*/
SoType
So@Gui@Viewer::getCameraType(void) const
{
return PRIVATE(this)->cameratype;
}
// *************************************************************************
/*!
Reposition the current camera so we can see the complete scene.
*/
void
So@Gui@Viewer::viewAll(void)
{
SoCamera * cam = PRIVATE(this)->camera;
if (cam && PRIVATE(this)->scenegraph) {
cam->viewAll(PRIVATE(this)->scenegraph, this->getViewportRegion());
}
}
// *************************************************************************
/*!
Store the current camera settings for later retrieval with
resetToHomePosition().
\sa resetToHomePosition()
*/
void
So@Gui@Viewer::saveHomePosition(void)
{
if (! PRIVATE(this)->camera) return; // probably a scene-less viewer
// We use SoType::createInstance() to store a copy of the camera,
// not just assuming it's either a perspective or an orthographic
// camera.
SoType t = PRIVATE(this)->camera->getTypeId();
assert(t.isDerivedFrom(SoNode::getClassTypeId()));
assert(t.canCreateInstance());
if (PRIVATE(this)->storedcamera) { PRIVATE(this)->storedcamera->unref(); }
PRIVATE(this)->storedcamera = (SoNode *)t.createInstance();
PRIVATE(this)->storedcamera->ref();
// We copy the field data directly, instead of using
// SoFieldContainer::copyContents(), as that has one problematic
// side-effect: the new camera node used for storing the data would
// also get the *name* of the old camera, which would overwrite the
// old name->ptr entry of the global dictionary behind
// SoNode::getByName(). This can cause surprising and hard to find
// bugs for app programmers, for instance when using
// SoNode::getByName() to get at a camera loaded from an iv-file.
PRIVATE(this)->storedcamera->copyFieldValues(PRIVATE(this)->camera);
}
// *************************************************************************
/*!
Restore the saved camera settings.
\sa saveHomePosition()
*/
void
So@Gui@Viewer::resetToHomePosition(void)
{
if (!PRIVATE(this)->camera) { return; } // probably a scene-less viewer
if (!PRIVATE(this)->storedcamera) { return; }
SoType t = PRIVATE(this)->camera->getTypeId();
SoType s = PRIVATE(this)->storedcamera->getTypeId();
// most common case
if (t == s) {
// We copy the field data directly, instead of using
// SoFieldContainer::copyContents(), for the reason described in
// detail in So@Gui@Viewer::saveHomePosition().
PRIVATE(this)->camera->copyFieldValues(PRIVATE(this)->storedcamera);
}
// handle common case #1
else if (t == SoOrthographicCamera::getClassTypeId() &&
s == SoPerspectiveCamera::getClassTypeId()) {
So@Gui@ViewerP::convertPerspective2Ortho((SoPerspectiveCamera *)PRIVATE(this)->storedcamera,
(SoOrthographicCamera *)PRIVATE(this)->camera);
}
// handle common case #2
else if (t == SoPerspectiveCamera::getClassTypeId() &&
s == SoOrthographicCamera::getClassTypeId()) {
So@Gui@ViewerP::convertOrtho2Perspective((SoOrthographicCamera *)PRIVATE(this)->storedcamera,
(SoPerspectiveCamera *)PRIVATE(this)->camera);
}
// otherwise, cameras have changed in ways we don't understand since
// the last saveHomePosition() invokation, and so we're just going
// to ignore the reset request
}
// *************************************************************************
/*!
Turn the camera headlight on or off.
Default is to have a headlight turned on.
\sa isHeadlight(), getHeadlight()
*/
void
So@Gui@Viewer::setHeadlight(SbBool on)
{
PRIVATE(this)->headlight->on = on;
}
// *************************************************************************
/*!
Returns status of the viewer headlight, whether it is on or off.
\sa setHeadlight(), getHeadlight()
*/
SbBool
So@Gui@Viewer::isHeadlight(void) const
{
return PRIVATE(this)->headlight->on.getValue();
}
// *************************************************************************
/*!
Returns the a pointer to the directional light node which is the
viewer headlight.
The fields of the node is available for user editing.
\sa isHeadlight(), setHeadlight()
*/
SoDirectionalLight *
So@Gui@Viewer::getHeadlight(void) const
{
return PRIVATE(this)->headlight;
}
// *************************************************************************
/*!
Set up a drawing style. The \a type argument specifies if the given
\a style should be interpreted as the drawstyle during animation or
when the camera is static.
Default values for the drawing style is to render the scene "as is"
in both still mode and while the camera is moving.
See the documentation for the \a DrawType and \a DrawStyle for more
information.
\sa getDrawStyle()
*/
void
So@Gui@Viewer::setDrawStyle(So@Gui@Viewer::DrawType type,
So@Gui@Viewer::DrawStyle style)
{
if (SO@GUI@_DEBUG) {
if ((type != STILL) && (type != INTERACTIVE)) {
SoDebugError::postWarning("So@Gui@Viewer::setDrawStyle",
"unknown drawstyle type setting 0x%x", type);
return;
}
}
if (style == this->getDrawStyle(type)) {
if (SO@GUI@_DEBUG && 0) { // debug
SoDebugError::postWarning("So@Gui@Viewer::setDrawStyle",
"drawstyle for type 0x%02x already 0x%02x",
type, style);
}
return;
}
PRIVATE(this)->drawstyles[type] = style;
PRIVATE(this)->changeDrawStyle(PRIVATE(this)->currentDrawStyle());
}
// *************************************************************************
/*!
Return current drawstyles for the given type (\a STILL or
\a INTERACTIVE).
\sa setDrawStyle()
*/
So@Gui@Viewer::DrawStyle
So@Gui@Viewer::getDrawStyle(const So@Gui@Viewer::DrawType type) const
{
if (SO@GUI@_DEBUG) {
if ((type != STILL) && (type != INTERACTIVE)) {
SoDebugError::postWarning("So@Gui@Viewer::setDrawStyle",
"unknown drawstyle type setting 0x%x", type);
return PRIVATE(this)->drawstyles[STILL];
}
}
return PRIVATE(this)->drawstyles[type];
}
// *************************************************************************
/*!
Set the viewer's buffer type. Available types are \c
So@Gui@Viewer::BUFFER_SINGLE, \c So@Gui@Viewer::BUFFER_DOUBLE and \c
So@Gui@Viewer::BUFFER_INTERACTIVE.
(With a buffer type of \c So@Gui@Viewer::BUFFER_INTERACTIVE, the
viewer will render with doublebuffering during user interaction and
with single buffering otherwise.)
Default is \c So@Gui@Viewer::BUFFER_DOUBLE.
\sa getBufferingType()
*/
void
So@Gui@Viewer::setBufferingType(So@Gui@Viewer::BufferType type)
{
if (type == PRIVATE(this)->buffertype) return;
if (type != BUFFER_SINGLE &&
type != BUFFER_DOUBLE &&
type != BUFFER_INTERACTIVE) {
if (SO@GUI@_DEBUG) {
SoDebugError::postWarning("So@Gui@Viewer::setBufferingType",
"unknown buffer type 0x%x", type);
}
return;
}
PRIVATE(this)->buffertype = type;
PRIVATE(this)->localsetbuffertype = TRUE;
inherited::setDoubleBuffer(type == BUFFER_DOUBLE);
PRIVATE(this)->localsetbuffertype = FALSE;
}
// *************************************************************************
/*!
Return the viewer's buffer type.
\sa setBufferingType()
*/
So@Gui@Viewer::BufferType
So@Gui@Viewer::getBufferingType(void) const
{
return PRIVATE(this)->buffertype;
}
// *************************************************************************
// Note: this documentation for setViewing() will also be used for all
// the miscellaneous viewer subclasses, so keep it general.
/*!
Set view mode.
If the view mode is on, user events will be caught and used to
influence the camera position / orientation. If view mode is off,
all events in the viewer canvas (like for instance keypresses or
mouseclicks and -movements) will be passed along to the scene graph.
Default is to have the view mode active.
\sa isViewing()
*/
void
So@Gui@Viewer::setViewing(SbBool enable)
{
if (PRIVATE(this)->viewingflag == enable) {
if (SO@GUI@_DEBUG) {
SoDebugError::postWarning("So@Gui@Viewer::setViewing",
"unnecessary called");
}
return;
}
PRIVATE(this)->viewingflag = enable;
// Turn off the selection indicators when we go back from picking
// mode into viewing mode.
if (PRIVATE(this)->viewingflag) {
SoGLRenderAction * action = this->getGLRenderAction();
if (action != NULL)
SoLocateHighlight::turnOffCurrentHighlight(action);
}
}
// *************************************************************************
/*!
Return state of view mode.
\c TRUE means that the mode of the viewer is set such that user
interaction with the mouse is used to modify the position and
orientation of the camera.
\sa setViewing()
*/
SbBool
So@Gui@Viewer::isViewing(void) const
{
return PRIVATE(this)->viewingflag;
}
// *************************************************************************
/*!
Set whether or not the mouse cursor representation should be visible
in the viewer canvas.
Default value is on.
\sa isCursorEnabled()
*/
void
So@Gui@Viewer::setCursorEnabled(SbBool on)
{
PRIVATE(this)->cursoron = on;
}
// *************************************************************************
/*!
Returns visibility status of mouse cursor.
\sa setCursorEnabled()
*/
SbBool
So@Gui@Viewer::isCursorEnabled(void) const
{
return PRIVATE(this)->cursoron;
}
// *************************************************************************
/*!
Turn on or off continuous automatic adjustments of the near and far
clipping planes.
If on, the distance from the camera position to the near and far
planes will be calculated to be a "best fit" around the geometry in
the scene, to maximize the "stretch" of values for the visible
geometry in the z-buffer. This is important, as z-buffer resolution
is usually limited enough that one will quickly see flickering in
the rasterization of close polygons upon lousy utilization of the
z-buffer.
Automatic calculations of near and far clip planes are on as
default.
For better control over what happens in boundary conditions (for
instance when the distance between near and far planes get very far,
or if geometry gets very close to the camera position), it is
possible to use the So@Gui@Viewer::setAutoClippingStrategy() method
to fine-tune the near/far clipping plane settings.
On a major note, be aware that turning auto-updating of near and far
clip planes \e off have a potentially serious detrimental effect on
performance, due to an important side effect: updating the near and
far clip planes triggers an SoGetBoundingBoxAction to traverse the
scene graph, which causes bounding boxes to be calculated and stored
in caches. The bounding box caches are then used by the
SoGLRenderAction traversal for view frustum culling operations. With
no bounding box caches, the rendering will not do culling, which can
cause much worse performance. Kongsberg Oil & Gas Technologies are
working on correcting this problem properly from within the Coin
library.
On a minor note, be aware that notifications will be temporarily
turned off for the scene's SoCamera when changing the near and far
clipping planes (which is done right before each redraw). This is
done to avoid notifications being sent through the scene graph right
before rendering, as that causes some latency. It is mentioned here
in case you have any client code which for some reason needs to
sense all changes to the scene camera. This is however unlikely, so
you can very probably ignore this.
\sa getAutoClipping()
*/
void
So@Gui@Viewer::setAutoClipping(SbBool enable)
{
if (SO@GUI@_DEBUG) {
if (PRIVATE(this)->adjustclipplanes == enable) {
SoDebugError::postWarning("So@Gui@Viewer::setAutoClipping",
"unnecessary called");
return;
}
}
PRIVATE(this)->adjustclipplanes = enable;
if (enable) { this->scheduleRedraw(); }
}
/*!
Set the strategy used for automatic updates of the distances to the
near and far clipping planes.
When auto clipping is enabled, the near plane distance is calculated
so that it is just in front of the scene bounding box. If this near
plane is behind or very close to the projection point, one of the
following strategies will be used to calculate the new clipping
plane.
The VARIABLE_NEAR_PLANE strategy considers the number of z buffer
bits available for the current OpenGL context, and uses \a value to
calculate the number of bits that is lost because of the far/near
ratio. \a value should be in the range [0.0, 1.0]. A higher \a value
will increase the z-buffer precision, but also push the near plane
further away from the projection point.
The CONSTANT_NEAR_PLANE strategy simply sets the near plane to
\a value. If \a value at some point approaches the far clipping
plane distance, the near plane distance will be set to far plane
distance divided by 5000.0.
The default strategy is VARIABLE_NEAR_PLANE.
It is also possible to register a callback method \a cb, which will
then be invoked after the near and far clipping planes has been
calculated by the So@Gui@Viewer code. The callback can then adjust
the values for the distance to the near and far planes to exactly
match the needs of the application (for instance at specific parts
in the scene), to limit the distance to either plane, or whatever
else needs to be controlled.
The signature of the So@Gui@AutoClippingCB callback must match:
\code
SbVec2f myfunc(void * data, const SbVec2f & nearfar);
\endcode
The first argument is the \a cbuserdata passed in along with the
callback function pointer itself (ie the callback function's
closure). The second argument is the near and far clipping plane
distances from the camera position, as calculated internally by the
viewer, including "slack".
The function callback can then modify the near and far clipping
plane distances to what will \e actually be used by the
viewer. These values will then be used unmodified for the viewer's
camera.
This is a good way of dynamically modifying the near and far
distances such that they at all times exactly matches the specific
layout of the application scene, for instance with regard to the
trade-off between z-buffer resolution and how early geometry is
clipped at the near plane (or at the far plane).
Note that the internal near/far calculations should be good enough
for the vast majority of scenes. Application programmers should only
need to set up their own adjustments upon "unusual" scenes, like for
instance scenes with a large world space, but where one would still
like to be able to get up extremely close on details in some parts
of the scene.
\sa setAutoClipping()
*/
void
So@Gui@Viewer::setAutoClippingStrategy(const AutoClippingStrategy strategy,
const float value,
So@Gui@AutoClippingCB * cb,
void * cbuserdata)
{
PRIVATE(this)->autoclipstrategy = strategy;
PRIVATE(this)->autoclipvalue = value;
PRIVATE(this)->autoclipcb = cb;
PRIVATE(this)->autoclipuserdata = cbuserdata;
if (PRIVATE(this)->autoclipstrategy == VARIABLE_NEAR_PLANE) {
// normalize the value so that the near plane isn't too near or
// too far from the projection point. FIXME: calibrate this
// normalization, pederb, 2002-04-25
float v = So@Gui@Clamp(value, 0.0f, 1.0f); // just in case
v *= 0.8f;
v += 0.1f; // v will be in range [0.1, 0.9]
PRIVATE(this)->autoclipvalue = v;
}
if (PRIVATE(this)->adjustclipplanes) {
this->scheduleRedraw();
}
}
// *************************************************************************
/*!
Return value of the automatic near/far clipplane adjustment indicator.
\sa setAutoClipping()
*/
SbBool
So@Gui@Viewer::isAutoClipping(void) const
{
return PRIVATE(this)->adjustclipplanes;
}
// *************************************************************************
/*!
Turn stereo viewing on or off.
Note: this function is being obsoleted, you should use the
setStereoType() function instead.
Coin does "correct" stereo rendering, using the method known as
"parallel axis asymmetric frustum perspective projection". For more
information, see this link:
http://astronomy.swin.edu.au/~pbourke/opengl/stereogl/
\sa isStereoViewing(), setStereoType()
*/
void
So@Gui@Viewer::setStereoViewing(SbBool enable)
{
PRIVATE(this)->stereoviewing = enable;
this->scheduleRedraw();
}
/*!
Returns a boolean indicating whether or not we're in stereo viewing
mode.
NOTE: in the original InventorXt API, this method was virtual. It is not
virtual here.
\sa setStereoViewing(), getStereoType()
*/
SbBool
So@Gui@Viewer::isStereoViewing(void) const
{
return PRIVATE(this)->stereoviewing;
}
// *************************************************************************
/*!
\enum So@Gui@Viewer::StereoType
Contains list of supported stereo rendering techniques.
\sa So@Gui@Viewer::setStereoType()
*/
/*!
\var So@Gui@Viewer::StereoType So@Gui@Viewer::STEREO_NONE
Use monoscopic rendering.
*/
/*!
\var So@Gui@Viewer::StereoType So@Gui@Viewer::STEREO_ANAGLYPH
Render stereo by superimposing two images of the same scene, but with
different color filters over the left and right view (or "eye").
This is a way of rendering stereo which works on any display, using
color-filter glasses. Such glasses are usually cheap and easy to
come by.
\sa setAnaglyphStereoColorMasks()
*/
/*!
\var So@Gui@Viewer::StereoType So@Gui@Viewer::STEREO_QUADBUFFER
Render stereo by using OpenGL quad-buffers. This is the most common
interface for stereo rendering for more expensive hardware devices,
such as shutter glasses and polarized glasses.
The well known Crystal Eyes glasses are commonly used with this type
of stereo display.
*/
/*!
\var So@Gui@Viewer::StereoType So@Gui@Viewer::STEREO_INTERLEAVED_ROWS
Interleaving / interlacing rows from the left and right eye is
another stereo rendering method requiring special hardware. One
example of a provider of shutter glasses working with interleaved
glasses is VRex:
http://www.vrex.com/
*/
/*!
\var So@Gui@Viewer::StereoType So@Gui@Viewer::STEREO_INTERLEAVED_COLUMNS
Same basic technique as So@Gui@Viewer::STEREO_INTERLEAVED_ROWS, only
it is vertical lines that are interleaved / interlaced, instead of
horizontal lines.
*/
/*!
Set up stereo rendering.
Coin does "correct" stereo rendering, using the method known as
"parallel axis asymmetric frustum perspective projection". For more
information, see this link:
http://astronomy.swin.edu.au/~pbourke/opengl/stereogl/
Note: it is prefered that one uses this function for control of
which type of stereo rendering to use, instead of the older
So@Gui@Viewer::setStereoViewing() and
So@Gui@GLWidget::setQuadBufferStereo() functions.
The default is to do monoscopic rendering, i.e. the default
So@Gui@Viewer::StereoType value is So@Gui@Viewer::STEREO_NONE.
\sa So@Gui@Viewer::StereoType, SoCamera::setStereoAdjustment
\since So@Gui@ 1.2
*/
SbBool
So@Gui@Viewer::setStereoType(So@Gui@Viewer::StereoType s)
{
if (s == this->getStereoType()) { return TRUE; }
// We need to know this to keep compatibility with older client
// code, which controlled stereo rendering with setStereoViewing()
// and setQuadBufferStereo() only.
PRIVATE(this)->stereotypesetexplicit = TRUE;
switch (s) {
case So@Gui@Viewer::STEREO_NONE:
this->setQuadBufferStereo(FALSE);
this->setStereoViewing(FALSE);
break;
case So@Gui@Viewer::STEREO_ANAGLYPH:
this->setStereoViewing(TRUE);
this->setQuadBufferStereo(FALSE);
break;
case So@Gui@Viewer::STEREO_QUADBUFFER:
this->setStereoViewing(TRUE);
this->setQuadBufferStereo(TRUE);
// Check, in case GL quad buffers not supported with the driver
// config:
if (!this->isQuadBufferStereo()) {
this->setStereoViewing(FALSE);
return FALSE;
}
break;
case So@Gui@Viewer::STEREO_INTERLEAVED_ROWS:
case So@Gui@Viewer::STEREO_INTERLEAVED_COLUMNS:
this->setStereoViewing(TRUE);
this->setQuadBufferStereo(FALSE);
this->setStencilBuffer(TRUE);
// Check, in case GL stencil buffers not supported with the driver
// config:
if (!this->getStencilBuffer()) {
this->setStereoViewing(FALSE);
return FALSE;
}
break;
default:
assert(FALSE); break;
}
PRIVATE(this)->stereotype = s;
return TRUE;
}
/*!
Returns the current type of stereo rendering used (or
So@Gui@Viewer::STEREO_NONE if monoscopic).
*/
So@Gui@Viewer::StereoType
So@Gui@Viewer::getStereoType(void) const
{
// Stereo can be set up without using setStereoType() through the
// older functions setStereoViewing() and setQuadBufferStereo(), so
// we need to check for this separately.
if (!PRIVATE(this)->stereotypesetexplicit) {
if (this->isQuadBufferStereo()) {
PRIVATE(this)->stereotype = So@Gui@Viewer::STEREO_QUADBUFFER;
}
else if (this->isStereoViewing()) {
PRIVATE(this)->stereotype = So@Gui@Viewer::STEREO_ANAGLYPH;
}
}
return PRIVATE(this)->stereotype;
}
// *************************************************************************
/*!
If display is configured to render in anaglyph stereo, this function
can be used to control which filter is used for each eye.
The default filters are red (i.e. color vector [TRUE,FALSE,FALSE])
for the left eye, and cyan (color vector [FALSE,TRUE,TRUE]) for the
right eye.
\sa So@Gui@Viewer::StereoType, setStereoType()
*/
void
So@Gui@Viewer::setAnaglyphStereoColorMasks(const SbBool left[3], const SbBool right[3])
{
for (unsigned int i = 0; i < 3; i++) {
PRIVATE(this)->stereoanaglyphmask[0][i] = left[i];
PRIVATE(this)->stereoanaglyphmask[1][i] = right[i];
}
this->scheduleRedraw();
}
/*!
Returns color masks for left and right eye filters in anaglyph
stereo.
\sa setAnaglyphStereoColorMasks()
*/
void
So@Gui@Viewer::getAnaglyphStereoColorMasks(SbBool left[3], SbBool right[3])
{
for (unsigned int i = 0; i < 3; i++) {
left[i] = PRIVATE(this)->stereoanaglyphmask[0][i];
right[i] = PRIVATE(this)->stereoanaglyphmask[1][i];
}
}
// *************************************************************************
/*!
Set the offset between the two viewpoints when in stereo mode.
Default value is 0.1.
NOTE: In the original InventorXt API, this method was not virtual.
\sa getStereoOffset()
*/
void
So@Gui@Viewer::setStereoOffset(const float dist)
{
PRIVATE(this)->stereooffset = dist;
this->scheduleRedraw();
}
/*!
Return the offset distance between the two viewpoints when in stereo
mode.
\sa setStereoOffset()
*/
float
So@Gui@Viewer::getStereoOffset(void) const
{
return PRIVATE(this)->stereooffset;
}
// *************************************************************************
/*!
Toggle between seeking to a point or seeking to an object.
Default is to seek to a point.
\sa isDetailSeek()
*/
void
So@Gui@Viewer::setDetailSeek(const SbBool on)
{
if (SO@GUI@_DEBUG) {
if (PRIVATE(this)->seektopoint == on) {
SoDebugError::postWarning("So@Gui@Viewer::setDetailSeek",
"unnecessary called");
return;
}
}
PRIVATE(this)->seektopoint = on;
}
// *************************************************************************
/*!
Returns a value indicating whether or not seeks will be performed
to the exact point of picking or just towards the picked object.
\sa setDetailSeek()
*/
SbBool
So@Gui@Viewer::isDetailSeek(void) const
{
return PRIVATE(this)->seektopoint;
}
// *************************************************************************
/*!
Set the duration of animating the camera repositioning
after a successful seek. Call with \a seconds equal to \a 0.0 to make
the camera jump immediately to the correct spot.
Default value is 2 seconds.
\sa getSeekTime()
*/
void
So@Gui@Viewer::setSeekTime(const float seconds)
{
if (seconds < 0.0f) {
if (SO@GUI@_DEBUG) {
SoDebugError::postWarning("So@Gui@Viewer::setSeekTime",
"an attempt was made to set a negative seek "
"time duration");
}
return;
}
PRIVATE(this)->seekperiod = seconds;
}
// *************************************************************************
/*!
Returns the camera repositioning duration following a seek action.
\sa setSeekTime()
*/
float
So@Gui@Viewer::getSeekTime(void) const
{
return PRIVATE(this)->seekperiod;
}
// *************************************************************************
/*!
Add a function to call when user interaction with the scene starts.
\sa removeStartCallback(), addFinishCallback()
*/
void
So@Gui@Viewer::addStartCallback(So@Gui@ViewerCB * func, void * data)
{
PRIVATE(this)->interactionstartCallbacks->addCallback((SoCallbackListCB *)func, data);
}
/*!
Remove one of the functions which has been set up to be called when user
interaction with the scene starts.
\sa addStartCallback(), removeFinishCallback()
*/
void
So@Gui@Viewer::removeStartCallback(So@Gui@ViewerCB * func, void * data)
{
PRIVATE(this)->interactionstartCallbacks->removeCallback((SoCallbackListCB *)func,
data);
}
// *************************************************************************
/*!
Add a function to call when user interaction with the scene ends.
\sa removeFinishCallback(), addStartCallback()
*/
void
So@Gui@Viewer::addFinishCallback(So@Gui@ViewerCB * func, void * data)
{
PRIVATE(this)->interactionendCallbacks->addCallback((SoCallbackListCB *)func, data);
}
/*!
Remove one of the functions which has been set up to be called when user
interaction with the scene ends.
\sa addFinishCallback(), removeStartCallback()
*/
void
So@Gui@Viewer::removeFinishCallback(So@Gui@ViewerCB * func, void * data)
{
PRIVATE(this)->interactionendCallbacks->removeCallback((SoCallbackListCB *)func,
data);
}
// *************************************************************************
/*!
Set the color of the overlay wireframe to \a color.
\sa getWireframeOverlayColor()
*/
void So@Gui@Viewer::setWireframeOverlayColor(const SbColor & color)
{
PRIVATE(this)->wireframeoverlaycolor = color;
this->scheduleRedraw();
}
// *************************************************************************
/*!
Returns the current color of the overlay wireframe. The default
color is [1,0,0], ie pure red.
\sa setWireframeOverlayColor()
*/
const SbColor &So@Gui@Viewer::getWireframeOverlayColor(void) const
{
return PRIVATE(this)->wireframeoverlaycolor;
}
// *************************************************************************
/*!
Overloaded to update the local bufferingtype variable.
\sa setBufferingType(), getBufferingType()
*/
void
So@Gui@Viewer::setDoubleBuffer(const SbBool on)
{
if (!PRIVATE(this)->localsetbuffertype)
PRIVATE(this)->buffertype = on ? BUFFER_DOUBLE : BUFFER_SINGLE;
inherited::setDoubleBuffer(on);
}
// *************************************************************************
/*!
Give the viewer a scenegraph to render and interact with. Overridden
from parent class so the viewer can add it's own nodes to control
rendering in different styles, rendering with a headlight, etc.
The \a root node will be inserted under the \e viewer's root node,
which also covers the nodes necessary to implement the different
preferences drawing style settings.
If no camera is part of the scene graph under \a root, one will
automatically be instantiated and added. You can get a reference to
this camera by using the So@Gui@Viewer::getCamera() method.
\sa getSceneGraph(), setCameraType()
*/
void
So@Gui@Viewer::setSceneGraph(SoNode * root)
{
if ((root != NULL) && (root == PRIVATE(this)->scenegraph)) {
if (SO@GUI@_DEBUG) {
SoDebugError::postWarning("So@Gui@Viewer::setSceneGraph",
"called with the same root as already set");
}
return;
}
// If the So@Gui@RenderArea hasn't yet set up its pointer to the
// So@Gui@Viewer "viewer root" (i.e. the viewer-generated root above
// the user-supplied root), do that first.
if (!inherited::getSceneGraph())
inherited::setSceneGraph(PRIVATE(this)->sceneroot);
if (PRIVATE(this)->scenegraph) {
if (this->getCamera())
this->setCamera(NULL);
// Release the old user-supplied graph.
PRIVATE(this)->usersceneroot->removeChild(PRIVATE(this)->scenegraph);
// old: PRIVATE(this)->sceneroot->removeChild(PRIVATE(this)->scenegraph);
}
PRIVATE(this)->scenegraph = root;
if (!root) return;
PRIVATE(this)->usersceneroot->addChild(PRIVATE(this)->scenegraph);
// Search for a camera in the user-supplied scenegraph.
SbBool oldsearch = SoBaseKit::isSearchingChildren();
SoBaseKit::setSearchingChildren(TRUE);
PRIVATE(this)->searchaction->reset();
PRIVATE(this)->searchaction->setType(SoCamera::getClassTypeId());
PRIVATE(this)->searchaction->apply(PRIVATE(this)->scenegraph);
SoBaseKit::setSearchingChildren(oldsearch);
SoCamera * scenecamera = NULL;
if ( PRIVATE(this)->searchaction->getPath() != NULL ) {
SoFullPath * fullpath =
(SoFullPath *) PRIVATE(this)->searchaction->getPath();
scenecamera = (SoCamera *)fullpath->getTail();
}
#if 0 // debug
SoDebugError::postInfo("So@Gui@Viewer::setSceneGraph",
"camera %sfound in graph",
scenecamera ? "" : "not ");
#endif // debug
// Make our own camera if none was available.
if (!scenecamera) {
if (SoGuiViewpointWrapper::hasViewpoints(root)) {
scenecamera = new SoGuiViewpointWrapper;
PRIVATE(this)->cameratype = SoGuiViewpointWrapper::getClassTypeId();
((SoGuiViewpointWrapper*)scenecamera)->setSceneGraph(root);
}
else {
scenecamera = (SoCamera *) PRIVATE(this)->cameratype.createInstance();
}
// If type==BROWSER, camera should be inserted in the private
// viewer "supergraph", if it's equal to EDITOR it should be
// inserted in the user-supplied scenegraph.
if (PRIVATE(this)->type == So@Gui@Viewer::BROWSER) {
PRIVATE(this)->sceneroot->insertChild(scenecamera, 1);
}
else { // PRIVATE(this)->type == So@Gui@Viewer::EDITOR
if (PRIVATE(this)->scenegraph->isOfType(SoGroup::getClassTypeId())) {
// At the uppermost leftmost position in the user-supplied
// scenegraph.
((SoGroup *)PRIVATE(this)->scenegraph)->insertChild(scenecamera, 0);
}
else {
// Make an extra depth level to fit the camera node into the
// user-scenegraph.
SoGroup * g = new SoGroup;
g->addChild(scenecamera);
g->addChild(PRIVATE(this)->scenegraph);
PRIVATE(this)->usersceneroot->removeChild(PRIVATE(this)->scenegraph);
PRIVATE(this)->usersceneroot->addChild(g);
PRIVATE(this)->scenegraph = g;
}
}
if (PRIVATE(this)->cameratype != SoGuiViewpointWrapper::getClassTypeId()) {
scenecamera->viewAll(PRIVATE(this)->scenegraph, this->getViewportRegion());
}
}
this->setCamera(scenecamera);
}
// *************************************************************************
// doc in super
SoNode *
So@Gui@Viewer::getSceneGraph(void)
{
// Overloaded from parent class to return the root of the scene
// graph set by the user, without the extras added by the viewer to
// control rendering.
return PRIVATE(this)->scenegraph;
}
// *************************************************************************
// Note: the following function documentation block will also be used
// for all the miscellaneous viewer subclasses, so keep it general.
/*!
Put the viewer in or out of "waiting-to-seek" mode.
If the user performs a mouse button click when the viewer is in
"waiting-to-seek" mode, the camera will be repositioned so the
camera focal point lies on the point of the geometry under the mouse
cursor.
\sa isSeekMode(), setDetailSeek()
*/
void
So@Gui@Viewer::setSeekMode(SbBool enable)
{
if (SO@GUI@_DEBUG) {
// User might have switched mode during seek, so if enable==FALSE,
// isViewing() is irrelevant.
if (enable) { assert(this->isViewing()); }
}
if (!enable && PRIVATE(this)->seeksensor->isScheduled()) {
PRIVATE(this)->seeksensor->unschedule();
this->interactiveCountDec();
}
PRIVATE(this)->inseekmode = enable;
}
// *************************************************************************
/*!
Return a flag which indicates whether or not the viewer is in
"waiting-to-seek" mode.
(The actual animated translation will not occur until the end user
really \e starts the seek operation, typically by clicking with the
left mousebutton.)
\sa setSeekMode()
*/
SbBool
So@Gui@Viewer::isSeekMode(void) const
{
return PRIVATE(this)->inseekmode;
}
// *************************************************************************
/*!
Call this method to initiate a seek action towards the 3D
intersection of the scene and the ray from the screen coordinate's
point and in the same direction as the camera is pointing.
Returns \c TRUE if the ray from the \a screenpos position intersect
with any parts of the onscreen geometry, otherwise \c FALSE.
*/
SbBool
So@Gui@Viewer::seekToPoint(const SbVec2s screenpos)
{
if (! PRIVATE(this)->camera)
return FALSE;
SoRayPickAction rpaction(this->getViewportRegion());
rpaction.setPoint(screenpos);
rpaction.setRadius(2);
rpaction.apply(PRIVATE(this)->sceneroot);
SoPickedPoint * picked = rpaction.getPickedPoint();
if (!picked) {
// FIXME: this inc seems bogus, but is needed now due to buggy
// code in for instance the examinerviewer
// processSoEvent(). 20020510 mortene.
#if 1
this->interactiveCountInc(); // decremented in setSeekMode(FALSE)
#endif // FIXME
this->setSeekMode(FALSE);
return FALSE;
}
SbVec3f hitpoint;
if (PRIVATE(this)->seektopoint) {
hitpoint = picked->getPoint();
}
else {
SoGetBoundingBoxAction bbaction(this->getViewportRegion());
bbaction.apply(picked->getPath());
SbBox3f bbox = bbaction.getBoundingBox();
hitpoint = bbox.getCenter();
}
this->seekToPoint(hitpoint);
return TRUE;
}
/*!
Call this method to initiate a seek action towards the give 3D world
coordinate point in the scene, \a scenepos.
\since So@Gui@ 1.3.0
*/
void
So@Gui@Viewer::seekToPoint(const SbVec3f & scenepos)
{
SbVec3f hitpoint(scenepos);
PRIVATE(this)->camerastartposition = PRIVATE(this)->camera->position.getValue();
PRIVATE(this)->camerastartorient = PRIVATE(this)->camera->orientation.getValue();
// move point to the camera coordinate system, consider
// transformations before camera in the scene graph
SbMatrix cameramatrix, camerainverse;
PRIVATE(this)->getCameraCoordinateSystem(PRIVATE(this)->camera,
PRIVATE(this)->sceneroot,
cameramatrix,
camerainverse);
camerainverse.multVecMatrix(hitpoint, hitpoint);
float fd = PRIVATE(this)->seekdistance;
if (!PRIVATE(this)->seekdistanceabs)
fd *= (hitpoint - PRIVATE(this)->camera->position.getValue()).length()/100.0f;
PRIVATE(this)->camera->focalDistance = fd;
SbVec3f dir = hitpoint - PRIVATE(this)->camerastartposition;
dir.normalize();
// find a rotation that rotates current camera direction into new
// camera direction.
SbVec3f olddir;
PRIVATE(this)->camera->orientation.getValue().multVec(SbVec3f(0, 0, -1), olddir);
SbRotation diffrot(olddir, dir);
PRIVATE(this)->cameraendposition = hitpoint - fd * dir;
PRIVATE(this)->cameraendorient = PRIVATE(this)->camera->orientation.getValue() * diffrot;
// Subclasses that want another cameraendorient than what is
// computed here should override this function and set the desired
// orientation there
this->computeSeekFinalOrientation();
if (PRIVATE(this)->seeksensor->isScheduled()) {
PRIVATE(this)->seeksensor->unschedule();
this->interactiveCountDec();
}
PRIVATE(this)->seeksensor->setBaseTime(SbTime::getTimeOfDay());
PRIVATE(this)->seeksensor->schedule();
this->interactiveCountInc();
}
// *************************************************************************
void
So@Gui@ViewerP::setStereoEye(SoCamera * thecamera,
const So@Gui@ViewerP::Eye eye,
So@Gui@ViewerP::StereoData & s) const
{
#ifdef HAVE_SOCAMERA_SETSTEREOMODE
// SoCamera::setStereoMode() is a fairly recent addition to Coin and
// TGS Inventor. If available, camera eye setup is quite
// straightforward.
if (eye == So@Gui@ViewerP::LEFT) {
thecamera->setStereoAdjustment(PUBLIC(this)->getStereoOffset());
thecamera->setStereoMode(SoCamera::LEFT_VIEW);
}
else if (eye == So@Gui@ViewerP::RIGHT) {
thecamera->setStereoMode(SoCamera::RIGHT_VIEW);
}
else {
assert(eye == So@Gui@ViewerP::RESTORE);
thecamera->setStereoMode(SoCamera::MONOSCOPIC);
}
#else // ! HAVE_SOCAMERA_SETSTEREOMODE
// To support older versions of Coin, and SGI/TGS Inventor, we also
// provide "manual" tuning of the camera left/right eye split.
if (eye == So@Gui@ViewerP::LEFT) {
s.camerapos = thecamera->position.getValue();
s.cameradir.setValue(0.0f, 0.0f, -1.0f);
s.offsetvec.setValue(1.0f, 0.0f, 0.0f);
s.offset = PUBLIC(this)->getStereoOffset() * 0.5f;
s.camerarot = thecamera->orientation.getValue();
s.camerarot.multVec(s.cameradir, s.cameradir);
s.camerarot.multVec(s.offsetvec, s.offsetvec);
s.focalpoint = s.camerapos + s.cameradir * thecamera->focalDistance.getValue();
s.nodenotify = thecamera->isNotifyEnabled();
s.positionnotify = thecamera->position.isNotifyEnabled();
s.orientationnotify = thecamera->orientation.isNotifyEnabled();
// turn off notification to avoid redraws
thecamera->enableNotify(FALSE);
thecamera->position.enableNotify(FALSE);
thecamera->orientation.enableNotify(FALSE);
thecamera->position = s.camerapos - s.offsetvec * s.offset;
SbVec3f dir = s.focalpoint - thecamera->position.getValue();
SbRotation rot(s.cameradir, dir);
thecamera->orientation = s.camerarot * rot;
}
else if (eye == So@Gui@ViewerP::RIGHT) {
thecamera->position = s.camerapos + s.offsetvec * s.offset;
SbVec3f dir = s.focalpoint - thecamera->position.getValue();
SbRotation rot(s.cameradir, dir);
thecamera->orientation = s.camerarot * rot;
}
else {
assert(eye == So@Gui@ViewerP::RESTORE);
thecamera->position = s.camerapos;
thecamera->orientation = s.camerarot;
thecamera->position.enableNotify(s.positionnotify);
thecamera->orientation.enableNotify(s.orientationnotify);
thecamera->enableNotify(s.nodenotify);
}
#endif // ! HAVE_SOCAMERA_SETSTEREOMODE
}
void
So@Gui@ViewerP::initStencilBufferForInterleavedStereo(void)
{
const SbViewportRegion & currentvp = PUBLIC(this)->getViewportRegion();
if (this->stereostencilmaskvp == currentvp) { return; } // the common case
So@Gui@Viewer::StereoType s = PUBLIC(this)->getStereoType();
assert((s == So@Gui@Viewer::STEREO_INTERLEAVED_ROWS) ||
(s == So@Gui@Viewer::STEREO_INTERLEAVED_COLUMNS));
// Find out whether or not we need to regenerate the mask data.
SbBool allocnewmask = (this->stereostencilmask == NULL);
const SbVec2s neworigin = currentvp.getViewportOriginPixels();
const SbVec2s newsize = currentvp.getViewportSizePixels();
const SbVec2s oldorigin = this->stereostencilmaskvp.getViewportOriginPixels();
const SbVec2s oldsize = this->stereostencilmaskvp.getViewportSizePixels();
allocnewmask = allocnewmask ||
((oldsize[0] + 7) / 8 * oldsize[1]) < ((newsize[0] + 7) / 8 * newsize[1]);
const SbBool fillmask = allocnewmask || (this->stereostenciltype != s) ||
((s == So@Gui@Viewer::STEREO_INTERLEAVED_ROWS) && (oldsize[0] != newsize[0]));
const SbBool layoutchange = !(this->stereostencilmaskvp == currentvp);
const short bytewidth = (newsize[0] + 7) / 8;
if (allocnewmask) {
delete[] this->stereostencilmask;
this->stereostencilmask = new GLubyte[bytewidth * newsize[1]];
}
this->stereostencilmaskvp = currentvp;
if (fillmask) {
GLubyte * mask = this->stereostencilmask;
if (s == So@Gui@Viewer::STEREO_INTERLEAVED_COLUMNS) {
// alternating columns of 0's and 1's
(void)memset(mask, 0x55, bytewidth * newsize[1]);
}
else {
// alternating rows of 0's and 1's
for (short h=0; h < newsize[1]; h++) {
const GLubyte fill = (h % 2) ? 0xff : 0x00;
(void)memset(mask + (h * bytewidth), fill, bytewidth);
}
}
this->stereostenciltype = s;
}
if (layoutchange) {
glClearStencil(0x0);
glClear(GL_STENCIL_BUFFER_BIT);
glStencilFunc(GL_ALWAYS, GL_REPLACE, GL_REPLACE);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glViewport(neworigin[0], neworigin[1], newsize[0], newsize[1]);
glOrtho(0, newsize[0], 0, newsize[1], -1.0f, 1.0f);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
// FIXME: I've noticed a problem with this approach. If there is
// something in the window system obscuring part of the canvas
// while this is called (as could e.g. happen with a size
// indicator, as with the Sawfish window manager), the stencil
// mask will not be set up for that part. 20041019 mortene.
//
// UPDATE 20041019 mortene: discussed this with pederb, and we
// believe this may be due to a bug in either the OpenGL driver
// (Nvidia 61.11, Linux) or window system or manager (Sawfish,
// XFree86 v4.1.0.1). Should test on other systems to see if they
// show the same artifact.
glRasterPos2f(0, 0);
glDrawPixels(newsize[0], newsize[1], GL_STENCIL_INDEX, GL_BITMAP,
this->stereostencilmask);
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
}
}
// Documented in superclass. Overridden from parent class to be able
// to do the necessary two-pass rendering e.g. if the drawing style is
// hidden line.
void
So@Gui@Viewer::actualRedraw(void)
{
SbTime redrawtime = SbTime::getTimeOfDay();
const SbBool clearcol = this->isClearBeforeRender();
const SbBool clearz = this->isClearZBufferBeforeRender();
const So@Gui@Viewer::StereoType stereotype = this->getStereoType();
if (stereotype != So@Gui@Viewer::STEREO_NONE) {
#if HAVE_SBCOLOR4F_GETBACKGROUNDCOLOR
const SbColor4f bgcol = this->getSceneManager()->getBackgroundColor();
#else
const SbColor4f bgcol(this->getSceneManager()->getBackgroundColor(), 0.0f);
#endif
SoCamera * camera = this->getCamera();
So@Gui@ViewerP::StereoData tmpstorage;
// Render left eye:
PRIVATE(this)->setStereoEye(camera, So@Gui@ViewerP::LEFT, tmpstorage);
switch (stereotype) {
case So@Gui@Viewer::STEREO_ANAGLYPH:
glDrawBuffer(this->isDoubleBuffer() ? GL_BACK : GL_FRONT);
glClearColor(bgcol[0], bgcol[1], bgcol[2], 0.0f);
glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT);
glColorMask(PRIVATE(this)->stereoanaglyphmask[0][0] ? GL_TRUE : GL_FALSE,
PRIVATE(this)->stereoanaglyphmask[0][1] ? GL_TRUE : GL_FALSE,
PRIVATE(this)->stereoanaglyphmask[0][2] ? GL_TRUE : GL_FALSE,
GL_TRUE);
PRIVATE(this)->reallyRedraw(FALSE, FALSE);
break;
case So@Gui@Viewer::STEREO_QUADBUFFER:
glDrawBuffer(this->isDoubleBuffer() ? GL_BACK_LEFT : GL_FRONT_LEFT);
PRIVATE(this)->reallyRedraw(clearcol, clearz);
break;
case So@Gui@Viewer::STEREO_INTERLEAVED_ROWS:
case So@Gui@Viewer::STEREO_INTERLEAVED_COLUMNS:
PRIVATE(this)->initStencilBufferForInterleavedStereo();
glEnable(GL_STENCIL_TEST);
// immutable data in the stencil buffer
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
glStencilFunc(GL_EQUAL, 0x1, 0x1);
PRIVATE(this)->reallyRedraw(clearcol, clearz);
break;
default: assert(FALSE); break;
}
// Render right eye:
PRIVATE(this)->setStereoEye(camera, So@Gui@ViewerP::RIGHT, tmpstorage);
switch (stereotype) {
case So@Gui@Viewer::STEREO_ANAGLYPH:
glColorMask(PRIVATE(this)->stereoanaglyphmask[1][0] ? GL_TRUE : GL_FALSE,
PRIVATE(this)->stereoanaglyphmask[1][1] ? GL_TRUE : GL_FALSE,
PRIVATE(this)->stereoanaglyphmask[1][2] ? GL_TRUE : GL_FALSE,
GL_TRUE);
PRIVATE(this)->reallyRedraw(FALSE, TRUE);
break;
case So@Gui@Viewer::STEREO_QUADBUFFER:
glDrawBuffer(this->isDoubleBuffer() ? GL_BACK_RIGHT : GL_FRONT_RIGHT);
PRIVATE(this)->reallyRedraw(clearcol, clearz);
break;
case So@Gui@Viewer::STEREO_INTERLEAVED_ROWS:
case So@Gui@Viewer::STEREO_INTERLEAVED_COLUMNS:
glStencilFunc(GL_NOTEQUAL, 0x1, 0x1);
PRIVATE(this)->reallyRedraw(FALSE, FALSE);
break;
default: assert(FALSE); break;
}
// Clean-up, post-rendering:
PRIVATE(this)->setStereoEye(camera, So@Gui@ViewerP::RESTORE, tmpstorage);
switch (stereotype) {
case So@Gui@Viewer::STEREO_ANAGLYPH:
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); // restore GL color mask
break;
case So@Gui@Viewer::STEREO_QUADBUFFER:
glDrawBuffer(this->isDoubleBuffer() ? GL_BACK : GL_FRONT);
break;
case So@Gui@Viewer::STEREO_INTERLEAVED_ROWS:
case So@Gui@Viewer::STEREO_INTERLEAVED_COLUMNS:
// FIXME: restore old val. 20040618 mortene.
glDisable(GL_STENCIL_TEST);
break;
default: assert(FALSE); break;
}
}
else { // No stereo:
PRIVATE(this)->reallyRedraw(clearcol, clearz);
}
if (PRIVATE(this)->superimpositions != NULL) {
SoGLRenderAction * raaction = this->getSceneManager()->getGLRenderAction();
SbBool first = TRUE;
SbBool zWasEnabled = FALSE;
for (int i = 0; i < PRIVATE(this)->superimpositions->getLength(); i++) {
if (PRIVATE(this)->superimpositionsenabled[i] != FALSE) {
if (first) {
// save Z buffer state and disable
zWasEnabled = glIsEnabled(GL_DEPTH_TEST) ? TRUE : FALSE;
glDisable(GL_DEPTH_TEST);
first = FALSE;
}
SoNode * scene = (SoNode *) (*PRIVATE(this)->superimpositions)[i];
raaction->apply(scene);
}
}
if (!first && zWasEnabled) glEnable(GL_DEPTH_TEST);
}
redrawtime = SbTime::getTimeOfDay() - redrawtime;
PRIVATE(this)->recordFPS(redrawtime.getValue());
}
// *************************************************************************
/*!
To be able to trigger callback functions when user interaction starts
and/or stops, we need to keep track of the viewer state (i.e. are we in
still mode or in animation mode?).
So@Gui@Viewer automatically adds callbacks to switch between still and
moving draw style, and to switch between single/double buffer when
the buffer type is \a INTERACTIVE.
\sa interactiveCountDec(), getInteractiveCount()
\sa addStartCallback(), addFinishCallback()
\sa removeStartCallback(), removeFinishCallback()
\sa setDrawStyle(), setBufferingType()
*/
void
So@Gui@Viewer::interactiveCountInc(void)
{
// Catch problems with missing interactiveCountDec() calls.
assert(PRIVATE(this)->interactionnesting < 100);
if (++(PRIVATE(this)->interactionnesting) == 1) {
PRIVATE(this)->interactionstartCallbacks->invokeCallbacks(this);
PRIVATE(this)->resetFrameCounter();
}
#if 0 // debug
SoDebugError::postInfo("So@Gui@Viewer::interactiveCountInc", "%d -> %d",
PRIVATE(this)->interactionnesting - 1,
PRIVATE(this)->interactionnesting);
#endif // debug
}
// *************************************************************************
/*!
To be able to trigger callback functions when user interaction starts
and/or stops, we need to keep track of the viewer state (i.e. are we in
still mode or in animation mode?).
So@Gui@Viewer automatically adds callbacks to switch between still and
moving draw style, and to switch between single/double buffer when
the buffer type is \a INTERACTIVE.
\sa interactiveCountInc(), getInteractiveCount()
\sa addStartCallback(), addFinishCallback()
\sa removeStartCallback(), removeFinishCallback()
\sa setDrawStyle(), setBufferingType()
*/
void
So@Gui@Viewer::interactiveCountDec(void)
{
// FIXME: The UI toolkits may cause the interactionnesting to go
// below zero by triggering press and release events in different
// widgets. mariusbu 20010709.
// FIXME: just to clarify; this is due to programming mistakes on
// our behalf and should be cleaned up. We're using a simple
// work-around / ignore strategy for now, though, as getting this
// 100% correct is hard (there are so many possible ways of user
// interaction with a viewer canvas) and the end-user will usually
// not notice any problems at all. So that's why we are using a
// warning instead of an assert(). 20010815 mortene.
// FIXME: here's one known way to trigger the bug: hit "s" in the
// examinerviewer in EXAMINE mode, then while seeking hit ESC to put
// the viewer in INTERACT mode. When the seek is completed, the
// count will become -1. 20010912 mortene.
// FIXME: and another one (tested with SoXt): click and hold LMB in
// the canvas while in INTERACT mode, then hit 'Esc' to switch to
// EXAMINE mode, then release LMB. 20020325 mortene.
if (SO@GUI@_DEBUG) {
if (PRIVATE(this)->interactionnesting <= 0) {
SoDebugError::postWarning("So@Gui@Viewer::interactiveCountDec",
"interaction count nesting went below zero. "
"This is due to an internal So@Gui@ bug.");
}
}
if (--(PRIVATE(this)->interactionnesting) <= 0) {
PRIVATE(this)->interactionendCallbacks->invokeCallbacks(this);
PRIVATE(this)->interactionnesting = 0;
}
}
// *************************************************************************
/*!
Return current interaction count nesting. If equal to zero, the viewer
is in animation mode, otherwise the camera is still.
\sa interactiveCountInc(), interactiveCountDec()
*/
int
So@Gui@Viewer::getInteractiveCount(void) const
{
return PRIVATE(this)->interactionnesting;
}
// *************************************************************************
/*!
Set the value used for calculating how close the camera and intersection
hit point should be made at the end of a seek operation.
The value can be interpreted as an absolute value in the given world
unit (which typically is meters) or as a percentage value of the
distance between the camera starting position and the intersection
hit point. This can be controlled through the
setSeekValueAsPercentage() method. It is as default used as an
absolute value.
Default value is 50 (absolute distance or percent).
\sa getSeekDistance(), setSeekValueAsPercentage(), setSeekTime()
*/
void
So@Gui@Viewer::setSeekDistance(const float distance)
{
if (distance <= 0.0f) {
if (SO@GUI@_DEBUG) {
SoDebugError::postWarning("So@Gui@Viewer::setSeekDistance",
"invalid seek distance value: %f",
distance);
}
return;
}
PRIVATE(this)->seekdistance = distance;
}
// *************************************************************************
/*!
Returns the current seek distance. Value given as an absolute scalar
length or as a percentage value of the original distance between
the hitpoint and the camera starting position.
\sa setSeekDistance(), isSeekValueAsPercentage()
*/
float
So@Gui@Viewer::getSeekDistance(void) const
{
return PRIVATE(this)->seekdistance;
}
// *************************************************************************
/*!
Control whether or not the seek distance value should be interpreted as
a percentage value or as an absolute distance. See documentation on
setSeekDistance() for more information.
\sa setSeekDistance(), isSeekValueAsPercentage()
*/
void
So@Gui@Viewer::setSeekValueAsPercentage(const SbBool on)
{
if (SO@GUI@_DEBUG) {
if ((on && this->isSeekValuePercentage()) ||
(!on && !this->isSeekValuePercentage())) {
SoDebugError::postWarning("So@Gui@Viewer::setSeekDistanceAsPercentage",
"unnecessary called, value already %s",
on ? "on" : "off");
return;
}
}
PRIVATE(this)->seekdistanceabs = on ? FALSE : TRUE;
}
// *************************************************************************
/*!
Returns an boolean which indicates if the seek distance value from
getSeekDistance() should be interpreted as a percentage value or
as an absolute value.
\sa setSeekValuePercentage(), getSeekDistance()
*/
SbBool
So@Gui@Viewer::isSeekValuePercentage(void) const
{
return PRIVATE(this)->seekdistanceabs ? FALSE : TRUE;
}
// ************************************************************************
/*!
This method can be overridden in subclasses if the final
orientation of the camera after a seek should be something other
than what is computed in So@Gui@Viewer::seekToPoint(const SbVec3f &
scenepos)
*/
void
So@Gui@Viewer::computeSeekFinalOrientation(void)
{
}
// *************************************************************************
/*!
If the current camera is of perspective type, switch to
orthographic, and vice versa.
Automatically calls So@Gui@Viewer::setCameraType() so the change
will immediately take place.
*/
void
So@Gui@Viewer::toggleCameraType(void)
{
SoType perspectivetype = SoPerspectiveCamera::getClassTypeId();
SoType orthotype = SoOrthographicCamera::getClassTypeId();
this->setCameraType(PRIVATE(this)->cameratype.isDerivedFrom(perspectivetype)
? orthotype : perspectivetype);
}
// ************************************************************************
/*!
Copies the settings of \a camera into our current camera. Cameras
must be of the same class type.
*/
void
So@Gui@Viewer::changeCameraValues(// virtual, protected
SoCamera * camera)
{
assert(camera != NULL);
SoCamera * cam = this->getCamera();
if (!cam) {
if (SO@GUI@_DEBUG) {
SoDebugError::postWarning("So@Gui@Viewer::changeCameraValues",
"no current camera in the scenegraph");
}
return;
}
if (cam->getTypeId() != camera->getTypeId()) {
if (SO@GUI@_DEBUG) {
SoDebugError::postWarning("So@Gui@Viewer::changeCameraValues",
"tried to copy data from camera of "
"different type");
}
return;
}
cam->copyFieldValues(camera, FALSE);
}
// *************************************************************************
// doc in super
void
So@Gui@Viewer::sizeChanged(const SbVec2s & size)
{
inherited::sizeChanged(size);
}
// *************************************************************************
// Documented in superclass.
SbBool
So@Gui@Viewer::processSoEvent(const SoEvent * const event)
{
const SoType type(event->getTypeId());
const SoKeyboardEvent * keyevent = NULL;
if (type.isDerivedFrom(SoKeyboardEvent::getClassTypeId())) {
keyevent = (SoKeyboardEvent *) event;
switch (keyevent->getKey()) {
// the ESC key switches between view and interact mode
case SoKeyboardEvent::ESCAPE:
if (keyevent->getState() == SoButtonEvent::DOWN) {
this->setViewing(this->isViewing() ? FALSE : TRUE);
return TRUE;
}
break;
// Let the end-user toggle between camera-interaction mode
// ("viewing") and scenegraph-interaction mode with ALT key(s).
case SoKeyboardEvent::LEFT_ALT:
case SoKeyboardEvent::RIGHT_ALT:
if (!this->isViewing() && (keyevent->getState() == SoButtonEvent::DOWN)) {
PRIVATE(this)->altdown = TRUE;
this->setViewing(TRUE);
return TRUE;
}
else if (PRIVATE(this)->altdown && (keyevent->getState() == SoButtonEvent::UP)) {
this->setViewing(FALSE);
PRIVATE(this)->altdown = FALSE;
return TRUE;
}
break;
default:
break;
}
}
// If not viewing, break off further handling and pass the event on
// to the So@Gui@RenderArea, which will pass it on to the
// scenegraph.
if (!this->isViewing()) { return inherited::processSoEvent(event); }
if (keyevent && (keyevent->getState() == SoButtonEvent::DOWN)) {
switch (keyevent->getKey()) {
case SoKeyboardEvent::S:
this->setSeekMode(this->isSeekMode() ? FALSE : TRUE);
return TRUE;
case SoKeyboardEvent::HOME:
this->resetToHomePosition();
return TRUE;
case SoKeyboardEvent::LEFT_ARROW:
PRIVATE(this)->moveCameraScreen(SbVec2f(-0.1f, 0.0f));
return TRUE;
case SoKeyboardEvent::UP_ARROW:
PRIVATE(this)->moveCameraScreen(SbVec2f(0.0f, 0.1f));
return TRUE;
case SoKeyboardEvent::RIGHT_ARROW:
PRIVATE(this)->moveCameraScreen(SbVec2f(0.1f, 0.0f));
return TRUE;
case SoKeyboardEvent::DOWN_ARROW:
PRIVATE(this)->moveCameraScreen(SbVec2f(0.0f, -0.1f));
return TRUE;
default:
break;
}
}
if (this->isSeekMode()) {
if (type.isDerivedFrom(SoMouseButtonEvent::getClassTypeId())) {
SoMouseButtonEvent * const e = (SoMouseButtonEvent *) event;
if (e->getButton() == SoMouseButtonEvent::BUTTON1) {
if (e->getState() == SoButtonEvent::DOWN) {
this->seekToPoint(e->getPosition());
}
else {
// We got an LMB UP-event while in seek-mode, and we just
// swallow the event.
}
return TRUE;
}
}
}
return FALSE;
}
// *************************************************************************
/*!
This method is for setting up a superimposed scene graph on top
of the viewer scene graph. It will be used for adding spin-rotation
coordinate systems, fly-viewer speed indicators and similar things.
This method is not part of the original InventorXt API.
*/
void
So@Gui@Viewer::addSuperimposition(SoNode * scene)
{
if (PRIVATE(this)->superimpositions == NULL)
PRIVATE(this)->superimpositions = new SbPList;
assert(scene != NULL);
scene->ref();
PRIVATE(this)->searchaction->reset();
PRIVATE(this)->searchaction->setType(SoCamera::getClassTypeId());
PRIVATE(this)->searchaction->setInterest(SoSearchAction::FIRST);
PRIVATE(this)->searchaction->apply(scene);
if (PRIVATE(this)->searchaction->getPath() == NULL) {
// FIXME: set up default environment if there is no camera in the
// superimposition scene - or not...
if (SO@GUI@_DEBUG) {
SoDebugError::postInfo("So@Gui@Viewer::addSuperimposition",
"cameraless superimpositions are not "
"supported");
}
scene->unrefNoDelete();
return;
}
PRIVATE(this)->superimpositions->append(scene);
PRIVATE(this)->superimpositionsenabled.append(TRUE);
}
// *************************************************************************
/*!
This method is not part of the original InventorXt API.
*/
void
So@Gui@Viewer::removeSuperimposition(SoNode * scene)
{
assert(scene);
int idx = -1;
if (PRIVATE(this)->superimpositions == NULL) goto error;
idx = PRIVATE(this)->superimpositions->find(scene);
if (idx == -1) goto error;
assert(PRIVATE(this)->superimpositions != NULL);
PRIVATE(this)->superimpositions->remove(idx);
PRIVATE(this)->superimpositionsenabled.remove(idx);
scene->unref();
return;
error:
if (SO@GUI@_DEBUG) {
SoDebugError::post("So@Gui@Viewer::removeSuperimposition",
"no such superimposition");
}
return;
}
// *************************************************************************
/*!
This method sets whether the superimposed scene graph should be traversed
or not.
This method is not part of the original InventorXt API.
*/
void
So@Gui@Viewer::setSuperimpositionEnabled(SoNode * scene,
const SbBool enable)
{
int idx = -1;
if (PRIVATE(this)->superimpositions == NULL) goto error;
idx = PRIVATE(this)->superimpositions->find(scene);
if (idx == -1) goto error;
PRIVATE(this)->superimpositionsenabled[idx] = enable;
return;
error:
if (SO@GUI@_DEBUG) {
SoDebugError::post("So@Gui@Viewer::setSuperimpositionEnabled",
"no such superimposition");
}
return;
}
// *************************************************************************
/*!
This method returns whether the superimposed scene is rendered or not.
This method is not part of the original InventorXt API.
*/
SbBool
So@Gui@Viewer::getSuperimpositionEnabled(SoNode * scene) const
{
int idx = -1;
if (PRIVATE(this)->superimpositions == NULL) goto error;
idx = PRIVATE(this)->superimpositions->find(scene);
if (idx == -1) goto error;
return PRIVATE(this)->superimpositionsenabled[idx];
error:
if (SO@GUI@_DEBUG) {
SoDebugError::post("So@Gui@Viewer::getSuperimpositionEnabled",
"no such superimposition");
}
return FALSE;
}
// *************************************************************************
#undef PRIVATE
#undef PUBLIC