// @configure_input@ /**************************************************************************\ * Copyright (c) Kongsberg Oil & Gas Technologies AS * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the copyright holder nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \**************************************************************************/ /**************************************************************************\ * * A WORD OF ADVICE * * It is fruitless to modify the contents of the So@Gui@RenderArea.cpp file * because it is autogenerated by configure from the SoGuiRenderArea.cpp.in * file which you will find in the src/Inventor/@Gui@/common/ directory. * Do your modifications to that file instead. * \**************************************************************************/ // ************************************************************************* /*! \class So@Gui@RenderArea Inventor/@Gui@/So@Gui@RenderArea.h \brief The So@Gui@RenderArea class adds scenegraph handling and event management. \ingroup components viewers The So@Gui@RenderArea class is a component that adds scenegraph management and input device event handling to the So@Gui@GLWidget component. The class has many convenient methods for controlling aspects of the rendering, like for instance transparency, aliasing and for scheduling of redraws. Native toolkit events are caught by So@Gui@RenderArea components, translated to Coin SoEvent instances and passed on to the scenegraph, in case the user is doing interactive operations on for instance Coin geometry draggers. So@Gui@RenderArea is the first non-abstract component in it's inheritance hierarchy that you can use directly from client application code to set up a scenegraph viewer canvas. For an So@Gui@RenderArea component to properly display your scenegraph, it must contain an SoCamera-derived node and at least one SoLight-derived lightsource node. Here's a complete, stand-alone example on how to set up an So@Gui@RenderArea with a scenegraph: \code #include #include #include #include #include #include #include #include // Set up a simple scenegraph, just for demonstration purposes. static SoSeparator * get_scene_graph(void) { SoSeparator * root = new SoSeparator; SoGroup * group = new SoGroup; SoRotor * rotor = new SoRotor; rotor->rotation = SbRotation(SbVec3f(0.2, 0.5, 0.9), M_PI/4.0); group->addChild(rotor); SoCube * cube = new SoCube; group->addChild(cube); SoArray * array = new SoArray; array->origin = SoArray::CENTER; array->addChild(group); array->numElements1 = 2; array->numElements2 = 2; array->separation1 = SbVec3f(4, 0, 0); array->separation2 = SbVec3f(0, 4, 0); root->addChild(array); return root; } int main(int argc, char ** argv) { @WIDGET@ window = So@Gui@::init(argv[0]); SoSeparator * root = new SoSeparator; root->ref(); SoPerspectiveCamera * camera; root->addChild(camera = new SoPerspectiveCamera); root->addChild(new SoDirectionalLight); SoSeparator * userroot = get_scene_graph(); root->addChild(userroot); So@Gui@RenderArea * renderarea = new So@Gui@RenderArea(window); camera->viewAll(userroot, renderarea->getViewportRegion()); renderarea->setSceneGraph(root); renderarea->setBackgroundColor(SbColor(0.0f, 0.2f, 0.3f)); if (argc > 1) { renderarea->setTitle(argv[1]); renderarea->setIconTitle(argv[1]); } renderarea->show(); So@Gui@::show(window); So@Gui@::mainLoop(); delete renderarea; root->unref(); return 0; } \endcode */ // ************************************************************************* #include #include // strchr() #ifdef HAVE_CONFIG_H #include #endif // HAVE_CONFIG_H #if SOQT_DEBUG // For the "soinfo" debugging backdoor. #include #include #endif // SOQT_DEBUG #include // glDrawBuffer() #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_JOYSTICK_LINUX #include #endif // HAVE_JOYSTICK_LINUX #ifdef HAVE_SPACENAV_SUPPORT #include #endif // HAVE_SPACENAV_SUPPORT #include #include #define RENDERAREA_DEBUG_REDRAWS 0 #define PRIVATE(obj) ((obj)->pimpl) #define PUBLIC(obj) ((obj)->pub) // ************************************************************************* SO@GUI@_OBJECT_SOURCE(So@Gui@RenderArea); // ************************************************************************* #ifndef DOXYGEN_SKIP_THIS class So@Gui@RenderAreaP { public: So@Gui@RenderAreaP(class So@Gui@RenderArea * pub); ~So@Gui@RenderAreaP(void); SbBool clear; SbBool clearZBuffer; SbBool clearOverlay; SoSceneManager * normalManager; SoSceneManager * overlayManager; SbColor * normalColormap; int normalColormapSize; int normalColormapStart; SbColor * overlayColormap; int overlayColormapSize; int overlayColormapStart; SbPList * devicelist; struct { So@Gui@Keyboard * keyboard; So@Gui@Mouse * mouse; #ifdef HAVE_SPACENAV_SUPPORT So@Gui@SpacenavDevice * spacenav; #endif // HAVE_SPACENAV_SUPPORT } devices; SbBool autoRedraw; void replaceSoSelectionMonitor(SoSelection * newsel, SoSelection * oldsel) const; SoSelection * normalselection; SoSelection * overlayselection; static const int GL_DEFAULT_MODE; void constructor(SbBool mouseInput, SbBool keyboardInput, SbBool build); static void renderCB(void * user, SoSceneManager * manager); static void selection_redraw_cb(void * data, SoSelection * sel); void setDevicesWindowSize(const SbVec2s size); // OpenGL info-window hack. enum { NONE, OPENGL, INVENTOR, TOOLKIT, DUMPSCENEGRAPH, DUMPCAMERAS, OFFSCREENGRAB }; int checkMagicSequences(const char c); void showOpenGLDriverInformation(void); void showInventorInformation(void); void showToolkitInformation(void); void dumpScenegraph(void); void dumpCameras(void); void offScreenGrab(void); SbBool invokeAppCB(@EVENT@ event); const SoEvent * getSoEvent(@EVENT@ event); So@Gui@RenderAreaEventCB * appeventhandler; void * appeventhandlerdata; private: So@Gui@RenderArea * pub; // public interface class SbString currentinput; // For the OpenGL info-window hack. }; const int So@Gui@RenderAreaP::GL_DEFAULT_MODE = (SO_GL_RGB | SO_GL_ZBUFFER | SO_GL_DOUBLE ); #if SO@GUI@_DEBUG && defined(__COIN__) // Disabled when compiling against SGI / TGS Inventor, as we're using // our Coin-specific extension SbString::sprintf() a lot. #define DEBUGGING_EGGS 1 #endif // SO@GUI@_DEBUG && __COIN__ // Note: assumes a valid current OpenGL context. void So@Gui@RenderAreaP::showOpenGLDriverInformation(void) { #if DEBUGGING_EGGS const GLubyte * vendor = glGetString(GL_VENDOR); const GLubyte * renderer = glGetString(GL_RENDERER); const GLubyte * version = glGetString(GL_VERSION); const GLubyte * extensions = glGetString(GL_EXTENSIONS); SbString info = "GL_VENDOR: \""; info += (const char *)vendor; info += "\"\n"; info += "GL_RENDERER: \""; info += (const char *)renderer; info += "\"\n"; info += "GL_VERSION: \""; info += (const char *)version; info += "\"\n"; info += "GL_EXTENSIONS: \"\n "; SbString exts = (const char *)extensions; const char * p; int count = 0; // (the extra parentheses in the while-expression kills a gcc warning) while ((p = strchr(exts.getString(), ' '))) { const char * start = exts.getString(); info += exts.getSubString(0, p - start); exts.deleteSubString(0, p - start); count++; if (count == 4) { // number of extensions listed on each line info += "\n "; count = 0; } } if (exts.getLength() > 0) { info += "\n "; info += exts; } info += "\"\n"; // FIXME: should also show available GLX / WGL / AGL // extensions. 20020802 mortene. // Misc implementation info { SbVec2f range; float granularity; PUBLIC(this)->getPointSizeLimits(range, granularity); SbString s; s.sprintf("glPointSize(): range=[%f, %f], granularity=%f\n", range[0], range[1], granularity); info += s; PUBLIC(this)->getLineWidthLimits(range, granularity); s.sprintf("glLineWidth(): range=[%f, %f], granularity=%f\n", range[0], range[1], granularity); info += s; GLint depthbits[1]; glGetIntegerv(GL_DEPTH_BITS, depthbits); s.sprintf("GL_DEPTH_BITS==%d\n", depthbits[0]); info += s; GLint colbits[4]; glGetIntegerv(GL_RED_BITS, &colbits[0]); glGetIntegerv(GL_GREEN_BITS, &colbits[1]); glGetIntegerv(GL_BLUE_BITS, &colbits[2]); glGetIntegerv(GL_ALPHA_BITS, &colbits[3]); s.sprintf("GL_[RED|GREEN|BLUE|ALPHA]_BITS==[%d, %d, %d, %d]\n", colbits[0], colbits[1], colbits[2], colbits[3]); info += s; GLint accumbits[4]; glGetIntegerv(GL_ACCUM_RED_BITS, &accumbits[0]); glGetIntegerv(GL_ACCUM_GREEN_BITS, &accumbits[1]); glGetIntegerv(GL_ACCUM_BLUE_BITS, &accumbits[2]); glGetIntegerv(GL_ACCUM_ALPHA_BITS, &accumbits[3]); s.sprintf("GL_ACCUM_[RED|GREEN|BLUE|ALPHA]_BITS==[%d, %d, %d, %d]\n", accumbits[0], accumbits[1], accumbits[2], accumbits[3]); info += s; GLint stencilbits; glGetIntegerv(GL_STENCIL_BITS, &stencilbits); s.sprintf("GL_STENCIL_BITS==%d\n", stencilbits); info += s; GLint maxdims[2]; glGetIntegerv(GL_MAX_VIEWPORT_DIMS, maxdims); s.sprintf("GL_MAX_VIEWPORT_DIMS==<%d, %d>\n", maxdims[0], maxdims[1]); info += s; GLint texdim; glGetIntegerv(GL_MAX_TEXTURE_SIZE, &texdim); s.sprintf("GL_MAX_TEXTURE_SIZE==%d\n", texdim); info += s; GLint maxlights; glGetIntegerv(GL_MAX_LIGHTS, &maxlights); s.sprintf("GL_MAX_LIGHTS==%d\n", maxlights); info += s; GLint maxplanes; glGetIntegerv(GL_MAX_CLIP_PLANES, &maxplanes); s.sprintf("GL_MAX_CLIP_PLANES==%d\n", maxplanes); info += s; // FIXME: other implementation specifics to print are // // * maximum stack depths (attribute, modelview matrix, name, // projection matrix, texture matrix) // // * max display list nesting // // * max 3D texture size (needs specific extension?) // // 20020802 mortene. } SbString s; s.sprintf("\n" "Rendering is %sdirect.\n", SoGuiGLWidgetP::isDirectRendering(PUBLIC(this)) ? "" : "in"); info += s; So@Gui@::createSimpleErrorDialog(NULL, "OpenGL driver information", info.getString()); #endif // DEBUGGING_EGGS } void So@Gui@RenderAreaP::showInventorInformation(void) { #if DEBUGGING_EGGS SbString info; info.sprintf("%s\n", SoDB::getVersion()); // Display calculated maximum resolution of SbTime::getTimeOfDay(). { const double DURATION = 0.2; // in seconds SbTime current = SbTime::getTimeOfDay(); SbTime end(current + DURATION); SbTime last = current; unsigned int ticks = 0; do { current = SbTime::getTimeOfDay(); if (current.getValue() != last.getValue()) { ticks++; last = current; } } while (current < end); SbString s; s.sprintf("\nSbTime::getTimeOfDay() resolution: ~ %d Hz\n", (int)(((double)ticks) / DURATION)); info += s; } // FIXME: dump list of available node classes? 20010927 mortene. So@Gui@::createSimpleErrorDialog(NULL, "Inventor implementation info", info.getString()); #endif // DEBUGGING_EGGS } void So@Gui@RenderAreaP::showToolkitInformation(void) { #if DEBUGGING_EGGS SbString info = "So@Gui@ version "; info += SO@GUI@_VERSION; info += "\n"; #if SO@GUI@_MAKE_DLL info += "Built as MSWindows DLL.\n"; #endif // !SO@GUI@_MAKE_DLL // FIXME: include information about the underlying toolkit library, // if possible, like we do for Qt below (ie Gtk version, Motif // implementation, MSWindows version, ...). 20010927 mortene. #ifdef SOQT_INTERNAL // Qt implementation info. { SbString s; s.sprintf("\nQt version: %s\n", qVersion()); info += s; } #endif // SOQT_INTERNAL // FIXME: information about DLL path(s) (both for the So@Gui@ and // Coin/Inventor library) would be _extremely_ useful for debugging // "remote" applications, as application programmers (including // ourselves) tend to spread those files around misc diskdrive // directories -- especially on MSWindows platforms. Mismatches for // run-time binding and link-time binding then causes bugs which are // impossible to make sense of. // // I don't know if any platforms have enough introspection // functionality to enable us to do this, though. Should // investigate. (update: GetModuleHandle() looks like the place to // start looking in the Win32 API.) // // 20010927 mortene. // OpenGL canvas settings. { SbString s; s.sprintf("\nCurrent OpenGL canvas:\n" " %sbuffer\n" " drawing to %sbuffer\n" " %s rendering%s\n" " %s mode\n" " with%s overlay planes\n", PUBLIC(this)->isDoubleBuffer() ? "double" : "single", PUBLIC(this)->isDrawToFrontBufferEnable() ? "front" : "back", PUBLIC(this)->isStereoBuffer() ? "stereo" : "mono", PUBLIC(this)->isQuadBufferStereo() ? " (OpenGL quadbuffer)" : "", PUBLIC(this)->isRGBMode() ? "RGB" : "colorindex", PUBLIC(this)->isOverlayRender() ? "" : "out"); // FIXME: information about the native OpenGL widget format? // 20010927 mortene. info += s; } // Underlying Inventor implementation. { SbString s; s.sprintf("\nInventor implementation: %s\n", SoDB::getVersion()); info += s; } So@Gui@::createSimpleErrorDialog(NULL, "So@Gui@ implementation info", info.getString()); #endif // DEBUGGING_EGGS } void So@Gui@RenderAreaP::dumpScenegraph(void) { #ifdef DEBUGGING_EGGS SoOutput out; SbString filename = SbTime::getTimeOfDay().format(); filename += "-dump.iv"; SbBool ok = out.openFile(filename.getString()); if (!ok) { SoDebugError::post("So@Gui@RenderAreaP::dumpScenegraph", "couldn't open file '%s'", filename.getString()); return; } SoWriteAction wa(&out); wa.apply(this->normalManager->getSceneGraph()); SoDebugError::postInfo("So@Gui@RenderAreaP::dumpScenegraph", "dumped scenegraph to '%s'", filename.getString()); #endif // DEBUGGING_EGGS } void So@Gui@RenderAreaP::dumpCameras(void) { #ifdef DEBUGGING_EGGS const SbBool kitsearch = SoBaseKit::isSearchingChildren(); SoBaseKit::setSearchingChildren(TRUE); SoSearchAction search; search.setType(SoCamera::getClassTypeId()); search.setInterest(SoSearchAction::ALL); search.setSearchingAll(TRUE); search.apply(this->normalManager->getSceneGraph()); SoBaseKit::setSearchingChildren(kitsearch); const SoPathList & pl = search.getPaths(); const unsigned int numcams = pl.getLength(); SoDebugError::postInfo("So@Gui@RenderAreaP::dumpCameras", "Number of cameras in scene graph: %d", numcams); for (unsigned int i = 0; i < numcams; i++) { const SoPath * p = pl[i]; SoNode * n = p->getTail(); assert(n->isOfType(SoCamera::getClassTypeId())); SoCamera * cam = (SoCamera *)n; const SbVec3f pos = cam->position.getValue(); const SbRotation rot = cam->orientation.getValue(); SbVec3f axis; float angle; rot.getValue(axis, angle); SoDebugError::postInfo("So@Gui@RenderAreaP::dumpCameras", "type==%s, name=='%s', position==<%f, %f, %f>, " "orientation-rotation==<%f, %f, %f>--%f", cam->getTypeId().getName().getString(), cam->getName().getString(), pos[0], pos[1], pos[2], axis[0], axis[1], axis[2], angle); } #endif // DEBUGGING_EGGS } /* Behaviour controlled by environment variables COIN_SOGRAB_GEOMETRY (maximum geometry - on-screen aspect is preserved) COIN_SOGRAB_FILENAME (filename template - can use %d to insert counter) Examples: export COIN_SOGRAB_GEOMETRY=1024x768 export COIN_SOGRAB_FILENAME=c:\\grab%03d.png */ void So@Gui@RenderAreaP::offScreenGrab(void) { #ifdef DEBUGGING_EGGS static int maxwidth = -1; static int maxheight = -1; static int counter = 0; static const char fallback_ext[] = ".rgb"; static const char fallback_name[] = "coingrab%03d.rgb"; /* FIXME: - schedule a regular render-pass and hook into the render pipe-line to check all the interesting GL context features that might need to be enabled for the offscreen renderer context. Then set up the offscreen renderer context to match those features, so the rendering becomes the same. - create a clone of the on-screen renderaction to get the same kind of custom rendering. - check if we might be able to hook up the on-screen pre-render callback to the offscreen renderer as well, if any point. - disable accidentally enabled seek mode again (from typing 'osgrab'). 20050606 larsa. */ counter++; if ( maxwidth <= 0 ) { const char * env = SoAny::si()->getenv("COIN_SOGRAB_GEOMETRY"); if ( env ) { sscanf(env, "%dx%d", &maxwidth, &maxheight); } if ( (maxwidth <= 0) || !env ) { SbVec2s vp = PUBLIC(this)->getViewportRegion().getWindowSize(); maxwidth = vp[0]; maxheight = vp[1]; } } if ( maxwidth <= 0 || maxheight <= 0 ) { SoDebugError::post("So@Gui@RenderAreaP::offScreenGrab", "invalid geometry: %dx%d", maxwidth, maxheight); return; } SbVec2s vp = PUBLIC(this)->getViewportRegion().getWindowSize(); const char * filenametpl = SoAny::si()->getenv("COIN_SOGRAB_FILENAME"); if ( !filenametpl ) filenametpl = fallback_name; SbString filename; filename.sprintf(filenametpl, counter); const char * ext = strrchr(filename.getString(), '.'); if ( !ext ) ext = fallback_ext; ext++; SbVec2s osvp(maxwidth, maxheight); if ( vp[0] > maxwidth || vp[1] > maxheight || (vp[0] < maxwidth && vp[1] < maxheight) ) { float onscaspect = float(vp[0]) / float(vp[1]); float offscaspect = float(maxwidth) / float(maxheight); osvp[1] = maxheight; osvp[0] = short(maxheight * onscaspect); if ( osvp[0] > maxwidth ) { osvp[0] = maxwidth; osvp[1] = short(maxwidth * (1.0f / onscaspect)); } } SoOffscreenRenderer os(osvp); if ( !os.render(PUBLIC(this)->getSceneManager()->getSceneGraph()) ) { return; } SbBool written = FALSE; if (strcmp(ext, "rgb") == 0) { written = os.writeToRGB(filename.getString()); } else { written = os.writeToFile(filename, ext); } if (written) { SoDebugError::postInfo("So@Gui@RenderAreaP::offScreenGrab", "wrote image #%d, %dx%d as '%s'", counter, osvp[0], osvp[1], filename.getString()); } else { SoDebugError::post("So@Gui@RenderAreaP::offScreenGrab", "tried to write image '%s', but failed for unknown " "reason", filename.getString()); } #endif // DEBUGGING_EGGS } int So@Gui@RenderAreaP::checkMagicSequences(const char c) { #if DEBUGGING_EGGS this->currentinput += c; if (0) { // handy for debugging keyboard handling SoDebugError::postInfo("So@Gui@RenderAreaP::checkMagicSequences", "'%s'", this->currentinput.getString()); } const int cl = this->currentinput.getLength(); static const char * keyseq[] = { "glinfo", "ivinfo", "soinfo", "dumpiv", "cameras", "osgrab" }; static const int id[] = { So@Gui@RenderAreaP::OPENGL, So@Gui@RenderAreaP::INVENTOR, So@Gui@RenderAreaP::TOOLKIT, So@Gui@RenderAreaP::DUMPSCENEGRAPH, So@Gui@RenderAreaP::DUMPCAMERAS, So@Gui@RenderAreaP::OFFSCREENGRAB }; for (unsigned int i = 0; i < (sizeof(keyseq) / sizeof(keyseq[0])); i++) { const int ml = strlen(keyseq[i]); if (cl >= ml && this->currentinput.getSubString(cl - ml) == keyseq[i]) { return id[i]; } } // Limit memory usage. if (cl > 1024) { this->currentinput = ""; } #endif // DEBUGGING_EGGS return So@Gui@RenderAreaP::NONE; } // This method sets the window size data in all the connected device // classes. void So@Gui@RenderAreaP::setDevicesWindowSize(const SbVec2s size) { if (!this->devicelist) return; const int num = this->devicelist->getLength(); for (int i = 0; i < num; i++) ((So@Gui@Device *)(*this->devicelist)[i])->setWindowSize(size); } // ************************************************************************* void So@Gui@RenderAreaP::renderCB(void * closure, SoSceneManager * manager) { assert(closure && manager); So@Gui@RenderArea * thisptr = (So@Gui@RenderArea *) closure; if (manager == PRIVATE(thisptr)->normalManager) { thisptr->render(); } else if (manager == PRIVATE(thisptr)->overlayManager) { thisptr->renderOverlay(); } else { #if SO@GUI@_DEBUG SoDebugError::post("So@Gui@RenderAreaP::renderCB", "invoked for unknown SoSceneManager (%p)", manager); #endif // SO@GUI@_DEBUG return; } } // Callback for automatic redraw on SoSelection changes. void So@Gui@RenderAreaP::selection_redraw_cb(void * closure, SoSelection * sel) { So@Gui@RenderArea * ra = (So@Gui@RenderArea *) closure; if (sel == PRIVATE(ra)->normalselection) ra->scheduleRedraw(); else if (sel == PRIVATE(ra)->overlayselection) ra->scheduleOverlayRedraw(); else assert(0 && "callback on unknown SoSelection node"); } // Private class constructor. So@Gui@RenderAreaP::So@Gui@RenderAreaP(So@Gui@RenderArea * api) { PUBLIC(this) = api; this->normalManager = new SoSceneManager; this->overlayManager = new SoSceneManager; this->normalColormap = NULL; this->normalColormapSize = 0; this->overlayColormap = NULL; this->overlayColormapSize = 0; this->clear = TRUE; this->clearZBuffer = TRUE; this->clearOverlay = TRUE; this->autoRedraw = TRUE; this->normalselection = NULL; this->overlayselection = NULL; this->devices.mouse = NULL; this->devices.keyboard = NULL; #ifdef HAVE_SPACENAV_SUPPORT this->devices.spacenav = NULL; #endif // HAVE_SPACENAV_SUPPORT } // Private class destructor. So@Gui@RenderAreaP::~So@Gui@RenderAreaP() { delete this->normalManager; delete this->overlayManager; delete [] this->normalColormap; delete [] this->overlayColormap; } // Common code for all constructors. void So@Gui@RenderAreaP::constructor(SbBool mouseInput, SbBool keyboardInput, SbBool build) { this->normalManager->setRenderCallback(So@Gui@RenderAreaP::renderCB, PUBLIC(this)); this->normalManager->activate(); this->overlayManager->setRenderCallback(So@Gui@RenderAreaP::renderCB, PUBLIC(this)); this->overlayManager->activate(); // FIXME: what is this magic number doing here - shouldn't we use // SoGLCacheContextElement::getUniqueCacheContext() for Coin, and // magic numbers just for SGI / TGS Inventor? // // On a side note: won't this code fail if we construct several // So@Gui@RenderArea instances with overlays? They will all use // cachecontext==1 for their SoGLRenderAction instances -- is that // kosher? // // 20010831 mortene. this->overlayManager->getGLRenderAction()->setCacheContext(1); this->appeventhandler = NULL; this->appeventhandlerdata = NULL; this->devicelist = new SbPList; if (mouseInput) { this->devices.mouse = new So@Gui@Mouse; PUBLIC(this)->registerDevice(this->devices.mouse); } if (keyboardInput) { this->devices.keyboard = new So@Gui@Keyboard; PUBLIC(this)->registerDevice(this->devices.keyboard); } if (! build) return; PUBLIC(this)->setClassName("So@Gui@RenderArea"); @WIDGET@ glarea = PUBLIC(this)->buildWidget(PUBLIC(this)->getParentWidget()); PUBLIC(this)->setBaseWidget(glarea); PUBLIC(this)->setSize(SbVec2s(400, 400)); } // This method invokes the application event handler, if one is set. SbBool So@Gui@RenderAreaP::invokeAppCB(@EVENT@ event) { if (this->appeventhandler != NULL) return this->appeventhandler(this->appeventhandlerdata, event); return FALSE; } // This method returns an SoEvent * corresponding to the given \a // event, or \c NULL if there are none. const SoEvent * So@Gui@RenderAreaP::getSoEvent(@EVENT@ event) { if (!this->devicelist) return (SoEvent *) NULL; const SoEvent * soevent = NULL; const int num = this->devicelist->getLength(); for (int i = 0; (i < num) && (soevent == NULL); i++) soevent = ((So@Gui@Device *)(*this->devicelist)[i])->translateEvent(event); return soevent; } #endif // DOXYGEN_SKIP_THIS // ************************************************************************* /*! Public constructor. */ So@Gui@RenderArea::So@Gui@RenderArea(@WIDGET@ parent, const char * name, SbBool embed, SbBool mouseInput, SbBool keyboardInput) : inherited(parent, name, embed, So@Gui@RenderAreaP::GL_DEFAULT_MODE, FALSE) { PRIVATE(this) = new So@Gui@RenderAreaP(this); PRIVATE(this)->constructor(mouseInput, keyboardInput, TRUE); } /*! Protected constructor used by derived classes. */ So@Gui@RenderArea::So@Gui@RenderArea(@WIDGET@ parent, const char * name, SbBool embed, SbBool mouseInput, SbBool keyboardInput, SbBool build) : inherited(parent, name, embed, So@Gui@RenderAreaP::GL_DEFAULT_MODE, FALSE) { PRIVATE(this) = new So@Gui@RenderAreaP(this); PRIVATE(this)->constructor(mouseInput, keyboardInput, build); } /*! Destructor. */ So@Gui@RenderArea::~So@Gui@RenderArea() { // Clean out any callbacks we may have registered with SoSelection // nodes. this->redrawOverlayOnSelectionChange(NULL); this->redrawOnSelectionChange(NULL); for (int i = PRIVATE(this)->devicelist->getLength() - 1; i >= 0; i--) { So@Gui@Device * device = (So@Gui@Device *) ((*PRIVATE(this)->devicelist)[i]); this->unregisterDevice(device); delete device; } delete PRIVATE(this)->devicelist; delete PRIVATE(this); } // ************************************************************************* /*! This method adds \a device to the list of devices handling events for this component. */ void So@Gui@RenderArea::registerDevice(So@Gui@Device * device) { int idx = PRIVATE(this)->devicelist->find(device); if (idx != -1) { #if SO@GUI@_DEBUG SoDebugError::postWarning("So@Gui@RenderArea::registerDevice", "device already registered"); #endif // SO@GUI@_DEBUG return; } PRIVATE(this)->devicelist->append(device); @WIDGET@ w = this->getGLWidget(); if (w != NULL) { #ifdef __COIN_SOXT__ device->enable(w, (SoXtEventHandler *) &So@Gui@GLWidget::eventHandler, (void *)this); #else device->enable(w, &So@Gui@GLWidgetP::eventHandler, (void *)this); #endif device->setWindowSize(this->getGLSize()); } } /*! This method removes \a device from the list of devices handling events for this component. */ void So@Gui@RenderArea::unregisterDevice(So@Gui@Device * device) { assert(PRIVATE(this)->devicelist != NULL); const int idx = PRIVATE(this)->devicelist->find(device); if (idx == -1) { #if SO@GUI@_DEBUG SoDebugError::post("So@Gui@RenderArea::unregisterDevice", "tried to remove nonexisting device"); #endif // SO@GUI@_DEBUG return; } PRIVATE(this)->devicelist->remove(idx); @WIDGET@ w = this->getGLWidget(); if (w != NULL) { device->disable(w, NULL, NULL); } } // ************************************************************************* // Documented in superclass. void So@Gui@RenderArea::afterRealizeHook(void) { inherited::afterRealizeHook(); #ifdef HAVE_JOYSTICK_LINUX if (So@Gui@LinuxJoystick::exists()) this->registerDevice(new So@Gui@LinuxJoystick); #endif // HAVE_JOYSTICK_LINUX #ifdef HAVE_SPACENAV_SUPPORT PRIVATE(this)->devices.spacenav = new So@Gui@SpacenavDevice; this->registerDevice(PRIVATE(this)->devices.spacenav); #endif // HAVE_SPACENAV_SUPPORT } /*! This method sets the scene graph to be rendered in the normal bitmap planes. \sa getSceneGraph(), setOverlaySceneGraph() */ void So@Gui@RenderArea::setSceneGraph(SoNode * scene) { PRIVATE(this)->normalManager->setSceneGraph(scene); } /*! This method returns a reference to the scene graph root node as set by the user. \sa So@Gui@RenderArea::getSceneManager() */ SoNode * So@Gui@RenderArea::getSceneGraph(void) { return PRIVATE(this)->normalManager->getSceneGraph(); } /*! This method sets the scene graph to render for the overlay bitmap planes. It will automatically take care of setting up overplay planes in the OpenGL canvas if the OpenGL hardware and driver supports it. Important note: not all graphics hardware and / or drivers for graphics hardware support overlay planes, so application programmers are adviced to find some other way of accomplishing what they want to do before resorting to using overlay planes. Using overlay planes will in practice severely limit the portability of applications which depend on them being available. \sa setSceneGraph(), getOverlaySceneGraph() */ void So@Gui@RenderArea::setOverlaySceneGraph(SoNode * scene) { SoNode * oldroot = this->getOverlaySceneGraph(); PRIVATE(this)->overlayManager->setSceneGraph(scene); if (!oldroot && scene) { this->setOverlayRender(TRUE); } else if (oldroot && !scene) { this->setOverlayRender(FALSE); } } /*! This method returns the scene graph for the overlay scene. */ SoNode * So@Gui@RenderArea::getOverlaySceneGraph(void) { return PRIVATE(this)->overlayManager->getSceneGraph(); } // ************************************************************************* /*! This method sets the background color of the scene. */ void So@Gui@RenderArea::setBackgroundColor(const SbColor & color) { assert(PRIVATE(this)->normalManager != NULL); PRIVATE(this)->normalManager->setBackgroundColor(color); this->scheduleRedraw(); } /*! This method returns the background color for the scene. */ const SbColor & So@Gui@RenderArea::getBackgroundColor(void) const { assert(PRIVATE(this)->normalManager != NULL); #if HAVE_SBCOLOR4F_GETBACKGROUNDCOLOR SbColor4f bgcol = PRIVATE(this)->normalManager->getBackgroundColor(); return SbColor(bgcol[0], bgcol[1], bgcol[2]); #else return PRIVATE(this)->normalManager->getBackgroundColor(); #endif } // ************************************************************************* /*! This method sets the index of the background color for the scene. */ void So@Gui@RenderArea::setBackgroundIndex(int idx) { assert(PRIVATE(this)->normalManager != NULL); PRIVATE(this)->normalManager->setBackgroundIndex(idx); this->scheduleRedraw(); } /*! This method returns the index of the background color for the scene. */ int So@Gui@RenderArea::getBackgroundIndex(void) const { assert(PRIVATE(this)->normalManager != NULL); return PRIVATE(this)->normalManager->getBackgroundIndex(); } // ************************************************************************* /*! This method sets the index of the background for the overlay scene. */ void So@Gui@RenderArea::setOverlayBackgroundIndex(int idx) { assert(PRIVATE(this)->overlayManager != NULL); PRIVATE(this)->overlayManager->setBackgroundIndex(idx); this->scheduleOverlayRedraw(); } /*! This method returns the index of the background for the overlay scene. */ int So@Gui@RenderArea::getOverlayBackgroundIndex(void) const { assert(PRIVATE(this)->overlayManager != NULL); return PRIVATE(this)->overlayManager->getBackgroundIndex(); } // ************************************************************************* /*! This method sets the colormap for the scene. */ void So@Gui@RenderArea::setColorMap(int start, int num, const SbColor * colors) { delete [] PRIVATE(this)->normalColormap; PRIVATE(this)->normalColormapStart = start; PRIVATE(this)->normalColormapSize = num; PRIVATE(this)->normalColormap = new SbColor [ num ]; for (int i = 0; i < num; i++) PRIVATE(this)->normalColormap[i] = colors[i]; this->scheduleRedraw(); } /*! This method sets the colormap for the overlay scene. */ void So@Gui@RenderArea::setOverlayColorMap(int start, int num, const SbColor * colors) { delete [] PRIVATE(this)->overlayColormap; PRIVATE(this)->overlayColormapStart = start; PRIVATE(this)->overlayColormapSize = num; PRIVATE(this)->overlayColormap = new SbColor [ num ]; for (int i = 0; i < num; i++) { PRIVATE(this)->overlayColormap[i] = colors[i]; } this->scheduleOverlayRedraw(); } // ************************************************************************* /*! This method sets the viewport region. */ void So@Gui@RenderArea::setViewportRegion(const SbViewportRegion & region) { if (region.getWindowSize()[0] == -1) return; #if SO@GUI@_DEBUG && 0 // debug SoDebugError::postInfo("So@Gui@RenderArea::setViewportRegion", "size=<%d, %d>", region.getWindowSize()[0], region.getWindowSize()[1]); #endif // debug PRIVATE(this)->normalManager->setViewportRegion(region); PRIVATE(this)->overlayManager->setViewportRegion(region); this->scheduleRedraw(); } /*! This method returns the viewport region. */ const SbViewportRegion & So@Gui@RenderArea::getViewportRegion(void) const { assert(PRIVATE(this)->normalManager != NULL); return PRIVATE(this)->normalManager->getGLRenderAction()->getViewportRegion(); } // ************************************************************************* /*! This method sets the transparency type to be used for the scene. */ void So@Gui@RenderArea::setTransparencyType(SoGLRenderAction::TransparencyType type) { assert(PRIVATE(this)->normalManager != NULL); PRIVATE(this)->normalManager->getGLRenderAction()->setTransparencyType(type); PRIVATE(this)->overlayManager->getGLRenderAction()->setTransparencyType(type); this->scheduleRedraw(); } /*! This method returns the transparency type used for the scene. */ SoGLRenderAction::TransparencyType So@Gui@RenderArea::getTransparencyType(void) const { assert(PRIVATE(this)->normalManager != NULL); return PRIVATE(this)->normalManager->getGLRenderAction()->getTransparencyType(); } // ************************************************************************* /*! This method sets the antialiasing used for the scene. The \a smoothing flag signifies whether or not line and point aliasing should be turned on. See documentation of SoGLRenderAction::setSmoothing(), which will be called from this function. \a numPasses gives the number of re-renderings to do of the scene, blending together the results from slight "jitters" of the camera view, into the OpenGL accumulation buffer. For further information, see documentation of SoGLRenderAction::setNumPasses() and So@Gui@GLWidget::setAccumulationBuffer(). */ void So@Gui@RenderArea::setAntialiasing(SbBool smoothing, int numPasses) { // FIXME: is this really necessary? I think we should either ignore // the call or store values for later migration if the scenemanager // instance(s) haven't been made yet. 20010331 mortene. assert(PRIVATE(this)->normalManager != NULL); // Instead of piping the call further to // SoSceneManager::setAntialiasing(), we duplicate the code found in // that function. The reason for this is that we want to work around // a bug found in SGI Inventor, where they define the // setAntialiasing() method, but doesn't actually implement it. So // we don't use it to avoid a linker error for those compiling So* // libraries on top of the older SGI Inventor versions with this // bug. // // We should perhaps throw in a configure check for the // SoSceneManager::setAntialiasing() method and only activate this // code when actually needed? // mortene@sim.no enum { MGRS = 2 }; SoSceneManager * mgrs[MGRS] = { PRIVATE(this)->normalManager, PRIVATE(this)->overlayManager }; for (int i=0; i < MGRS; i++) { SoGLRenderAction * glra = mgrs[i]->getGLRenderAction(); if (glra) { glra->setSmoothing(smoothing); glra->setNumPasses(numPasses); } } this->scheduleRedraw(); } /*! This method returns the antialiasing used for the scene. */ void So@Gui@RenderArea::getAntialiasing(SbBool & smoothing, int & numPasses) const { // FIXME: there's an API design flaw here, as it is assumed that the // antialiasing setting for the renderaction in the "normal" // rendering context always matches what is the case for the // renderaction in the overlay manager. This is not necessarily // true. Could be solved by an additional argument to // getAntialiasing(): a boolean indicator on whether or not we want // the overlay context with a default value (false) to keep API // compatibility. 20010331 mortene. assert(PRIVATE(this)->normalManager != NULL); // About why we don't use SoSceneManager::getAntialiasing() // directly, see comment in SoGuiRenderArea::setAntiAliasing(). SoGLRenderAction * glra = PRIVATE(this)->normalManager->getGLRenderAction(); smoothing = glra->isSmoothing(); numPasses = glra->getNumPasses(); } /*! This method sets whether the render buffer should be cleared before rendering. The first argument specifies whether or not to clear out the pixels in the buffer, the second argument specifies whether or not the z-buffer values should be cleared between renderings. Setting the first argument to \c FALSE can for instance be used when you want to clear out the buffer yourself, for instance by drawing a background image "under" the 3D scene rendered by Coin / Inventor. */ void So@Gui@RenderArea::setClearBeforeRender(SbBool enable, SbBool zbEnable) { PRIVATE(this)->clear = enable; PRIVATE(this)->clearZBuffer = zbEnable; this->scheduleRedraw(); } /*! This method returns whether the render buffer is cleared before each render. */ SbBool So@Gui@RenderArea::isClearBeforeRender(void) const { return PRIVATE(this)->clear; } /*! This method returns whether the render buffer's Z buffer is cleared before each render. */ SbBool So@Gui@RenderArea::isClearZBufferBeforeRender(void) const { return PRIVATE(this)->clearZBuffer; } /*! This method sets whether the overlay render buffer should be cleared before each render or not. */ void So@Gui@RenderArea::setClearBeforeOverlayRender(SbBool enable) { PRIVATE(this)->clearOverlay = enable; this->scheduleOverlayRedraw(); } /*! This method returns whether the overlay render buffer is cleared before each redraw or not. */ SbBool So@Gui@RenderArea::isClearBeforeOverlayRender(void) const { return PRIVATE(this)->clearOverlay; } /*! This method sets whether redrawing should be handled automatically or not when data in the scenegraph changes. The default setting causes the renderarea to automatically trigger a redraw of the scenegraph contents. */ void So@Gui@RenderArea::setAutoRedraw(SbBool enable) { if (enable) { PRIVATE(this)->normalManager->activate(); PRIVATE(this)->overlayManager->activate(); } else { PRIVATE(this)->normalManager->deactivate(); PRIVATE(this)->overlayManager->deactivate(); } PRIVATE(this)->autoRedraw = enable; } /*! This method returns whether redrawing is handled automatically not. */ SbBool So@Gui@RenderArea::isAutoRedraw(void) const { return PRIVATE(this)->autoRedraw; } /*! This method sets the redraw priority. */ void So@Gui@RenderArea::setRedrawPriority(uint32_t priority) { PRIVATE(this)->normalManager->setRedrawPriority(priority); PRIVATE(this)->overlayManager->setRedrawPriority(priority); } /*! This method returns the redraw priority. */ uint32_t So@Gui@RenderArea::getRedrawPriority(void) const { assert(PRIVATE(this)->normalManager != NULL); return PRIVATE(this)->normalManager->getRedrawPriority(); } /*! This function returns the default redraw priority. */ uint32_t So@Gui@RenderArea::getDefaultRedrawPriority(void) { return SoSceneManager::getDefaultRedrawPriority(); } /*! This method causes the immediate rendering of the scene, by calling So@Gui@RenderArea::redraw(). */ void So@Gui@RenderArea::render(void) { this->redraw(); } /*! This method renders the overlay scene. */ void So@Gui@RenderArea::renderOverlay(void) { this->redrawOverlay(); } /*! This method schedules a redraw to happen at a later time (when the application has processed it's other events first). */ void So@Gui@RenderArea::scheduleRedraw(void) { #if SO@GUI@_DEBUG && RENDERAREA_DEBUG_REDRAWS // debug SoDebugError::postInfo("So@Gui@RenderArea::scheduleRedraw", "invoked"); #endif // debug assert(PRIVATE(this)->normalManager != NULL); PRIVATE(this)->normalManager->scheduleRedraw(); // Redraw when idle. } /*! This method schedules a redraw of the overlay scene. */ void So@Gui@RenderArea::scheduleOverlayRedraw(void) { assert(PRIVATE(this)->overlayManager != NULL); PRIVATE(this)->overlayManager->scheduleRedraw(); // Redraw when idle. } #ifndef DOXYGEN_SKIP_THIS void So@Gui@RenderAreaP::replaceSoSelectionMonitor(SoSelection * newsel, SoSelection * oldsel) const { if (newsel) { newsel->ref(); } if (oldsel) { oldsel->removeChangeCallback(So@Gui@RenderAreaP::selection_redraw_cb, PUBLIC(this)); oldsel->unref(); } if (newsel) { newsel->addChangeCallback(So@Gui@RenderAreaP::selection_redraw_cb, PUBLIC(this)); } } #endif // DOXYGEN_SKIP_THIS // FIXME: the interface below is badly designed, see the comment in // the function documentation. 20001002 mortene. /*! Do automatic redraw of the scenegraph when a selection under the SoSelection node is changed. Pass \c NULL to deactivate. (Only one SoSelection node can be monitored at any given time. This is obviously a rather silly design flaw. We choose to match the original Inventor API here, but this will probably change in the next major revision of the library.) */ void So@Gui@RenderArea::redrawOnSelectionChange(SoSelection * selection) { PRIVATE(this)->replaceSoSelectionMonitor(selection, PRIVATE(this)->normalselection); PRIVATE(this)->normalselection = selection; } /*! Do automatic redraw of the scenegraph in the overlay planes when a selection under the SoSelection node is changed. Pass \c NULL to deactivate. \sa So@Gui@RenderArea::redrawOnSelectionChange() */ void So@Gui@RenderArea::redrawOverlayOnSelectionChange(SoSelection * selection) { PRIVATE(this)->replaceSoSelectionMonitor(selection, PRIVATE(this)->overlayselection); PRIVATE(this)->overlayselection = selection; } /*! This method sets the render area event callback. */ void So@Gui@RenderArea::setEventCallback(So@Gui@RenderAreaEventCB * func, void * user) { PRIVATE(this)->appeventhandler = func; PRIVATE(this)->appeventhandlerdata = user; } /*! This method sets the normal scene SoSceneManager object. The previous set scene manager is deleted, and there is no way to currently avoid that. This might change in the future. */ void So@Gui@RenderArea::setSceneManager(SoSceneManager * manager) { assert(PRIVATE(this)->normalManager != NULL); PRIVATE(this)->normalManager->setRenderCallback(NULL, NULL); // NOTE: Although deleting the previous scene manager here is correct // behaviour from a compatibility-POV, I think it is ugly and should // be addressed in some other way if possible. It is also inconsistent // with overlay scenemanager management. 20061015 larsa delete PRIVATE(this)->normalManager; PRIVATE(this)->normalManager = manager; if (PRIVATE(this)->normalManager) { PRIVATE(this)->normalManager->setSize(this->getGLSize()); } } /*! This method returns the normal scene SoSceneManager object. Having a reference to the SoSceneManager instance is useful for getting at the \e real root node of the rendering scenegraph, including camera, headlight and miscellaneous drawstyle nodes. The getSceneGraph() method will only return the \e user scenegrah for So@Gui@RenderArea subclass So@Gui@Viewer and further subclasses. The reason this is not always what you want is because certain actions (like the SoRayPickAction) needs to traverse a valid camera if it should work as expected. If you need to get a pointer to the \e real root node use this method to get the SoSceneManager instance reference used by the So@Gui@RenderArea, then use SoSceneManager::getSceneGraph() to get the root node Coin uses for rendering. */ SoSceneManager * So@Gui@RenderArea::getSceneManager(void) const { return PRIVATE(this)->normalManager; } /*! This method sets the overlay scene SoSceneManager object. The previous set scene manager is not freed and will leak unless the user frees it. */ void So@Gui@RenderArea::setOverlaySceneManager(SoSceneManager * manager) { PRIVATE(this)->overlayManager = manager; if (PRIVATE(this)->overlayManager) { PRIVATE(this)->overlayManager->setSize(this->getGLSize()); } } /*! This method returns the overlay scene SoSceneManager object. */ SoSceneManager * So@Gui@RenderArea::getOverlaySceneManager(void) const { return PRIVATE(this)->overlayManager; } /*! This method sets the SoGLRenderAction object for the normal scene. */ void So@Gui@RenderArea::setGLRenderAction(SoGLRenderAction * action) { assert(PRIVATE(this)->normalManager != NULL); PRIVATE(this)->normalManager->setGLRenderAction(action); // Force an update of the SoGLRenderAction to the correct // updatearea, aspectratio, etc. this->sizeChanged(this->getSize()); } /*! This method returns the SoGLRenderAction object for the normal scene. */ SoGLRenderAction * So@Gui@RenderArea::getGLRenderAction(void) const { assert(PRIVATE(this)->normalManager != NULL); return PRIVATE(this)->normalManager->getGLRenderAction(); } /*! This method sets the SoGLRenderAction object for rendering the overlay scenegraph. */ void So@Gui@RenderArea::setOverlayGLRenderAction(SoGLRenderAction * action) { assert(PRIVATE(this)->overlayManager != NULL); PRIVATE(this)->overlayManager->setGLRenderAction(action); } /*! This method returns the SoGLRenderAction object for the overlay scene graph. */ SoGLRenderAction * So@Gui@RenderArea::getOverlayGLRenderAction(void) const { assert(PRIVATE(this)->overlayManager != NULL); return PRIVATE(this)->overlayManager->getGLRenderAction(); } /*! This method is called from the render() method and takes care of setting up the context for OpenGL rendering (by making the OpenGL canvas the current context and specifying either the front or back buffer for rendering, depending on whether we're in singlebuffer or doublebuffer mode). After setting up the OpenGL context, it calls actualRedraw() for the actual scenegraph rendering to take place. Finally, the OpenGL buffers are either swapped back-to-front (for doublebuffering) or flushed (for singlebuffering), and our OpenGL context is unlocked. The application programmer may override this method if extreme low-level control of the rendering process is necessary. Usually, you should be able to get away with overriding actualRedraw() for special cases, though. */ void So@Gui@RenderArea::redraw(void) { #if SO@GUI@_DEBUG && RENDERAREA_DEBUG_REDRAWS // debug SoDebugError::postInfo("So@Gui@RenderArea::redraw", "start (isVisible=%s waitForExpose=%s)", this->isVisible() ? "TRUE" : "FALSE", this->waitForExpose ? "TRUE" : "FALSE"); #endif // debug if (! this->isVisible() || !this->hasNormalGLArea() || this->waitForExpose) return; this->glLockNormal(); // this makes the GL context "current" SbBool drawfront = ! this->isDoubleBuffer() || this->isDrawToFrontBufferEnable(); glDrawBuffer(drawfront ? GL_FRONT : GL_BACK); this->actualRedraw(); if (!drawfront) { this->glSwapBuffers(); } else { this->glFlushBuffer(); } this->glUnlockNormal(); #if SO@GUI@_DEBUG && RENDERAREA_DEBUG_REDRAWS // debug SoDebugError::postInfo("So@Gui@RenderArea::render", "done"); #endif // debug } // Note: the following function documentation block will also be used // for all the miscellaneous viewer subclasses, so keep it general. /*! This method instantly redraws the normal (non-overlay) scenegraph by calling SoSceneManager::render(). Subclasses may override this method to add their own rendering before or after Coin renders it's scenegraph. The following is a complete example that demonstrates one way of adding both a background image and foreground (overlay) geometry to the "normal" rendering: \code // This example shows how to put a permanent background image on // your viewer canvas, below the 3D graphics, plus overlay // foreground geometry. Written by mortene. // Copyright Kongsberg Oil & Gas Technologies 2002. // ************************************************************************* #include #include #include #include #include #include #include #include #include #include #include #include // ************************************************************************* class MyExaminerViewer : public So@Gui@ExaminerViewer { public: MyExaminerViewer(@WIDGET@ parent, const char * filename); ~MyExaminerViewer(); protected: virtual void actualRedraw(void); private: SoSeparator * bckgroundroot; SoSeparator * foregroundroot; SoRotationXYZ * arrowrotation; }; MyExaminerViewer::MyExaminerViewer(@WIDGET@ parent, const char * filename) : So@Gui@ExaminerViewer(parent) { // Coin should not clear the pixel-buffer, so the background image // is not removed. this->setClearBeforeRender(FALSE, TRUE); // Set up background scenegraph with image in it. this->bckgroundroot = new SoSeparator; this->bckgroundroot->ref(); SoOrthographicCamera * cam = new SoOrthographicCamera; cam->position = SbVec3f(0, 0, 1); cam->height = 1; // SoImage will be at z==0.0. cam->nearDistance = 0.5; cam->farDistance = 1.5; SoImage * img = new SoImage; img->vertAlignment = SoImage::HALF; img->horAlignment = SoImage::CENTER; img->filename = filename; this->bckgroundroot->addChild(cam); this->bckgroundroot->addChild(img); // Set up foreground, overlayed scenegraph. this->foregroundroot = new SoSeparator; this->foregroundroot->ref(); SoLightModel * lm = new SoLightModel; lm->model = SoLightModel::BASE_COLOR; SoBaseColor * bc = new SoBaseColor; bc->rgb = SbColor(1, 1, 0); cam = new SoOrthographicCamera; cam->position = SbVec3f(0, 0, 5); cam->height = 10; cam->nearDistance = 0; cam->farDistance = 10; const double ARROWSIZE = 2.0; SoTranslation * posit = new SoTranslation; posit->translation = SbVec3f(-2.5 * ARROWSIZE, 1.5 * ARROWSIZE, 0); arrowrotation = new SoRotationXYZ; arrowrotation->axis = SoRotationXYZ::Z; SoTranslation * offset = new SoTranslation; offset->translation = SbVec3f(ARROWSIZE/2.0, 0, 0); SoCube * cube = new SoCube; cube->width = ARROWSIZE; cube->height = ARROWSIZE/15.0; this->foregroundroot->addChild(cam); this->foregroundroot->addChild(lm); this->foregroundroot->addChild(bc); this->foregroundroot->addChild(posit); this->foregroundroot->addChild(arrowrotation); this->foregroundroot->addChild(offset); this->foregroundroot->addChild(cube); } MyExaminerViewer::~MyExaminerViewer() { this->bckgroundroot->unref(); this->foregroundroot->unref(); } void MyExaminerViewer::actualRedraw(void) { // Must set up the OpenGL viewport manually, as upon resize // operations, Coin won't set it up until the SoGLRenderAction is // applied again. And since we need to do glClear() before applying // the action.. const SbViewportRegion vp = this->getViewportRegion(); SbVec2s origin = vp.getViewportOriginPixels(); SbVec2s size = vp.getViewportSizePixels(); glViewport(origin[0], origin[1], size[0], size[1]); const SbColor col = this->getBackgroundColor(); glClearColor(col[0], col[1], col[2], 0.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Render our scenegraph with the image. SoGLRenderAction * glra = this->getGLRenderAction(); glra->apply(this->bckgroundroot); // Render normal scenegraph. So@Gui@ExaminerViewer::actualRedraw(); // Increase arrow angle with 1/1000 ° every frame. arrowrotation->angle = arrowrotation->angle.getValue() + (0.001 / M_PI * 180); // Render overlay front scenegraph. glClear(GL_DEPTH_BUFFER_BIT); glra->apply(this->foregroundroot); } // ************************************************************************* int main(int argc, char ** argv) { if (argc != 2) { (void)fprintf(stderr, "\n\n\tUsage: %s \n\n", argv[0]); exit(1); } @WIDGET@ window = So@Gui@::init(argv[0]); MyExaminerViewer * viewer = new MyExaminerViewer(window, argv[1]); viewer->setSceneGraph(new SoCone); viewer->show(); So@Gui@::show(window); So@Gui@::mainLoop(); delete viewer; return 0; } // ************************************************************************* \endcode */ void So@Gui@RenderArea::actualRedraw(void) { assert(PRIVATE(this)->normalManager != NULL); if ( !this->isVisible() ) return; PRIVATE(this)->normalManager->render(PRIVATE(this)->clear, PRIVATE(this)->clearZBuffer); } /*! This method redraws the overlay scene. */ void So@Gui@RenderArea::redrawOverlay(void) { if (!this->isVisible() || this->waitForExpose || !this->hasOverlayGLArea()) { return; } this->glLockOverlay(); this->actualOverlayRedraw(); this->glFlushBuffer(); this->glUnlockOverlay(); } /*! This method renders the overlay scene. */ void So@Gui@RenderArea::actualOverlayRedraw(void) { assert(PRIVATE(this)->overlayManager != NULL); if (! this->isVisible()) return; PRIVATE(this)->overlayManager->render(PRIVATE(this)->clearOverlay, PRIVATE(this)->clearZBuffer); } /*! This method is invoked to initialize the normal graphics. */ void So@Gui@RenderArea::initGraphic(void) { SoSceneManager * mgr = this->getSceneManager(); if (mgr) { mgr->reinitialize(); mgr->setRGBMode(this->isRGBMode()); SoGLRenderAction * renderaction = mgr->getGLRenderAction(); renderaction->setCacheContext(SoAny::si()->getSharedCacheContextId(this)); SbBool isdirect = SoGuiGLWidgetP::isDirectRendering(this); renderaction->setRenderingIsRemote(!isdirect); } // FIXME: if not RGB mode, load colormap. pederb. inherited::initGraphic(); } /*! This method is invoked to initialize the overlay graphics. */ void So@Gui@RenderArea::initOverlayGraphic(void) { SoSceneManager * mgr = this->getOverlaySceneManager(); if (mgr) { mgr->reinitialize(); // FIXME: does overlays really _have_ to be in colorindex mode? // 20020916 mortene. mgr->setRGBMode(FALSE); SoGLRenderAction * renderaction = mgr->getGLRenderAction(); SbBool isdirect = SoGuiGLWidgetP::isDirectRendering(this); renderaction->setRenderingIsRemote(!isdirect); // FIXME: shouldn't we also setCacheContext() on the renderaction? // 20020916 mortene. } // FIXME: if not RGB mode, load colormap. pederb. // FIXME: shouldn't we do inherited::initOverlayGraphic() ? 20010831 mortene. } // doc in super void So@Gui@RenderArea::sizeChanged(const SbVec2s & size) { #if SO@GUI@_DEBUG && 0 SoDebugError::postInfo("So@Gui@RenderArea::sizeChanged", "invoked, <%d, %d>", size[0], size[1]); #endif // SO@GUI@_DEBUG SbVec2s newsize(size); if (size[0] == -1) return; assert(PRIVATE(this)->normalManager != NULL); assert(PRIVATE(this)->overlayManager != NULL); // Workaround for a bug in Qt/Mac 3.1.0 and 3.1.1 (which has been // confirmed fixed in 3.1.2): // // If the OpenGL context overlaps with the QSizeGrip widget // (generated by default), resizing does not work any more. The // workaround is to leave 15 pixels at the lower border of the // window blank... #if defined Q_OS_MAC && ((QT_VERSION == 0x030100) || (QT_VERSION == 0x030101)) // Environment variable to override Qt/Mac 3.1.x workarounds. const char * forcenoresizeworkaround = SoAny::si()->getenv("FORCE_NO_QTMAC_31_RESIZE_WORKAROUND"); if (!forcenoresizeworkaround || (atoi(forcenoresizeworkaround) == 0)) { if (this->getTypeId() == So@Gui@RenderArea::getClassTypeId()) { // SoQtRenderArea used as standalone component newsize -= SbVec2s(0, 15); // spit out a warning that this is a Qt/Mac bug, not an SoQt problem const char * env = SoAny::si()->getenv("SOQT_NO_QTMAC_BUG_WARNINGS"); if (!env || !atoi(env)) { SoDebugError::postWarning("SoQtRenderArea::sizeChanged", "\nThis version of Qt/Mac contains a bug " "that makes it necessary to leave the\n" "lowermost 15 pixels of the viewer window " "blank. Set the environment variable\n" "FORCE_NO_QTMAC_31_RESIZE_WORKAROUND=1 to " "override this workaround. \n" "You can turn off warnings about Qt/Mac " "bugs permanently by setting \n" "SOQT_NO_QTMAC_BUG_WARNINGS=1.\n"); } } } #endif this->setGLSize(newsize); const SbVec2s glsize = this->getGLSize(); #if SO@GUI@_DEBUG && 0 SoDebugError::postInfo("So@Gui@RenderArea::sizeChanged", "glsize==<%d, %d>", glsize[0], glsize[1]); #endif // SO@GUI@_DEBUG if (glsize[0] <= 0 || glsize[1] <= 0) return; this->setViewportRegion(SbViewportRegion(glsize)); PRIVATE(this)->setDevicesWindowSize(glsize); // FIXME: aren't both these calls unnecessary as we set the full // viewportregion a few lines above this one? 20020103 mortene. PRIVATE(this)->normalManager->setWindowSize(glsize); PRIVATE(this)->normalManager->setSize(glsize); // FIXME: aren't both these calls unnecessary as we set the full // viewportregion a few lines above this one? 20020103 mortene. PRIVATE(this)->overlayManager->setWindowSize(glsize); PRIVATE(this)->overlayManager->setSize(glsize); inherited::sizeChanged(glsize); // this->scheduleRedraw(); // already done through setViewportRegion() } // Documented in superclass. void So@Gui@RenderArea::widgetChanged(@WIDGET@ widget) { PRIVATE(this)->normalManager->reinitialize(); PRIVATE(this)->overlayManager->reinitialize(); // FIXME: colorindex mode not supported yet, so this has no // effect. 20001121 mortene. #if 0 PRIVATE(this)->normalManager->setRGBMode(this->isRGBMode()); PRIVATE(this)->overlayManager->setRGBMode(this->isRGBMode()); #endif // FIXME: should also walk through all registered devices and do a // disable() on the old widget and enable() on the new one. // 20001121 mortene. } // Documented in superclass. @WIDGET@ So@Gui@RenderArea::buildWidget(@WIDGET@ parent) { @WIDGET@ widget = inherited::buildWidget(parent); if (PRIVATE(this)->devicelist != NULL) { const int num = PRIVATE(this)->devicelist->getLength(); for (int i = 0; i < num; i++) { So@Gui@Device * device = (So@Gui@Device *) (*PRIVATE(this)->devicelist)[i]; #ifdef __COIN_SOXT__ device->enable(this->getGLWidget(), (SoXtEventHandler *) &So@Gui@GLWidget::eventHandler, (void *) this); #else device->enable(this->getGLWidget(), &So@Gui@GLWidgetP::eventHandler, (void *) this); #endif } } return widget; } // Documented in superclass. const char * So@Gui@RenderArea::getDefaultWidgetName(void) const { static const char defaultWidgetName[] = "So@Gui@WidgetName"; return defaultWidgetName; } // Documented in superclass. const char * So@Gui@RenderArea::getDefaultTitle(void) const { static const char defaultTitle[] = "@Gui@ RenderArea"; return defaultTitle; } // Documented in superclass. const char * So@Gui@RenderArea::getDefaultIconTitle(void) const { static const char defaultIconTitle[] = "@Gui@ RenderArea"; return defaultIconTitle; } // ************************************************************************* /*! Toolkit-native events are attempted converted to Coin-generic events in the So@Gui@RenderArea::processEvent() method. If this succeeds, they are forwarded to this method. This is a virtual method, and is overridden in it's subclasses to catch events of particular interest to the viewer classes, for instance. Return \c TRUE iff the event was processed. If not it should be passed on further up in the inheritance hierarchy by the caller. This last point is extremely important to take note of if you are expanding the toolkit with your own viewer class. This method is not part of the original SGI InventorXt API. Note that you can still override the toolkit-native processEvent() method instead of this "generic" method. */ SbBool So@Gui@RenderArea::processSoEvent(const SoEvent * const event) { if (PRIVATE(this)->overlayManager->processEvent(event)) { return TRUE; } if (PRIVATE(this)->normalManager->processEvent(event)) { return TRUE; } return FALSE; } // ************************************************************************* /*! Overrides So@Gui@GLWidget::processEvent() to attempt to convert toolkit-native events to Coin-generic events. If this succeeds, the generic SoEvent is forwarded to So@Gui@RenderArea::processSoEvent(). */ void So@Gui@RenderArea::processEvent(@EVENT@ event) { // FIXME: This method is not reentrant, but certain GUI toolkits may // run the event-loop recursively with events injected while processing // other events. We must detect when this happens, and queue up the // event for delayed processing after the primary event is done processing. // This means event instances can not be static and rewritten in the // device handlers like they are currently... 20051013 larsa if (PRIVATE(this)->invokeAppCB(event)) { return; } const SoEvent * soevent = PRIVATE(this)->getSoEvent(event); if (soevent != NULL) { #if SO@GUI@_DEBUG || defined(DEBUGGING_EGGS) // Undocumented feature: there are several "magic" sequences of // keys when tapped into the rendering canvas which'll pop up a // dialog box with information about that particular feature. // // See code comments behind "case" statements below for which // sequences are available so far. if (soevent->isOfType(SoKeyboardEvent::getClassTypeId())) { SoKeyboardEvent * ke = (SoKeyboardEvent *)soevent; if (ke->getState() == SoButtonEvent::UP) { char c = ke->getPrintableCharacter(); switch (PRIVATE(this)->checkMagicSequences(c)) { case So@Gui@RenderAreaP::NONE: break; case So@Gui@RenderAreaP::OPENGL: // "glinfo" this->glLockNormal(); PRIVATE(this)->showOpenGLDriverInformation(); this->glUnlockNormal(); break; case So@Gui@RenderAreaP::INVENTOR: // "ivinfo" PRIVATE(this)->showInventorInformation(); break; case So@Gui@RenderAreaP::TOOLKIT: // "soinfo" PRIVATE(this)->showToolkitInformation(); break; case So@Gui@RenderAreaP::DUMPSCENEGRAPH: // "dumpiv" PRIVATE(this)->dumpScenegraph(); break; case So@Gui@RenderAreaP::DUMPCAMERAS: // "cameras" PRIVATE(this)->dumpCameras(); break; case So@Gui@RenderAreaP::OFFSCREENGRAB: // "osgrab" PRIVATE(this)->offScreenGrab(); break; default: assert(FALSE && "unknown debug key sequence"); break; } } } #endif // SO@GUI@_DEBUG SbBool processed = this->processSoEvent(soevent); if (processed) return; } inherited::processEvent(event); } // ************************************************************************* // doc from parent SbBool So@Gui@RenderArea::glScheduleRedraw(void) { this->scheduleRedraw(); if (this->hasOverlayGLArea() && this->getOverlaySceneGraph()) { this->scheduleOverlayRedraw(); } return TRUE; } // ************************************************************************* /*! This method posts and processes an SoEvent object to the So@Gui@RenderArea-based component and returns the result value from the event handler. This is a synchronous operation. */ SbBool So@Gui@RenderArea::sendSoEvent(const SoEvent * event) { return this->processSoEvent(event); } // ************************************************************************* #undef PRIVATE #undef PUBLIC