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);