From f62e5b9ce3462758b48f4c711eb7c3bf4bcc7061 Mon Sep 17 00:00:00 2001 From: Julian Smith Date: Mon, 16 Nov 2020 18:43:46 +0000 Subject: [PATCH] CompositeViewer: Support for multiple view windows using osgViewer::CompositeViewer. Overview: Previously Flightgear always used a single osgViewer::Viewer(), which inherits from both osgViewer::ViewerBase and osgViewer::View, giving a single view window. If CompositeViewer is enabled, we instead use a osgViewer::CompositeViewer which contains a list of osgViewer::View's. Each of these View's can have its own eye position, so we can have multiple different views of the same scene. Enable at runtime with: --composite-viewer=1 Changes to allow use of osgViewer::CompositeViewer: Previously FGRenderer had this method: osgViewer::Viewer* getViewer(); This has been replaced by these two new methods: osgViewer::ViewerBase* getViewerBase(); osgViewer::View* getView(); If CompositeViewer is not enabled (the default), the actual runtime state is unchanged, and getViewerBase() and getView() both return a pointer to the singleton osgViewer::Viewer() object. If CompositeViewer is enabled, getViewerBase() returns a pointer to a singleton osgViewer::CompositeViewer object, and getView() returns a pointer to the first osgViewer::View in the osgViewer::CompositeViewer's list. The other significant change to FGRenderer() is the new method: osg::FrameStamp* getFrameStamp() If CompositeViewer is not enabled, this simply returns getView()->getFrameStamp(). If CompositeViewer is enabled it returns getViewerBase()->getFrameStamp(). It is important that code that previously called getView()->getFrameStamp() is changed to use the new method, because when CompositeViewer is enabled individual osgViewer::View's appear to return an osg::FrameStamp with zero frame number). All code that uses FGRenderer has been patched up to use the new methods so that things work as before regardless of whether CompositeViewer is enabled or not. We make FGRenderer::update() call SviewUpdate() which updates any extra views. Extra view windows: If CompositeViewer is enabled, one can create top-level extra view windows by calling SviewCreate(). See src/Viewer/sview.hxx for details. Each extra view window has its own simgear::compositor::Compositor instance. Currently SviewCreate() can create extra view windows that clone the current view, or view from one point to another (e.g. from one multiplayer aircraft to the user's aircradt) or keep two aircraft in view, one at a fixed distance in the foreground. SviewCreate() can be called from nasal via new nasal commands "view-clone", "view-last-pair", "view-last-pair-double" and "view-push". Associated changes to fgdata gives access to these via the View menu. The "view-push" command tags the current view for later use by "view-last-pair" and "view-last-pair-double". Extra view windows created by SviewCreate() use a new view system called Sview, which allows views to be constructed at runtime instead of being hard-coded in *-set.xml files. This is work in progress and views aren't all fully implemented. For example Pilot view gets things slightly wrong with large roll values, Tower View AGL is not implemented, and we don't implement damping. See top of src/Viewer/sview.cxx for an overview. OpenSceneGraph-3.4 issues: OSG-3.4's event handling seems to be incorrect with CompositeViewer - events get sent for the wrong window which causes issues with resize and closing. It doesn't seem to be possible to work around this, so closing extra view windows can end up closing the main window for example. OSG-3.6 seems to fix the problems. We warn if CompositeViewer is enabled and OpenSceneGraph is 3.4. --- src/Canvas/gui_mgr.cxx | 4 +- src/GUI/CocoaFileDialog.mm | 2 +- src/GUI/FGWindowsMenuBar.cxx | 4 +- src/GUI/MessageBox.cxx | 4 +- src/GUI/MouseCursor.cxx | 4 +- src/GUI/WindowsFileDialog.cxx | 4 +- src/Main/fg_commands.cxx | 52 + src/Main/fg_init.cxx | 8 +- src/Main/globals.cxx | 17 +- src/Main/main.cxx | 4 - src/Main/options.cxx | 2 + src/Main/positioninit.cxx | 2 +- src/Network/http/ScreenshotUriHandler.cxx | 2 +- src/Scenery/terrain_stg.cxx | 2 +- src/Scenery/tilemgr.cxx | 15 +- src/Viewer/CMakeLists.txt | 2 + src/Viewer/CameraGroup.cxx | 20 +- src/Viewer/CameraGroup.hxx | 11 +- src/Viewer/FGEventHandler.cxx | 29 + src/Viewer/PUICamera.cxx | 10 +- src/Viewer/PUICamera.hxx | 3 +- src/Viewer/fg_os_osgviewer.cxx | 161 ++- src/Viewer/fgviewer.cxx | 2 +- src/Viewer/renderer.cxx | 232 +++- src/Viewer/renderer.hxx | 19 +- src/Viewer/splash.cxx | 6 + src/Viewer/sview.cxx | 1394 +++++++++++++++++++++ src/Viewer/sview.hxx | 80 ++ src/Viewer/viewmgr.cxx | 33 + src/Viewer/viewmgr.hxx | 14 + test_suite/FGTestApi/scene_graph.cxx | 2 +- 31 files changed, 1972 insertions(+), 172 deletions(-) create mode 100644 src/Viewer/sview.cxx create mode 100644 src/Viewer/sview.hxx diff --git a/src/Canvas/gui_mgr.cxx b/src/Canvas/gui_mgr.cxx index 9bcf85af7..282af1266 100644 --- a/src/Canvas/gui_mgr.cxx +++ b/src/Canvas/gui_mgr.cxx @@ -672,7 +672,7 @@ void GUIMgr::init() _event_handler = new GUIEventHandler(desktop); globals->get_renderer() - ->getViewer() + ->getView() ->getEventHandlers() // GUI is on top of everything so lets install as first event handler .push_front( _event_handler ); @@ -706,7 +706,7 @@ void GUIMgr::shutdown() if( _event_handler ) { globals->get_renderer() - ->getViewer() + ->getView() ->removeEventHandler( _event_handler ); _event_handler = 0; } diff --git a/src/GUI/CocoaFileDialog.mm b/src/GUI/CocoaFileDialog.mm index 694b28462..5c8c760e0 100644 --- a/src/GUI/CocoaFileDialog.mm +++ b/src/GUI/CocoaFileDialog.mm @@ -85,7 +85,7 @@ void CocoaFileDialog::exec() // it window-modal. NSWindow* cocoaWindow = nil; std::vector windows; - globals->get_renderer()->getViewer()->getWindows(windows); + globals->get_renderer()->getViewerBase()->getWindows(windows); for (auto gw : windows) { // OSG doesn't use RTTI, so no dynamic cast. Let's check the class type diff --git a/src/GUI/FGWindowsMenuBar.cxx b/src/GUI/FGWindowsMenuBar.cxx index b1f70bf70..eec30f64e 100644 --- a/src/GUI/FGWindowsMenuBar.cxx +++ b/src/GUI/FGWindowsMenuBar.cxx @@ -26,11 +26,11 @@ namespace { HWND getMainViewerHWND() { osgViewer::Viewer::Windows windows; - if (!globals->get_renderer() || !globals->get_renderer()->getViewer()) { + if (!globals->get_renderer() || !globals->get_renderer()->getViewerBase()) { return 0; } - globals->get_renderer()->getViewer()->getWindows(windows); + globals->get_renderer()->getViewerBase()->getWindows(windows); osgViewer::Viewer::Windows::const_iterator it = windows.begin(); for(; it != windows.end(); ++it) { if (strcmp((*it)->className(), "GraphicsWindowWin32")) { diff --git a/src/GUI/MessageBox.cxx b/src/GUI/MessageBox.cxx index 7a21bb246..3514938ab 100644 --- a/src/GUI/MessageBox.cxx +++ b/src/GUI/MessageBox.cxx @@ -61,11 +61,11 @@ bool isCanvasImplementationRegistered() HWND getMainViewerHWND() { osgViewer::Viewer::Windows windows; - if (!globals || !globals->get_renderer() || !globals->get_renderer()->getViewer()) { + if (!globals || !globals->get_renderer() || !globals->get_renderer()->getViewerBase()) { return 0; } - globals->get_renderer()->getViewer()->getWindows(windows); + globals->get_renderer()->getViewerBase()->getWindows(windows); osgViewer::Viewer::Windows::const_iterator it = windows.begin(); for(; it != windows.end(); ++it) { if (strcmp((*it)->className(), "GraphicsWindowWin32")) { diff --git a/src/GUI/MouseCursor.cxx b/src/GUI/MouseCursor.cxx index 46d6c7d11..c21542d4d 100644 --- a/src/GUI/MouseCursor.cxx +++ b/src/GUI/MouseCursor.cxx @@ -62,7 +62,7 @@ public: { mActualCursor = mCursor; - globals->get_renderer()->getViewer()->getWindows(mWindows); + globals->get_renderer()->getViewerBase()->getWindows(mWindows); } virtual void setCursor(Cursor aCursor) @@ -160,7 +160,7 @@ FGMouseCursor* FGMouseCursor::instance() #ifdef SG_WINDOWS // set osgViewer cursor inherit, otherwise it will interefere std::vector gws; - globals->get_renderer()->getViewer()->getWindows(gws); + globals->get_renderer()->getViewerBase()->getWindows(gws); for (auto gw : gws) { gw->setCursor(osgViewer::GraphicsWindow::InheritCursor); } diff --git a/src/GUI/WindowsFileDialog.cxx b/src/GUI/WindowsFileDialog.cxx index 998372eca..3b9799994 100755 --- a/src/GUI/WindowsFileDialog.cxx +++ b/src/GUI/WindowsFileDialog.cxx @@ -20,11 +20,11 @@ namespace { HWND getMainViewerHWND() { osgViewer::Viewer::Windows windows; - if (!globals->get_renderer() || !globals->get_renderer()->getViewer()) { + if (!globals->get_renderer() || !globals->get_renderer()->getViewerBase()) { return 0; } - globals->get_renderer()->getViewer()->getWindows(windows); + globals->get_renderer()->getViewerBase()->getWindows(windows); osgViewer::Viewer::Windows::const_iterator it = windows.begin(); for(; it != windows.end(); ++it) { if (strcmp((*it)->className(), "GraphicsWindowWin32")) { diff --git a/src/Main/fg_commands.cxx b/src/Main/fg_commands.cxx index b00f0b2d4..f83489af7 100644 --- a/src/Main/fg_commands.cxx +++ b/src/Main/fg_commands.cxx @@ -361,6 +361,54 @@ do_view_cycle (const SGPropertyNode * arg, SGPropertyNode * root) return true; } + +/** + * Built-in command: view-push. + */ +static bool +do_view_push (const SGPropertyNode * arg, SGPropertyNode * root) +{ + SG_LOG(SG_GENERAL, SG_ALERT, "do_view_push() called"); + globals->get_viewmgr()->view_push(); + return true; +} + + +/** + * Built-in command: clone view. + */ +static bool +do_view_clone (const SGPropertyNode * arg, SGPropertyNode * root) +{ + SG_LOG(SG_GENERAL, SG_ALERT, "do_view_clone() called"); + globals->get_viewmgr()->clone_current_view(); + return true; +} + + +/** + * Built-in command: view last pair. + */ +static bool +do_view_last_pair (const SGPropertyNode * arg, SGPropertyNode * root) +{ + SG_LOG(SG_GENERAL, SG_ALERT, "do_view_last_pair() called"); + globals->get_viewmgr()->clone_last_pair(); + return true; +} + + +/** + * Built-in command: double view last pair. + */ +static bool +do_view_last_pair_double (const SGPropertyNode * arg, SGPropertyNode * root) +{ + SG_LOG(SG_GENERAL, SG_ALERT, "do_view_last_pair_double() called"); + globals->get_viewmgr()->clone_last_pair_double(); + return true; +} + /** * Built-in command: toggle a bool property value. * @@ -948,6 +996,10 @@ static struct { { "save-tape", do_save_tape }, { "load-tape", do_load_tape }, { "view-cycle", do_view_cycle }, + { "view-push", do_view_push }, + { "view-clone", do_view_clone }, + { "view-last-pair", do_view_last_pair }, + { "view-last-pair-double", do_view_last_pair_double }, /* { "set-sea-level-air-temp-degc", do_set_sea_level_degc }, { "set-outside-air-temp-degc", do_set_oat_degc }, diff --git a/src/Main/fg_init.cxx b/src/Main/fg_init.cxx index 2178a5ed7..58fd61142 100644 --- a/src/Main/fg_init.cxx +++ b/src/Main/fg_init.cxx @@ -1283,7 +1283,7 @@ void fgStartNewReset() FGRenderer* render = globals->get_renderer(); // needed or we crash in multi-threaded OSG mode - render->getViewer()->stopThreading(); + render->getViewerBase()->stopThreading(); // order is important here since tile-manager shutdown needs to // access the scenery object @@ -1294,8 +1294,8 @@ void fgStartNewReset() // don't cancel the pager until after shutdown, since AIModels (and // potentially others) can queue delete requests on the pager. - render->getViewer()->getDatabasePager()->cancel(); - render->getViewer()->getDatabasePager()->clear(); + render->getView()->getDatabasePager()->cancel(); + render->getView()->getDatabasePager()->clear(); osgDB::Registry::instance()->clearObjectCache(); // Pager requests depend on this, so don't clear it until now @@ -1376,7 +1376,7 @@ void fgStartNewReset() eventHandler->reset(); globals->set_renderer(render); render->init(); - render->setViewer(viewer.get()); + render->setView(viewer.get()); sgUserDataInit( globals->get_props() ); diff --git a/src/Main/globals.cxx b/src/Main/globals.cxx index ecd2a1a95..e2ba0e8fa 100644 --- a/src/Main/globals.cxx +++ b/src/Main/globals.cxx @@ -195,15 +195,15 @@ FGGlobals::~FGGlobals() // stop OSG threading first, to avoid thread races while we tear down // scene-graph pieces // there are some scenarios where renderer is already gone. - osg::ref_ptr vw; + osg::ref_ptr vb; if (renderer) { - vw = renderer->getViewer(); - if (vw) { + vb = renderer->getViewerBase(); + if (vb) { // https://code.google.com/p/flightgear-bugs/issues/detail?id=1291 // explicitly stop trheading before we delete the renderer or // viewMgr (which ultimately holds refs to the CameraGroup, and // GraphicsContext) - vw->stopThreading(); + vb->stopThreading(); } } @@ -212,9 +212,10 @@ FGGlobals::~FGGlobals() // don't cancel the pager until after shutdown, since AIModels (and // potentially others) can queue delete requests on the pager. - if (vw && vw->getDatabasePager()) { - vw->getDatabasePager()->cancel(); - vw->getDatabasePager()->clear(); + osgViewer::View* v = renderer->getView(); + if (v && v->getDatabasePager()) { + v->getDatabasePager()->cancel(); + v->getDatabasePager()->clear(); } osgDB::Registry::instance()->clearObjectCache(); @@ -230,7 +231,7 @@ FGGlobals::~FGGlobals() delete subsystem_mgr; subsystem_mgr = nullptr; // important so ::get_subsystem returns NULL - vw = nullptr; + vb = nullptr; set_matlib(NULL); delete time_params; diff --git a/src/Main/main.cxx b/src/Main/main.cxx index 5bf2df63d..f1e85ba64 100755 --- a/src/Main/main.cxx +++ b/src/Main/main.cxx @@ -743,10 +743,6 @@ int fgMainInit( int argc, char **argv ) fntInit(); globals->get_renderer()->preinit(); -#if defined(ENABLE_COMPOSITOR) - flightgear::addSentryTag("compositor", "yes"); -#endif - if (fgGetBool("/sim/ati-viewport-hack", true)) { SG_LOG(SG_GENERAL, SG_WARN, "Enabling ATI/AMD viewport hack"); flightgear::addSentryTag("ati-viewport-hack", "enabled"); diff --git a/src/Main/options.cxx b/src/Main/options.cxx index 4d5d5a7ca..bff9bc434 100644 --- a/src/Main/options.cxx +++ b/src/Main/options.cxx @@ -1617,6 +1617,7 @@ where: OPTION_INT - property is an integer OPTION_CHANNEL - name of option is the name of a channel OPTION_FUNC - the option trigger a function + property : b_param : if type==OPTION_BOOL, value set to the property (has_param is false for boolean) s_param : if type==OPTION_STRING, @@ -1870,6 +1871,7 @@ struct OptionDesc { {"developer", true, OPTION_IGNORE | OPTION_BOOL, "", false, "", nullptr }, {"jsbsim-output-directive-file", true, OPTION_STRING, "/sim/jsbsim/output-directive-file", false, "", nullptr }, {"disable-gui", false, OPTION_FUNC, "", false, "", fgOptDisableGUI }, + {"composite-viewer", true, OPTION_INT, "/sim/rendering/composite-viewer-enabled", "", "", nullptr}, {nullptr, false, 0, nullptr, false, nullptr, nullptr} }; diff --git a/src/Main/positioninit.cxx b/src/Main/positioninit.cxx index 1894e3cb9..9e75f4728 100644 --- a/src/Main/positioninit.cxx +++ b/src/Main/positioninit.cxx @@ -436,7 +436,7 @@ static InitPosResult setInitialPosFromCarrier( const string& carrier ) static InitPosResult checkCarrierSceneryLoaded(const SGSharedPtr carrierRef) { SGVec3d cartPos = carrierRef->getCartPos(); - auto framestamp = globals->get_renderer()->getViewer()->getFrameStamp(); + auto framestamp = globals->get_renderer()->getFrameStamp(); simgear::CheckSceneryVisitor csnv(globals->get_scenery()->getPager(), toOsg(cartPos), 100.0 /* range in metres */, diff --git a/src/Network/http/ScreenshotUriHandler.cxx b/src/Network/http/ScreenshotUriHandler.cxx index 3fcb40aaf..4f2a1731c 100644 --- a/src/Network/http/ScreenshotUriHandler.cxx +++ b/src/Network/http/ScreenshotUriHandler.cxx @@ -213,7 +213,7 @@ public: if ( NULL == osgDB::Registry::instance()->getReaderWriterForExtension(_type)) throw sg_format_exception("Unsupported image type: " + type, type); - osg::Camera * camera = findLastCamera(globals->get_renderer()->getViewer(), window); + osg::Camera * camera = findLastCamera(globals->get_renderer()->getViewerBase(), window); if ( NULL == camera) throw sg_error("Can't find a camera for window '" + window + "'"); diff --git a/src/Scenery/terrain_stg.cxx b/src/Scenery/terrain_stg.cxx index 7eb8eea1a..b89640be6 100644 --- a/src/Scenery/terrain_stg.cxx +++ b/src/Scenery/terrain_stg.cxx @@ -371,7 +371,7 @@ bool FGStgTerrain::scenery_available(const SGGeod& position, double range_m) SGVec3f p = SGVec3f::fromGeod(SGGeod::fromGeodM(position, elev)); osg::FrameStamp* framestamp - = globals->get_renderer()->getViewer()->getFrameStamp(); + = globals->get_renderer()->getFrameStamp(); FGScenery* pSceneryManager = globals->get_scenery(); simgear::CheckSceneryVisitor csnv(pSceneryManager->getPager(), toOsg(p), range_m, framestamp); diff --git a/src/Scenery/tilemgr.cxx b/src/Scenery/tilemgr.cxx index bcb606ebd..d973a5da7 100644 --- a/src/Scenery/tilemgr.cxx +++ b/src/Scenery/tilemgr.cxx @@ -76,8 +76,8 @@ public: if (_pagedLODMaximumProp->getType() == simgear::props::NONE) { // not set, use OSG default / environment value variable - osg::ref_ptr viewer(globals->get_renderer()->getViewer()); - int current = viewer->getDatabasePager()->getTargetMaximumNumberOfPageLOD(); + osg::ref_ptr view(globals->get_renderer()->getView()); + int current = view->getDatabasePager()->getTargetMaximumNumberOfPageLOD(); _pagedLODMaximumProp->setIntValue(current); } _pagedLODMaximumProp->addChangeListener(this, true); @@ -106,9 +106,9 @@ public: _manager->_enableCache = prop->getBoolValue(); } else if (prop == _pagedLODMaximumProp) { int v = prop->getIntValue(); - osg::ref_ptr viewer(globals->get_renderer()->getViewer()); - if (viewer) { - osgDB::DatabasePager* pager = viewer->getDatabasePager(); + osg::ref_ptr view(globals->get_renderer()->getView()); + if (view) { + osgDB::DatabasePager* pager = view->getDatabasePager(); if (pager) pager->setTargetMaximumNumberOfPageLOD(v); } } else if (prop == _lodDetailed || prop == _lodBareDelta || prop == _lodRoughDelta) { @@ -344,7 +344,7 @@ void FGTileMgr::schedule_needed(const SGBucket& curr_bucket, double vis) // update timestamps, so all tiles scheduled now are *newer* than any tile previously loaded osg::FrameStamp* framestamp - = globals->get_renderer()->getViewer()->getFrameStamp(); + = globals->get_renderer()->getFrameStamp(); tile_cache.set_current_time(framestamp->getReferenceTime()); SGBucket b; @@ -379,8 +379,7 @@ void FGTileMgr::schedule_needed(const SGBucket& curr_bucket, double vis) */ void FGTileMgr::update_queues(bool& isDownloadingScenery) { - osg::FrameStamp* framestamp - = globals->get_renderer()->getViewer()->getFrameStamp(); + osg::FrameStamp* framestamp = globals->get_renderer()->getFrameStamp(); double current_time = framestamp->getReferenceTime(); double vis = _visibilityMeters->getDoubleValue(); TileEntry *e; diff --git a/src/Viewer/CMakeLists.txt b/src/Viewer/CMakeLists.txt index 5e8330d67..eca4f7ef6 100644 --- a/src/Viewer/CMakeLists.txt +++ b/src/Viewer/CMakeLists.txt @@ -12,6 +12,7 @@ set(SOURCES splash.cxx view.cxx viewmgr.cxx + sview.cxx ) set(HEADERS @@ -24,6 +25,7 @@ set(HEADERS splash.hxx view.hxx viewmgr.hxx + sview.hxx ) if (Qt5Core_FOUND) diff --git a/src/Viewer/CameraGroup.cxx b/src/Viewer/CameraGroup.cxx index d05f19037..2d88a3616 100644 --- a/src/Viewer/CameraGroup.cxx +++ b/src/Viewer/CameraGroup.cxx @@ -23,6 +23,7 @@ #include "FGEventHandler.hxx" #include "WindowBuilder.hxx" #include "WindowSystemAdapter.hxx" +#include "sview.hxx" #include #include @@ -192,8 +193,8 @@ typedef std::vector SGPropertyNodeVec; osg::ref_ptr CameraGroup::_defaultGroup; -CameraGroup::CameraGroup(osgViewer::Viewer* viewer) : - _viewer(viewer) +CameraGroup::CameraGroup(osgViewer::View* view) : + _viewer(view) { } @@ -688,6 +689,9 @@ void CameraGroup::buildCamera(SGPropertyNode* cameraNode) osg::ref_ptr options = SGReaderWriterOptions::fromPath(globals->get_fg_root()); options->setPropertyNode(globals->get_props()); + + SViewSetCompositorParams(options, compositor_path); + Compositor *compositor = Compositor::create(_viewer, window->gc, viewport, @@ -783,10 +787,10 @@ void CameraGroup::buildGUICamera(SGPropertyNode* cameraNode, camera->setStats(0); } -CameraGroup* CameraGroup::buildCameraGroup(osgViewer::Viewer* viewer, +CameraGroup* CameraGroup::buildCameraGroup(osgViewer::View* view, SGPropertyNode* gnode) { - CameraGroup* cgroup = new CameraGroup(viewer); + CameraGroup* cgroup = new CameraGroup(view); cgroup->_listener.reset(new CameraGroupListener(cgroup, gnode)); for (int i = 0; i < gnode->nChildren(); ++i) { @@ -862,7 +866,7 @@ computeCameraIntersection(const CameraGroup *cgroup, osgUtil::IntersectionVisitor iv(picker); iv.setTraversalMask(simgear::PICK_BIT); - const_cast(cgroup)->getViewer()->getCamera()->accept(iv); + const_cast(cgroup)->getView()->getCamera()->accept(iv); if (picker->containsIntersections()) { intersections = picker->getIntersections(); return true; @@ -921,7 +925,7 @@ void warpGUIPointer(CameraGroup* cgroup, int x, int y) gw->getEventQueue()->mouseWarped(wx, wy); gw->requestWarpPointer(wx, wy); osgGA::GUIEventAdapter* eventState - = cgroup->getViewer()->getEventQueue()->getCurrentEventState(); + = cgroup->getView()->getEventQueue()->getCurrentEventState(); double viewerX = (eventState->getXmin() + ((wx / double(traits->width)) @@ -930,10 +934,10 @@ void warpGUIPointer(CameraGroup* cgroup, int x, int y) = (eventState->getYmin() + ((wyUp / double(traits->height)) * (eventState->getYmax() - eventState->getYmin()))); - cgroup->getViewer()->getEventQueue()->mouseWarped(viewerX, viewerY); + cgroup->getView()->getEventQueue()->mouseWarped(viewerX, viewerY); } -void CameraGroup::buildDefaultGroup(osgViewer::Viewer* viewer) +void CameraGroup::buildDefaultGroup(osgViewer::View* viewer) { // Look for windows, camera groups, and the old syntax of // top-level cameras diff --git a/src/Viewer/CameraGroup.hxx b/src/Viewer/CameraGroup.hxx index cd1111ef4..781b446f5 100644 --- a/src/Viewer/CameraGroup.hxx +++ b/src/Viewer/CameraGroup.hxx @@ -30,6 +30,7 @@ #include #include #include +#include #include @@ -122,14 +123,14 @@ public: /** Create a camera group associated with an osgViewer::Viewer. * @param viewer the viewer */ - CameraGroup(osgViewer::Viewer* viewer); + CameraGroup(osgViewer::View* viewer); virtual ~CameraGroup(); /** Set the default CameraGroup, which is the only one that * matters at this time. * @param group the group to set. */ - static void buildDefaultGroup(osgViewer::Viewer* viewer); + static void buildDefaultGroup(osgViewer::View* view); static void setDefault(CameraGroup* group) { _defaultGroup = group; } /** Get the default CameraGroup. * @return the default camera group. @@ -138,7 +139,7 @@ public: /** Get the camera group's Viewer. * @return the viewer */ - osgViewer::Viewer* getViewer() { return _viewer.get(); } + osgViewer::View* getView() { return _viewer.get(); } /** Create an osg::Camera from a property node and add it to the * camera group. * @param cameraNode the property node. @@ -192,7 +193,7 @@ protected: typedef std::vector> CameraList; CameraList _cameras; - osg::ref_ptr _viewer; + osg::ref_ptr _viewer; static osg::ref_ptr _defaultGroup; std::unique_ptr _listener; @@ -206,7 +207,7 @@ protected: * @param wbuilder the window builder to be used for this camera group. * @param the camera group property node. */ - static CameraGroup* buildCameraGroup(osgViewer::Viewer* viewer, + static CameraGroup* buildCameraGroup(osgViewer::View* viewer, SGPropertyNode* node); }; diff --git a/src/Viewer/FGEventHandler.cxx b/src/Viewer/FGEventHandler.cxx index 26fd2eb6c..54f03f986 100644 --- a/src/Viewer/FGEventHandler.cxx +++ b/src/Viewer/FGEventHandler.cxx @@ -12,7 +12,9 @@ #include "CameraGroup.hxx" #include "FGEventHandler.hxx" #include "WindowSystemAdapter.hxx" +#include "WindowBuilder.hxx" #include "renderer.hxx" +#include "sview.hxx" #ifdef SG_MAC // hack - during interactive resize on Mac, OSG queues and then flushes @@ -85,12 +87,18 @@ bool eventToViewport(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& us, int& x, int& y) { + flightgear::WindowBuilder* window_builder = flightgear::WindowBuilder::getWindowBuilder(); + flightgear::GraphicsWindow* main_window = window_builder->getDefaultWindow(); + x = -1; y = -1; const osg::GraphicsContext* eventGC = ea.getGraphicsContext(); if( !eventGC ) return false; // TODO how can this happen? + if (eventGC != main_window->gc.get()) { + return false; + } const osg::GraphicsContext::Traits* traits = eventGC->getTraits(); osg::Camera* guiCamera = getGUICamera(CameraGroup::getDefault()); if (!guiCamera) @@ -115,6 +123,20 @@ eventToViewport(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& us, return false; } } + +/* A hack for when we are linked with OSG-3.4 and CompositeViewer is +enabled. It seems that OSG-3.4 incorrectly calls our event handler for +extra view windows (e.g. resize/close events), so we try to detect +this. Unfortunately OSG also messes up 's graphics context pointer so this +does't alwys work. */ +bool isMainWindow(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& us) +{ + int x; + int y; + bool ret = eventToViewport(ea, us, x, y); + return ret; +} + } bool FGEventHandler::handle(const osgGA::GUIEventAdapter& ea, @@ -224,6 +246,9 @@ bool FGEventHandler::handle(const osgGA::GUIEventAdapter& ea, return true; case osgGA::GUIEventAdapter::RESIZE: SG_LOG(SG_VIEW, SG_DEBUG, "FGEventHandler::handle: RESIZE event " << ea.getWindowHeight() << " x " << ea.getWindowWidth() << ", resizable: " << resizable); + if (!isMainWindow(ea, us)) { + return true; + } CameraGroup::getDefault()->resized(); if (resizable) globals->get_renderer()->resize(ea.getWindowWidth(), ea.getWindowHeight()); @@ -236,6 +261,10 @@ bool FGEventHandler::handle(const osgGA::GUIEventAdapter& ea, #endif return true; case osgGA::GUIEventAdapter::CLOSE_WINDOW: + if (!isMainWindow(ea, us)) { + return true; + } + // Fall through. case osgGA::GUIEventAdapter::QUIT_APPLICATION: fgOSExit(0); return true; diff --git a/src/Viewer/PUICamera.cxx b/src/Viewer/PUICamera.cxx index 3bdf23387..9dfacaea0 100644 --- a/src/Viewer/PUICamera.cxx +++ b/src/Viewer/PUICamera.cxx @@ -275,13 +275,13 @@ PUICamera::~PUICamera() // depending on if we're doing shutdown or reset, various things can be // null here. auto renderer = globals->get_renderer(); - auto viewer = renderer ? renderer->getViewer() : nullptr; - if (viewer) { - viewer->removeEventHandler(_eventHandler); + auto view = renderer ? renderer->getView() : nullptr; + if (view) { + view->removeEventHandler(_eventHandler); } } -void PUICamera::init(osg::Group* parent, osgViewer::Viewer* viewer) +void PUICamera::init(osg::Group* parent, osgViewer::View* view) { setName("PUI FBO camera"); @@ -351,7 +351,7 @@ void PUICamera::init(osg::Group* parent, osgViewer::Viewer* viewer) // the rendering order (i.e top-most UI layer has the front-most event // handler) _eventHandler = new PUIEventHandler(this); - viewer->getEventHandlers().push_front(_eventHandler); + view->getEventHandlers().push_front(_eventHandler); } // remove once we require OSG 3.4 diff --git a/src/Viewer/PUICamera.hxx b/src/Viewer/PUICamera.hxx index 6196ef82b..b9aac7705 100644 --- a/src/Viewer/PUICamera.hxx +++ b/src/Viewer/PUICamera.hxx @@ -19,6 +19,7 @@ #include #include +#include namespace osg { @@ -51,7 +52,7 @@ public: // osg::Camera already defines a resize() so use this name void resizeUi(int width, int height); - void init(osg::Group* parent, osgViewer::Viewer* viewer); + void init(osg::Group* parent, osgViewer::View* view); private: void manuallyResizeFBO(int width, int height); diff --git a/src/Viewer/fg_os_osgviewer.cxx b/src/Viewer/fg_os_osgviewer.cxx index 5df7302dc..8b83facf5 100644 --- a/src/Viewer/fg_os_osgviewer.cxx +++ b/src/Viewer/fg_os_osgviewer.cxx @@ -189,7 +189,7 @@ class NotifyLevelListener : public SGPropertyChangeListener public: void valueChanged(SGPropertyNode* node) { - osg::NotifySeverity severity = osg::WARN; + osg::NotifySeverity severity = osg::getNotifyLevel(); string val = simgear::strutils::lowercase(node->getStringValue()); if (val == "fatal") { @@ -216,38 +216,94 @@ void fgOSOpenWindow(bool stencil) { osg::setNotifyHandler(new NotifyLogger); - viewer = new osgViewer::Viewer; - viewer->setDatabasePager(FGScenery::getPagerSingleton()); + auto composite_viewer = dynamic_cast( + globals->get_renderer()->getViewerBase() + ); + if (0) {} + else if (composite_viewer) { + /* We are using CompositeViewer. */ + SG_LOG(SG_VIEW, SG_ALERT, "Using CompositeViewer"); + osgViewer::ViewerBase* viewer = globals->get_renderer()->getViewerBase(); + SG_LOG(SG_VIEW, SG_ALERT, "Creating osgViewer::View"); + osgViewer::View* view = new osgViewer::View; + view->setFrameStamp(composite_viewer->getFrameStamp()); + globals->get_renderer()->setView(view); + assert(globals->get_renderer()->getView() == view); + view->setDatabasePager(FGScenery::getPagerSingleton()); + + // https://www.mail-archive.com/osg-users@lists.openscenegraph.org/msg29820.html + view->getDatabasePager()->setUnrefImageDataAfterApplyPolicy(true, false); + osg::GraphicsContext::createNewContextID(); + + //viewer->setThreadingModel(osgViewer::Viewer::SingleThreaded); - std::string mode; - mode = fgGetString("/sim/rendering/multithreading-mode", "SingleThreaded"); - flightgear::addSentryTag("osg-thread-mode", mode); - - if (mode == "AutomaticSelection") - viewer->setThreadingModel(osgViewer::Viewer::AutomaticSelection); - else if (mode == "CullDrawThreadPerContext") - viewer->setThreadingModel(osgViewer::Viewer::CullDrawThreadPerContext); - else if (mode == "DrawThreadPerContext") - viewer->setThreadingModel(osgViewer::Viewer::DrawThreadPerContext); - else if (mode == "CullThreadPerCameraDrawThreadPerContext") - viewer->setThreadingModel(osgViewer::Viewer::CullThreadPerCameraDrawThreadPerContext); - else - viewer->setThreadingModel(osgViewer::Viewer::SingleThreaded); - WindowBuilder::initWindowBuilder(stencil); - CameraGroup::buildDefaultGroup(viewer.get()); - - FGEventHandler* manipulator = globals->get_renderer()->getEventHandler(); - WindowSystemAdapter* wsa = WindowSystemAdapter::getWSA(); - if (wsa->windows.size() != 1) { - manipulator->setResizable(false); + std::string mode; + mode = fgGetString("/sim/rendering/multithreading-mode", "SingleThreaded"); + SG_LOG( SG_VIEW, SG_INFO, "mode=" << mode); + if (mode == "AutomaticSelection") + viewer->setThreadingModel(osgViewer::Viewer::AutomaticSelection); + else if (mode == "CullDrawThreadPerContext") + viewer->setThreadingModel(osgViewer::Viewer::CullDrawThreadPerContext); + else if (mode == "DrawThreadPerContext") + viewer->setThreadingModel(osgViewer::Viewer::DrawThreadPerContext); + else if (mode == "CullThreadPerCameraDrawThreadPerContext") + viewer->setThreadingModel(osgViewer::Viewer::CullThreadPerCameraDrawThreadPerContext); + else + viewer->setThreadingModel(osgViewer::Viewer::SingleThreaded); + + WindowBuilder::initWindowBuilder(stencil); + CameraGroup::buildDefaultGroup(view); + + FGEventHandler* manipulator = globals->get_renderer()->getEventHandler(); + WindowSystemAdapter* wsa = WindowSystemAdapter::getWSA(); + if (wsa->windows.size() != 1) { + manipulator->setResizable(false); + } + view->getCamera()->setProjectionResizePolicy(osg::Camera::FIXED); + view->addEventHandler(manipulator); + // Let FG handle the escape key with a confirmation + viewer->setKeyEventSetsDone(0); + // The viewer won't start without some root. + view->setSceneData(new osg::Group); + globals->get_renderer()->setView(view); + } + else { + /* Not using CompositeViewer. */ + SG_LOG(SG_VIEW, SG_DEBUG, "Not CompositeViewer."); + SG_LOG(SG_VIEW, SG_DEBUG, "Creating osgViewer::Viewer"); + viewer = new osgViewer::Viewer; + viewer->setDatabasePager(FGScenery::getPagerSingleton()); + + std::string mode; + mode = fgGetString("/sim/rendering/multithreading-mode", "SingleThreaded"); + flightgear::addSentryTag("osg-thread-mode", mode); + + if (mode == "AutomaticSelection") + viewer->setThreadingModel(osgViewer::Viewer::AutomaticSelection); + else if (mode == "CullDrawThreadPerContext") + viewer->setThreadingModel(osgViewer::Viewer::CullDrawThreadPerContext); + else if (mode == "DrawThreadPerContext") + viewer->setThreadingModel(osgViewer::Viewer::DrawThreadPerContext); + else if (mode == "CullThreadPerCameraDrawThreadPerContext") + viewer->setThreadingModel(osgViewer::Viewer::CullThreadPerCameraDrawThreadPerContext); + else + viewer->setThreadingModel(osgViewer::Viewer::SingleThreaded); + WindowBuilder::initWindowBuilder(stencil); + CameraGroup::buildDefaultGroup(viewer.get()); + + FGEventHandler* manipulator = globals->get_renderer()->getEventHandler(); + WindowSystemAdapter* wsa = WindowSystemAdapter::getWSA(); + if (wsa->windows.size() != 1) { + manipulator->setResizable(false); + } + viewer->getCamera()->setProjectionResizePolicy(osg::Camera::FIXED); + viewer->addEventHandler(manipulator); + // Let FG handle the escape key with a confirmation + viewer->setKeyEventSetsDone(0); + // The viewer won't start without some root. + viewer->setSceneData(new osg::Group); + globals->get_renderer()->setView(viewer.get()); } - viewer->getCamera()->setProjectionResizePolicy(osg::Camera::FIXED); - viewer->addEventHandler(manipulator); - // Let FG handle the escape key with a confirmation - viewer->setKeyEventSetsDone(0); - // The viewer won't start without some root. - viewer->setSceneData(new osg::Group); - globals->get_renderer()->setViewer(viewer.get()); } SGPropertyNode* simHost = 0, *simFrameCount, *simTotalHostTime, *simFrameResetCount, *frameWait; void fgOSResetProperties() @@ -286,8 +342,9 @@ static int status = 0; void fgOSExit(int code) { - viewer->setDone(true); - viewer->getDatabasePager()->cancel(); + FGRenderer* renderer = globals->get_renderer(); + renderer->getViewerBase()->setDone(true); + renderer->getView()->getDatabasePager()->cancel(); status = code; // otherwise we crash if OSG does logging during static destruction, eg @@ -299,12 +356,13 @@ SGTimeStamp _lastUpdate; int fgOSMainLoop() { - viewer->setReleaseContextAtEndOfFrameHint(false); - if (!viewer->isRealized()) { - viewer->realize(); + osgViewer::ViewerBase* viewer_base = globals->get_renderer()->getViewerBase(); + viewer_base->setReleaseContextAtEndOfFrameHint(false); + if (!viewer_base->isRealized()) { + viewer_base->realize(); } - while (!viewer->done()) { + while (!viewer_base->done()) { fgIdleHandler idleFunc = globals->get_renderer()->getEventHandler()->getIdleHandler(); if (idleFunc) { @@ -338,7 +396,7 @@ int fgOSMainLoop() } } globals->get_renderer()->update(); - viewer->frame( globals->get_sim_time_sec() ); + viewer_base->frame( globals->get_sim_time_sec() ); } flightgear::addSentryBreadcrumb("main loop exited", "info"); @@ -383,13 +441,16 @@ void fgOSInit(int* argc, char** argv) void fgOSCloseWindow() { - if (viewer) { - // https://code.google.com/p/flightgear-bugs/issues/detail?id=1291 - // https://sourceforge.net/p/flightgear/codetickets/1830/ - // explicitly stop threading before we delete the renderer or - // viewMgr (which ultimately holds refs to the CameraGroup, and - // GraphicsContext) - viewer->stopThreading(); + if (globals && globals->get_renderer()) { + osgViewer::ViewerBase* viewer_base = globals->get_renderer()->getViewerBase(); + if (viewer_base) { + // https://code.google.com/p/flightgear-bugs/issues/detail?id=1291 + // https://sourceforge.net/p/flightgear/codetickets/1830/ + // explicitly stop threading before we delete the renderer or + // viewMgr (which ultimately holds refs to the CameraGroup, and + // GraphicsContext) + viewer_base->stopThreading(); + } } FGScenery::resetPagerSingleton(); flightgear::CameraGroup::setDefault(NULL); @@ -399,8 +460,9 @@ void fgOSCloseWindow() void fgOSFullScreen() { + osgViewer::ViewerBase* viewer_base = globals->get_renderer()->getViewerBase(); std::vector windows; - viewer->getWindows(windows); + viewer_base->getWindows(windows); if (windows.empty()) return; // Huh?!? @@ -544,11 +606,14 @@ static int _cursor = -1; void fgSetMouseCursor(int cursor) { _cursor = cursor; - if (!viewer) + if (!globals || !globals->get_renderer()) + return; + osgViewer::ViewerBase* viewer_base = globals->get_renderer()->getViewerBase(); + if (!viewer_base) return; std::vector windows; - viewer->getWindows(windows); + viewer_base->getWindows(windows); for (osgViewer::GraphicsWindow* gw : windows) { setMouseCursor(gw, cursor); } diff --git a/src/Viewer/fgviewer.cxx b/src/Viewer/fgviewer.cxx index fcd7bcaca..7027f61b2 100644 --- a/src/Viewer/fgviewer.cxx +++ b/src/Viewer/fgviewer.cxx @@ -115,7 +115,7 @@ fgviewerMain(int argc, char** argv) // construct the viewer. FGRenderer* fgrenderer = new FGRenderer(); osgViewer::Viewer* viewer = new osgViewer::Viewer(arguments); - fgrenderer->setViewer(viewer); + fgrenderer->setView(viewer); osg::Camera* camera = viewer->getCamera(); osgViewer::Renderer* renderer = static_cast(camera->getRenderer()); diff --git a/src/Viewer/renderer.cxx b/src/Viewer/renderer.cxx index 3f2f9e9ef..a73cc2748 100644 --- a/src/Viewer/renderer.cxx +++ b/src/Viewer/renderer.cxx @@ -359,8 +359,8 @@ FGRenderer::~FGRenderer() } // replace the viewer's scene completely - if (getViewer()) { - getViewer()->setSceneData(new osg::Group); + if (getView()) { + getView()->setSceneData(new osg::Group); } delete _sky; @@ -398,28 +398,39 @@ FGRenderer::addChangeListener(SGPropertyChangeListener* l, const char* path) } // Initialize various GL/view parameters +// +// Note that this appears to be called *after* FGRenderer::init(). +// void FGRenderer::preinit( void ) { // important that we reset the viewer sceneData here, to ensure the reference // time for everything is in sync; otherwise on reset the Viewer and // GraphicsWindow clocks are out of sync. - osgViewer::Viewer* viewer = getViewer(); - viewer->setName("osgViewer"); + osgViewer::View* view = getView(); + view->setName("osgViewer"); _viewerSceneRoot = new osg::Group; _viewerSceneRoot->setName("viewerSceneRoot"); - viewer->setSceneData(_viewerSceneRoot); + view->setSceneData(_viewerSceneRoot); + view->setDatabasePager(FGScenery::getPagerSingleton()); _quickDrawable = nullptr; _splash = new SplashScreen; _viewerSceneRoot->addChild(_splash); - _frameStamp = new osg::FrameStamp; - viewer->setFrameStamp(_frameStamp.get()); + if (composite_viewer) { + // Nothing to do - composite_viewer->addView() will tell view to use + // composite_viewer's FrameStamp. + } + else { + _frameStamp = new osg::FrameStamp; + view->setFrameStamp(_frameStamp.get()); + } + // Scene doesn't seem to pass the frame stamp to the update // visitor automatically. - _updateVisitor->setFrameStamp(_frameStamp.get()); - viewer->setUpdateVisitor(_updateVisitor.get()); + _updateVisitor->setFrameStamp(getFrameStamp()); + getViewerBase()->setUpdateVisitor(_updateVisitor.get()); fgSetDouble("/sim/startup/splash-alpha", 1.0); // hide the menubar if it overlaps the window, so the splash screen @@ -436,6 +447,31 @@ FGRenderer::init( void ) sgUserDataInit( globals->get_props() ); + SGPropertyNode* composite_viewer_enabled_prop = fgGetNode("/sim/rendering/composite-viewer-enabled", true); + // After we've read composite_viewer_enabled_prop here, changing its value + // will have no affect, so mark it as read-only for clarity. + composite_viewer_enabled_prop->setAttributes(SGPropertyNode::READ); + if (composite_viewer_enabled_prop->getBoolValue()) { + const char* osg_version = osgGetVersion(); + if (simgear::strutils::starts_with(osg_version, "3.4")) { + SG_LOG( SG_GENERAL, SG_POPUP, + "CompositeViewer is enabled and requires OpenSceneGraph-3.6, but\n" + " Flightgear has been built with OpenSceneGraph-" << osg_version << ".\n" + " There may be problems when opening/closing extra view windows.\n" + ); + } + composite_viewer_enabled = 1; + SG_LOG(SG_VIEW, SG_ALERT, "Creating osgViewer::CompositeViewer"); + composite_viewer = new osgViewer::CompositeViewer; + + // https://stackoverflow.com/questions/15207076/openscenegraph-and-multiple-viewers + composite_viewer->setReleaseContextAtEndOfFrameHint(false); + composite_viewer->setThreadingModel(osgViewer::Viewer::SingleThreaded); + } + else { + composite_viewer_enabled = 0; + SG_LOG(SG_VIEW, SG_ALERT, "Not creating osgViewer::CompositeViewer"); + } _scenery_loaded = fgGetNode("/sim/sceneryloaded", true); _position_finalized = fgGetNode("/sim/position-finalized", true); @@ -542,7 +578,7 @@ void FGRenderer::setupRoot() void FGRenderer::setupView( void ) { - osgViewer::Viewer* viewer = globals->get_renderer()->getViewer(); + osgViewer::View* view = globals->get_renderer()->getView(); osg::initNotifyLevel(); // The number of polygon-offset "units" to place between layers. In @@ -571,7 +607,7 @@ FGRenderer::setupView( void ) fgGetNode("/environment", true), opt.get()); - viewer->getCamera() + view->getCamera() ->setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR); @@ -647,22 +683,25 @@ FGRenderer::setupView( void ) #if defined(HAVE_PUI) _puiCamera = new flightgear::PUICamera; - _puiCamera->init(guiCamera, viewer); + _puiCamera->init(guiCamera, view); #endif #if defined(ENABLE_QQ_UI) - std::string rootQMLPath = fgGetString("/sim/gui/qml-root-path"); - auto graphicsWindow = dynamic_cast(guiCamera->getGraphicsContext()); + osgViewer::Viewer* viewer = dynamic_cast(view); + if (viewer) { + std::string rootQMLPath = fgGetString("/sim/gui/qml-root-path"); + auto graphicsWindow = dynamic_cast(guiCamera->getGraphicsContext()); - if (!rootQMLPath.empty()) { - _quickDrawable = new QQuickDrawable; - _quickDrawable->setup(graphicsWindow, viewer); + if (!rootQMLPath.empty()) { + _quickDrawable = new QQuickDrawable; + _quickDrawable->setup(graphicsWindow, viewer); - _quickDrawable->setSource(QUrl::fromLocalFile(QString::fromStdString(rootQMLPath))); - - osg::Geode* qqGeode = new osg::Geode; - qqGeode->addDrawable(_quickDrawable); - guiCamera->addChild(qqGeode); + _quickDrawable->setSource(QUrl::fromLocalFile(QString::fromStdString(rootQMLPath))); + + osg::Geode* qqGeode = new osg::Geode; + qqGeode->addDrawable(_quickDrawable); + guiCamera->addChild(qqGeode); + } } #endif guiCamera->insertChild(0, FGPanelNode::create2DPanelNode()); @@ -706,7 +745,6 @@ FGRenderer::update( ) { } return; } - osgViewer::Viewer* viewer = globals->get_renderer()->getViewer(); if (_splash_alpha->getDoubleValue()>0.0) { @@ -748,38 +786,53 @@ FGRenderer::update( ) { // Force update of center dependent values ... current__view->set_dirty(); - osg::Camera *camera = viewer->getCamera(); + assert(composite_viewer_enabled != -1); + std::vector cameras; + if (composite_viewer) { + assert(!viewer); + unsigned n = composite_viewer->getNumViews(); + for (unsigned i=0; igetView(i); + osg::Camera* camera = view->getCamera(); + cameras.push_back(camera); + } + } + else { + cameras.push_back(viewer->getCamera()); + } + for (osg::Camera* camera: cameras) { + osg::Vec4 clear_color = _altitude_ft->getDoubleValue() < 250000 + ? toOsg(l->adj_fog_color()) + // skydome ends at ~262000ft (default rendering) + // ~328000 ft (ALS) and would produce a strange + // looking greyish space -> black looks much + // better :-) + : osg::Vec4(0, 0, 0, 1); + camera->setClearColor(clear_color); - osg::Vec4 clear_color = _altitude_ft->getDoubleValue() < 250000 - ? toOsg(l->adj_fog_color()) - // skydome ends at ~262000ft (default rendering) - // ~328000 ft (ALS) and would produce a strange - // looking greyish space -> black looks much - // better :-) - : osg::Vec4(0, 0, 0, 1); - camera->setClearColor(clear_color); + updateSky(); - updateSky(); - - // need to call the update visitor once - _frameStamp->setCalendarTime(*globals->get_time_params()->getGmt()); - _updateVisitor->setViewData(current__view->getViewPosition(), - current__view->getViewOrientation()); - SGVec3f direction(l->sun_vec()[0], l->sun_vec()[1], l->sun_vec()[2]); - _updateVisitor->setLight(direction, l->scene_ambient(), - l->scene_diffuse(), l->scene_specular(), - l->adj_fog_color(), - l->get_sun_angle()*SGD_RADIANS_TO_DEGREES); - _updateVisitor->setVisibility(actual_visibility); - simgear::GroundLightManager::instance()->update(_updateVisitor.get()); - osg::Node::NodeMask cullMask = ~simgear::LIGHTS_BITS & ~simgear::PICK_BIT; - cullMask |= simgear::GroundLightManager::instance() - ->getLightNodeMask(_updateVisitor.get()); - if (_panel_hotspots->getBoolValue()) - cullMask |= simgear::PICK_BIT; - camera->setCullMask(cullMask); - camera->setCullMaskLeft(cullMask); - camera->setCullMaskRight(cullMask); + // need to call the update visitor once + getFrameStamp()->setCalendarTime(*globals->get_time_params()->getGmt()); + _updateVisitor->setViewData(current__view->getViewPosition(), + current__view->getViewOrientation()); + //_updateVisitor->setViewData(eye2, center3); + SGVec3f direction(l->sun_vec()[0], l->sun_vec()[1], l->sun_vec()[2]); + _updateVisitor->setLight(direction, l->scene_ambient(), + l->scene_diffuse(), l->scene_specular(), + l->adj_fog_color(), + l->get_sun_angle()*SGD_RADIANS_TO_DEGREES); + _updateVisitor->setVisibility(actual_visibility); + simgear::GroundLightManager::instance()->update(_updateVisitor.get()); + osg::Node::NodeMask cullMask = ~simgear::LIGHTS_BITS & ~simgear::PICK_BIT; + cullMask |= simgear::GroundLightManager::instance() + ->getLightNodeMask(_updateVisitor.get()); + if (_panel_hotspots->getBoolValue()) + cullMask |= simgear::PICK_BIT; + camera->setCullMask(cullMask); + camera->setCullMaskLeft(cullMask); + camera->setCullMaskRight(cullMask); + } } void @@ -958,10 +1011,65 @@ PickList FGRenderer::pick(const osg::Vec2& windowPos) return result; } -void -FGRenderer::setViewer(osgViewer::Viewer* viewer_) +osgViewer::ViewerBase* FGRenderer::getViewerBase() { - viewer = viewer_; + if (composite_viewer) { + return composite_viewer.get(); + } + else { + return viewer.get(); + } +} + +osgViewer::View* FGRenderer::getView() +{ + assert(composite_viewer_enabled != -1); + if (composite_viewer) { + assert(composite_viewer->getNumViews()); + return composite_viewer->getView(0); + } + else { + return viewer.get(); + } +} + +const osgViewer::View* FGRenderer::getView() const +{ + FGRenderer* this_ = const_cast(this); + return this_->getView(); +} + +void +FGRenderer::setView(osgViewer::View* view) +{ + assert(composite_viewer_enabled != -1); + if (composite_viewer) { + if (composite_viewer->getNumViews() == 0) { + SG_LOG(SG_VIEW, SG_ALERT, "adding view to composite_viewer."); + composite_viewer->stopThreading(); + composite_viewer->addView(view); + composite_viewer->startThreading(); + } + } + else { + osgViewer::Viewer* viewer_ = dynamic_cast(view); + assert(viewer_); + viewer = viewer_; + } +} + +osg::FrameStamp* +FGRenderer::getFrameStamp() +{ + assert(composite_viewer_enabled != -1); + if (composite_viewer) { + assert(!viewer); + return composite_viewer->getFrameStamp(); + } + else { + assert(viewer); + return viewer->getFrameStamp(); + } } void @@ -991,8 +1099,8 @@ FGRenderer::setPlanes( double zNear, double zFar ) bool fgDumpSceneGraphToFile(const char* filename) { - osgViewer::Viewer* viewer = globals->get_renderer()->getViewer(); - return osgDB::writeNodeFile(*viewer->getSceneData(), filename); + osgViewer::View* view = globals->get_renderer()->getView(); + return osgDB::writeNodeFile(*view->getSceneData(), filename); } bool @@ -1199,14 +1307,14 @@ protected: bool printVisibleSceneInfo(FGRenderer* renderer) { - osgViewer::Viewer* viewer = renderer->getViewer(); + osgViewer::View* view = renderer->getView(); VisibleSceneInfoVistor vsv; Viewport* vp = 0; - if (!viewer->getCamera()->getViewport() && viewer->getNumSlaves() > 0) { - const osg::View::Slave& slave = viewer->getSlave(0); + if (!view->getCamera()->getViewport() && view->getNumSlaves() > 0) { + const osg::View::Slave& slave = view->getSlave(0); vp = slave._camera->getViewport(); } - vsv.doTraversal(viewer->getCamera(), viewer->getSceneData(), vp); + vsv.doTraversal(view->getCamera(), view->getSceneData(), vp); return true; } diff --git a/src/Viewer/renderer.hxx b/src/Viewer/renderer.hxx index b01416125..a938351a3 100644 --- a/src/Viewer/renderer.hxx +++ b/src/Viewer/renderer.hxx @@ -10,6 +10,9 @@ #include #include +#include + + namespace osg { class Camera; @@ -61,12 +64,20 @@ public: /** Just pick into the scene and return the pick callbacks on the way ... */ PickList pick(const osg::Vec2& windowPos); + + /* Returns either composite_viewer or viewer. */ + osgViewer::ViewerBase* getViewerBase(); /** Get and set the OSG Viewer object, if any. */ - osgViewer::Viewer* getViewer() { return viewer.get(); } - const osgViewer::Viewer* getViewer() const { return viewer.get(); } - void setViewer(osgViewer::Viewer* viewer); + osgViewer::View* getView(); + const osgViewer::View* getView() const; + void setView(osgViewer::View* view); + + /** Calls osgViewer::CompositeViewer::getFrameStamp() if we are using + composite viewer, otherwise osgViewer::Viewer::getFrameStamp(). */ + osg::FrameStamp* getFrameStamp(); + /** Get and set the manipulator object, if any. */ flightgear::FGEventHandler* getEventHandler() { return eventHandler.get(); } @@ -84,7 +95,9 @@ public: void setPlanes( double zNear, double zFar ); protected: + int composite_viewer_enabled = -1; osg::ref_ptr viewer; + osg::ref_ptr composite_viewer; osg::ref_ptr eventHandler; osg::ref_ptr _frameStamp; diff --git a/src/Viewer/splash.cxx b/src/Viewer/splash.cxx index 8d2634c87..f022f8eb6 100644 --- a/src/Viewer/splash.cxx +++ b/src/Viewer/splash.cxx @@ -204,6 +204,12 @@ void SplashScreen::createNodes() nullptr, -1.0, osg::Vec4(1.0, 0.0, 0.0, 1.0)); } + if (fgGetBool("/sim/rendering/composite-viewer-enabled")) { + addText(geode, osg::Vec2(0.5f, 0.65f), 0.03, + "CompositeViewer", + osgText::Text::CENTER_CENTER, + nullptr, -1.0, osg::Vec4(1.0, 0.0, 0.0, 1.0)); + } /////////// diff --git a/src/Viewer/sview.cxx b/src/Viewer/sview.cxx new file mode 100644 index 000000000..c00a6fb92 --- /dev/null +++ b/src/Viewer/sview.cxx @@ -0,0 +1,1394 @@ +/* +Implementation of 'step' view system. + +The basic idea here is calculate view position and direction by a series +of explicit steps. Steps can move to the origin of the user aircraft or a +multiplayer aircraft, or modify the current position by a fixed vector (e.g. +to move from aircraft origin to the pilot's eyepoint), or rotate the current +direction by a fixed transformation etc. We can also have a step that sets the +direction to point to a previously-calculated position. + +This is similar to what is already done by the View code, but making the +individual steps explicit gives us more flexibility. + +The dynamic nature of step views allows view cloning with composite-viewer. + +We also allow views to be defined and created at runtime instead of being +hard-coded in *-set.xml files. For example this makes it possible to define a +view from the user's aircraft's pilot to the centre of a multiplayer aircraft +(or to a multiplayer aircraft's pilot). +*/ + +/* +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "sview.hxx" + +#include
+#include
+#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + + +static std::ostream& operator << (std::ostream& out, const osg::Vec3f& vec) +{ + return out << "Vec3f{" + << " x=" << vec._v[0] + << " y=" << vec._v[1] + << " z=" << vec._v[2] + << "}"; +} + +static std::ostream& operator << (std::ostream& out, const osg::Quat& quat) +{ + return out << "Quat{" + << " x=" << quat._v[0] + << " y=" << quat._v[1] + << " z= " << quat._v[2] + << " w=" << quat._v[3] + << "}"; +} + +static std::ostream& operator << (std::ostream& out, const osg::Matrixd& matrix) +{ + osg::Vec3f translation; + osg::Quat rotation; + osg::Vec3f scale; + osg::Quat so; + matrix.decompose(translation, rotation, scale, so); + return out << "Matrixd {" + << " translation=" << translation + << " rotation=" << rotation + << " scale=" << scale + << " so=" << so + << "}"; +} + + +/* A position and direction. Repeatedly modified by SviewStep::evaluate() when +calculating final camera position/orientation. */ +struct SviewPosDir +{ + SGGeod position; + double heading; + double pitch; + double roll; + + /* Only used by SviewStepTarget; usually will be from a previously + evaluated Sview. */ + SGGeod target; + + /* The final position and direction, in a form suitable for setting an + osg::Camera's view matrix. */ + SGVec3d position2; + SGQuatd direction2; + + friend std::ostream& operator<< (std::ostream& out, const SviewPosDir& posdir) + { + out << "SviewPosDir {" + << " position=" << posdir.position + << " heading=" << posdir.heading + << " pitch=" << posdir.pitch + << " roll=" << posdir.roll + << " target=" << posdir.target + << " position2=" << posdir.position2 + << " direction2=" << posdir.direction2 + << "}" + ; + return out; + } +}; + + +/* Abstract base class for a single view step. A view step modifies a +SviewPosDir, e.g. translating the position and/or rotating the direction. */ +struct SviewStep +{ + /* Updates . */ + virtual void evaluate(SviewPosDir& posdir) = 0; + + virtual void stream(std::ostream& out) const + { + out << " "; + } + + virtual ~SviewStep() {} + + std::string m_description; + + friend std::ostream& operator<< (std::ostream& out, const SviewStep& step) + { + out << ' ' << typeid(step).name(); + step.stream(out); + return out; + } +}; + + +/* A step that sets position to aircraft origin and direction to aircraft's +longitudal axis. */ +struct SviewStepAircraft : SviewStep +{ + /* For the user aircraft, should be /. For a multiplayer aircraft, + it should be /ai/models/multiplayer[]. */ + SviewStepAircraft(SGPropertyNode* root) + { + /* todo: set up a listener or something so that we cope when + multiplayer callsign disapears then reappears in a different index of + /ai/models/multiplayer[]. */ + m_longitude = root->getNode("position/longitude-deg"); + m_latitude = root->getNode("position/latitude-deg"); + m_altitude = root->getNode("position/altitude-ft"); + m_heading = root->getNode("orientation/true-heading-deg"); + m_pitch = root->getNode("orientation/pitch-deg"); + m_roll = root->getNode("orientation/roll-deg"); + m_description = root->getStringValue("callsign"); + if (m_description == "") { + m_description = "User aircraft"; + } + SG_LOG(SG_VIEW, SG_ALERT, "m_longitude->getPath()=" << m_longitude->getPath()); + } + + void evaluate(SviewPosDir& posdir) override + { + posdir.position = SGGeod::fromDegFt( + m_longitude->getDoubleValue(), + m_latitude->getDoubleValue(), + m_altitude->getDoubleValue() + ); + + posdir.heading = m_heading->getDoubleValue(); + posdir.pitch = m_pitch->getDoubleValue(); + posdir.roll = m_roll->getDoubleValue(); + } + + virtual void stream(std::ostream& out) const + { + out << " "; + } + + private: + + SGPropertyNode_ptr m_longitude; + SGPropertyNode_ptr m_latitude; + SGPropertyNode_ptr m_altitude; + + SGPropertyNode_ptr m_heading; + SGPropertyNode_ptr m_pitch; + SGPropertyNode_ptr m_roll; +}; + + +/* Moves position by fixed vector, does not change direction. + +E.g. for moving from aircraft origin to pilot viewpoint. */ +struct SviewStepMove : SviewStep +{ + SviewStepMove(double forward, double up, double right) + : + m_offset(right, up, forward) + { + SG_LOG(SG_VIEW, SG_INFO, "forward=" << forward << " up=" << up << " right=" << right); + } + + void evaluate(SviewPosDir& posdir) override + { + /* These calculations are copied from View::recalcLookFrom(). */ + + /* The rotation rotating from the earth centerd frame to the horizontal + local frame. */ + SGQuatd hlOr = SGQuatd::fromLonLat(posdir.position); + + /* The rotation from the horizontal local frame to the basic view + orientation. */ + SGQuatd hlToBody = SGQuatd::fromYawPitchRollDeg(posdir.heading, posdir.pitch, posdir.roll); + + /* Compute the eyepoints orientation and position wrt the earth + centered frame - that is global coorinates. */ + SGQuatd ec2body = hlOr * hlToBody; + + /* The cartesian position of the basic view coordinate. */ + SGVec3d position = SGVec3d::fromGeod(posdir.position); + + /* This is rotates the x-forward, y-right, z-down coordinate system the + where simulation runs into the OpenGL camera system with x-right, y-up, + z-back. */ + SGQuatd q(-0.5, -0.5, 0.5, 0.5); + + position += (ec2body * q).backTransform(m_offset); + posdir.position = SGGeod::fromCart(position); + } + + virtual void stream(std::ostream& out) const + { + out << " " << m_offset; + } + + private: + SGVec3d m_offset; +}; + +/* Modifies heading, pitch and roll by fixed amounts; does not change position. + +E.g. can be used to preserve direction (relative to aircraft) of Helicopter +view at the time it was cloned. */ +struct SviewStepRotate : SviewStep +{ + SviewStepRotate(double heading=0, double pitch=0, double roll=0) + : + m_heading(heading), + m_pitch(pitch), + m_roll(roll) + { + SG_LOG(SG_VIEW, SG_INFO, "heading=" << heading << " pitch=" << pitch << " roll=" << roll); + } + + void evaluate(SviewPosDir& posdir) override + { + /* Should we use SGQuatd to evaluate things? */ + posdir.heading += -m_heading; + posdir.pitch += -m_pitch; + posdir.roll += m_roll; + } + + virtual void stream(std::ostream& out) const + { + out << " " + << ' ' << m_heading + << ' ' << m_pitch + << ' ' << m_roll + ; + } + + private: + double m_heading; + double m_pitch; + double m_roll; +}; + +/* Multiply heading, pitch and roll by constants. */ +struct SviewStepDirectionMultiply : SviewStep +{ + SviewStepDirectionMultiply(double heading=0, double pitch=0, double roll=0) + : + m_heading(heading), + m_pitch(pitch), + m_roll(roll) + { + SG_LOG(SG_VIEW, SG_INFO, "heading=" << heading << " pitch=" << pitch << " roll=" << roll); + } + + void evaluate(SviewPosDir& posdir) override + { + posdir.heading *= m_heading; + posdir.pitch *= m_pitch; + posdir.roll *= m_roll; + } + + virtual void stream(std::ostream& out) const + { + out << " " + << ' ' << m_heading + << ' ' << m_pitch + << ' ' << m_roll + ; + } + + private: + double m_heading; + double m_pitch; + double m_roll; +}; + +/* Copies current position to posdir.target. Used by SviewEyeTarget() to make +current position be available as target later on, e.g. by SviewStepTarget. */ +struct SviewStepCopyToTarget : SviewStep +{ + void evaluate(SviewPosDir& posdir) override + { + posdir.target = posdir.position; + } + + virtual void stream(std::ostream& out) const + { + out << " "; + } +}; + + +/* Move position to nearest tower. */ +struct SviewStepNearestTower : SviewStep +{ + /* For user aircraft should be /sim; for multiplayer aircraft it + should be ai/models/multiplayer[]/sim. */ + SviewStepNearestTower(SGPropertyNode* sim) + : + m_latitude(sim->getNode("tower/latitude-deg")), + m_longitude(sim->getNode("tower/longitude-deg")), + m_altitude(sim->getNode("tower/altitude-ft")) + { + m_description = "Nearest tower"; + } + + void evaluate(SviewPosDir& posdir) override + { + posdir.position = SGGeod::fromDegFt( + m_longitude->getDoubleValue(), + m_latitude->getDoubleValue(), + m_altitude->getDoubleValue() + ); + SG_LOG(SG_VIEW, SG_BULK, "moved posdir.postion to: " << posdir.position); + } + + virtual void stream(std::ostream& out) const + { + out << " " + << ' ' << m_latitude + << ' ' << m_longitude + << ' ' << m_altitude + ; + } + + SGPropertyNode_ptr m_latitude; + SGPropertyNode_ptr m_longitude; + SGPropertyNode_ptr m_altitude; +}; + + +/* Rotates view direction to point at a previously-calculated target. */ +struct SviewStepTarget : SviewStep +{ + /* Alters posdir.direction to point to posdir.target. */ + void evaluate(SviewPosDir& posdir) override + { + /* See View::recalcLookAt(). */ + + SGQuatd geodEyeOr = SGQuatd::fromYawPitchRollDeg(posdir.heading, posdir.pitch, posdir.roll); + SGQuatd geodEyeHlOr = SGQuatd::fromLonLat(posdir.position); + + SGQuatd ec2eye = geodEyeHlOr*geodEyeOr; + SGVec3d eyeCart = SGVec3d::fromGeod(posdir.position); + + SGVec3d atCart = SGVec3d::fromGeod(posdir.target); + + /* add target offsets to at_position... + Compute the eyepoints orientation and position wrt the earth centered + frame - that is global coorinates _absolute_view_pos = eyeCart; */ + + /* the view direction. */ + SGVec3d dir = normalize(atCart - eyeCart); + + /* the up directon. */ + SGVec3d up = ec2eye.backTransform(SGVec3d(0, 0, -1)); + + /* rotate -dir to the 2-th unit vector + rotate up to 1-th unit vector + Note that this matches the OpenGL camera coordinate system with + x-right, y-up, z-back. */ + posdir.direction2 = SGQuatd::fromRotateTo(-dir, 2, up, 1); + + posdir.position2 = SGVec3d::fromGeod(posdir.position); + + SG_LOG(SG_VIEW, SG_BULK, "have set posdir.position2: " << posdir.position2); + SG_LOG(SG_VIEW, SG_BULK, "have set posdir.direction2: " << posdir.direction2); + } + + virtual void stream(std::ostream& out) const + { + out << " "; + } + +}; + + +/* Converts position, heading, pitch and roll into position2 and direction2, +which will be used to set the camera parameters. */ +struct SviewStepFinal : SviewStep +{ + void evaluate(SviewPosDir& posdir) override + { + /* See View::recalcLookFrom(). */ + + /* The rotation rotating from the earth centerd frame to the horizontal + local frame. */ + SGQuatd hlOr = SGQuatd::fromLonLat(posdir.position); + + /* The rotation from the horizontal local frame to the basic view + orientation. */ + SGQuatd hlToBody = SGQuatd::fromYawPitchRollDeg(posdir.heading, posdir.pitch, posdir.roll); + + /* Compute the eyepoints orientation and position wrt the earth + centered frame - that is global coorinates. */ + SGQuatd ec2body = hlOr * hlToBody; + + /* The cartesian position of the basic view coordinate. */ + SGVec3d position = SGVec3d::fromGeod(posdir.position); + + /* This is rotates the x-forward, y-right, z-down coordinate system the + where simulation runs into the OpenGL camera system with x-right, y-up, + z-back. */ + SGQuatd q(-0.5, -0.5, 0.5, 0.5); + + posdir.position2 = position; + posdir.direction2 = ec2body * q; + } + + virtual void stream(std::ostream& out) const + { + out << " "; + } +}; + + +/* Contains a list of SviewStep's that are used to evaluate a SviewPosDir +(position and orientation) +*/ +struct SviewSteps +{ + const std::string& description() + { + if (m_name == "") { + if (!m_steps.empty()) { + m_name = m_steps.front()->m_description; + } + } + return m_name; + } + + void add_step(std::shared_ptr step) + { + m_steps.push_back(step); + } + + void add_step(SviewStep* step) + { + return add_step(std::shared_ptr(step)); + } + + void evaluate(SviewPosDir& posdir, bool debug=false) + { + if (debug) SG_LOG(SG_VIEW, SG_ALERT, "evaluating m_name=" << m_name); + for (auto step: m_steps) { + step->evaluate(posdir); + if (debug) SG_LOG(SG_VIEW, SG_ALERT, "posdir=" << posdir); + } + }; + + std::string m_name; + std::vector> m_steps; + + friend std::ostream& operator << (std::ostream& out, const SviewSteps& viewpos) + { + out << viewpos.m_name << " (" << viewpos.m_steps.size() << ")"; + for (auto step: viewpos.m_steps) { + out << " " << *step; + } + return out; + } +}; + + +static void ShowViews(); + + +/* Base class for views. +*/ +struct SviewView +{ + SviewView(osgViewer::View* osg_view) + : + m_osg_view(osg_view) + { + s_id += 1; + } + + /* Description that also includes integer identifier. */ + const std::string& description2() + { + if (m_description2 == "") { + char buffer[32]; + snprintf(buffer, sizeof(buffer), "[%i] ", s_id); + m_description2 = buffer + description(); + } + return m_description2; + } + + /* Description of this view, used in window title etc. */ + virtual const std::string& description() = 0; + + virtual ~SviewView() + { + if (!m_osg_view) { + return; + } + osgViewer::ViewerBase* viewer_base = m_osg_view->getViewerBase(); + auto composite_viewer = dynamic_cast(viewer_base); + assert(composite_viewer); + for (unsigned i=0; igetNumViews(); ++i) { + osgViewer::View* view = composite_viewer->getView(i); + SG_LOG(SG_VIEW, SG_ALERT, "composite_viewer view i=" << i << " view=" << view); + } + //ShowViews(); /* unsafe because we are being removed from s_views. */ + SG_LOG(SG_VIEW, SG_ALERT, "removing m_osg_view=" << m_osg_view); + composite_viewer->stopThreading(); + composite_viewer->removeView(m_osg_view); + composite_viewer->startThreading(); + } + + /* Returns false if window has been closed. */ + virtual bool update(double dt) = 0; + + /* Sets this view's camera position/orientation from . */ + void posdir_to_view(SviewPosDir posdir) + { + /* FGViewMgr::update(). */ + osg::Vec3d position = toOsg(posdir.position2); + osg::Quat orientation = toOsg(posdir.direction2); + + osg::Camera* camera = m_osg_view->getCamera(); + osg::Matrix old_m = camera->getViewMatrix(); + /* This calculation is copied from CameraGroup::update(). As of + 2020-10-10 we don't yet update the projection matrix so views cannot + zoom. */ + const osg::Matrix new_m( + osg::Matrix::translate(-position) + * osg::Matrix::rotate(orientation.inverse()) + ); + SG_LOG(SG_VIEW, SG_BULK, "old_m: " << old_m); + SG_LOG(SG_VIEW, SG_BULK, "new_m: " << new_m); + camera->setViewMatrix(new_m); + } + + osgViewer::View* m_osg_view = nullptr; + simgear::compositor::Compositor* m_compositor = nullptr; + std::string m_description2; + + static int s_id; +}; + +int SviewView::s_id = 0; + + +/* A view which keeps two aircraft visible, with one at a constant distance in +the foreground. +*/ +struct SviewDouble : SviewView +{ + /* and should evaluate to position of local and remote + aircraft. We ignore directions in and - we are only + interested in the two positions. */ + SviewDouble( + osgViewer::View* view, + const SviewSteps& steps_local, + const SviewSteps& steps_remote, + double local_chase_distance=25, + double angle_deg=15 + ) + : + SviewView(view), + m_steps_local(steps_local), + m_steps_remote(steps_remote), + m_local_chase_distance(local_chase_distance), + m_angle_rad(angle_deg * 3.1415926 / 180) + { + const std::string eye = m_steps_local.description(); + const std::string target = m_steps_remote.description(); + m_description = "Double view " + eye + " - " + target; + } + + const std::string& description() override + { + return m_description; + } + + virtual bool update(double dt) override + { + /* + We choose eye position so that we show the local aircraft a fixed + amount below the view midpoint, and the remote aircraft the same fixed + amount above the view midpoint. + + L: middle of local aircraft. + R: middle of remote aircraft. + E: desired eye-point + + ---- R + / \ + E | + | L | .................... H (horizon) + | | + \ / + ---- + + We require that: + + EL is local aircraft's chase-distance (though at the moment we use + a fixed value) so that local aircraft is in perfect view. + + Angle LER is fixed to give good view of both aircraft in + window. (Should be related to the vertical angular size of the + window, but at the moment we use a fixed value.) + + We need to calculate angle RLE, and add to HLR, in order to find + position of E (eye) relative to L. Then for view pitch we use midpoint + of angle of ER and angle EL, so that local and remote aircraft are + symmetrically below and above the centre of the view. + + We find angle RLE by using cosine rule twice in the triangle RLE: + ER^2 = EL^2 + LR^2 - 2*EL*LR*cos(RLE) + LR^2 = ER^2 + EL^2 - 2*ER*EL*cos(LER) + + Wen end up with a quadratic for ER with solution: + ER = EL * cos(LER) + sqrt(LR^2 - EL^22*sin(LER)^2) + (We discard the -sqrt because it ends up with ER being negative.) + + and: + cos(RLE) = (LR^2 + LE^2 - ER^2) / (2*LE*LR) + + So we can find RLE using acos(). + */ + + bool valid = m_osg_view->getCamera()->getGraphicsContext()->valid(); + SG_LOG(SG_VIEW, SG_BULK, "valid=" << valid); + if (!valid) return false; + + bool debug = false; + if (0) { + time_t t = time(NULL) / 10; + //if (debug) SG_LOG(SG_VIEW, SG_ALERT, "m_debug_time=" << m_debug_time << " t=" << t); + if (t != m_debug_time) { + m_debug_time = t; + debug = true; + } + } + + /* Find positions of local and remote aircraft. */ + SviewPosDir posdir_local; + SviewPosDir posdir_remote; + m_steps_local.evaluate(posdir_local); + m_steps_remote.evaluate(posdir_remote); + + if (debug) { + SG_LOG(SG_VIEW, SG_ALERT, " m_steps_local: " << m_steps_local << ": " << posdir_local); + SG_LOG(SG_VIEW, SG_ALERT, " m_steps_remote: " << m_steps_remote << ": " << posdir_remote); + } + + /* Create cartesian coordinates so we can calculate distance . */ + SGVec3d local_pos = SGVec3d::fromGeod(posdir_local.target); + SGVec3d remote_pos = SGVec3d::fromGeod(posdir_remote.target); + double lr = sqrt(distSqr(local_pos, remote_pos)); + const double pi = 3.1415926; + + /* Desired angle between local and remote aircraft in final view. */ + double ler = m_angle_rad; + + /* Distance of eye from local aircraft. */ + double le = m_local_chase_distance; + + /* Find , the distance of eye from remote aircraft. Have to be + careful to cope when there is no solution if remote is too close, and + choose the +ve sqrt(). */ + double er_root_term = lr*lr - le*le*sin(ler)*sin(ler); + if (er_root_term < 0) { + /* This can happen if LR is too small, i.e. remote aircraft too + close to local aircraft. */ + er_root_term = 0; + } + double er = le * cos(ler) + sqrt(er_root_term); + + /* Now find rle, angle at local aircraft between vector to remote + aircraft and vector to desired eye position. Again we have to cope when + a real solution is not possible. */ + double cos_rle = (lr*lr + le*le - er*er) / (2*le*lr); + if (cos_rle > 1) cos_rle = 1; + if (cos_rle < -1) cos_rle = -1; + double rle = acos(cos_rle); + double rle_deg = rle * 180 / pi; + + /* Now find the actual eye position. We do this by calculating heading + and pitch from local aircraft L to eye position E, then using a + temporary SviewStepMove. */ + double lr_vertical = posdir_remote.target.getElevationM() - posdir_local.target.getElevationM(); + double lr_horizontal = SGGeodesy::distanceM(posdir_local.target, posdir_remote.target); + double hlr = atan2(lr_vertical, lr_horizontal); + posdir_local.heading = SGGeodesy::courseDeg( + posdir_local.target, + posdir_remote.target + ); + posdir_local.pitch = (hlr + rle) * 180 / pi; + posdir_local.roll = 0; + auto move = SviewStepMove(-le, 0, 0); + move.evaluate(posdir_local); + + /* At this point, posdir_local.position is eye position. We make + posdir_local.direction2 point from this eye position to halfway between + the remote and local aircraft. */ + double er_vertical = posdir_remote.target.getElevationM() + - posdir_local.position.getElevationM(); + double her = asin(er_vertical / er); + double hel = (hlr + rle) - pi; + posdir_local.pitch = (her + hel) / 2 * 180 / pi; + auto stepfinal = SviewStepFinal(); + stepfinal.evaluate(posdir_local); + posdir_to_view(posdir_local); + + if (debug) { + SG_LOG(SG_VIEW, SG_ALERT, "" + << " lr=" << lr + << " ler=" << ler + << " er_root_term=" << er_root_term + << " er=" << er + << " cos_rle=" << cos_rle + << " rle_deg=" << rle_deg + << " lr_vertical=" << lr_vertical + << " lr_horizontal=" << lr_horizontal + << " hlr=" << hlr + << " posdir_local=" << posdir_local + << " posdir_remote=" << posdir_remote + ); + } + return true; + } + + SviewSteps m_steps_local; + SviewSteps m_steps_remote; + double m_local_chase_distance; + double m_angle_rad; + + std::string m_description; + time_t m_debug_time = 0; +}; + + +/* A view defined by an eye and target. Used for clones of main window views. +*/ +struct SviewViewEyeTarget : SviewView +{ + /* Construct as a clone of the current view in /sim/current-view/. We look + in /sim/current-view/ to get view parameters, and from them we construct + eye and target steps. */ + SviewViewEyeTarget(osgViewer::View* view) + : + SviewView(view) + { + SG_LOG(SG_VIEW, SG_INFO, "m_osg_view=" << m_osg_view); + + SGPropertyNode* global_view; + SGPropertyNode* root; + SGPropertyNode* sim; + SGPropertyNode* view_config; + int view_number_raw; + std::string root_path; + std::string type; + + /* Unfortunately we need to look at things like + /sim/view[]/config/eye-heading-deg-path, even when handling multiplayer + aircraft. */ + view_number_raw = globals->get_props()->getIntValue("/sim/current-view/view-number-raw"); + global_view = globals->get_props()->getNode("/sim/view", view_number_raw /*index*/, true /*create*/); + + /* is typically "" or /ai/models/multiplayer[]. */ + root_path = global_view->getStringValue("config/root"); + type = global_view->getStringValue("type"); + + root = globals->get_props()->getNode(root_path); + if (root_path == "" || root_path == "/") { + /* user aircraft. */ + sim = root->getNode("sim"); + } + else { + /* Multiplayer sim is /ai/models/multiplayer[]/set/sim. */ + sim = root->getNode("set/sim"); + } + + { + SGPropertyNode* view_node = sim->getNode("view", view_number_raw, true /*create*/); + view_config = view_node->getNode("config"); + } + + SG_LOG(SG_VIEW, SG_ALERT, "view_number_raw=" << view_number_raw); + SG_LOG(SG_VIEW, SG_ALERT, "type=" << type); + SG_LOG(SG_VIEW, SG_ALERT, "root_path=" << root_path); + SG_LOG(SG_VIEW, SG_ALERT, "root=" << root); + SG_LOG(SG_VIEW, SG_ALERT, "sim=" << sim); + assert(root && sim); + SG_LOG(SG_VIEW, SG_ALERT, "root->getPath()=" << root->getPath()); + SG_LOG(SG_VIEW, SG_ALERT, "sim->getPath()=" << sim->getPath()); + SG_LOG(SG_VIEW, SG_ALERT, "view_config->getPath()=" << view_config->getPath()); + + if (view_config->getBoolValue("eye-fixed")) { + /* E.g. Tower view. */ + SG_LOG(SG_VIEW, SG_INFO, "eye-fixed"); + + /* Add a step to move to centre of aircraft. */ + m_target.add_step(new SviewStepAircraft(root)); + m_target.add_step(new SviewStepMove( + view_config->getDoubleValue("target-z-offset-m"), + view_config->getDoubleValue("target-y-offset-m"), + view_config->getDoubleValue("target-x-offset-m") + )); + SG_LOG(SG_VIEW, SG_DEBUG, "m_target=" << m_target); + + /* Add a step to set pitch and roll to zero, otherwise view from + tower (as calculated by SviewStepTarget) rolls/pitches with + aircraft. */ + m_target.add_step(new SviewStepDirectionMultiply( + 1 /* heading */, + 0 /* pitch */, + 0 /* roll */ + )); + SG_LOG(SG_VIEW, SG_DEBUG, "m_target=" << m_target); + + /* Current position is the target, so add a step that copies it to + SviewPosDir.target. */ + m_target.add_step(new SviewStepCopyToTarget); + + /* Added steps to set .m_eye up so that it looks from the nearest + tower. */ + m_eye.add_step(new SviewStepNearestTower(sim)); + m_eye.add_step(new SviewStepTarget); + + /* Add a step that moves towards the target a little to avoid eye + being inside the tower walls. + + [At some point it might be good to make this movement not change + the height too.] */ + m_eye.add_step(new SviewStepMove(30, 0, 0)); + } + else { + SG_LOG(SG_VIEW, SG_INFO, "not eye-fixed"); + + /* E.g. Pilot view or Helicopter view. We assume eye position is + relative to aircraft. */ + if (type == "lookat") { + /* E.g. Helicopter view. Move to centre of aircraft. + config/target-z-offset-m seems to be +ve when moving backwards + relative to the aircraft, so we need to negate the value we + pass to SviewStepMove(). */ + m_target.add_step(new SviewStepAircraft(root)); + SG_LOG(SG_VIEW, SG_DEBUG, "m_target=" << m_target); + + m_target.add_step(new SviewStepMove( + view_config->getDoubleValue("target-z-offset-m"), + view_config->getDoubleValue("target-y-offset-m"), + view_config->getDoubleValue("target-x-offset-m") + )); + SG_LOG(SG_VIEW, SG_DEBUG, "m_target=" << m_target); + + m_target.add_step(new SviewStepCopyToTarget); + + m_eye.add_step(new SviewStepAircraft(root)); + SG_LOG(SG_VIEW, SG_DEBUG, "m_eye=" << m_eye); + + m_eye.add_step(new SviewStepMove( + view_config->getDoubleValue("target-z-offset-m"), + view_config->getDoubleValue("target-y-offset-m"), + view_config->getDoubleValue("target-x-offset-m") + )); + SG_LOG(SG_VIEW, SG_DEBUG, "m_eye=" << m_eye); + + /* Add a step that crudely preserves or don't preserve + aircraft's heading, pitch and roll; this enables us to mimic + Helicopter and Chase views. In theory we should evaluate the + specified paths, but in practise we only need to multiply + current values by 0 or 1. + + todo: add damping. */ + m_eye.add_step(new SviewStepDirectionMultiply( + global_view->getStringValue("config/eye-heading-deg-path")[0] ? 1 : 0, + global_view->getStringValue("config/eye-pitch-deg-path")[0] ? 1 : 0, + global_view->getStringValue("config/eye-roll-deg-path")[0] ? 1 : 0 + )); + SG_LOG(SG_VIEW, SG_DEBUG, "m_eye=" << m_eye); + + /* Add a step that applies the current view rotation. */ + m_eye.add_step(new SviewStepRotate( + root->getDoubleValue("sim/current-view/heading-offset-deg"), + root->getDoubleValue("sim/current-view/pitch-offset-deg"), + root->getDoubleValue("sim/current-view/roll-offset-deg") + )); + SG_LOG(SG_VIEW, SG_DEBUG, "m_eye=" << m_eye); + + /* Add step that moves eye away from aircraft. + config/z-offset-m defaults to /sim/chase-distance-m (see + fgdata:defaults.xml) which is -ve, e.g. -25m. */ + m_eye.add_step(new SviewStepMove( + -view_config->getDoubleValue("z-offset-m"), + -view_config->getDoubleValue("y-offset-m"), + -view_config->getDoubleValue("x-offset-m") + )); + SG_LOG(SG_VIEW, SG_DEBUG, "m_eye=" << m_eye); + } + else { + /* E.g. pilot view. + + Add steps to move to the pilot's eye position. + + config/z-offset-m seems to be +ve when moving backwards + relative to the aircraft, so we need to negate the value we + pass to SviewStepMove(). */ + m_eye.add_step(new SviewStepAircraft(root)); + m_eye.add_step(new SviewStepMove( + view_config->getDoubleValue("z-offset-m"), + view_config->getDoubleValue("y-offset-m"), + view_config->getDoubleValue("x-offset-m") + )); + SG_LOG(SG_VIEW, SG_DEBUG, "m_eye=" << m_eye); + + /* Add a step to apply the current view rotation. E.g. on + harrier-gr3 this corrects initial view direction (which is + pitch down by 15deg). + + However this doesn't work properly - the cockpit rotates + in a small circle when the aircraft rolls. This is because + SviewStepRotate's simple application of heading/pitch/roll + doesn't work if aircraft has non-zero roll. We need to use + SGQuatd - see View::recalcLookFrom(). */ + m_eye.add_step(new SviewStepRotate( + -view_config->getDoubleValue("heading-offset-deg"), + -view_config->getDoubleValue("pitch-offset-deg"), + -view_config->getDoubleValue("roll-offset-deg") + )); + SG_LOG(SG_VIEW, SG_DEBUG, "m_eye=" << m_eye); + + /* Add a step to preserve the current user-supplied offset to + view direction. */ + m_eye.add_step(new SviewStepRotate( + root->getDoubleValue("sim/current-view/heading-offset-deg") + - view_config->getDoubleValue("heading-offset-deg"), + -(root->getDoubleValue("sim/current-view/pitch-offset-deg") + - view_config->getDoubleValue("pitch-offset-deg")), + root->getDoubleValue("sim/current-view/roll-offset-deg") + - view_config->getDoubleValue("roll-offset-deg") + )); + SG_LOG(SG_VIEW, SG_DEBUG, "m_eye=" << m_eye); + } + + /* Finally add a step that converts + lat,lon,height,heading,pitch,roll into SGVec3d position and SGQuatd + orientation. */ + m_eye.add_step(new SviewStepFinal); + } + SG_LOG(SG_VIEW, SG_ALERT, "m_eye=" << m_eye); + SG_LOG(SG_VIEW, SG_ALERT, "m_target=" << m_target); + + m_description = m_eye.description() + " - " + m_target.description(); + } + + const std::string& description() override + { + return m_description; + } + + /* Construct from separate eye and target definitions. */ + SviewViewEyeTarget( + osgViewer::View* view, + const SviewSteps& eye, + const SviewSteps& target + ) + : + SviewView(view), + m_eye(eye), + m_target(target) + { + m_target.add_step(new SviewStepCopyToTarget); + m_eye.add_step(new SviewStepTarget); + SG_LOG(SG_VIEW, SG_ALERT, " m_eye:" << m_eye); + SG_LOG(SG_VIEW, SG_ALERT, " m_target:" << m_target); + m_debug = true; + SG_LOG(SG_VIEW, SG_ALERT, " m_debug:" << m_debug); + m_description = m_eye.description() + " - " + m_target.description(); + } + + bool update(double dt) override + { + bool valid = m_osg_view->getCamera()->getGraphicsContext()->valid(); + SG_LOG(SG_VIEW, SG_BULK, "valid=" << valid); + if (!valid) return false; + + SviewPosDir posdir; + bool debug = false; + if (m_debug) { + time_t t = time(NULL) / 10; + //if (debug) SG_LOG(SG_VIEW, SG_ALERT, "m_debug_time=" << m_debug_time << " t=" << t); + if (t != m_debug_time) { + m_debug_time = t; + debug = true; + } + } + if (debug) SG_LOG(SG_VIEW, SG_ALERT, "evaluating target:"); + m_target.evaluate(posdir, debug); + if (debug) SG_LOG(SG_VIEW, SG_ALERT, "evaluating eye:"); + m_eye.evaluate(posdir, debug); + + posdir_to_view(posdir); + return true; + } + + SviewSteps m_eye; + SviewSteps m_target; + std::string m_description; + bool m_debug = false; + time_t m_debug_time = 0; +}; + + +/* All our view windows. */ +static std::vector> s_views; + +/* Recent views, for use by SviewAddLastPair(). */ +static std::deque> s_recent_views; + + +static void ShowViews() +{ + for (auto view: s_views) { + if (!view) SG_LOG(SG_GENERAL, SG_ALERT, " view is null"); + else { + const osg::GraphicsContext* vgc = (view->m_compositor) ? view->m_compositor->getGraphicsContext() : nullptr; + SG_LOG(SG_GENERAL, SG_ALERT, " gc=" << vgc); + } + } +} + + +void SviewPush() +{ + if (s_recent_views.size() >= 2) { + s_recent_views.pop_front(); + } + /* Make a dummy view whose eye and target members will copy the current + view. */ + std::shared_ptr v(new SviewViewEyeTarget(nullptr /*view*/)); + s_recent_views.push_back(v); + SG_LOG(SG_VIEW, SG_ALERT, "Have pushed view: " << v); +} + + +void SviewUpdate(double dt) +{ + bool verbose = 0; + for (size_t i=0; im_osg_view + << ' ' << s_views[i]->m_compositor + ); + } + bool valid = s_views[i]->update(dt); + if (valid) { + const osg::Matrix& view_matrix = s_views[i]->m_osg_view->getCamera()->getViewMatrix(); + const osg::Matrix& projection_matrix = s_views[i]->m_osg_view->getCamera()->getProjectionMatrix(); + s_views[i]->m_compositor->update(view_matrix, projection_matrix); + i += 1; + } + else { + /* Window has been closed. + + This doesn't work reliably with OpenSceneGraph-3.4. We seem to + sometimes get valid=false when a different window from s_views[i] + is closed. And sometimes OSG seems to end up sending a close-window + even to the main window when the user has closed an sview window. + */ + auto view_it = s_views.begin() + i; + SG_LOG(SG_VIEW, SG_INFO, "deleting SviewView i=" << i << ": " << (*view_it)->description2()); + for (size_t j=0; jm_osg_view + << ' ' << s_views[j]->m_compositor + ); + } + s_views.erase(view_it); + + for (size_t j=0; jm_osg_view + << ' ' << s_views[j]->m_compositor + ); + } + verbose = true; + } + } +} + +void SviewClear() +{ + s_views.clear(); +} + + +/* These are set by SViewSetCompositorParams(). */ +static osg::ref_ptr s_compositor_options; +static std::string s_compositor_path; + + +struct EventHandler : osgGA::EventHandler +{ + bool handle(osgGA::Event* event, osg::Object* object, osg::NodeVisitor* nv) override + { + osgGA::GUIEventAdapter* ea = event->asGUIEventAdapter(); + if (ea) { + osgGA::GUIEventAdapter::EventType et = ea->getEventType(); + if (et != osgGA::GUIEventAdapter::FRAME) { + SG_LOG(SG_GENERAL, SG_BULK, "sview event handler called. ea->getEventType()=" << ea->getEventType()); + } + } + else { + SG_LOG(SG_GENERAL, SG_BULK, "sview event handler called..."); + } + return true; + } +}; + + +std::shared_ptr SviewCreate(const std::string& type) +{ + /* + We create various things: + An osgViewer::View, added to the global osgViewer::CompositeViewer. + An osg::GraphicsContext (has associated top-level window). + A simgear::compositor::Compositor. + */ + + FGRenderer* renderer = globals->get_renderer(); + osgViewer::ViewerBase* viewer_base = renderer->getViewerBase(); + osgViewer::CompositeViewer* composite_viewer = dynamic_cast(viewer_base); + if (!composite_viewer) { + SG_LOG(SG_GENERAL, SG_ALERT, "SviewCreate() doing nothing because not composite-viewer mode not enabled."); + return nullptr; + } + + SG_LOG(SG_GENERAL, SG_ALERT, "Creating new view type=" << type); + + osgViewer::View* main_view = renderer->getView(); + osg::Node* scene_data = main_view->getSceneData(); + + SG_LOG(SG_GENERAL, SG_ALERT, "main_view->getNumSlaves()=" << main_view->getNumSlaves()); + + osgViewer::View* view = new osgViewer::View(); + EventHandler* event_handler = new EventHandler; + view->addEventHandler(event_handler); + + std::shared_ptr sview_view; + + if (0) {} + else if (type == "current") { + sview_view.reset(new SviewViewEyeTarget(view)); + } + else if (type == "last_pair") { + if (s_recent_views.size() < 2) { + SG_LOG(SG_VIEW, SG_ALERT, "Need two pushed views"); + return nullptr; + } + auto it = s_recent_views.end(); + std::shared_ptr target = *(--it); + std::shared_ptr eye = *(--it); + if (!target || !eye) { + SG_LOG(SG_VIEW, SG_ALERT, "target=" << target << " eye=" << eye); + return nullptr; + } + sview_view.reset(new SviewViewEyeTarget(view, eye->m_eye, target->m_eye)); + } + else if (type == "last_pair_double") { + if (s_recent_views.size() < 2) { + SG_LOG(SG_VIEW, SG_ALERT, "Need two pushed views"); + return nullptr; + } + auto it = s_recent_views.end(); + std::shared_ptr remote = *(--it); + std::shared_ptr local = *(--it); + if (!local || !remote) { + SG_LOG(SG_VIEW, SG_ALERT, "remote=" << local << " remote=" << remote); + return nullptr; + } + sview_view.reset(new SviewDouble(view, local->m_target, remote->m_target)); + } + else { + SG_LOG(SG_GENERAL, SG_ALERT, "unrecognised type=" << type); + return nullptr; + } + + osg::ref_ptr traits = new osg::GraphicsContext::Traits; + osg::ref_ptr gc; + + if (1) { + /* Create a new window. */ + + osg::GraphicsContext::WindowingSystemInterface* wsi = osg::GraphicsContext::getWindowingSystemInterface(); + assert(wsi); + flightgear::WindowBuilder* window_builder = flightgear::WindowBuilder::getWindowBuilder(); + flightgear::GraphicsWindow* main_window = window_builder->getDefaultWindow(); + osg::ref_ptr main_gc = main_window->gc; + const osg::GraphicsContext::Traits* main_traits = main_gc->getTraits(); + + /* Arbitrary initial position of new window. */ + traits->x = 100; + traits->y = 100; + + /* We set new window size as fraction of main window. This keeps the + aspect ratio of new window same as that of the main window which allows + us to use main window's projection matrix directly. Presumably we could + calculate a suitable projection matrix to match an arbitrary aspect + ratio, but i don't know the maths well enough to do this. */ + traits->width = main_traits->width / 2; + traits->height = main_traits->height / 2; + traits->windowDecoration = true; + traits->doubleBuffer = true; + traits->sharedContext = 0; + + traits->readDISPLAY(); + if (traits->displayNum < 0) traits->displayNum = 0; + if (traits->screenNum < 0) traits->screenNum = 0; + + int bpp = fgGetInt("/sim/rendering/bits-per-pixel"); + int cbits = (bpp <= 16) ? 5 : 8; + int zbits = (bpp <= 16) ? 16 : 24; + traits->red = traits->green = traits->blue = cbits; + traits->depth = zbits; + + traits->mipMapGeneration = true; + traits->windowName = "Flightgear " + sview_view->description2(); + traits->sampleBuffers = fgGetInt("/sim/rendering/multi-sample-buffers", traits->sampleBuffers); + traits->samples = fgGetInt("/sim/rendering/multi-samples", traits->samples); + traits->vsync = fgGetBool("/sim/rendering/vsync-enable", traits->vsync); + traits->stencil = 8; + + gc = osg::GraphicsContext::createGraphicsContext(traits); + assert(gc.valid()); + } + + /* need to ensure that the window is cleared make sure that the complete + window is set the correct colour rather than just the parts of the window + that are under the camera's viewports. */ + //gc->setClearColor(osg::Vec4f(0.2f,0.2f,0.6f,1.0f)); + //gc->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + view->setSceneData(scene_data); + view->setDatabasePager(FGScenery::getPagerSingleton()); + + /* https://www.mail-archive.com/osg-users@lists.openscenegraph.org/msg29820.html + Passing (false, false) here seems to cause a hang on startup. */ + view->getDatabasePager()->setUnrefImageDataAfterApplyPolicy(true, false); + osg::GraphicsContext::createNewContextID(); + + osg::Camera* main_camera = main_view->getCamera(); + osg::Camera* camera = view->getCamera(); + camera->setGraphicsContext(gc.get()); + + if (0) { + /* Show the projection matrix. */ + double left; + double right; + double bottom; + double top; + double zNear; + double zFar; + auto projection_matrix = main_camera->getProjectionMatrix(); + bool ok = projection_matrix.getFrustum(left, right, bottom, top, zNear, zFar); + SG_LOG(SG_GENERAL, SG_ALERT, "projection_matrix:" + << " ok=" << ok + << " left=" << left + << " right=" << right + << " bottom=" << bottom + << " top=" << top + << " zNear=" << zNear + << " zFar=" << zFar + ); + } + + camera->setProjectionMatrix(main_camera->getProjectionMatrix()); + camera->setViewMatrix(main_camera->getViewMatrix()); + camera->setCullMask(0xffffffff); + camera->setCullMaskLeft(0xffffffff); + camera->setCullMaskRight(0xffffffff); + + /* This appears to avoid unhelpful culling of nearby objects. Though the + above SG_LOG() says zNear=0.1 zFar=120000, so not sure what's going on. */ + camera->setComputeNearFarMode(osgUtil::CullVisitor::DO_NOT_COMPUTE_NEAR_FAR); + + /* + from CameraGroup::buildGUICamera(): + camera->setInheritanceMask(osg::CullSettings::ALL_VARIABLES + & ~(osg::CullSettings::COMPUTE_NEAR_FAR_MODE + | osg::CullSettings::CULLING_MODE + | osg::CullSettings::CLEAR_MASK + )); + camera->setCullingMode(osg::CullSettings::NO_CULLING); + + + main_viewport seems to be null so this doesn't work. + osg::Viewport* main_viewport = main_view->getCamera()->getViewport(); + SG_LOG(SG_GENERAL, SG_ALERT, "main_viewport=" << main_viewport); + + osg::Viewport* viewport = new osg::Viewport(*main_viewport); + + view->getCamera()->setViewport(viewport); + */ + view->getCamera()->setViewport(0, 0, traits->width, traits->height); + + view->setName("Cloned view"); + + view->setFrameStamp(composite_viewer->getFrameStamp()); + + simgear::compositor::Compositor* compositor = simgear::compositor::Compositor::create( + view, + gc, + view->getCamera()->getViewport(), + s_compositor_path, + s_compositor_options + ); + + sview_view->m_compositor = compositor; + s_views.push_back(sview_view); + + /* stop/start threading: + https://www.mail-archive.com/osg-users@lists.openscenegraph.org/msg54341.html + */ + composite_viewer->stopThreading(); + composite_viewer->addView(view); + composite_viewer->startThreading(); + + SG_LOG(SG_GENERAL, SG_ALERT, "main_view->getNumSlaves()=" << main_view->getNumSlaves()); + SG_LOG(SG_GENERAL, SG_ALERT, "view->getNumSlaves()=" << view->getNumSlaves()); + + SG_LOG(SG_VIEW, SG_ALERT, "have added extra view. views are now:"); + for (unsigned i=0; igetNumViews(); ++i) { + osgViewer::View* view = composite_viewer->getView(i); + SG_LOG(SG_VIEW, SG_ALERT, "composite_viewer view i=" << i << " view=" << view); + } + + return sview_view; +} + +void SViewSetCompositorParams( + osg::ref_ptr options, + const std::string& compositor_path + ) +{ + s_compositor_options = options; + s_compositor_path = compositor_path; +} diff --git a/src/Viewer/sview.hxx b/src/Viewer/sview.hxx new file mode 100644 index 000000000..1cd6d5b14 --- /dev/null +++ b/src/Viewer/sview.hxx @@ -0,0 +1,80 @@ +#pragma once + +/* +Support for extra view windows. Requires that composite-viewer is enabled at +startup with --composite-viewer=1. +*/ + +#include + +#include + + +/* Should be called before the first call to SviewCreate() so that +SviewCreate() can create new simgear::compositor::Compositor instances with the +same parameters as were used for the main window. + +options +compositor_path + Suitable for passing to simgear::compositor::Compositor(). +*/ +void SViewSetCompositorParams( + osg::ref_ptr options, + const std::string& compositor_path + ); + +/* Pushes current main window view to internal circular list of two items used +by SviewCreate() with 'last_pair' or 'last_pair_double'. */ +void SviewPush(); + +/* Updates camera position/orientation/zoom of all sviews - should be called +each frame. Will also handle closing of Sview windows. */ +void SviewUpdate(double dt); + +/* Deletes all internal views; e.g. used when restarting. */ +void SviewClear(); + +/* A view, typically an extra view window. The definition of this is not +public. */ +struct SviewView; + +/* +This is the main interface to the Sview system. We create a new SviewView in a +new top-level window. It will be updated as required by SviewUpdate(). + +As of 2020-11-18, the new window will be half width and height of the main +window, and will have top-left corner at (100, 100). It can be dragged, resized +and closed by the user. + +type: + This controls what sort of view we create: + + "current" + Clones the current view. + "last_pair" + Look from first pushed view's eye to second pushed view's eye. Returns + nullptr if SviewPush hasn't been called at least twice. + "last_pair_double" + Keep first pushed view's aircraft in foreground and second pushed + view's aircraft in background. Returns nullptr if SviewPush hasn't been + called at least twice. + +Returns: + Shared ptr to SviewView instance. As of 2020-11-18 there is little that + the caller can do with this. We handle closing of the SviewView's window + internally. + +As of 2020-11-17, extra views have various limitations including: + + No event handling, so no panning, zooming etc. + + Tower View AGL is like Tower View so no zooming to keep ground visible. + + Cockpit view has a incorrect calculation giving slightly incorrect + translation when rolling. + + No damping in chase views. + + Hard-coded chase distances. +*/ +std::shared_ptr SviewCreate(const std::string& type); diff --git a/src/Viewer/viewmgr.cxx b/src/Viewer/viewmgr.cxx index bbd9c00b5..7d5955536 100644 --- a/src/Viewer/viewmgr.cxx +++ b/src/Viewer/viewmgr.cxx @@ -33,8 +33,12 @@ #include
#include "view.hxx" +#include "sview.hxx" +#include "renderer.hxx" #include "CameraGroup.hxx" +#include "Scenery/scenery.hxx" + // Constructor FGViewMgr::FGViewMgr(void) @@ -134,6 +138,7 @@ FGViewMgr::unbind () _viewNumberProp.clear(); ViewPropertyEvaluator::clear(); + SviewClear(); } void @@ -155,6 +160,7 @@ FGViewMgr::update (double dt) cameraGroup->update(toOsg(currentView->getViewPosition()), toOsg(currentView->getViewOrientation())); } + SviewUpdate(dt); } void FGViewMgr::clear() @@ -222,6 +228,33 @@ FGViewMgr::prev_view() return get_current_view(); } +void FGViewMgr::view_push() +{ + SviewPush(); +} + +void FGViewMgr::clone_current_view() +{ + clone_internal("current"); +} + +void FGViewMgr::clone_last_pair() +{ + clone_internal("last_pair"); +} + +void FGViewMgr::clone_last_pair_double() +{ + clone_internal("last_pair_double"); +} + +#include + +void FGViewMgr::clone_internal(const std::string& type) +{ + SviewCreate(type); +} + void FGViewMgr::add_view( flightgear::View * v ) { diff --git a/src/Viewer/viewmgr.hxx b/src/Viewer/viewmgr.hxx index 27a494259..d2215f168 100644 --- a/src/Viewer/viewmgr.hxx +++ b/src/Viewer/viewmgr.hxx @@ -75,6 +75,19 @@ public: flightgear::View* next_view(); flightgear::View* prev_view(); + // + void view_push(); + + // Experimental. Only works if --compositer-viewer=1 was specified. Creates + // new window with clone of current view. As of 2020-09-03, the clone's + // scenery is not displayed correctly. + void clone_current_view(); + + // + void clone_last_pair(); + + void clone_last_pair_double(); + // setters void clear(); @@ -84,6 +97,7 @@ private: simgear::TiedPropertyList _tiedProperties; void setCurrentViewIndex(int newview); + void clone_internal(const std::string& type); bool _inited = false; std::vector config_list; diff --git a/test_suite/FGTestApi/scene_graph.cxx b/test_suite/FGTestApi/scene_graph.cxx index b053987e5..6818f8984 100644 --- a/test_suite/FGTestApi/scene_graph.cxx +++ b/test_suite/FGTestApi/scene_graph.cxx @@ -45,7 +45,7 @@ void initScenery() osg::ref_ptr viewer = new osgViewer::Viewer; FGRenderer* render = globals->get_renderer(); render->init(); - render->setViewer(viewer.get()); + render->setView(viewer.get()); // Start up the scenery subsystem. globals->add_new_subsystem(SGSubsystemMgr::DISPLAY);