first commit

This commit is contained in:
Your Name
2022-10-20 20:29:11 +08:00
commit 4d531f8044
3238 changed files with 1387862 additions and 0 deletions

45
src/Viewer/CMakeLists.txt Normal file
View File

@@ -0,0 +1,45 @@
include(FlightGearComponent)
set(SOURCES
CameraGroup.cxx
FGEventHandler.cxx
WindowBuilder.cxx
WindowSystemAdapter.cxx
fg_os_osgviewer.cxx
fgviewer.cxx
ViewPropertyEvaluator.cxx
renderer.cxx
splash.cxx
view.cxx
viewmgr.cxx
sview.cxx
GraphicsPresets.cxx
)
set(HEADERS
CameraGroup.hxx
FGEventHandler.hxx
WindowBuilder.hxx
WindowSystemAdapter.hxx
fgviewer.hxx
renderer.hxx
splash.hxx
view.hxx
viewmgr.hxx
sview.hxx
GraphicsPresets.hxx
VRManager.hxx
)
if (ENABLE_OSGXR)
list(APPEND SOURCES
VRManager.cxx
)
endif()
if (YES)
list(APPEND HEADERS PUICamera.hxx)
list(APPEND SOURCES PUICamera.cxx)
endif()
flightgear_component(Viewer "${SOURCES}" "${HEADERS}")

1200
src/Viewer/CameraGroup.cxx Normal file

File diff suppressed because it is too large Load Diff

290
src/Viewer/CameraGroup.hxx Normal file
View File

@@ -0,0 +1,290 @@
// Copyright (C) 2008 Tim Moore
//
// 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.
#ifndef CAMERAGROUP_HXX
#define CAMERAGROUP_HXX 1
#include <map>
#include <string>
#include <vector>
#include <memory>
#include <osg/Matrix>
#include <osg/ref_ptr>
#include <osg/Referenced>
#include <osg/Node>
#include <osg/TextureRectangle>
#include <osg/Texture2D>
#include <osg/TexGen>
#include <osgUtil/RenderBin>
#include <osgViewer/View>
#include <simgear/scene/viewer/Compositor.hxx>
namespace osg
{
class Camera;
}
namespace osgViewer
{
class Viewer;
}
class SGPropertyNode;
namespace flightgear
{
class CameraGroupListener;
class GraphicsWindow;
class CameraGroup;
/** A wrapper around osg::Camera that contains some extra information.
*/
struct CameraInfo : public osg::Referenced
{
/** properties of a camera.
*/
enum Flags
{
VIEW_ABSOLUTE = 0x1, /**< The camera view is absolute, not
relative to the master camera. */
PROJECTION_ABSOLUTE = 0x2, /**< The projection is absolute. */
ORTHO = 0x4, /**< The projection is orthographic */
GUI = 0x8, /**< Camera draws the GUI. */
DO_INTERSECTION_TEST = 0x10,/**< scene intersection tests this
camera. */
FIXED_NEAR_FAR = 0x20, /**< take the near far values in the
projection for real. */
ENABLE_MASTER_ZOOM = 0x40, /**< Can apply the zoom algorithm. */
VR_MIRROR = 0x80, /**< Switch to a mirror of VR. */
SPLASH = 0x100 /**< For splash screen. */
};
CameraInfo(unsigned flags_) :
flags(flags_),
physicalWidth(0), physicalHeight(0), bezelHeightTop(0),
bezelHeightBottom(0), bezelWidthLeft(0), bezelWidthRight(0),
relativeCameraParent(0), reloadCompositorCallback(nullptr) { }
/** The name as given in the config file.
*/
std::string name;
/** Properties of the camera. @see CameraGroup::Flags.
*/
unsigned flags;
/** Physical size parameters.
*/
double physicalWidth;
double physicalHeight;
double bezelHeightTop;
double bezelHeightBottom;
double bezelWidthLeft;
double bezelWidthRight;
/** Non-owning reference to the parent camera for relative camera
* configurations.
*/
const CameraInfo *relativeCameraParent;
/** The reference points in the parents projection space.
*/
osg::Vec2d parentReference[2];
/** The reference points in the current projection space.
*/
osg::Vec2d thisReference[2];
/** View offset from the viewer master camera.
*/
osg::Matrix viewOffset;
/** Projection offset from the viewer master camera.
*/
osg::Matrix projOffset;
/** Current view and projection matrices for this camera.
* They are only used by other child cameras through relativeCameraParent
* so they can avoid recalculating them.
*/
osg::Matrix viewMatrix, projMatrix;
/** The Compositor used to manage the pipeline of this camera.
*/
std::unique_ptr<simgear::compositor::Compositor> compositor;
/** Compositor path. Used to recreate the pipeline when reloading.
* If the path is empty, it means that this camera isn't using a custom
* Compositor path and should use the default one.
*/
std::string compositor_path;
struct ReloadCompositorCallback : public virtual osg::Referenced
{
virtual void preReloadCompositor(CameraGroup *, CameraInfo *) = 0;
virtual void postReloadCompositor(CameraGroup *, CameraInfo *) = 0;
};
osg::ref_ptr<ReloadCompositorCallback> reloadCompositorCallback;
};
class CameraGroup : public osg::Referenced
{
public:
/** Create a camera group associated with an osgViewer::Viewer.
* @param viewer the 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::View* view);
static void setDefault(CameraGroup* group) { _defaultGroup = group; }
/** Get the default CameraGroup.
* @return the default camera group.
*/
static CameraGroup* getDefault() { return _defaultGroup.get(); }
/** Get the camera group's Viewer.
* @return the viewer
*/
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.
* @return a CameraInfo object for the camera.
*/
CameraInfo* buildCamera(SGPropertyNode* cameraNode);
/** Remove a camera from the camera group.
* @param info the camera info to remove.
*/
void removeCamera(CameraInfo *info);
/** Create a camera from properties that will draw the splash screen and add
* it to the camera group.
* @param cameraNode the property node. This can be 0, in which
* case a default GUI camera is created.
* @param window the GraphicsWindow to use for the splash camera. If
* this is 0, the window is determined from the property node.
* @return a CameraInfo object for the GUI camera.
*/
void buildSplashCamera(SGPropertyNode* cameraNode,
GraphicsWindow* window = 0);
/** Create a camera from properties that will draw the GUI and add
* it to the camera group.
* @param cameraNode the property node. This can be 0, in which
* case a default GUI camera is created.
* @param window the GraphicsWindow to use for the GUI camera. If
* this is 0, the window is determined from the property node.
* @return a CameraInfo object for the GUI camera.
*/
void buildGUICamera(SGPropertyNode* cameraNode,
GraphicsWindow* window = 0);
/** Update the view for the camera group.
* @param position the world position of the view
* @param orientation the world orientation of the view.
*/
void update(const osg::Vec3d& position,
const osg::Quat& orientation);
/** Set the parameters of the viewer's master camera. This won't
* affect cameras that have CameraFlags::PROJECTION_ABSOLUTE set.
* XXX Should znear and zfar be settable?
* @param vfov the vertical field of view angle
* @param aspectRatio the master camera's aspect ratio. This
* doesn't actually change the viewport, but should reflect the
* current viewport.
*/
void setCameraParameters(float vfov, float aspectRatio);
/** Set the cull mask on all non-GUI cameras
*/
void setCameraCullMasks(osg::Node::NodeMask nm);
/** Update camera properties after a resize event.
*/
void resized();
void buildDistortionCamera(const SGPropertyNode* psNode,
osg::Camera* camera);
/**
* get aspect ratio of master camera's viewport
*/
double getMasterAspectRatio() const;
CameraInfo *getGUICamera() const;
typedef std::vector<osg::ref_ptr<CameraInfo>> CameraList;
const CameraList& getCameras();
protected:
friend CameraGroupListener;
friend bool computeIntersections(const CameraGroup* cgroup,
const osg::Vec2d& windowPos,
osgUtil::LineSegmentIntersector::Intersections&
intersections);
friend void reloadCompositors(CameraGroup *cgroup);
CameraList _cameras;
osg::ref_ptr<osgViewer::View> _viewer;
static osg::ref_ptr<CameraGroup> _defaultGroup;
std::unique_ptr<CameraGroupListener> _listener;
// Near, far for the master camera if used.
float _zNear;
float _zFar;
float _nearField;
/** Create a compositor for a VR mirror.
* @param gc the graphics context to use.
* @param viewport the viewport to render to.
* @return a new compositor or nullptr if no VR mirror is needed.
*/
simgear::compositor::Compositor *buildVRMirrorCompositor(osg::GraphicsContext *gc,
osg::Viewport *viewport);
/** Build a complete CameraGroup from a property node.
* @param viewer the viewer associated with this camera group.
* @param wbuilder the window builder to be used for this camera group.
* @param the camera group property node.
*/
static CameraGroup* buildCameraGroup(osgViewer::View* viewer,
SGPropertyNode* node);
};
/** Get the osg::Camera that draws the GUI, if any, from a camera
* group.
* @param cgroup the camera group
* @return the GUI camera or 0
*/
osg::Camera* getGUICamera(CameraGroup* cgroup);
/** Choose a camera using an event and do intersection testing on its
* view of the scene. Only cameras with the DO_INTERSECTION_TEST flag
* set are considered.
* @param cgroup the CameraGroup
* @param ea the event containing a window and mouse coordinates
* @param intersections container for the result of intersection
* testing.
* @return true if any intersections are found
*/
bool computeIntersections(const CameraGroup* cgroup,
const osg::Vec2d& windowPos,
osgUtil::LineSegmentIntersector::Intersections&
intersections);
/** Warp the pointer to coordinates in the GUI camera of a camera group.
* @param cgroup the camera group
* @param x x window coordinate of pointer
* @param y y window coordinate of pointer, in "y down" coordinates.
*/
void warpGUIPointer(CameraGroup* cgroup, int x, int y);
/** Force a reload of all Compositor instances in the CameraGroup,
* except the one used by the GUI camera.
*/
void reloadCompositors(CameraGroup *cgroup);
}
#endif

View File

@@ -0,0 +1,547 @@
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <osg/Camera>
#include <osg/GraphicsContext>
#include <osg/Math>
#include <osg/Viewport>
#include <osgViewer/Viewer>
#include <GUI/FlightGear_pu.h>
#include <Main/fg_props.hxx>
#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
// a large number of resize events, without doing any drawing.
extern void puCleanUpJunk ( void ) ;
#endif
namespace flightgear
{
const int displayStatsKey = 1;
const int printStatsKey = 2;
// The manipulator is responsible for updating a Viewer's camera. Its
// event handling method is also a convenient place to run the FG idle
// and draw handlers.
FGEventHandler::FGEventHandler() :
idleHandler(0),
keyHandler(0),
mouseClickHandler(0),
mouseMotionHandler(0),
statsHandler(new FGStatsHandler),
statsEvent(new osgGA::GUIEventAdapter),
statsType(osgViewer::StatsHandler::NO_STATS),
currentModifiers(0),
resizable(true),
mouseWarped(false),
scrollButtonPressed(false),
changeStatsCameraRenderOrder(false),
m_composite_viewer_enabled(fgGetNode("/sim/rendering/composite-viewer-enabled", true))
{
using namespace osgGA;
statsHandler->setKeyEventTogglesOnScreenStats(displayStatsKey);
statsHandler->setKeyEventPrintsOutStats(printStatsKey);
statsEvent->setEventType(GUIEventAdapter::KEYDOWN);
for (int i = 0; i < 128; i++)
release_keys[i] = i;
_display = fgGetNode("/sim/rendering/on-screen-statistics", true);
_print = fgGetNode("/sim/rendering/print-statistics", true);
}
void FGEventHandler::clear()
{
_display.clear();
_print.clear();
}
void FGEventHandler::reset()
{
_display = fgGetNode("/sim/rendering/on-screen-statistics", true);
_print = fgGetNode("/sim/rendering/print-statistics", true);
statsHandler->reset();
}
#if 0
void FGEventHandler::init(const osgGA::GUIEventAdapter& ea,
osgGA::GUIActionAdapter& us)
{
currentModifiers = osgToFGModifiers(ea.getModKeyMask());
(void)handle(ea, us);
}
#endif
// Calculate event coordinates in the viewport of the GUI camera, if
// possible. Otherwise sets (x, y) to (-1, -1).
FGEventHandler::WindowType
FGEventHandler::eventToViewport(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& us,
int& x, int& y)
{
WindowType ret = WindowType_NONE;
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 WindowType_NONE; // TODO how can this happen?
const osg::GraphicsContext::Traits* traits = eventGC->getTraits();
osg::Viewport* vport;
if (m_composite_viewer_enabled->getBoolValue()
&& eventGC != main_window->gc.get()) {
// CompositeViewer is enabled and this is not the main window.
simgear::compositor::Compositor* compositor = SviewGetEventViewport(ea);
if (!compositor) {
SG_LOG(SG_GENERAL, SG_DEBUG, "SviewGetEventViewport() returned nullptr");
return WindowType_NONE;
}
vport = compositor->getViewport();
if (!vport) {
SG_LOG(SG_GENERAL, SG_ALERT, "compositor->getViewport() is nullptr");
return WindowType_NONE;
}
ret = WindowType_SVIEW;
}
else {
osg::Camera* guiCamera = getGUICamera(CameraGroup::getDefault());
if (!guiCamera)
return WindowType_NONE;
vport = guiCamera->getViewport();
if (!vport)
return WindowType_NONE;
ret = WindowType_MAIN;
}
// Scale x, y to the dimensions of the window
double wx = (((ea.getX() - ea.getXmin()) / (ea.getXmax() - ea.getXmin()))
* (float)traits->width);
double wy = (((ea.getY() - ea.getYmin()) / (ea.getYmax() - ea.getYmin()))
* (float)traits->height);
if (vport->x() <= wx && wx <= vport->x() + vport->width()
&& vport->y() <= wy && wy <= vport->y() + vport->height()) {
// Finally, into viewport coordinates. Change y to "increasing
// downwards".
x = wx - vport->x();
y = vport->height() - (wy - vport->y());
}
return ret;
}
/* 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 <ea>'s graphics context pointer so this
does't alwys work. */
bool FGEventHandler::isMainWindow(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& us)
{
int x;
int y;
return eventToViewport(ea, us, x, y) == WindowType_MAIN;
}
void FGEventHandler::setWindowRectangleInteriorWithCorrection(osgViewer::GraphicsWindow* window, int x, int y, int width, int height)
{
// Store (x y) in our state so that our handle() event handler can
// compare the requested position with the actual position and update
// (m_setWindowRectangle_delta_x m_setWindowRectangle_delta_y) accordingly.
//
SG_LOG(SG_VIEW, SG_DEBUG, "FGEventHandler::setWindowRectangle(): pos=(" << x << " " << y << ")"
<< " delta=(" << m_setWindowRectangle_delta_x << " " << m_setWindowRectangle_delta_y << ")"
);
m_setWindowRectangle_called = true;
m_setWindowRectangle_called_x = x;
m_setWindowRectangle_called_y = y;
window->setWindowRectangle(
x - m_setWindowRectangle_delta_x,
y - m_setWindowRectangle_delta_y,
width,
height
);
}
bool FGEventHandler::handle(const osgGA::GUIEventAdapter& ea,
osgGA::GUIActionAdapter& us)
{
// Event handlers seem to be called even if the according event has already
// been handled. Already handled events shouldn't be handled multiple times
// so we need to exit here manually.
if (ea.getHandled()
// Let mouse move events pass to correctly handle mouse cursor hide
// timeout while moving just on the canvas gui.
// TODO We should clean up the whole mouse input and make hide
// timeout independent of the event handler which consumed the
// event.
// if we see a release, still process for active pick callbacks
// https://sourceforge.net/p/flightgear/codetickets/2347/
&& ea.getEventType() != osgGA::GUIEventAdapter::MOVE && ea.getEventType() != osgGA::GUIEventAdapter::DRAG && ea.getEventType() != osgGA::GUIEventAdapter::RELEASE) {
return false;
}
int x = 0;
int y = 0;
switch (ea.getEventType()) {
case osgGA::GUIEventAdapter::FRAME:
mouseWarped = false;
handleStats(us);
return true;
case osgGA::GUIEventAdapter::KEYDOWN:
case osgGA::GUIEventAdapter::KEYUP:
{
int key, modmask;
handleKey(ea, key, modmask);
eventToViewport(ea, us, x, y);
if (keyHandler)
(*keyHandler)(key, modmask, x, y);
return true;
}
case osgGA::GUIEventAdapter::PUSH:
case osgGA::GUIEventAdapter::RELEASE:
{
WindowType window_type = eventToViewport(ea, us, x, y);
bool mainWindow = (window_type == WindowType_MAIN);
int button = 0;
switch (ea.getButton()) {
case osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON:
button = 0;
break;
case osgGA::GUIEventAdapter::MIDDLE_MOUSE_BUTTON:
button = 1;
break;
case osgGA::GUIEventAdapter::RIGHT_MOUSE_BUTTON:
button = 2;
break;
}
if (mouseClickHandler)
(*mouseClickHandler)(button,
(ea.getEventType()
== osgGA::GUIEventAdapter::RELEASE), x, y, mainWindow, &ea);
return true;
}
case osgGA::GUIEventAdapter::SCROLL:
{
WindowType window_type = eventToViewport(ea, us, x, y);
bool mainWindow = (window_type == WindowType_MAIN);
int button;
if (ea.getScrollingMotion() == osgGA::GUIEventAdapter::SCROLL_2D) {
if (ea.getScrollingDeltaY() > 0)
button = 3;
else if (ea.getScrollingDeltaY() < 0)
button = 4;
else
button = -1;
#if defined(SG_MAC)
// bug https://code.google.com/p/flightgear-bugs/issues/detail?id=1286
// Mac (Cocoa) interprets shuft+wheel as horizontal scroll
if (ea.getModKeyMask() & osgGA::GUIEventAdapter::MODKEY_SHIFT) {
if (ea.getScrollingDeltaX() > 0)
button = 3;
else if (ea.getScrollingDeltaX() < 0)
button = 4;
}
#endif
} else if (ea.getScrollingMotion() == osgGA::GUIEventAdapter::SCROLL_UP)
button = 3;
else
button = 4;
if (mouseClickHandler && button != -1) {
(*mouseClickHandler)(button, 0, x, y, mainWindow, &ea);
(*mouseClickHandler)(button, 1, x, y, mainWindow, &ea);
}
return true;
}
case osgGA::GUIEventAdapter::MOVE:
case osgGA::GUIEventAdapter::DRAG:
// If we warped the mouse, then disregard all pointer motion
// events for this frame. We really want to flush the event
// queue of mouse events, but don't have the ability to do
// that with osgViewer.
if (mouseWarped)
return true;
if (eventToViewport(ea, us, x, y) != WindowType_NONE && mouseMotionHandler)
(*mouseMotionHandler)(x, y, &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) {
if (m_setWindowRectangle_called) {
// Update m_setWindowRectangle_delta_x and
// m_setWindowRectangle_delta_y so that our
// setWindowRectangle() compensates for the window furniture
// differences in future calls.
//
m_setWindowRectangle_called = false;
int error_x = ea.getWindowX() - m_setWindowRectangle_called_x;
int error_y = ea.getWindowY() - m_setWindowRectangle_called_y;
SG_LOG(SG_VIEW, SG_BULK, "m_setWindowRectangle_called is set:"
<< " m_setWindowRectangle_delta=("
<< m_setWindowRectangle_delta_x << " " << m_setWindowRectangle_delta_y << ")"
<< " m_setWindowRectangle_called_=("
<< m_setWindowRectangle_called_x << " " << m_setWindowRectangle_called_y << ")"
<< " ea.getWindow=(" << ea.getWindowX() << " " << ea.getWindowY() << ")"
<< " error=(" << error_x << " " << error_y << ")"
);
m_setWindowRectangle_delta_x += error_x;
m_setWindowRectangle_delta_y += error_y;
}
globals->get_renderer()->resize(ea.getWindowWidth(), ea.getWindowHeight(), ea.getWindowX(), ea.getWindowY());
}
statsHandler->handle(ea, us);
#ifdef SG_MAC
// work around OSG Cocoa-Viewer issue with resize event handling,
// where resize events are queued up, then dispatched in a batch, with
// no interveningd drawing calls.
puCleanUpJunk();
#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;
default:
return false;
}
}
int FGEventHandler::translateKey(const osgGA::GUIEventAdapter& ea)
{
using namespace osgGA;
static std::map<int, int> numlockKeyMap;
static std::map<int, int> noNumlockKeyMap;
if (numlockKeyMap.empty()) {
// init these first time around
// OSG reports NumPad keycodes independent of the NumLock modifier.
// Both KP-4 and KP-Left are reported as KEY_KP_Left (0xff96), so we
// have to generate the locked keys ourselves.
numlockKeyMap[GUIEventAdapter::KEY_KP_Insert] = '0';
numlockKeyMap[GUIEventAdapter::KEY_KP_End] = '1';
numlockKeyMap[GUIEventAdapter::KEY_KP_Down] = '2';
numlockKeyMap[GUIEventAdapter::KEY_KP_Page_Down] = '3';
numlockKeyMap[GUIEventAdapter::KEY_KP_Left] = '4';
numlockKeyMap[GUIEventAdapter::KEY_KP_Begin] = '5';
numlockKeyMap[GUIEventAdapter::KEY_KP_Right] = '6';
numlockKeyMap[GUIEventAdapter::KEY_KP_Home] = '7';
numlockKeyMap[GUIEventAdapter::KEY_KP_Up] = '8';
numlockKeyMap[GUIEventAdapter::KEY_KP_Page_Up] = '9';
numlockKeyMap[GUIEventAdapter::KEY_KP_Delete] = '.';
// The comment above is incorrect on Mac osgViewer, at least. So we
// need to map the 'num-locked' key codes to real values.
numlockKeyMap[GUIEventAdapter::KEY_KP_0] = '0';
numlockKeyMap[GUIEventAdapter::KEY_KP_1] = '1';
numlockKeyMap[GUIEventAdapter::KEY_KP_2] = '2';
numlockKeyMap[GUIEventAdapter::KEY_KP_3] = '3';
numlockKeyMap[GUIEventAdapter::KEY_KP_4] = '4';
numlockKeyMap[GUIEventAdapter::KEY_KP_5] = '5';
numlockKeyMap[GUIEventAdapter::KEY_KP_6] = '6';
numlockKeyMap[GUIEventAdapter::KEY_KP_7] = '7';
numlockKeyMap[GUIEventAdapter::KEY_KP_8] = '8';
numlockKeyMap[GUIEventAdapter::KEY_KP_9] = '9';
numlockKeyMap[GUIEventAdapter::KEY_KP_Decimal] = '.';
// mapping when NumLock is off
noNumlockKeyMap[GUIEventAdapter::KEY_KP_Insert] = PU_KEY_INSERT;
noNumlockKeyMap[GUIEventAdapter::KEY_KP_End] = PU_KEY_END;
noNumlockKeyMap[GUIEventAdapter::KEY_KP_Down] = PU_KEY_DOWN;
noNumlockKeyMap[GUIEventAdapter::KEY_KP_Page_Down] = PU_KEY_PAGE_DOWN;
noNumlockKeyMap[GUIEventAdapter::KEY_KP_Left] = PU_KEY_LEFT;
noNumlockKeyMap[GUIEventAdapter::KEY_KP_Begin] = '5';
noNumlockKeyMap[GUIEventAdapter::KEY_KP_Right] = PU_KEY_RIGHT;
noNumlockKeyMap[GUIEventAdapter::KEY_KP_Home] = PU_KEY_HOME;
noNumlockKeyMap[GUIEventAdapter::KEY_KP_Up] = PU_KEY_UP;
noNumlockKeyMap[GUIEventAdapter::KEY_KP_Page_Up] = PU_KEY_PAGE_UP;
noNumlockKeyMap[GUIEventAdapter::KEY_KP_Delete] = 127;
}
int key = ea.getKey();
// XXX Probably other translations are needed too.
switch (key) {
case GUIEventAdapter::KEY_Escape: key = 0x1b; break;
case GUIEventAdapter::KEY_Return: key = '\n'; break;
case GUIEventAdapter::KEY_BackSpace: key = '\b'; break;
case GUIEventAdapter::KEY_Delete: key = 0x7f; break;
case GUIEventAdapter::KEY_Tab: key = '\t'; break;
case GUIEventAdapter::KEY_Left: key = PU_KEY_LEFT; break;
case GUIEventAdapter::KEY_Up: key = PU_KEY_UP; break;
case GUIEventAdapter::KEY_Right: key = PU_KEY_RIGHT; break;
case GUIEventAdapter::KEY_Down: key = PU_KEY_DOWN; break;
case GUIEventAdapter::KEY_Page_Up: key = PU_KEY_PAGE_UP; break;
case GUIEventAdapter::KEY_Page_Down: key = PU_KEY_PAGE_DOWN; break;
case GUIEventAdapter::KEY_Home: key = PU_KEY_HOME; break;
case GUIEventAdapter::KEY_End: key = PU_KEY_END; break;
case GUIEventAdapter::KEY_Insert: key = PU_KEY_INSERT; break;
case GUIEventAdapter::KEY_F1: key = PU_KEY_F1; break;
case GUIEventAdapter::KEY_F2: key = PU_KEY_F2; break;
case GUIEventAdapter::KEY_F3: key = PU_KEY_F3; break;
case GUIEventAdapter::KEY_F4: key = PU_KEY_F4; break;
case GUIEventAdapter::KEY_F5: key = PU_KEY_F5; break;
case GUIEventAdapter::KEY_F6: key = PU_KEY_F6; break;
case GUIEventAdapter::KEY_F7: key = PU_KEY_F7; break;
case GUIEventAdapter::KEY_F8: key = PU_KEY_F8; break;
case GUIEventAdapter::KEY_F9: key = PU_KEY_F9; break;
case GUIEventAdapter::KEY_F10: key = PU_KEY_F10; break;
case GUIEventAdapter::KEY_F11: key = PU_KEY_F11; break;
case GUIEventAdapter::KEY_F12: key = PU_KEY_F12; break;
case GUIEventAdapter::KEY_KP_Enter: key = '\r'; break;
case GUIEventAdapter::KEY_KP_Add: key = '+'; break;
case GUIEventAdapter::KEY_KP_Divide: key = '/'; break;
case GUIEventAdapter::KEY_KP_Multiply: key = '*'; break;
case GUIEventAdapter::KEY_KP_Subtract: key = '-'; break;
}
#ifdef __APPLE__
// Num Lock is always true on Mac
auto numPadIter = numlockKeyMap.find(key);
if (numPadIter != numlockKeyMap.end()) {
key = numPadIter->second;
}
#else
if (ea.getModKeyMask() & osgGA::GUIEventAdapter::MODKEY_NUM_LOCK) {
// NumLock on: map to numeric keys
auto numPadIter = numlockKeyMap.find(key);
if (numPadIter != numlockKeyMap.end()) {
key = numPadIter->second;
}
} else {
// NumLock off: map to PU arrow keys
auto numPadIter = noNumlockKeyMap.find(key);
if (numPadIter != noNumlockKeyMap.end()) {
key = numPadIter->second;
}
}
#endif
return key;
}
int FGEventHandler::translateModifiers(const osgGA::GUIEventAdapter& ea)
{
int result = 0;
const auto modifiers = ea.getModKeyMask();
if (modifiers & osgGA::GUIEventAdapter::MODKEY_SHIFT)
result |= KEYMOD_SHIFT;
if (modifiers & osgGA::GUIEventAdapter::MODKEY_CTRL)
result |= KEYMOD_CTRL;
if (modifiers & osgGA::GUIEventAdapter::MODKEY_ALT)
result |= KEYMOD_ALT;
if (modifiers & osgGA::GUIEventAdapter::MODKEY_META)
result |= KEYMOD_META;
if (modifiers & osgGA::GUIEventAdapter::MODKEY_SUPER)
result |= KEYMOD_SUPER;
if (modifiers & osgGA::GUIEventAdapter::MODKEY_HYPER)
result |= KEYMOD_HYPER;
return result;
}
void FGEventHandler::handleKey(const osgGA::GUIEventAdapter& ea, int& key,
int& modifiers)
{
key = translateKey(ea);
modifiers = translateModifiers(ea);
currentModifiers = modifiers;
const auto eventType = ea.getEventType();
if (eventType == osgGA::GUIEventAdapter::KEYUP)
modifiers |= KEYMOD_RELEASED;
// Release the letter key, for which the key press was reported. This
// is to deal with Ctrl-press -> a-press -> Ctrl-release -> a-release
// correctly.
if (key >= 0 && key < 128) {
if (modifiers & KEYMOD_RELEASED) {
key = release_keys[key];
} else {
release_keys[key] = key;
if (key >= 1 && key <= 26) {
release_keys[key + '@'] = key;
release_keys[key + '`'] = key;
} else if (key >= 'A' && key <= 'Z') {
release_keys[key - '@'] = key;
release_keys[tolower(key)] = key;
} else if (key >= 'a' && key <= 'z') {
release_keys[key - '`'] = key;
release_keys[toupper(key)] = key;
}
}
}
}
void FGEventHandler::handleStats(osgGA::GUIActionAdapter& us)
{
int type = _display->getIntValue() % osgViewer::StatsHandler::LAST;
if (type != statsType) {
statsEvent->setKey(displayStatsKey);
do {
statsType = (statsType + 1) % osgViewer::StatsHandler::LAST;
statsHandler->handle(*statsEvent, us);
if (changeStatsCameraRenderOrder) {
statsHandler->getCamera()->setRenderOrder(osg::Camera::POST_RENDER, 99999);
changeStatsCameraRenderOrder = false;
}
} while (statsType != type);
_display->setIntValue(statsType);
}
if (_print->getBoolValue()) {
statsEvent->setKey(printStatsKey);
statsHandler->handle(*statsEvent, us);
_print->setBoolValue(false);
}
}
bool eventToWindowCoords(const osgGA::GUIEventAdapter* ea,
double& x, double& y)
{
using namespace osg;
const GraphicsContext* gc = ea->getGraphicsContext();
if (!gc || !gc->getTraits()) {
return false;
}
const GraphicsContext::Traits* traits = gc->getTraits() ;
// Scale x, y to the dimensions of the window
x = (((ea->getX() - ea->getXmin()) / (ea->getXmax() - ea->getXmin()))
* (double)traits->width);
y = (((ea->getY() - ea->getYmin()) / (ea->getYmax() - ea->getYmin()))
* (double)traits->height);
if (ea->getMouseYOrientation() == osgGA::GUIEventAdapter::Y_INCREASING_DOWNWARDS)
y = (double)traits->height - y;
return true;
}
}

View File

@@ -0,0 +1,155 @@
#ifndef FGEVENTHANDLER_H
#define FGEVENTHANDLER_H 1
#include <map>
#include <osg/Quat>
#include <osgGA/GUIEventHandler>
#include <osgViewer/ViewerEventHandlers>
#include <Main/fg_os.hxx>
namespace flightgear
{
class FGStatsHandler : public osgViewer::StatsHandler
{
public:
FGStatsHandler()
{
// Adjust font type/size for >=OSG3.
// OSG defaults aren't working/available for FG.
_font = "Fonts/helvetica_medium.txf";
_characterSize = 12.0f;
}
};
class FGEventHandler : public osgGA::GUIEventHandler {
public:
FGEventHandler();
virtual ~FGEventHandler() {}
virtual const char* className() const {return "FGEventHandler"; }
#if 0
virtual void init(const osgGA::GUIEventAdapter& ea,
osgGA::GUIActionAdapter& us);
#endif
bool handle(const osgGA::GUIEventAdapter& ea,
osgGA::GUIActionAdapter& us) override;
void setIdleHandler(fgIdleHandler idleHandler)
{
this->idleHandler = idleHandler;
}
fgIdleHandler getIdleHandler() const
{
return idleHandler;
}
void setKeyHandler(fgKeyHandler keyHandler)
{
this->keyHandler = keyHandler;
}
fgKeyHandler getKeyHandler() const
{
return keyHandler;
}
void setMouseClickHandler(fgMouseClickHandler mouseClickHandler)
{
this->mouseClickHandler = mouseClickHandler;
}
fgMouseClickHandler getMouseClickHandler()
{
return mouseClickHandler;
}
void setMouseMotionHandler(fgMouseMotionHandler mouseMotionHandler)
{
this->mouseMotionHandler = mouseMotionHandler;
}
fgMouseMotionHandler getMouseMotionHandler()
{
return mouseMotionHandler;
}
void setChangeStatsCameraRenderOrder(bool c)
{
changeStatsCameraRenderOrder = c;
}
int getCurrentModifiers() const
{
return currentModifiers;
}
void setMouseWarped()
{
mouseWarped = true;
}
/** Whether or not resizing is supported. It might not be when
* using multiple displays.
*/
bool getResizable() { return resizable; }
void setResizable(bool _resizable) { resizable = _resizable; }
void reset();
void clear();
// Wrapper for osgViewer::GraphicsWindow::setWindowRectangle() that takes
// coordinates excluding window furniture.
//
void setWindowRectangleInteriorWithCorrection(osgViewer::GraphicsWindow* window, int x, int y, int width, int height);
static int translateKey(const osgGA::GUIEventAdapter& ea);
static int translateModifiers(const osgGA::GUIEventAdapter& ea);
protected:
osg::ref_ptr<osg::Node> _node;
fgIdleHandler idleHandler;
fgKeyHandler keyHandler;
fgMouseClickHandler mouseClickHandler;
fgMouseMotionHandler mouseMotionHandler;
osg::ref_ptr<FGStatsHandler> statsHandler;
osg::ref_ptr<osgGA::GUIEventAdapter> statsEvent;
int statsType;
int currentModifiers;
void handleKey(const osgGA::GUIEventAdapter& ea, int& key, int& modifiers);
bool resizable;
bool mouseWarped;
// workaround for osgViewer double scroll events
bool scrollButtonPressed;
int release_keys[128];
void handleStats(osgGA::GUIActionAdapter& us);
bool changeStatsCameraRenderOrder;
SGPropertyNode_ptr _display, _print;
private:
bool m_setWindowRectangle_called = false;
int m_setWindowRectangle_called_x = 0;
int m_setWindowRectangle_called_y = 0;
int m_setWindowRectangle_delta_x = 0;
int m_setWindowRectangle_delta_y = 0;
SGPropertyNode_ptr m_composite_viewer_enabled;
enum WindowType
{
WindowType_NONE,
WindowType_MAIN,
WindowType_SVIEW
};
WindowType
eventToViewport(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& us,
int& x, int& y);
bool isMainWindow(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& us);
};
bool eventToWindowCoords(const osgGA::GUIEventAdapter* ea, double& x, double& y);
}
#endif

View File

@@ -0,0 +1,550 @@
#include "config.h"
#include <Viewer/GraphicsPresets.hxx>
// std
#include <unordered_set>
// SG
#include <simgear/io/iostreams/sgstream.hxx>
#include <simgear/misc/sg_dir.hxx>
#include <simgear/props/props_io.hxx>
#include <simgear/structure/commands.hxx>
#include <simgear/structure/exception.hxx>
// FG
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include <Main/locale.hxx>
#include <Main/sentryIntegration.hxx>
#include <Scenery/scenery.hxx>
using namespace std;
namespace {
const char* kPresetPropPath = "/sim/rendering/preset";
const char* kPresetNameProp = "/sim/rendering/preset-name";
const char* kPresetDescriptionProp = "/sim/rendering/preset-description";
const char* kPresetActiveProp = "/sim/rendering/preset-active";
const char* kRestartRequiredProp = "/sim/rendering/restart-required";
const char* kSceneryReloadRequiredProp = "/sim/rendering/scenery-reload-required";
const char* kCompositorReloadRequiredProp = "/sim/rendering/compositor-reload-required";
// define the property prefixes which graphics presets are allowed to
// modify. Changes to properties outside these prefixes will be
// blocked
const string_list kWitelistedPrefixes = {
"/sim/rendering"};
} // namespace
namespace flightgear {
class GraphicsPresets::GraphicsConfigChangeListener : public SGPropertyChangeListener
{
struct WatchedProp {
std::string previousValue;
SGPropertyNode_ptr node;
};
using PropertyNodeList = std::vector<WatchedProp>;
PropertyNodeList _watchedProps;
public:
GraphicsConfigChangeListener()
{
_presetProp = fgGetNode(kPresetPropPath, true);
}
void registerWithProperty(SGPropertyNode_ptr n)
{
auto it = findProp(n);
if (it != _watchedProps.end()) {
// this would happen if a preset somehow set the same property more than once
SG_LOG(SG_GUI, SG_ALERT, "GraphicsPresets: Duplicate registration for:" << n->getPath());
return;
}
WatchedProp p = {n->getStringValue(), n};
_watchedProps.push_back(p);
n->addChangeListener(this);
}
void unregisterFromProperties()
{
for (const auto& w : _watchedProps) {
w.node->removeChangeListener(this);
}
_watchedProps.clear();
}
void valueChanged(SGPropertyNode* prop) override
{
if (!_presetProp->hasValue()) {
return;
}
auto it = findProp(prop);
assert(it != _watchedProps.end());
const std::string newValue = prop->getStringValue();
if (newValue == it->previousValue) {
return;
}
SG_LOG(SG_GUI, SG_INFO, "GraphicsPreset clearing; setting:" << prop->getPath() << " was modified");
flightgear::addSentryBreadcrumb("clearing graphics preset, config was customised at:" + prop->getPath(), "info");
_presetProp->clearValue();
auto gp = globals->get_subsystem<GraphicsPresets>();
gp->clearPreset();
}
private:
SGPropertyNode_ptr _presetProp;
PropertyNodeList::iterator findProp(SGPropertyNode* node)
{
return std::find_if(_watchedProps.begin(), _watchedProps.end(), [node](const WatchedProp& w) {
return w.node == node;
});
}
};
/**
@brief monitor a collection of properties, and set a flag property to true when any of them are
modified. Used to track the list of 'reload-required' and 'restart-requried' properties defined in
FG_DATA/Video/graphics-properties.xml
*/
class GraphicsPresets::RequiredPropertyListener : public SGPropertyChangeListener
{
public:
RequiredPropertyListener(const std::string& requiredProp, SGPropertyNode_ptr props)
{
_requiredProp = fgGetNode(requiredProp, true);
_requiredProp->setBoolValue(false);
// would happen if graphics-properties.xml was malformed
if (!props)
return;
for (const auto& c : props->getChildren("property")) {
// tolerate exterior whitespace in the XML
string path = simgear::strutils::strip(c->getStringValue());
if (path.empty())
continue;
SGPropertyNode_ptr n = fgGetNode(path, true);
if (n) {
n->addChangeListener(this);
}
} // of properties iteration
}
void clearRequiredFlag()
{
if (_requiredProp->getBoolValue()) {
_requiredProp->setBoolValue(false);
}
}
void valueChanged(SGPropertyNode* prop) override
{
if (!_requiredProp->getBoolValue()) {
SG_LOG(SG_GUI, SG_INFO, "GraphicsPreset: saw modification of:" << prop->getPath() << ", setting:" << _requiredProp->getPath() << " to true");
_requiredProp->setBoolValue(true);
}
}
private:
SGPropertyNode_ptr _requiredProp;
};
static bool do_apply_preset(const SGPropertyNode* arg, SGPropertyNode* root)
{
auto gp = globals->get_subsystem<GraphicsPresets>();
bool result = false;
if (arg->hasChild("path")) {
SGPath p = SGPath::fromUtf8(arg->getStringValue("path"));
if (!p.exists()) {
SG_LOG(SG_IO, SG_ALERT, "apply-graphics-preset: no file at:" << p);
return false;
}
result = gp->applyCustomPreset(p);
} else if (arg->hasChild("preset-name")) {
// helper for PUI UI: PUI ComboBox gives us the name, not the ID.
// so allow specify a preset by (localized) name.
gp->applyPresetByName(arg->getStringValue("preset-name"));
} else if (arg->hasChild("preset")) {
fgSetString(kPresetPropPath, arg->getStringValue("preset"));
result = gp->applyCurrentPreset();
} else {
// just apply the current selected one
result = gp->applyCurrentPreset();
}
if (arg->getBoolValue("reload-scenery")) {
if (fgGetBool(kSceneryReloadRequiredProp)) {
SG_LOG(SG_GUI, SG_MANDATORY_INFO, "apply-graphics-preset: triggering scenery reload");
globals->get_scenery()->reinit(); // this will set
}
}
return result;
}
static bool do_save_preset(const SGPropertyNode* arg, SGPropertyNode* root)
{
if (!arg->hasChild("path")) {
SG_LOG(SG_GUI, SG_ALERT, "do_save_preset: no out path argument provided");
return false;
}
const string spath = arg->getStringValue("path");
if (spath == "!ask") {
}
const SGPath path = SGPath::fromUtf8(spath);
const string name = arg->getStringValue("name");
const string description = arg->getStringValue("description");
auto gp = globals->get_subsystem<GraphicsPresets>();
return gp->saveToXML(path, name, description);
}
static bool do_list_standard_presets(const SGPropertyNode* arg, SGPropertyNode* root)
{
auto gp = globals->get_subsystem<GraphicsPresets>();
if (!arg->hasValue("destination-path")) {
SG_LOG(SG_GUI, SG_ALERT, "list-graphics-preset: no destination path supplied");
return false;
}
SGPropertyNode_ptr destRoot = fgGetNode(arg->getStringValue("destination-path"), true /* create */);
if (arg->hasValue("clear-destination")) {
destRoot->removeAllChildren();
}
// format the way PUI combo-box (actualy, fgValueList) like it
if (arg->getBoolValue("as-combobox-values")) {
for (const auto& preset : gp->listPresets()) {
SGPropertyNode_ptr v = destRoot->addChild("value");
v->setStringValue(preset.name);
}
} else {
for (const auto& preset : gp->listPresets()) {
SGPropertyNode_ptr pn = destRoot->addChild("preset");
pn->setStringValue("name", preset.name);
pn->setStringValue("id", preset.id);
pn->setStringValue("description", preset.description);
}
}
return true;
}
GraphicsPresets::GraphicsPresets()
{
// needs to be done early so that applyInitialPreset
// can setup the registration
_listener.reset(new GraphicsConfigChangeListener);
}
GraphicsPresets::~GraphicsPresets()
{
}
void GraphicsPresets::applyInitialPreset()
{
const string currentPreset = fgGetString(kPresetPropPath);
fgSetBool(kPresetActiveProp, false);
if (!currentPreset.empty()) {
SG_LOG(SG_GUI, SG_INFO, "Applying graphics preset:" << currentPreset);
addSentryBreadcrumb("Startup selection of preset:" + currentPreset, "info");
applyCurrentPreset();
}
}
void GraphicsPresets::init()
{
// create the change listeners. Because we do the initial application before this, we won't
// see any changes caused by any initial preset load (or autosave.xml load), only
// future changes made by the user via settings UI.
SGPropertyNode_ptr graphicsPropsXML(new SGPropertyNode);
try {
readProperties(globals->findDataPath("Video/graphics-properties.xml"), graphicsPropsXML.get());
_restartListener.reset(new RequiredPropertyListener{kRestartRequiredProp, graphicsPropsXML->getChild("restart-required")});
_sceneryReloadListener.reset(new RequiredPropertyListener{kSceneryReloadRequiredProp, graphicsPropsXML->getChild("scenery-reload-required")});
_compositorReloadListener.reset(new RequiredPropertyListener{kCompositorReloadRequiredProp, graphicsPropsXML->getChild("compositor-reload-required")});
SGPropertyNode_ptr toSave = graphicsPropsXML->getChild("save-to-file");
if (toSave) {
for (const auto& p : toSave->getChildren("property")) {
string t = simgear::strutils::strip(p->getStringValue());
if (t.at(0) == '/') {
t = t.substr(1); // remove leading '/'
}
_propertiesToSave.push_back(t);
}
}
} catch (sg_exception& e) {
SG_LOG(SG_GUI, SG_ALERT, "Failed to read graphics-properties.xml");
}
globals->get_commands()->addCommand("apply-graphics-preset", do_apply_preset);
globals->get_commands()->addCommand("save-graphics-preset", do_save_preset);
globals->get_commands()->addCommand("list-graphics-presets", do_list_standard_presets);
}
void GraphicsPresets::update(double delta_time_sec)
{
SG_UNUSED(delta_time_sec);
}
void GraphicsPresets::shutdown()
{
globals->get_commands()->removeCommand("apply-graphics-preset");
globals->get_commands()->removeCommand("save-graphics-preset");
globals->get_commands()->removeCommand("list-graphics-presets");
_listener->unregisterFromProperties();
_listener.reset();
}
bool GraphicsPresets::applyCurrentPreset()
{
_listener->unregisterFromProperties();
const string presetId = fgGetString(kPresetPropPath);
GraphicsPresetInfo info;
const auto ok = loadStandardPreset(presetId, info);
if (!ok) {
return false;
}
addSentryBreadcrumb("loading graphics preset:" + presetId, "info");
return innerApplyPreset(info, true);
}
bool GraphicsPresets::loadStandardPreset(const std::string& id, GraphicsPresetInfo& info)
{
const auto path = globals->findDataPath("Video/" + id + "-preset.xml");
if (!path.exists()) {
SG_LOG(SG_GUI, SG_ALERT, "No such graphics preset '" << id << "' found");
return false;
}
return loadPresetXML(path, info);
}
auto GraphicsPresets::listPresets() -> GraphicsPresetVec
{
GraphicsPresetVec result;
auto videoPaths = globals->get_data_paths("Video");
for (const auto& vp : videoPaths) {
simgear::Dir videoDir(vp);
for (const auto& presetFile : videoDir.children(simgear::Dir::TYPE_FILE, "-preset.xml")) {
GraphicsPresetInfo info;
loadPresetXML(presetFile, info);
result.push_back(info);
}
} // of Video/ data dirs iteration
// Sort the resulting list by the order number or alphabetically if some
// presets have the same order number
sort(result.begin(), result.end(), [](auto &a, auto &b) {
if (a.orderNum != b.orderNum)
return a.orderNum < b.orderNum;
return a.name < b.name;
});
return result;
}
bool GraphicsPresets::applyCustomPreset(const SGPath& path)
{
GraphicsPresetInfo info;
const auto ok = loadPresetXML(path, info);
if (!ok) {
return false;
}
addSentryBreadcrumb("loading graphics preset from:" + path.utf8Str(), "info");
return innerApplyPreset(info, true);
}
bool GraphicsPresets::applyPresetByName(const std::string& name)
{
const auto presets = listPresets();
auto it = std::find_if(presets.begin(), presets.end(), [name](const GraphicsPresetInfo& pi) { return simgear::strutils::iequals(name, pi.name); });
if (it == presets.end()) {
SG_LOG(SG_GUI, SG_ALERT, "Couldn't find graphics preset with name:" << name);
return false;
}
fgSetString(kPresetPropPath, it->id);
return applyCurrentPreset();
}
bool GraphicsPresets::innerApplyPreset(const GraphicsPresetInfo& info, bool overwriteAutosaved)
{
fgSetString(kPresetNameProp, info.name);
fgSetString(kPresetDescriptionProp, info.description);
if (info.id == "custom") {
fgSetBool(kPresetActiveProp, false);
}
std::unordered_set<string> leafProps;
copyPropertiesIf(info.properties, globals->get_props(), [overwriteAutosaved, &leafProps](const SGPropertyNode* src) {
if (src->getParent() == nullptr)
return true; // root node passes
// due to the slightly odd way SGPropertyNode::getPath works, we
// don't need to omit settings here; it will be dropped
// automatically.
const auto path = src->getPath(true);
auto it = std::find_if(kWitelistedPrefixes.begin(), kWitelistedPrefixes.end(), [&path](const string& p) {
// if we're high up in the tree, eg looking at /sim, then we
// want to check if at least one prefix includes that path
if (path.length() < p.length()) {
return simgear::strutils::starts_with(p, path);
}
// if the prefix is longer (more specific) than our path, we
// want to consider the full prefix
return simgear::strutils::starts_with(path, p);
});
if (it == kWitelistedPrefixes.end()) {
return false; // skip entirely
}
// find the corresponding destination node
auto dstNode = globals->get_props()->getNode(path);
// if destination exists, and we're not over-writing, check its
// ARCHIVE flag.
if (!overwriteAutosaved && dstNode && (dstNode->getAttribute(SGPropertyNode::ARCHIVE) == false)) {
return false;
}
// only watch the leaf properties
const bool isLeaf = src->nChildren() == 0;
if (isLeaf) {
leafProps.insert(path);
}
return true; // easy, just copy it
});
_listener->unregisterFromProperties();
for (const auto& p : leafProps) {
_listener->registerWithProperty(fgGetNode(p));
}
fgSetBool(kPresetActiveProp, true);
return true;
}
void GraphicsPresets::clearPreset()
{
fgSetString(kPresetNameProp, "");
fgSetString(kPresetDescriptionProp, "");
fgSetBool(kPresetActiveProp, false);
_listener->unregisterFromProperties();
}
bool GraphicsPresets::loadPresetXML(const SGPath& p, GraphicsPresetInfo& info)
{
SGPropertyNode_ptr props(new SGPropertyNode);
try {
readProperties(p, props.get());
} catch (sg_exception& e) {
SG_LOG(SG_IO, SG_ALERT, "XML errors loading " << p.str() << "\n\t" << e.getFormattedMessage());
return false;
}
const string id = props->getStringValue("id");
const string rawName = props->getStringValue("name");
const string rawDesc = props->getStringValue("description");
int orderNum = props->getIntValue("order-num", 99);
if (id.empty() || rawName.empty() || rawDesc.empty()) {
SG_LOG(SG_IO, SG_ALERT, "Missing preset info loading: " << p.str());
return false;
}
info.id = id;
info.name = globals->get_locale()->getLocalizedString(rawName, "graphics-presets");
info.description = globals->get_locale()->getLocalizedString(rawDesc, "graphics-presets");
info.orderNum = orderNum;
if (info.name.empty())
info.name = rawName; // no translation defined
if (info.description.empty())
info.description = rawDesc;
info.properties = props->getChild("settings");
if (!info.properties) {
SG_LOG(SG_IO, SG_ALERT, "Missing settings loading: " << p.str());
return false;
}
// read devices list
info.devices.clear();
SGPropertyNode_ptr devices = props->getChild("devices");
if (devices) {
for (auto d : devices->getChildren("device")) {
const auto t = simgear::strutils::strip(d->getStringValue());
info.devices.push_back(t);
}
}
return true;
}
bool GraphicsPresets::saveToXML(const SGPath& path, const std::string& name, const std::string& desc)
{
SGPropertyNode_ptr presetXML(new SGPropertyNode);
presetXML->setStringValue("id", path.file_base()); // without .xml
presetXML->setStringValue("name", name);
presetXML->setStringValue("description", desc);
auto settingsNode = presetXML->getChild("settings", 0, true);
for (const auto& path : _propertiesToSave) {
auto srcNode = fgGetNode(path);
if (!srcNode || !srcNode->hasValue())
continue;
auto dstNode = settingsNode->getNode(path, true);
copyProperties(srcNode, dstNode);
}
try {
sg_ofstream os(path, std::ios::out | std::ios::trunc);
writeProperties(os, presetXML, true /*write all*/);
} catch (sg_exception& e) {
SG_LOG(SG_GENERAL, SG_ALERT, "Failed to save presets file to:" << path << "\nt\tFailed:" << e.getFormattedMessage());
return false;
}
return true;
}
} // namespace flightgear

View File

@@ -0,0 +1,92 @@
#pragma once
#include <memory>
#include <string>
#include <vector>
#include <simgear/misc/strutils.hxx>
#include <simgear/props/propsfwd.hxx>
#include <simgear/structure/subsystem_mgr.hxx>
// forward decls
class SGPath;
namespace flightgear {
class GraphicsPresets : public SGSubsystem
{
public:
GraphicsPresets();
~GraphicsPresets();
static const char* staticSubsystemClassId() { return "graphics-presets"; }
struct GraphicsPresetInfo {
std::string id;
std::string name; // localized
std::string description; // localized
int orderNum;
string_list devices;
SGPropertyNode_ptr properties;
};
using GraphicsPresetVec = std::vector<GraphicsPresetInfo>;
// implement SGSubsystem intergace
void init() override;
void shutdown() override;
void update(double delta_time_sec) override;
/**
@brief init() is called too late (after fgOSInit), so we call this method early,
to load the initial preset if set, at that time.
*/
void applyInitialPreset();
/**
* @brief Apply the settings defined in the current graphics preset,
* to the property tree
*
*/
bool applyCurrentPreset();
bool applyCustomPreset(const SGPath& path);
/**
@brief apply a preset identified by its (localized) name. This is helpful
for the rnedering dialog since PUI combo-boxes only record the name of
items and no other data.
*/
bool applyPresetByName(const std::string& name);
/**
@brief retrieve all standard presets which are defined
*/
GraphicsPresetVec listPresets();
bool saveToXML(const SGPath& path, const std::string& name, const std::string& desc);
private:
void clearPreset();
bool loadStandardPreset(const std::string& id, GraphicsPresetInfo& info);
bool innerApplyPreset(const GraphicsPresetInfo& info, bool overwriteAutosaved);
bool loadPresetXML(const SGPath& p, GraphicsPresetInfo& info);
class RequiredPropertyListener;
class GraphicsConfigChangeListener;
std::unique_ptr<GraphicsConfigChangeListener> _listener;
std::unique_ptr<RequiredPropertyListener> _restartListener;
std::unique_ptr<RequiredPropertyListener> _sceneryReloadListener;
std::unique_ptr<RequiredPropertyListener> _compositorReloadListener;
string_list _propertiesToSave;
};
} // namespace flightgear

385
src/Viewer/PUICamera.cxx Normal file
View File

@@ -0,0 +1,385 @@
// Copyright (C) 2017 James Turner
//
// 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 "config.h"
#include "PUICamera.hxx"
#include <osg/StateSet>
#include <osg/State>
#include <osg/Texture2D>
#include <osg/Version>
#include <osg/RenderInfo>
#include <osg/Geometry>
#include <osg/Geode>
#include <osg/BlendFunc>
#include <osg/NodeVisitor>
#include <osgGA/GUIEventAdapter>
#include <osgGA/GUIEventHandler>
#include <osgUtil/CullVisitor>
#include <osgViewer/Viewer>
#include <simgear/scene/util/SGNodeMasks.hxx>
#include <GUI/FlightGear_pu.h>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include <Main/locale.hxx>
#include <Viewer/CameraGroup.hxx>
#include <Viewer/FGEventHandler.hxx>
#include <Viewer/renderer.hxx>
#include <Input/input.hxx>
#include <Input/FGMouseInput.hxx>
// Old versions of PUI are missing these defines
#ifndef PU_SCROLL_UP_BUTTON
#define PU_SCROLL_UP_BUTTON 3
#endif
#ifndef PU_SCROLL_DOWN_BUTTON
#define PU_SCROLL_DOWN_BUTTON 4
#endif
using namespace flightgear;
double static_pixelRatio = 1.0;
class PUIDrawable : public osg::Drawable
{
public:
PUIDrawable()
{
setUseDisplayList(false);
setDataVariance(Object::DYNAMIC);
}
void drawImplementation(osg::RenderInfo& renderInfo) const override
{
osg::State* state = renderInfo.getState();
state->setActiveTextureUnit(0);
state->setClientActiveTextureUnit(0);
state->disableAllVertexArrays();
state->applyMode(GL_FOG, false);
state->applyMode(GL_DEPTH_TEST, false);
state->applyMode(GL_LIGHTING, false);
glPushAttrib(GL_ALL_ATTRIB_BITS);
glPushClientAttrib(~0u);
glEnable ( GL_BLEND ) ;
glBlendFunc ( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ) ;
// reset pixel storage stuff for PLIB FNT drawing via glBitmap
glPixelStorei(GL_PACK_ALIGNMENT, 4);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
puDisplay();
glPopClientAttrib();
glPopAttrib();
}
osg::Object* cloneType() const override { return new PUIDrawable; }
osg::Object* clone(const osg::CopyOp&) const override { return new PUIDrawable; }
private:
};
class PUIEventHandler : public osgGA::GUIEventHandler
{
public:
PUIEventHandler(PUICamera* cam) :
_puiCamera(cam)
{
_mouse0RightButtonNode = fgGetNode("/devices/status/mice/mouse[0]/button[2]", true);
}
bool handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter &aa, osg::Object *, osg::NodeVisitor *nv) override
{
if (ea.getHandled()) return false;
// PUI expects increasing downward mouse coords
const int fixedY = (ea.getMouseYOrientation() == osgGA::GUIEventAdapter::Y_INCREASING_UPWARDS) ?
ea.getWindowHeight() - ea.getY() : ea.getY();
const int scaledX = static_cast<int>(ea.getX() / static_pixelRatio);
const int scaledY = static_cast<int>(fixedY / static_pixelRatio);
switch(ea.getEventType())
{
case(osgGA::GUIEventAdapter::DRAG):
if (!_is_dragging)
return false;
// No break
case(osgGA::GUIEventAdapter::MOVE):
return puMouse(scaledX, scaledY);
case(osgGA::GUIEventAdapter::PUSH):
case(osgGA::GUIEventAdapter::RELEASE):
{
// during splash/reset, either of these can return nullptr
const auto input = globals->get_subsystem<FGInput>();
const auto mouseSubsystem = input ? input->get_subsystem<FGMouseInput>() : nullptr;
if (mouseSubsystem && !mouseSubsystem->isActiveModePassThrough()) {
return false;
}
bool mouse_up = (ea.getEventType() == osgGA::GUIEventAdapter::RELEASE);
bool handled = puMouse(osgButtonToPUI(ea), mouse_up, scaledX, scaledY);
if (!mouse_up && handled)
{
_is_dragging = true;
}
// Release drag if no more buttons are pressed
else if (mouse_up && !ea.getButtonMask())
{
_is_dragging = false;
}
return handled;
}
case(osgGA::GUIEventAdapter::KEYDOWN):
case(osgGA::GUIEventAdapter::KEYUP):
{
const bool isKeyRelease = (ea.getEventType() == osgGA::GUIEventAdapter::KEYUP);
const int key = flightgear::FGEventHandler::translateKey(ea);
bool handled = puKeyboard(key, isKeyRelease);
return handled;
}
case osgGA::GUIEventAdapter::SCROLL:
{
const int button = buttonForScrollEvent(ea);
if (button != PU_NOBUTTON) {
// sent both down and up events for a single scroll, for
// compatability
bool handled = puMouse(button, PU_DOWN, scaledX, scaledY);
handled |= puMouse(button, PU_UP, scaledX, scaledY);
return handled;
}
return false;
}
case (osgGA::GUIEventAdapter::RESIZE):
_puiCamera->resizeUi(ea.getWindowWidth(), ea.getWindowHeight());
break;
default:
return false;
}
return false;
}
private:
int osgButtonToPUI(const osgGA::GUIEventAdapter &ea) const
{
switch (ea.getButton()) {
case osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON:
return 0;
case osgGA::GUIEventAdapter::MIDDLE_MOUSE_BUTTON:
return 1;
case osgGA::GUIEventAdapter::RIGHT_MOUSE_BUTTON:
return 2;
}
return 0;
}
int buttonForScrollEvent(const osgGA::GUIEventAdapter &ea) const
{
if (ea.getScrollingMotion() == osgGA::GUIEventAdapter::SCROLL_2D) {
int button = PU_NOBUTTON;
if (ea.getScrollingDeltaY() > 0)
button = PU_SCROLL_UP_BUTTON;
else if (ea.getScrollingDeltaY() < 0)
button = PU_SCROLL_DOWN_BUTTON;
#if defined(SG_MAC)
// bug https://code.google.com/p/flightgear-bugs/issues/detail?id=1286
// Mac (Cocoa) interprets shift+wheel as horizontal scroll
if (ea.getModKeyMask() & osgGA::GUIEventAdapter::MODKEY_SHIFT) {
if (ea.getScrollingDeltaX() > 0) {
button = PU_SCROLL_UP_BUTTON;
} else if (ea.getScrollingDeltaX() < 0) {
button = PU_SCROLL_DOWN_BUTTON;
}
}
#endif
return button;
} else if (ea.getScrollingMotion() == osgGA::GUIEventAdapter::SCROLL_UP) {
return PU_SCROLL_UP_BUTTON;
}
return PU_SCROLL_DOWN_BUTTON;
}
PUICamera* _puiCamera = nullptr;
bool _is_dragging = false;
SGPropertyNode_ptr _mouse0RightButtonNode;
};
// The pu getWindow callback is supposed to return a window ID that
// would allow drawing a GUI on different windows. All that stuff is
// broken in multi-threaded OSG, and we only have one GUI "window"
// anyway, so just return a constant.
int PUICamera::puGetWindow()
{
return 1;
}
void PUICamera::puGetWindowSize(int* width, int* height)
{
*width = 0;
*height = 0;
osg::Camera* camera = getGUICamera(CameraGroup::getDefault());
if (!camera)
return;
osg::Viewport* vport = camera->getViewport();
*width = static_cast<int>(vport->width() / static_pixelRatio);
*height = static_cast<int>(vport->height() / static_pixelRatio);
}
void PUICamera::initPUI()
{
puSetWindowFuncs(PUICamera::puGetWindow, nullptr,
PUICamera::puGetWindowSize, nullptr);
puRealInit();
}
PUICamera::PUICamera() :
osg::Camera()
{
}
PUICamera::~PUICamera()
{
SG_LOG(SG_GL, SG_INFO, "Deleting PUI camera");
// depending on if we're doing shutdown or reset, various things can be
// null here.
auto renderer = globals->get_renderer();
auto view = renderer ? renderer->getView() : nullptr;
if (view) {
view->removeEventHandler(_eventHandler);
}
}
void PUICamera::init(osg::Group* parent, osgViewer::View* view)
{
setName("PUI FBO camera");
_fboTexture = new osg::Texture2D;
_fboTexture->setInternalFormat(GL_RGBA);
_fboTexture->setResizeNonPowerOfTwoHint(false);
_fboTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
_fboTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
// setup the camera as render to texture
setReferenceFrame(osg::Transform::ABSOLUTE_RF);
setViewMatrix(osg::Matrix::identity());
setClearMask( GL_COLOR_BUFFER_BIT );
setClearColor( osg::Vec4( 0.0, 0.0, 0.0, 0.0 ) );
setAllowEventFocus(false);
setCullingActive(false);
setRenderTargetImplementation( osg::Camera::FRAME_BUFFER_OBJECT );
setRenderOrder(osg::Camera::PRE_RENDER);
attach(osg::Camera::COLOR_BUFFER, _fboTexture);
// set the camera's node mask, ensure the pick bit is clear
setNodeMask(SG_NODEMASK_GUI_BIT);
// geode+drawable to call puDisplay, as a child of this FBO-camera
osg::Geode* geode = new osg::Geode;
geode->setName("PUIDrawableGeode");
geode->addDrawable(new PUIDrawable);
addChild(geode);
// geometry (full-screen quad) to draw the output
_fullScreenQuad = new osg::Geometry;
_fullScreenQuad = osg::createTexturedQuadGeometry(osg::Vec3(0.0, 0.0, 0.0),
osg::Vec3(200.0, 0.0, 0.0),
osg::Vec3(0.0, 200.0, 0.0));
_fullScreenQuad->setSupportsDisplayList(false);
_fullScreenQuad->setName("PUI fullscreen quad");
// state used for drawing the quad (not for rendering PUI, that's done in the
// drawbale above)
osg::StateSet* stateSet = _fullScreenQuad->getOrCreateStateSet();
stateSet->setRenderBinDetails(1001, "RenderBin");
stateSet->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON);
stateSet->setTextureAttribute(0, _fboTexture);
// use GL_ONE becuase we pre-multiplied by alpha when building the FBO texture
stateSet->setAttribute(new osg::BlendFunc(osg::BlendFunc::ONE, osg::BlendFunc::ONE_MINUS_SRC_ALPHA));
stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
stateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF);
stateSet->setMode(GL_FOG, osg::StateAttribute::OFF);
stateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
// geode to display the FSquad in the parent scene (which is GUI camera)
osg::Geode* fsQuadGeode = new osg::Geode;
fsQuadGeode->addDrawable(_fullScreenQuad);
fsQuadGeode->setName("PUI fullscreen Geode");
fsQuadGeode->setNodeMask(SG_NODEMASK_GUI_BIT);
parent->addChild(this);
parent->addChild(fsQuadGeode);
osg::Camera* camera = getGUICamera(CameraGroup::getDefault());
if (camera) {
osg::Viewport* vport = camera->getViewport();
resizeUi(vport->width(), vport->height());
}
// push_front so we keep the order of event handlers the opposite of
// the rendering order (i.e top-most UI layer has the front-most event
// handler)
_eventHandler = new PUIEventHandler(this);
view->getEventHandlers().push_front(_eventHandler);
}
// remove once we require OSG 3.4
void PUICamera::manuallyResizeFBO(int width, int height)
{
_fboTexture->setTextureSize(width, height);
_fboTexture->dirtyTextureObject();
}
void PUICamera::resizeUi(int width, int height)
{
static_pixelRatio = fgGetDouble("/sim/rendering/gui-pixel-ratio", 1.0);
const int scaledWidth = static_cast<int>(width / static_pixelRatio);
const int scaledHeight = static_cast<int>(height / static_pixelRatio);
setViewport(0, 0, scaledWidth, scaledHeight);
osg::Camera::resize(scaledWidth, scaledHeight);
resizeAttachments(scaledWidth, scaledHeight);
const float puiZ = 1.0;
// resize the full-screen quad
osg::Vec3Array* fsQuadVertices = static_cast<osg::Vec3Array*>(_fullScreenQuad->getVertexArray());
(*fsQuadVertices)[0] = osg::Vec3(0.0, height, puiZ);
(*fsQuadVertices)[1] = osg::Vec3(0.0, 0.0, puiZ);
(*fsQuadVertices)[2] = osg::Vec3(width, 0.0, puiZ);
(*fsQuadVertices)[3] = osg::Vec3(width, height, puiZ);
fsQuadVertices->dirty();
}

70
src/Viewer/PUICamera.hxx Normal file
View File

@@ -0,0 +1,70 @@
// Copyright (C) 2017 James Turner
//
// 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.
#ifndef PUICAMERA_HXX
#define PUICAMERA_HXX
#include <osg/Camera>
#include <osg/Version>
#include <osgViewer/View>
namespace osg
{
class Texture2D;
class Geometry;
}
namespace osgViewer {
class Viewer;
}
namespace osgGA {
class GUIEventHandler;
}
namespace flightgear
{
class PUICamera : public osg::Camera
{
public:
static void initPUI();
PUICamera();
virtual ~PUICamera();
osg::Object* cloneType() const override { return new PUICamera; }
osg::Object* clone(const osg::CopyOp&) const override { return new PUICamera; }
// osg::Camera already defines a resize() so use this name
void resizeUi(int width, int height);
void init(osg::Group* parent, osgViewer::View* view);
private:
void manuallyResizeFBO(int width, int height);
osg::Texture2D* _fboTexture = nullptr;
osg::Geometry* _fullScreenQuad = nullptr;
osgGA::GUIEventHandler* _eventHandler = nullptr;
static void puGetWindowSize(int *width, int *height);
static int puGetWindow();
};
} // of namespace flightgear
#endif // PUICAMERA_HXX

298
src/Viewer/VRManager.cxx Normal file
View File

@@ -0,0 +1,298 @@
// Copyright (C) 2021 James Hogan
//
// 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 "VRManager.hxx"
#include "WindowBuilder.hxx"
#include "renderer.hxx"
#include <osgXR/Settings>
#include <simgear/scene/util/RenderConstants.hxx>
#include <simgear/scene/viewer/CompositorPass.hxx>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
namespace flightgear
{
// Unfortunately, this can't be scoped inside VRManager::instance().
// If its initialisation completes after main() calls atexit(fgExitCleanup),
// then its destruction should take place before fgExitCleanup() is called.
static osg::ref_ptr<VRManager> managerInstance;
VRManager::VRManager() :
_reloadCompositorCallback(new ReloadCompositorCallback(this)),
_propXrLayersValidation("/sim/vr/openxr/layers/validation"),
_propXrExtensionsDepthInfo("/sim/vr/openxr/extensions/depth-info"),
_propXrExtensionsVisibilityMask("/sim/vr/openxr/extensions/visibility-mask"),
_propXrRuntimeName("/sim/vr/openxr/runtime/name"),
_propXrSystemName("/sim/vr/openxr/system/name"),
_propStateString("/sim/vr/state-string"),
_propPresent("/sim/vr/present"),
_propRunning("/sim/vr/running"),
_propEnabled("/sim/vr/enabled"),
_propDepthInfo("/sim/vr/depth-info"),
_propVisibilityMask("/sim/vr/visibility-mask"),
_propValidationLayer("/sim/vr/validation-layer"),
_propMode("/sim/vr/mode"),
_propSwapchainMode("/sim/vr/swapchain-mode"),
_propMirrorEnabled("/sim/vr/mirror-enabled"),
_propMirrorMode("/sim/vr/mirror-mode"),
_listenerEnabled(this, &osgXR::Manager::setEnabled),
_listenerDepthInfo(this, &VRManager::setDepthInfo),
_listenerVisibilityMask(this, &VRManager::setVisibilityMask),
_listenerValidationLayer(this, &VRManager::setValidationLayer),
_listenerMode(this, &VRManager::setVRMode),
_listenerSwapchainMode(this, &VRManager::setSwapchainMode),
_listenerMirrorMode(this, &VRManager::setMirrorMode)
{
uint32_t fgVersion = (FLIGHTGEAR_MAJOR_VERSION << 16 |
FLIGHTGEAR_MINOR_VERSION << 8 |
FLIGHTGEAR_PATCH_VERSION);
_settings->setApp("FlightGear", fgVersion);
_settings->preferEnvBlendMode(osgXR::Settings::BLEND_MODE_OPAQUE);
// Inform osgXR what node masks to use
setVisibilityMaskNodeMasks(simgear::NodeMask::LEFT_BIT,
simgear::NodeMask::RIGHT_BIT);
// Hook into viewer, but don't enable VR just yet
osgViewer::View *view = globals->get_renderer()->getView();
if (view) {
setViewer(globals->get_renderer()->getViewerBase());
view->apply(this);
}
syncReadOnlyProperties();
_propEnabled.node(true)->addChangeListener(&_listenerEnabled, true);
_propDepthInfo.node(true)->addChangeListener(&_listenerDepthInfo, true);
_propVisibilityMask.node(true)->addChangeListener(&_listenerVisibilityMask, true);
_propValidationLayer.node(true)->addChangeListener(&_listenerValidationLayer, true);
_propMode.node(true)->addChangeListener(&_listenerMode, true);
_propSwapchainMode.node(true)->addChangeListener(&_listenerSwapchainMode, true);
_propMirrorMode.node(true)->addChangeListener(&_listenerMirrorMode, true);
// No need for a change listener, but it should still be resolvable
_propMirrorEnabled.node(true);
}
VRManager *VRManager::instance()
{
static bool initialised = false;
if (!initialised) {
managerInstance = new VRManager;
initialised = true;
}
return managerInstance;
}
void VRManager::syncProperties()
{
// If the state has changed, properties may need synchronising
if (checkAndResetStateChanged()) {
syncReadOnlyProperties();
syncSettingProperties();
}
}
void VRManager::syncReadOnlyProperties()
{
_propXrLayersValidation = hasValidationLayer();
_propXrExtensionsDepthInfo = hasDepthInfoExtension();
_propXrExtensionsVisibilityMask = hasVisibilityMaskExtension();
_propXrRuntimeName = getRuntimeName();
_propXrSystemName = getSystemName();
_propStateString = getStateString();
_propPresent = getPresent();
_propRunning = isRunning();
}
void VRManager::syncSettingProperties()
{
bool enabled = getEnabled();
if (_propEnabled != enabled)
_propEnabled = enabled;
}
bool VRManager::getUseMirror() const
{
return _propMirrorEnabled && isRunning();
}
void VRManager::setValidationLayer(bool validationLayer)
{
_settings->setValidationLayer(validationLayer);
syncSettings();
}
void VRManager::setDepthInfo(bool depthInfo)
{
_settings->setDepthInfo(depthInfo);
syncSettings();
}
void VRManager::setVisibilityMask(bool visibilityMask)
{
_settings->setVisibilityMask(visibilityMask);
syncSettings();
}
void VRManager::setVRMode(const std::string& mode)
{
osgXR::Settings::VRMode vrMode = osgXR::Settings::VRMODE_AUTOMATIC;
if (mode == "AUTOMATIC") {
vrMode = osgXR::Settings::VRMODE_AUTOMATIC;
} else if (mode == "SLAVE_CAMERAS") {
vrMode = osgXR::Settings::VRMODE_SLAVE_CAMERAS;
} else if (mode == "SCENE_VIEW") {
vrMode = osgXR::Settings::VRMODE_SCENE_VIEW;
}
_settings->setVRMode(vrMode);
syncSettings();
}
void VRManager::setSwapchainMode(const std::string& mode)
{
osgXR::Settings::SwapchainMode swapchainMode = osgXR::Settings::SWAPCHAIN_AUTOMATIC;
if (mode == "AUTOMATIC") {
swapchainMode = osgXR::Settings::SWAPCHAIN_AUTOMATIC;
} else if (mode == "MULTIPLE") {
swapchainMode = osgXR::Settings::SWAPCHAIN_MULTIPLE;
} else if (mode == "SINGLE") {
swapchainMode = osgXR::Settings::SWAPCHAIN_SINGLE;
}
_settings->setSwapchainMode(swapchainMode);
syncSettings();
}
void VRManager::setMirrorMode(const std::string& mode)
{
osgXR::MirrorSettings::MirrorMode mirrorMode = osgXR::MirrorSettings::MIRROR_AUTOMATIC;
int viewIndex = -1;
if (mode == "AUTOMATIC") {
mirrorMode = osgXR::MirrorSettings::MIRROR_AUTOMATIC;
} else if (mode == "NONE") {
mirrorMode = osgXR::MirrorSettings::MIRROR_NONE;
} else if (mode == "LEFT") {
mirrorMode = osgXR::MirrorSettings::MIRROR_SINGLE;
viewIndex = 0;
} else if (mode == "RIGHT") {
mirrorMode = osgXR::MirrorSettings::MIRROR_SINGLE;
viewIndex = 1;
} else if (mode == "LEFT_RIGHT") {
mirrorMode = osgXR::MirrorSettings::MIRROR_LEFT_RIGHT;
}
_settings->getMirrorSettings().setMirror(mirrorMode, viewIndex);
}
void VRManager::update()
{
osgXR::Manager::update();
syncProperties();
}
void VRManager::doCreateView(osgXR::View *xrView)
{
// Restarted in osgXR::Manager::update()
_viewer->stopThreading();
// Construct a property tree for the camera
SGPropertyNode_ptr camNode = new SGPropertyNode;
WindowBuilder *windowBuilder = WindowBuilder::getWindowBuilder();
setValue(camNode->getNode("window/name", true),
windowBuilder->getDefaultWindowName());
// Build a camera
CameraGroup *cgroup = CameraGroup::getDefault();
CameraInfo *info = cgroup->buildCamera(camNode);
// Notify osgXR about the new compositor's scene slave cameras
if (info) {
_camInfos[xrView] = info;
_xrViews[info] = xrView;
info->reloadCompositorCallback = _reloadCompositorCallback;
postReloadCompositor(cgroup, info);
}
}
void VRManager::doDestroyView(osgXR::View *xrView)
{
// Restarted in osgXR::Manager::update()
_viewer->stopThreading();
CameraGroup *cgroup = CameraGroup::getDefault();
auto it = _camInfos.find(xrView);
if (it != _camInfos.end()) {
osg::ref_ptr<CameraInfo> info = (*it).second;
_camInfos.erase(it);
auto it2 = _xrViews.find(info.get());
if (it2 != _xrViews.end())
_xrViews.erase(it2);
cgroup->removeCamera(info.get());
}
}
void VRManager::onRunning()
{
// Reload compositors to trigger switch to mirror of VR
CameraGroup *cgroup = CameraGroup::getDefault();
reloadCompositors(cgroup);
}
void VRManager::onStopped()
{
// As long as we're not in the process of destroying FlightGear, reload
// compositors to trigger switch away from mirror of VR
if (!isDestroying())
{
CameraGroup *cgroup = CameraGroup::getDefault();
reloadCompositors(cgroup);
}
}
void VRManager::preReloadCompositor(CameraGroup *cgroup, CameraInfo *info)
{
osgXR::View *xrView = _xrViews[info];
auto& passes = info->compositor->getPassList();
for (auto& pass: passes)
if (pass->type == "scene")
xrView->removeSlave(pass->camera);
}
void VRManager::postReloadCompositor(CameraGroup *cgroup, CameraInfo *info)
{
osgXR::View *xrView = _xrViews[info];
auto& passes = info->compositor->getPassList();
for (auto& pass: passes)
if (pass->type == "scene")
xrView->addSlave(pass->camera);
}
}

171
src/Viewer/VRManager.hxx Normal file
View File

@@ -0,0 +1,171 @@
// Copyright (C) 2021 James Hogan
//
// 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.
#ifndef VRMANAGER_HXX
#define VRMANAGER_HXX 1
#include <config.h>
#ifdef ENABLE_OSGXR
#include <osg/ref_ptr>
#include <osg/observer_ptr>
#include <osgXR/Manager>
#include <simgear/props/propertyObject.hxx>
#include <simgear/scene/viewer/CompositorPass.hxx>
#include "CameraGroup.hxx"
#include <map>
namespace flightgear
{
class VRManager : public osgXR::Manager
{
public:
class ReloadCompositorCallback : public CameraInfo::ReloadCompositorCallback
{
public:
ReloadCompositorCallback(VRManager *manager) :
_manager(manager)
{
};
virtual void preReloadCompositor(CameraGroup *cgroup, CameraInfo *info)
{
_manager->preReloadCompositor(cgroup, info);
}
virtual void postReloadCompositor(CameraGroup *cgroup, CameraInfo *info)
{
_manager->postReloadCompositor(cgroup, info);
}
protected:
osg::observer_ptr<VRManager> _manager;
};
VRManager();
static VRManager *instance();
void syncProperties();
void syncReadOnlyProperties();
void syncSettingProperties();
// Settings
bool getUseMirror() const;
void setValidationLayer(bool validationLayer);
void setDepthInfo(bool depthInfo);
void setVisibilityMask(bool visibilityMask);
void setVRMode(const std::string& mode);
void setSwapchainMode(const std::string& mode);
void setMirrorMode(const std::string& mode);
// osgXR::Manager overrides
void update() override;
void doCreateView(osgXR::View *xrView) override;
void doDestroyView(osgXR::View *xrView) override;
void onRunning() override;
void onStopped() override;
void preReloadCompositor(CameraGroup *cgroup, CameraInfo *info);
void postReloadCompositor(CameraGroup *cgroup, CameraInfo *info);
protected:
typedef std::map<osgXR::View *, osg::ref_ptr<CameraInfo>> XRViewToCamInfo;
XRViewToCamInfo _camInfos;
typedef std::map<CameraInfo *, osg::ref_ptr<osgXR::View>> CamInfoToXRView;
CamInfoToXRView _xrViews;
osg::ref_ptr<ReloadCompositorCallback> _reloadCompositorCallback;
// Properties
SGPropObjBool _propXrLayersValidation;
SGPropObjBool _propXrExtensionsDepthInfo;
SGPropObjBool _propXrExtensionsVisibilityMask;
SGPropObjString _propXrRuntimeName;
SGPropObjString _propXrSystemName;
SGPropObjString _propStateString;
SGPropObjBool _propPresent;
SGPropObjBool _propRunning;
SGPropObjBool _propEnabled;
SGPropObjBool _propDepthInfo;
SGPropObjBool _propVisibilityMask;
SGPropObjBool _propValidationLayer;
SGPropObjString _propMode;
SGPropObjString _propSwapchainMode;
SGPropObjBool _propMirrorEnabled;
SGPropObjString _propMirrorMode;
// Property listeners
template <typename T, typename R = T>
class Listener : public SGPropertyChangeListener
{
public:
typedef void (VRManager::*SetterFn)(R v);
Listener(VRManager *manager, SetterFn setter) :
_manager(manager),
_setter(setter)
{
}
void valueChanged(SGPropertyNode *node) override
{
(_manager->*_setter)(node->template getValue<T>());
}
protected:
VRManager *_manager;
SetterFn _setter;
};
typedef Listener<bool> ListenerBool;
typedef Listener<std::string, const std::string&> ListenerString;
ListenerBool _listenerEnabled;
ListenerBool _listenerDepthInfo;
ListenerBool _listenerVisibilityMask;
ListenerBool _listenerValidationLayer;
ListenerString _listenerMode;
ListenerString _listenerSwapchainMode;
ListenerString _listenerMirrorMode;
};
}
#endif // ENABLE_OSGXR
#endif

View File

@@ -0,0 +1,744 @@
// 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 "ViewPropertyEvaluator.hxx"
#include "Main/globals.hxx"
#include <algorithm>
#include <cassert>
namespace ViewPropertyEvaluator {
/* We represent a spec as graph, using alternating Sequence and Node
objects so that different specs share common information; e.g. this ensures
that we don't install more than one listener for the same SGPropertyNode.
Evaluating top-level nodes:
Currently ViewPropertyEvaluator::getDoubleValue() will always
reevaluate the top-level SGPropertyNode by calling its getDoubleValue()
member. This usually gives the desired behaviour because most
final property nodes that we are used with don't appear to make
valueChanged() callbacks, and it's anyway probably more efficient to
not use such callbacks for rapidly-changing values.
However it would be good to be clearer about this, e.g. maybe we could
have a second bracket notation to indicate that we should evaluate and
cache the SGPropertyNode but not its string/double value. E.g.:
ViewPropertyEvaluator::getDoubleValue(
"{(/sim/view[0]/config/root)/position/altitude-ft}"
);
- would not attempt to install a valueChanged() callback for the
top-level SGPropertyNode.
*/
struct Sequence;
struct Node;
struct Sequence
{
Sequence();
std::vector<std::shared_ptr<Node>> _nodes;
std::vector<Node*> _parents;
bool _rescan;
std::string _value;
};
struct Node : SGPropertyChangeListener
{
explicit Node(const char* spec);
/* SGPropertyChangeListener callback. */
void valueChanged(SGPropertyNode* node);
const char* _begin;
const char* _end;
bool _rescan;
std::string _value;
std::vector<Sequence*> _parents;
// Only used if _begin.._end is (...).
std::shared_ptr<Sequence> _child;
SGPropertyNode_ptr _sgnode;
SGPropertyNode_ptr _sgnode_listen;
};
/* Helper for dumping a Sequence to an ostream. Prefixes all lines with
<indent>. If <deep> is true, recursively shows all child sequences and
nodes. */
struct SequenceDump
{
SequenceDump(const Sequence& sequence, const std::string& indent="", bool deep=false);
const Sequence& _sequence;
const std::string& _indent;
bool _deep;
friend std::ostream& operator << (std::ostream& out, const SequenceDump& self);
};
/* Helper for dumping a Node to an ostream. Prefixes all lines with
<indent>. If <deep> is true, recursively shows all child sequences and
nodes. */
struct NodeDump
{
NodeDump(const Node& node, const std::string& indent="", bool deep=false);
const Node& _node;
const std::string& _indent;
bool _deep;
friend std::ostream& operator << (std::ostream& out, const NodeDump& self);
};
/* Support for debug statistics. */
struct Debug
{
/* Support for tracking how many property system accesses we are making. */
struct Stat
{
Stat() : n(0) {}
int n;
};
/* Increments counter for <name>. Periodically outputs stats with
SG_LOG(SG_VIEW, SG_DEBUG, ...) and detailed information about Sequences
and Nodes with SG_LOG(SG_VIEW, SG_BULK, ...). */
void statsAdd(const char* name);
void statsReset();
struct StatsShow {};
friend std::ostream& operator << (std::ostream& out, const StatsShow&);
/* Track how many listeners we have. */
void listensAdd(SGPropertyNode_ptr node);
void listensRemove(SGPropertyNode_ptr node);
time_t statsT0 = 0;
std::map<std::string, std::shared_ptr<Stat>> stats;
std::vector<SGPropertyNode_ptr> listens;
};
Debug debug;
/* Forces this node and all of its sequence and node parents to be
re-read the next time they are evaluated - e.g. the next call of
getNodeStringValue() will call getSequenceStringValue() on _child and
write the result into the <_value> member before returning <_value>. */
void rescanNode(Node& node);
/* Forces this sequence and all of its node and sequence parents to
be re-read the next time they are evaluated - e.g. the next call of
getSequenceStringValue() will call getNodeStringValue() on each child
node and concatenate the results into the <_value> member before
returning <_value>. */
void rescanSequence(Sequence& sequence);
/* Returns Sequence for spec starting at <spec>, which can be subsequently
used to evaluate the spec efficiently. We require that the string <spec>
will remain unchanged forever. */
std::shared_ptr<Sequence> getSequence(const char* spec);
/* Returns evaluated spec for <node> using caching. Mainly used
internally.
If node spec is of the form "<...>", we look up the value in the
property system. */
const std::string& getNodeStringValue(Node& node);
/* Returns evaluated spec for <sequence> using caching. Mainly used
internally. Concatenates the string value of each child node. */
const std::string& getSequenceStringValue(Sequence& sequence);
/* <sequence> must be from a spec with a top-level '(...)'. Returns the
specified property-tree node. */
SGPropertyNode* getSequenceNode(Sequence& sequence);
double getSequenceDoubleValue(Sequence& sequence, double default_=0);
/* Finds end of section of spec starting at <spec>, to be used as the
region of a Sequence; this will contain one or more regions corresponding
to a Node. Any ')' without a corresponding '(' is treated as a terminator.
*/
const char* getSequenceEnd(const char* spec)
{
int nesting = 0;
for (const char* s=spec; ; ++s) {
if (*s == 0) {
assert(nesting == 0);
return s;
}
if (*s == '(') {
nesting += 1;
}
if (*s == ')') {
if (nesting == 0) {
return s;
}
nesting -= 1;
}
}
}
/* Finds end of section of spec starting at <spec>, to be used as the
region of a Node.
We parse things similarly to getSequenceEnd() except that we also terminate
early in the following situations:
The first character is not '(' and we find a '('.
The first character is '(' and we find the corresponding ')'.
*/
const char* getNodeEnd(const char* spec)
{
int nesting = 0;
for (const char* s=spec; ; ++s) {
if (*s == 0) {
assert(nesting == 0);
return s;
}
if (*s == '(') {
if (spec[0] != '(') {
return s;
}
nesting += 1;
}
if (*s == ')') {
if (nesting == 0) {
return s;
}
nesting -= 1;
if (spec[0] == '(' && nesting == 0) {
return s + 1;
}
}
}
}
/* Raw constructor. We only set up basic values; the rest is done
by getNodeInternal(). */
Node::Node(const char* spec)
:
_begin(spec),
_end(getNodeEnd(_begin)),
_rescan(true)
{
}
void Node::valueChanged(SGPropertyNode* node)
{
debug.statsAdd("valueChanged");
SG_LOG(SG_VIEW, SG_DEBUG, "valueChanged():"
<< " node->_sgnode_listen->getPath()='" << _sgnode_listen->getPath() << "'"
<< " node->getPath()='" << node->getPath() << "'"
);
rescanNode(*this);
}
Sequence::Sequence()
:
_rescan(true)
{
}
void rescanNode(Node& node)
{
node._rescan = true;
for (auto sequence: node._parents) {
rescanSequence(*sequence);
}
}
void rescanSequence(Sequence& sequence)
{
sequence._rescan = true;
for (auto node: sequence._parents) {
rescanNode(*node);
}
}
/* Caches of various structures so that we can look things up quickly.
*/
/* This is the main cache. It uses raw C string pointers (pointing
to specs) as keys, so lookups only require a handful of pointer
comparisons. */
std::map<const char*, std::shared_ptr<Sequence>> spec_to_sequence;
/* These are only used when parsing new specs and creating new nodes and
sequencies, so are not speed-critical. */
std::map<std::string, std::shared_ptr<Sequence>> string_to_sequence;
std::map<std::string, std::shared_ptr<Node>> string_to_node;
/* Finds or creates new Sequence for (possibly initial) portion of <spec>.
*/
std::shared_ptr<Sequence> getSequenceInternal(const char* spec, Node* parent);
/* Finds or creates new Node for (possibly initial) portion of <spec>.
*/
std::shared_ptr<Node> getNodeInternal(const char* spec, Sequence* parent);
std::shared_ptr<Sequence> getSequenceInternal(const char* spec, Node* parent)
{
if (spec[0] == 0 || spec[0] == ')') {
return NULL;
}
std::shared_ptr<Sequence> sequence;
std::string spec_string(spec, getSequenceEnd(spec));
auto it = string_to_sequence.find(spec_string);
if (it == string_to_sequence.end()) {
sequence.reset(new Sequence);
for(const char* s = spec;;) {
std::shared_ptr<Node> node
= getNodeInternal(s, sequence.get() /*parent*/);
if (!node) break;
sequence->_nodes.push_back(node);
s += (node->_end - node->_begin);
}
string_to_sequence[spec_string] = sequence;
}
else {
sequence = it->second;
}
if (parent) {
auto it = std::find(
sequence->_parents.begin(),
sequence->_parents.end(),
parent
);
if (it == sequence->_parents.end()) {
sequence->_parents.push_back(parent);
}
}
return sequence;
}
std::shared_ptr<Node> getNodeInternal(const char* spec, Sequence* parent)
{
if (spec[0] == 0 || spec[0] == ')') {
return NULL;
}
std::shared_ptr<Node> node;
std::string s(spec, getNodeEnd(spec));
auto it = string_to_node.find(s);
if (it == string_to_node.end()) {
node.reset(new Node(spec));
if (node->_begin[0] == '(') {
node->_child = getSequenceInternal(node->_begin + 1, node.get() /*parent*/);
}
string_to_node[s] = node;
}
else {
node = it->second;
}
if (parent) {
if (std::find(node->_parents.begin(), node->_parents.end(), parent)
== node->_parents.end()) {
node->_parents.push_back(parent);
}
}
return node;
}
/* Finds or creates new Sequence for (possibly initial) portion of <spec>
and sets spec_to_sequence[spec] so that it can be quickly looked up in
future. */
std::shared_ptr<Sequence> getSequence(const char* spec)
{
auto it = spec_to_sequence.find(spec);
if (it != spec_to_sequence.end()) {
return it->second;
}
std::shared_ptr<Sequence> sequence
= getSequenceInternal(spec, NULL /*parent*/);
spec_to_sequence[spec] = sequence;
SG_LOG(SG_VIEW, SG_DEBUG,
"Created new sequence:\n"
<< SequenceDump(*sequence, " ", true /*deep*/)
);
return sequence;
}
/* Evaluates <node> to find path and returns corresponding SGPropertyNode*
in global properties. If <cache> is true, we install a listener on
the returned node, so that we force a rescan of all the node's parent
Sequence's if its value changes. */
SGPropertyNode* getNodeSGNode(Node& node, bool cache=true)
{
assert(node._begin[0] == '(');
assert(node._child);
if (node._rescan) {
node._rescan = false;
if (!node._sgnode || node._child->_rescan) {
const std::string& path = getSequenceStringValue(*node._child);
SGPropertyNode* sgnode = NULL;
if (path != "") {
debug.statsAdd( "propertypath_getNode");
sgnode = globals->get_props()->getNode(path, true /*create*/);
if (!sgnode) {
debug.statsAdd( "propertypath_getNode_failed");
SG_LOG(SG_VIEW, SG_DEBUG, ": getNodeSGNode(): getNode() failed, path='" << path << "'");
}
}
if (sgnode != node._sgnode) {
if (node._sgnode_listen) {
node._sgnode_listen->removeChangeListener(&node);
debug.listensRemove(node._sgnode_listen);
node._sgnode_listen = NULL;
}
if (node._sgnode) {
node._sgnode->removeChangeListener(&node);
}
node._sgnode = sgnode;
if (node._sgnode && cache) {
node._sgnode_listen = node._sgnode;
node._sgnode->addChangeListener(&node, false /*initial*/);
debug.listensAdd(node._sgnode);
}
}
if (!node._sgnode && path != "") {
/* Ideally we'd ask Simgear's property system to call us
back if <path> was created, but this is non-trivial, and
not actually required when we are used by view.cxx. */
}
}
}
return node._sgnode;
}
const std::string& getNodeStringValue(Node& node)
{
if (node._rescan) {
if (node._begin[0] == '(') {
getNodeSGNode(node);
if (node._sgnode) {
debug.statsAdd( "property_getStringValue");
node._value = node._sgnode->getStringValue();
}
else {
node._value = "";
}
}
else {
node._rescan = false;
node._value = std::string(node._begin, node._end);
}
}
return node._value;
}
const std::string& getSequenceStringValue(Sequence& sequence)
{
if (sequence._rescan) {
sequence._rescan = false;
sequence._value = "";
for (auto node: sequence._nodes) {
sequence._value += getNodeStringValue(*node);
}
}
return sequence._value;
}
/* Assumes that sequence has a single child Node object with a non-empty
Sequence child object whose value is a path in the property system. */
SGPropertyNode* getSequenceNode(Sequence& sequence)
{
assert(sequence._nodes.size() == 1);
Node& node = *sequence._nodes.front();
return getNodeSGNode(node, false /*cache*/);
}
/* This is different from getSequenceStringValue() in that it assumes that
a spec has a single top-level (...) and so the top-level sequence has a
single child Node object which in turn has a Sequence child object whose
value is a path in the property system.
We always call the SGPropertyNode's getDoubleValue() method - we don't
cache the double value. But the underlying SGPropertyNode* will be cached.
*/
double getSequenceDoubleValue(Sequence& sequence, double default_)
{
SGPropertyNode* node = getSequenceNode(sequence);
if (!node) {
return default_;
}
if (!node->getParent()) {
/* Root node. */
return default_;
}
if (node->getType() == simgear::props::BOOL) {
/* 2020-03-22: there is a problem with aircraft rah-66 setting type
of the root node to bool which gives string value "false". So we
force a return of default_. */
return default_;
}
if (node->getStringValue()[0] == 0) {
/* If we reach here, the node exists but its value is an empty
string, so node->getDoubleValue() would return 0 which isn't
useful, so instead we return default_. */
return default_;
}
return node->getDoubleValue();
}
bool getSequenceBoolValue(Sequence& sequence, bool default_)
{
SGPropertyNode* node = getSequenceNode(sequence);
if (node) {
if (node->getStringValue()[0] != 0) {
return node->getBoolValue();
}
}
return default_;
}
const std::string& getStringValue(const char* spec)
{
std::shared_ptr<Sequence> sequence = getSequence(spec);
return getSequenceStringValue(*sequence);
}
double getDoubleValue(const char* spec, double default_)
{
std::shared_ptr<Sequence> sequence = getSequence(spec);
if (sequence->_nodes.size() != 1 || sequence->_nodes.front()->_begin[0] != '(') {
SG_LOG(SG_VIEW, SG_DEBUG, "bad sequence for getDoubleValue() - must have outermost '(...)': '" << spec);
abort();
}
double ret = getSequenceDoubleValue(*sequence, default_);
return ret;
}
bool getBoolValue(const char* spec, bool default_)
{
std::shared_ptr<Sequence> sequence = getSequence(spec);
if (sequence->_nodes.size() != 1 || sequence->_nodes.front()->_begin[0] != '(') {
SG_LOG(SG_VIEW, SG_DEBUG, "bad sequence for getBoolValue() - must have outermost '(...)': '" << spec);
abort();
}
bool ret = getSequenceBoolValue(*sequence, default_);
return ret;
}
std::ostream& operator << (std::ostream& out, const Dump& dump)
{
out << "ViewPropertyEvaluator\n";
out << " Number of specs: " << spec_to_sequence.size() << ":\n";
int i = 0;
for (auto it: spec_to_sequence) {
out << " " << (i+1) << "/" << spec_to_sequence.size() << ": spec: " << it.first << "\n";
out << SequenceDump(*it.second, " ", true /*deep*/);
i += 1;
}
out << " Number of sequences: " << string_to_sequence.size() << "\n";
i = 0;
for (auto it: string_to_sequence) {
out << " " << (i+1) << "/" << string_to_sequence.size()
<< ": spec='" << it.first << "'"
<< ": " << SequenceDump(*it.second, "", false /*deep*/)
;
i += 1;
}
out << " Number of nodes: " << string_to_node.size() << "\n";
i = 0;
for (auto it: string_to_node) {
out << " " << (i+1) << "/" << string_to_node.size()
<< ": spec='" << it.first << "'"
<< ": " << NodeDump(*it.second, "", false /*deep*/)
;
i += 1;
}
out << " Number of listens: " << debug.listens.size() << "\n";
i = 0;
for (auto it: debug.listens) {
out << " " << (i+1) << "/" << debug.listens.size()
<< ": " << it->getPath()
<< "\n";
i += 1;
}
return out;
}
DumpOne::DumpOne(const char* spec)
: _spec(spec)
{}
std::ostream& operator << (std::ostream& out, const DumpOne& dumpone)
{
out << "ViewPropertyEvaluator\n";
std::shared_ptr<Sequence> sequence = getSequence(dumpone._spec);
if (sequence) {
out << " " << ": spec: '" << dumpone._spec << "'\n";
out << SequenceDump(*sequence, " ", true /*deep*/);
}
return out;
}
void clear()
{
spec_to_sequence.clear();
string_to_sequence.clear();
string_to_node.clear();
debug = Debug();
}
/* === Everything below here is for diagnostics and/or debugging. */
SequenceDump::SequenceDump(const Sequence& sequence, const std::string& indent, bool deep)
:
_sequence(sequence),
_indent(indent),
_deep(deep)
{}
NodeDump::NodeDump(const Node& node, const std::string& indent, bool deep)
:
_node(node),
_indent(indent),
_deep(deep)
{}
void Debug::listensAdd(SGPropertyNode_ptr node)
{
debug.listens.push_back(node);
}
void Debug::listensRemove(SGPropertyNode_ptr node)
{
auto it = std::find(debug.listens.begin(), debug.listens.end(), node);
if (it == debug.listens.end()) {
SG_LOG(SG_VIEW, SG_ALERT, "Unable to find node in debug.listens");
}
else {
debug.listens.erase(it);
}
}
std::ostream& operator << (std::ostream& out, const Debug::StatsShow&)
{
time_t t = time(NULL);
time_t dt = t - debug.statsT0;
if (dt == 0) dt = 1;
out << "ViewPropertyEvaluator stats: dt=" << dt << "\n";
for (auto it: debug.stats) {
const std::string& name = it.first;
int n = it.second->n;
out << " : n=" << n << " n/sec=" << (1.0 * n / dt) << ": " << name << "\n";
}
return out;
}
void Debug::statsReset() {
for (auto it: debug.stats) {
it.second->n = 0;
}
debug.statsT0 = time(NULL);
}
void Debug::statsAdd(const char* name) {
if (debug.statsT0 == 0) debug.statsT0 = time(NULL);
std::shared_ptr<Debug::Stat>& stat = debug.stats[name];
if (!stat) {
stat.reset(new(Debug::Stat));
}
stat->n += 1;
if (1) {
static time_t t0 = time(NULL);
time_t t = time(NULL);
if (t - t0 > 10) {
t0 = t;
SG_LOG(SG_VIEW, SG_DEBUG, StatsShow());
statsReset();
/* Output all specs with SG_BULK. */
SG_LOG(SG_VIEW, SG_BULK, Dump());
}
}
}
std::ostream& operator << (std::ostream& out, const SequenceDump& self)
{
std::string spec;
for (auto node: self._sequence._nodes) {
spec += std::string(node->_begin, node->_end);
}
out << self._indent
<< "Sequence at " << &self._sequence << ":"
<< " _rescan=" << self._sequence._rescan
<< " _parents.size()=" << self._sequence._parents.size()
<< " _nodes.size()=" << self._sequence._nodes.size()
<< " _value='" << self._sequence._value << "'"
<< " spec='" << spec << "'"
<< "\n";
if (self._deep) {
for (auto node: self._sequence._nodes) {
out << NodeDump(*node, self._indent + " ", self._deep);
}
}
return out;
}
std::ostream& operator << (std::ostream& out, const NodeDump& self)
{
out << self._indent
<< "Node at " << &self._node << ":"
<< " _rescan=" << self._node._rescan
<< " _parents.size()=" << self._node._parents.size()
<< " _child=" << self._node._child.get()
<< " _value='" << self._node._value << "'"
<< " _sgnode=" << self._node._sgnode
;
if (self._node._sgnode) {
out
<< " (path=" << self._node._sgnode->getPath()
<< ", value=" << self._node._sgnode->getStringValue()
<< ")";
}
out
<< " _begin.._end='" << std::string(self._node._begin, self._node._end) << "'"
<< "\n";
if (self._deep) {
if (self._node._child) {
out << SequenceDump(*self._node._child, self._indent + " ", self._deep);
}
}
return out;
}
void dump()
{
std::cerr << Dump() << "\n";
}
}

View File

@@ -0,0 +1,132 @@
// 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.
#pragma once
#include "simgear/props/props.hxx"
#include <map>
#include <ostream>
#include <string>
#include <vector>
namespace ViewPropertyEvaluator {
/*
Overview:
We provide efficient evaluation of 'nested properties', where the path
of the property to be evaluated is determined by the value of other,
possibly dynamically-changing, properties.
Details:
Code is expected to specify a nested property as a C string 'spec'
using (...) to denote evaluation of a substring as a property path
where required. We use the raw address of C string specs as keys in an
internal std::map, to provide fast lookup of previously-created specs
using just a small number of raw pointer comparisons. This requires
that the C string specs remain valid and unchanged; typically they will
be immediate strings specifed directly in source code.
We set up listeners to detect changes to relevant property nodes so
that we can force re-evaluation of dependent nested properties when
required. Thus most lookups end up not touching the property system at
all, and instead access cached values directly.
For example this:
SGPropertyNode* node = ViewPropertyEvaluator::getDoubleValue(
"((/sim/view[0]/config/root)/position/altitude-ft)"
);
- will behave like this:
globals->get_props()->getDoubleValue(
globals->get_props()->getStringValue(
"/sim/view[0]/config/root",
true |*create*|
)
+ "/position/altitude-ft",
true |*create *|
);
In this example, an internal listener will be set up to detect changes
to property '/sim/view[0]/config/root'; if this listener has not fired,
we will use a cached SGPropertyNode_ptr* directly without querying the
property tree.
Note that with ViewPropertyEvaluator::getDoubleValue, while the final
SGPropertyNode* is cached, its value is not cached. Instead we always
call SGPropertyNode::getDoubleValue(). This is in anticipation of
typical usage where the final double values will change frequently, so
caching its value becomes less useful.
Missing property nodes:
If a spec (or part of a spec) evaluates to a property node path that
does not exist, a new property node is created with value "".
[This allows us to set up a listener for the property node so we can
detect changes to its value; it might be nice to instead add something
to Simgear that allows one to listen to creation of a particular path.]
*/
/* Evaluates a spec as a string. The returned reference will be valid for
ever (until ViewPropertyEvaluator::clear() is called); its value will be
unchanged until the next time the spec (or a spec that depends on it) is
evaluated.
For example, getStringValue("/sim/chase-distance-m") will return
"/sim/chase-distance-m", while getStringValue("(/sim/chase-distance-m)")
will return "-25" or similar, depending on the aircraft.
*/
const std::string& getStringValue(const char* spec);
/* Evaluates a spec as a double. Only makes sense if <spec> has top-level
"(...)".
For example, getDoubleValue("(/sim/chase-distance-m)") will return -25.0 or
similar, depending on the aircraft.
When this function is used, it doesn't install a listener for the top-level
node, instead it always calls <top-level-node>->getDoubleValue().
*/
double getDoubleValue(const char* spec, double default_=0);
/* Similar to getDoubleValue(). */
bool getBoolValue(const char* spec, bool default_=false);
/* Outputs detailed information about all specs that have been seen.
E.g.:
SG_LOG(SG_VIEW, SG_DEBUG, "ViewPropertyEvaluator:\n" << ViewPropertyEvaluator::Dump());
*/
struct Dump {};
std::ostream& operator << (std::ostream& out, const Dump& dump);
struct DumpOne {
explicit DumpOne(const char* spec);
const char* _spec;
};
std::ostream& operator << (std::ostream& out, const DumpOne& dumpone);
/* Clears all internal state. */
void clear();
}

View File

@@ -0,0 +1,334 @@
// Copyright (C) 2008 Tim Moore
//
// 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 "config.h"
#include "WindowBuilder.hxx"
#include "WindowSystemAdapter.hxx"
#include <Main/fg_props.hxx>
#include <osg/Version>
#include <GUI/MessageBox.hxx>
#include <sstream>
#include <limits>
#if defined(SG_MAC)
#include <osgViewer/api/Cocoa/GraphicsWindowCocoa>
#endif
using namespace std;
using namespace osg;
namespace flightgear
{
string makeName(const string& prefix, int num)
{
stringstream stream;
stream << prefix << num;
return stream.str();
}
ref_ptr<WindowBuilder> WindowBuilder::windowBuilder;
string WindowBuilder::defaultWindowName("FlightGear");
// default to true (historical behaviour), we will clear the flag if
// we run another GUI.
bool WindowBuilder::poseAsStandaloneApp = true;
void WindowBuilder::initWindowBuilder(bool stencil)
{
windowBuilder = new WindowBuilder(stencil);
}
WindowBuilder::WindowBuilder(bool stencil) : defaultCounter(0)
{
makeDefaultTraits(stencil);
}
void WindowBuilder::makeDefaultTraits(bool stencil)
{
GraphicsContext::WindowingSystemInterface* wsi
= osg::GraphicsContext::getWindowingSystemInterface();
#if defined(HAVE_QT) && OSG_VERSION_GREATER_THAN(3, 5, 9)
if (usingQtGraphicsWindow) {
// use the correct WSI for OpenSceneGraph >= 3.6
wsi = osg::GraphicsContext::getWindowingSystemInterface("FlightGearQt5");
}
#endif
defaultTraits = new osg::GraphicsContext::Traits;
auto traits = defaultTraits.get();
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;
if (stencil)
traits->stencil = 8;
traits->doubleBuffer = true;
traits->mipMapGeneration = true;
traits->windowName = "FlightGear";
// XXX should check per window too.
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);
const bool wantFullscreen = fgGetBool("/sim/startup/fullscreen");
unsigned screenwidth = 0;
unsigned screenheight = 0;
// this is a deprecated method, should be screen-aware.
wsi->getScreenResolution(*traits, screenwidth, screenheight);
// handle fullscreen manually
traits->windowDecoration = !wantFullscreen;
if (!traits->windowDecoration) {
// fullscreen
traits->supportsResize = false;
traits->width = screenwidth;
traits->height = screenheight;
SG_LOG(SG_VIEW,SG_DEBUG,"Using full screen size for window: " << screenwidth << " x " << screenheight);
} else {
// window
int w = fgGetInt("/sim/startup/xsize");
int h = fgGetInt("/sim/startup/ysize");
traits->supportsResize = true;
traits->width = w;
traits->height = h;
if ((w>0)&&(h>0))
{
traits->x = ((unsigned)w>screenwidth) ? 0 : (screenwidth-w)/3;
traits->y = ((unsigned)h>screenheight) ? 0 : (screenheight-h)/3;
}
SG_LOG(SG_VIEW,SG_DEBUG,"Using initial window size: " << w << " x " << h);
}
}
} // of namespace flightgear
namespace
{
// Helper functions that set a value based on a property if it exists,
// returning 1 if the value was set.
inline int setFromProperty(string& place, const SGPropertyNode* node,
const char* name)
{
const SGPropertyNode* valNode = node->getNode(name);
if (valNode) {
place = valNode->getStringValue();
return 1;
}
return 0;
}
inline int setFromProperty(int& place, const SGPropertyNode* node,
const char* name)
{
const SGPropertyNode* valNode = node->getNode(name);
if (valNode) {
place = valNode->getIntValue();
return 1;
}
return 0;
}
inline int setFromProperty(bool& place, const SGPropertyNode* node,
const char* name)
{
const SGPropertyNode* valNode = node->getNode(name);
if (valNode) {
place = valNode->getBoolValue();
return 1;
}
return 0;
}
}
namespace flightgear
{
void WindowBuilder::setFullscreenTraits(const SGPropertyNode* winNode, GraphicsContext::Traits* traits)
{
const SGPropertyNode* orrNode = winNode->getNode("overrideRedirect");
bool overrideRedirect = orrNode && orrNode->getBoolValue();
traits->overrideRedirect = overrideRedirect;
traits->windowDecoration = false;
unsigned int width = 0;
unsigned int height = 0;
auto wsi = osg::GraphicsContext::getWindowingSystemInterface();
wsi->getScreenResolution(*traits, width, height);
traits->width = width;
traits->height = height;
traits->supportsResize = false;
traits->x = 0;
traits->y = 0;
}
bool WindowBuilder::setWindowedTraits(const SGPropertyNode* winNode, GraphicsContext::Traits* traits)
{
bool customTraits = false;
int resizable = 0;
const SGPropertyNode* fullscreenNode = winNode->getNode("fullscreen");
if (fullscreenNode && !fullscreenNode->getBoolValue())
{
traits->windowDecoration = true;
resizable = 1;
}
resizable |= setFromProperty(traits->windowDecoration, winNode, "decoration");
resizable |= setFromProperty(traits->width, winNode, "width");
resizable |= setFromProperty(traits->height, winNode, "height");
if (resizable) {
traits->supportsResize = true;
customTraits = true;
}
return customTraits;
}
void WindowBuilder::setMacPoseAsStandaloneApp(GraphicsContext::Traits* traits)
{
#if defined(SG_MAC)
// this logic is unecessary if using a Qt window, since everything
// plays together nicely
int flags = osgViewer::GraphicsWindowCocoa::WindowData::CheckForEvents;
// avoid both QApplication and OSG::CocoaViewer doing single-application
// init (Apple menu, making front process, etc)
if (poseAsStandaloneApp) {
flags |= osgViewer::GraphicsWindowCocoa::WindowData::PoseAsStandaloneApp;
}
traits->inheritedWindowData = new osgViewer::GraphicsWindowCocoa::WindowData(flags);
#endif
}
GraphicsWindow* WindowBuilder::buildWindow(const SGPropertyNode* winNode, bool isMainWindow)
{
WindowSystemAdapter* wsa = WindowSystemAdapter::getWSA();
string windowName;
if (winNode->hasChild("window-name"))
windowName = winNode->getStringValue("window-name");
else if (winNode->hasChild("name"))
windowName = winNode->getStringValue("name");
if (isMainWindow) {
SG_LOG(SG_GENERAL, SG_DEBUG, "Changing defaultWindowName from "
<< defaultWindowName << " to " << windowName);
defaultWindowName = windowName;
}
GraphicsWindow* result = 0;
if (!windowName.empty()) {
// look for an existing window and return that
result = wsa->findWindow(windowName);
if (result)
return result;
}
auto traits = new GraphicsContext::Traits(*defaultTraits);
// Attempt to share context with the window that was created first
if (!wsa->windows.empty())
traits->sharedContext = wsa->windows.front()->gc;
int traitsSet = setFromProperty(traits->hostName, winNode, "host-name");
traitsSet |= setFromProperty(traits->displayNum, winNode, "display");
traitsSet |= setFromProperty(traits->screenNum, winNode, "screen");
const SGPropertyNode* fullscreenNode = winNode->getNode("fullscreen");
if (fullscreenNode && fullscreenNode->getBoolValue()) {
setFullscreenTraits(winNode, traits);
traitsSet = 1;
} else {
traitsSet |= setWindowedTraits(winNode, traits);
}
traitsSet |= setFromProperty(traits->x, winNode, "x");
traitsSet |= setFromProperty(traits->y, winNode, "y");
if (!windowName.empty() && windowName != traits->windowName) {
traits->windowName = windowName;
traitsSet = 1;
} else if (traitsSet) {
traits->windowName = makeName("FlightGear", defaultCounter++);
}
setMacPoseAsStandaloneApp(traits);
bool drawGUI = false;
traitsSet |= setFromProperty(drawGUI, winNode, "gui");
if (traitsSet) {
GraphicsContext* gc = GraphicsContext::createGraphicsContext(traits);
if (gc) {
GraphicsWindow* window = WindowSystemAdapter::getWSA()
->registerWindow(gc, traits->windowName);
if (drawGUI)
window->flags |= GraphicsWindow::GUI;
return window;
} else {
return 0;
}
} else {
// XXX What if the window has no traits, but does have a name?
// We should create a "default window" registered with that name.
return getDefaultWindow();
}
}
GraphicsWindow* WindowBuilder::getDefaultWindow()
{
GraphicsWindow* defaultWindow
= WindowSystemAdapter::getWSA()->findWindow(defaultWindowName);
if (defaultWindow)
return defaultWindow;
// create if it, if necessary
GraphicsContext::Traits* traits
= new GraphicsContext::Traits(*defaultTraits);
traits->windowName = "FlightGear";
setMacPoseAsStandaloneApp(traits);
// this may be the point, where we discover OpenGL is broken on the
// system.
GraphicsContext* gc = GraphicsContext::createGraphicsContext(traits);
if (!gc) {
flightgear::fatalMessageBoxThenExit("Unable to create window",
"FlightGear was unable to create a window supporting 3D rendering (OpenGL). "
"This is normally due to outdated graphics drivers, please check if updates are available. ",
"Depending on your OS and graphics chipset, updates might come from AMD, nVidia or Intel.");
return nullptr; // unreachable anyway
}
defaultWindow = WindowSystemAdapter::getWSA()->registerWindow(gc, defaultWindowName);
return defaultWindow;
}
void WindowBuilder::setPoseAsStandaloneApp(bool b)
{
poseAsStandaloneApp = b;
}
} // of namespace flightgear

View File

@@ -0,0 +1,87 @@
// Copyright (C) 2008 Tim Moore
//
// 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.
#ifndef FLIGHTGEAR_WINDOWBUILDER_HXX
#define FLIGHTGEAR_WINDOWBUILDER_HXX 1
#include <osg/ref_ptr>
#include <osg/Referenced>
#include <osg/GraphicsContext>
#include <string>
class SGPropertyNode;
namespace flightgear
{
class GraphicsWindow;
/** Singleton Builder class for creating a GraphicsWindow from property
* nodes. This involves initializing an osg::GraphicsContext::Traits
* structure from the property node values and creating an
* osgViewer::GraphicsWindow.
*/
class WindowBuilder : public osg::Referenced
{
public:
/** Initialize the singleton window builder.
* @param stencil whether windows should allocate stencil planes
*/
static void initWindowBuilder(bool stencil);
/** Get the singleton window builder
*/
static WindowBuilder* getWindowBuilder() { return windowBuilder.get(); }
/** Create a window from its property node description.
* @param winNode The window's root property node
* @return a graphics window.
*/
GraphicsWindow* buildWindow(const SGPropertyNode* winNode, bool isMainWindow=false);
/** Get a window whose properties come from FlightGear's
* command line arguments and their defaults. The window is opened
* if it has not been already.
* @return the default graphics window
*/
GraphicsWindow* getDefaultWindow();
/** Get the name used to look up the default window.
*/
const std::string& getDefaultWindowName() { return defaultWindowName; }
static void setPoseAsStandaloneApp(bool b);
protected:
WindowBuilder(bool stencil);
void setFullscreenTraits(const SGPropertyNode* winNode, osg::GraphicsContext::Traits* traits);
bool setWindowedTraits(const SGPropertyNode* winNode, osg::GraphicsContext::Traits* traits);
void setMacPoseAsStandaloneApp(osg::GraphicsContext::Traits* traits);
void makeDefaultTraits(bool stencil);
osg::ref_ptr<osg::GraphicsContext::Traits> defaultTraits;
int defaultCounter;
bool usingQtGraphicsWindow = false;
static osg::ref_ptr<WindowBuilder> windowBuilder;
static std::string defaultWindowName;
static bool poseAsStandaloneApp;
};
/** Silly function for making the default window and camera
* names. This concatenates a string with in integer.
*/
std::string makeName(const std::string& prefix, int num);
}
#endif

View File

@@ -0,0 +1,68 @@
// Copyright (C) 2008 Tim Moore
//
// 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.
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include<algorithm>
#include <functional>
#include "CameraGroup.hxx"
#include "WindowSystemAdapter.hxx"
#include <osg/Camera>
#include <osg/GraphicsContext>
#include <osg/Viewport>
using namespace osg;
using namespace std;
namespace flightgear
{
ref_ptr<WindowSystemAdapter> WindowSystemAdapter::_wsa;
void GraphicsContextOperation::operator()(GraphicsContext* gc)
{
run(gc);
++done;
}
WindowSystemAdapter::WindowSystemAdapter() :
_nextWindowID(0)
{
}
GraphicsWindow*
WindowSystemAdapter::registerWindow(GraphicsContext* gc,
const string& windowName)
{
GraphicsWindow* window = new GraphicsWindow(gc, windowName,
_nextWindowID++);
windows.push_back(window);
return window;
}
GraphicsWindow* WindowSystemAdapter::findWindow(const string& name)
{
for (WindowVector::iterator iter = windows.begin(), e = windows.end();
iter != e;
++iter) {
if ((*iter)->name == name)
return iter->get();
}
return 0;
}
}

View File

@@ -0,0 +1,134 @@
// Copyright (C) 2008 Tim Moore
//
// 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.
#ifndef FLIGHTGEAR_WINDOWSYSTEMADAPTER_HXX
#define FLIGHTGEAR_WINDOWSYSTEMADAPTER_HXX 1
#include <functional>
#include <string>
#include <osg/Referenced>
#include <osg/Camera>
#include <osg/GraphicsThread>
#include <osg/ref_ptr>
#include <simgear/structure/SGAtomic.hxx>
namespace osg
{
class GraphicsContext;
}
// Flexible window support
namespace flightgear
{
/** A window with a graphics context and an integer ID
*/
class GraphicsWindow : public osg::Referenced
{
public:
GraphicsWindow(osg::GraphicsContext* gc_, const std::string& name_,
int id_, unsigned flags_ = 0) :
gc(gc_), name(name_), id(id_), flags(flags_)
{
}
/** The OSG graphics context for this window.
*/
osg::ref_ptr<osg::GraphicsContext> gc;
/** The window's internal name.
*/
std::string name;
/** A unique ID for the window.
*/
int id;
enum Flags {
GUI = 1 /**< The GUI (and 2D cockpit) will be drawn on this window. */
};
/** Flags for the window.
*/
unsigned flags;
};
typedef std::vector<osg::ref_ptr<GraphicsWindow> > WindowVector;
/**
* An operation that is run once with a particular GraphicsContext
* current. It will probably be deferred and may run in a different
* thread.
*/
class GraphicsContextOperation : public osg::GraphicsOperation
{
public:
GraphicsContextOperation(const std::string& name) :
osg::GraphicsOperation(name, false)
{
}
/** Don't override this!
*/
virtual void operator()(osg::GraphicsContext* gc);
/** The body of the operation.
*/
virtual void run(osg::GraphicsContext* gc) = 0;
/** Test if the operation has completed.
* @return true if the run() method has finished.
*/
bool isFinished() const { return done != 0; }
private:
SGAtomic done;
};
/** Adapter from windows system / graphics context management API to
* functions used by flightgear. This papers over the difference
* between osgViewer::Viewer, which handles multiple windows, graphics
* threads, etc., and the embedded viewer used with GLUT and SDL.
*/
class WindowSystemAdapter : public osg::Referenced
{
public:
WindowSystemAdapter();
virtual ~WindowSystemAdapter() {}
/** Vector of all the registered windows.
*/
WindowVector windows;
/** Register a window, assigning it an ID.
* @param gc graphics context
* @param windowName internal name (not displayed)
* @return a graphics window
*/
GraphicsWindow* registerWindow(osg::GraphicsContext* gc,
const std::string& windowName);
/** Find a window by name.
* @param name the window name
* @return the window or 0
*/
GraphicsWindow* findWindow(const std::string& name);
/** Get the global WindowSystemAdapter
* @return the adapter
*/
static WindowSystemAdapter* getWSA() { return _wsa.get(); }
/** Set the global adapter
* @param wsa the adapter
*/
static void setWSA(WindowSystemAdapter* wsa) { _wsa = wsa; }
protected:
int _nextWindowID;
static osg::ref_ptr<WindowSystemAdapter> _wsa;
};
}
#endif

694
src/Viewer/fg_os_osgviewer.cxx Executable file
View File

@@ -0,0 +1,694 @@
// fg_os_osgviewer.cxx -- common functions for fg_os interface
// implemented as an osgViewer
//
// Copyright (C) 2007 Tim Moore timoore@redhat.com
// Copyright (C) 2007 Mathias Froehlich
//
// 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.
#ifdef __linux__
#include <sched.h>
#endif
#include <config.h>
#include <algorithm>
#include <cstdlib>
#include <iostream>
#include <sstream>
#include <string>
#include <simgear/compiler.h>
#include <simgear/debug/OsgIoCapture.hxx>
#include <simgear/debug/logstream.hxx>
#include <simgear/props/props_io.hxx>
#include <simgear/structure/exception.hxx>
#include <osg/Camera>
#include <osg/GraphicsContext>
#include <osg/Group>
#include <osg/Matrixd>
#include <osg/Notify>
#include <osg/Version>
#include <osg/View>
#include <osg/Viewport>
#include <osgViewer/GraphicsWindow>
#include <osgViewer/Viewer>
#include <osgViewer/ViewerEventHandlers>
#include "CameraGroup.hxx"
#include "FGEventHandler.hxx"
#include "VRManager.hxx"
#include "WindowBuilder.hxx"
#include "WindowSystemAdapter.hxx"
#include "renderer.hxx"
#include <Main/fg_os.hxx>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include <Main/sentryIntegration.hxx>
#include <Main/util.hxx>
#include <Scenery/scenery.hxx>
#if defined(SG_MAC)
#include <GUI/CocoaHelpers.h>
#endif
#if defined(SG_WINDOWS)
#include <process.h> // _getpid()
#endif
// Static linking of OSG needs special macros
#ifdef OSG_LIBRARY_STATIC
#include <osgDB/Registry>
USE_GRAPHICSWINDOW();
// Image formats
USE_OSGPLUGIN(bmp);
USE_OSGPLUGIN(dds);
USE_OSGPLUGIN(hdr);
USE_OSGPLUGIN(pic);
USE_OSGPLUGIN(pnm);
USE_OSGPLUGIN(rgb);
USE_OSGPLUGIN(tga);
#ifdef OSG_JPEG_ENABLED
USE_OSGPLUGIN(jpeg);
#endif
#ifdef OSG_PNG_ENABLED
USE_OSGPLUGIN(png);
#endif
#ifdef OSG_TIFF_ENABLED
USE_OSGPLUGIN(tiff);
#endif
// Model formats
USE_OSGPLUGIN(3ds);
USE_OSGPLUGIN(ac);
USE_OSGPLUGIN(ive);
USE_OSGPLUGIN(osg);
USE_OSGPLUGIN(txf);
#endif
// fg_os implementation using OpenSceneGraph's osgViewer::Viewer class
// to create the graphics window and run the event/update/render loop.
//
// fg_os implementation
//
using namespace std;
using namespace flightgear;
using namespace osg;
osg::ref_ptr<osgViewer::Viewer> viewer;
static void setStereoMode(const char* mode)
{
DisplaySettings::StereoMode stereoMode = DisplaySettings::QUAD_BUFFER;
bool stereoOn = true;
if (strcmp(mode, "QUAD_BUFFER") == 0) {
stereoMode = DisplaySettings::QUAD_BUFFER;
} else if (strcmp(mode, "ANAGLYPHIC") == 0) {
stereoMode = DisplaySettings::ANAGLYPHIC;
} else if (strcmp(mode, "HORIZONTAL_SPLIT") == 0) {
stereoMode = DisplaySettings::HORIZONTAL_SPLIT;
} else if (strcmp(mode, "VERTICAL_SPLIT") == 0) {
stereoMode = DisplaySettings::VERTICAL_SPLIT;
} else if (strcmp(mode, "LEFT_EYE") == 0) {
stereoMode = DisplaySettings::LEFT_EYE;
} else if (strcmp(mode, "RIGHT_EYE") == 0) {
stereoMode = DisplaySettings::RIGHT_EYE;
} else if (strcmp(mode, "HORIZONTAL_INTERLACE") == 0) {
stereoMode = DisplaySettings::HORIZONTAL_INTERLACE;
} else if (strcmp(mode, "VERTICAL_INTERLACE") == 0) {
stereoMode = DisplaySettings::VERTICAL_INTERLACE;
} else if (strcmp(mode, "CHECKERBOARD") == 0) {
stereoMode = DisplaySettings::CHECKERBOARD;
} else {
stereoOn = false;
}
DisplaySettings::instance()->setStereo(stereoOn);
DisplaySettings::instance()->setStereoMode(stereoMode);
}
static const char* getStereoMode()
{
DisplaySettings::StereoMode stereoMode = DisplaySettings::instance()->getStereoMode();
bool stereoOn = DisplaySettings::instance()->getStereo();
if (!stereoOn) return "OFF";
if (stereoMode == DisplaySettings::QUAD_BUFFER) {
return "QUAD_BUFFER";
} else if (stereoMode == DisplaySettings::ANAGLYPHIC) {
return "ANAGLYPHIC";
} else if (stereoMode == DisplaySettings::HORIZONTAL_SPLIT) {
return "HORIZONTAL_SPLIT";
} else if (stereoMode == DisplaySettings::VERTICAL_SPLIT) {
return "VERTICAL_SPLIT";
} else if (stereoMode == DisplaySettings::LEFT_EYE) {
return "LEFT_EYE";
} else if (stereoMode == DisplaySettings::RIGHT_EYE) {
return "RIGHT_EYE";
} else if (stereoMode == DisplaySettings::HORIZONTAL_INTERLACE) {
return "HORIZONTAL_INTERLACE";
} else if (stereoMode == DisplaySettings::VERTICAL_INTERLACE) {
return "VERTICAL_INTERLACE";
} else if (stereoMode == DisplaySettings::CHECKERBOARD) {
return "CHECKERBOARD";
}
return "OFF";
}
class NotifyLevelListener : public SGPropertyChangeListener
{
public:
void valueChanged(SGPropertyNode* node)
{
osg::NotifySeverity severity = osg::getNotifyLevel();
string val = simgear::strutils::lowercase(node->getStringValue());
if (val == "fatal") {
severity = osg::FATAL;
} else if (val == "warn") {
severity = osg::WARN;
} else if (val == "notice") {
severity = osg::NOTICE;
} else if (val == "info") {
severity = osg::INFO;
} else if ((val == "debug") || (val == "debug-info")) {
severity = osg::DEBUG_INFO;
}
osg::setNotifyLevel(severity);
}
};
void updateOSGNotifyLevel()
{
}
void fgOSOpenWindow(bool stencil)
{
osg::setNotifyHandler(new NotifyLogger);
auto composite_viewer = dynamic_cast<osgViewer::CompositeViewer*>(
globals->get_renderer()->getViewerBase());
if (0) {
} else if (composite_viewer) {
/* We are using CompositeViewer. */
SG_LOG(SG_VIEW, SG_DEBUG, "Using CompositeViewer");
osgViewer::ViewerBase* viewer = globals->get_renderer()->getViewerBase();
SG_LOG(SG_VIEW, SG_DEBUG, "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");
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());
}
}
SGPropertyNode *simHost = 0, *simFrameCount, *simTotalHostTime, *simFrameResetCount, *frameWait;
void fgOSResetProperties()
{
SGPropertyNode* osgLevel = fgGetNode("/sim/rendering/osg-notify-level", true);
simTotalHostTime = fgGetNode("/sim/rendering/sim-host-total-ms", true);
simHost = fgGetNode("/sim/rendering/sim-host-avg-ms", true);
simFrameCount = fgGetNode("/sim/rendering/sim-frame-count", true);
simFrameResetCount = fgGetNode("/sim/rendering/sim-frame-count-reset", true);
frameWait = fgGetNode("/sim/time/frame-wait-ms", true);
simFrameResetCount->setBoolValue(false);
NotifyLevelListener* l = new NotifyLevelListener;
globals->addListenerToCleanup(l);
osgLevel->addChangeListener(l, true);
osg::Camera* guiCamera = getGUICamera(CameraGroup::getDefault());
if (guiCamera) {
Viewport* guiViewport = guiCamera->getViewport();
fgSetInt("/sim/startup/xsize", guiViewport->width());
fgSetInt("/sim/startup/ysize", guiViewport->height());
}
DisplaySettings* displaySettings = DisplaySettings::instance();
fgTie("/sim/rendering/osg-displaysettings/split-stereo-autoadjust-aspect-ratio", displaySettings, &DisplaySettings::getSplitStereoAutoAdjustAspectRatio, &DisplaySettings::setSplitStereoAutoAdjustAspectRatio);
fgTie("/sim/rendering/osg-displaysettings/eye-separation", displaySettings, &DisplaySettings::getEyeSeparation, &DisplaySettings::setEyeSeparation);
fgTie("/sim/rendering/osg-displaysettings/screen-distance", displaySettings, &DisplaySettings::getScreenDistance, &DisplaySettings::setScreenDistance);
fgTie("/sim/rendering/osg-displaysettings/screen-width", displaySettings, &DisplaySettings::getScreenWidth, &DisplaySettings::setScreenWidth);
fgTie("/sim/rendering/osg-displaysettings/screen-height", displaySettings, &DisplaySettings::getScreenHeight, &DisplaySettings::setScreenHeight);
fgTie("/sim/rendering/osg-displaysettings/stereo-mode", getStereoMode, setStereoMode);
fgTie("/sim/rendering/osg-displaysettings/double-buffer", displaySettings, &DisplaySettings::getDoubleBuffer, &DisplaySettings::setDoubleBuffer);
fgTie("/sim/rendering/osg-displaysettings/depth-buffer", displaySettings, &DisplaySettings::getDepthBuffer, &DisplaySettings::setDepthBuffer);
fgTie("/sim/rendering/osg-displaysettings/rgb", displaySettings, &DisplaySettings::getRGB, &DisplaySettings::setRGB);
#ifdef ENABLE_OSGXR
fgSetBool("/sim/vr/built", true);
#else
fgSetBool("/sim/vr/built", false);
#endif
}
static int status = 0;
void fgOSExit(int code)
{
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
// GraphicsWindowX11, since OSG statics may have been created before the
// sglog static, despite our best efforts in boostrap.cxx
osg::setNotifyHandler(new osg::StandardNotifyHandler);
}
SGTimeStamp _lastUpdate;
static void ShowAffinities()
{
#ifdef __linux__
char command[1024];
snprintf(command, sizeof(command), "for i in `ls /proc/%i/task/`; do taskset -p $i; done 1>&2", getpid());
SG_LOG(SG_VIEW, SG_ALERT, "Running: " << command);
system(command);
#endif
}
#ifdef __linux__
static std::ostream& operator<<(std::ostream& out, const cpu_set_t& mask)
{
out << "0x";
unsigned char* mask2 = (unsigned char*)&mask;
for (unsigned i = 0; i < sizeof(mask); ++i) {
char buffer[8];
snprintf(buffer, sizeof(buffer), "%02x", (unsigned)mask2[i]);
out << buffer;
}
return out;
}
#endif
/* Listen to /sim/affinity-control and, on Linux only, responds to
value='clear' and 'revert':
'clear'
Stores current affinities for all thread then resets all affinities so
that all threads can run on any cpu core.
'revert'
Restores thread affinities stored from previous 'clear'.
*/
struct AffinityControl : SGPropertyChangeListener {
AffinityControl()
{
m_node = globals->get_props()->getNode("/sim/affinity-control", true /*create*/);
m_node->addChangeListener(this);
}
void valueChanged(SGPropertyNode* node) override
{
#ifdef __linux__
std::string s = m_node->getStringValue();
if (s == m_state) {
SG_LOG(SG_VIEW, SG_ALERT, "Ignoring m_node=" << s << " because same as m_state.");
} else if (s == "clear") {
char buffer[64];
snprintf(buffer, sizeof(buffer), "/proc/%i/task", getpid());
SGPath path(buffer);
simgear::Dir dir(path);
m_thread_masks.clear();
simgear::PathList pids = dir.children(
simgear::Dir::TYPE_DIR | simgear::Dir::NO_DOT_OR_DOTDOT);
for (SGPath path : pids) {
std::string leaf = path.file();
int pid = atoi(leaf.c_str());
cpu_set_t mask;
int e = sched_getaffinity(pid, sizeof(mask), &mask);
SG_LOG(SG_VIEW, SG_ALERT, "Called sched_getaffinity()"
<< " pid=" << pid << " => e=" << e << " mask=" << mask);
if (!e) {
m_thread_masks[pid] = mask;
memset(&mask, 255, sizeof(mask));
e = sched_setaffinity(pid, sizeof(mask), &mask);
SG_LOG(SG_VIEW, SG_ALERT, "Called sched_setaffinity()"
<< " pid=" << pid << " => e=" << e << " mask=" << mask);
//assert(!e);
}
}
m_state = s;
} else if (s == "revert") {
for (auto it : m_thread_masks) {
pid_t pid = it.first;
cpu_set_t mask = it.second;
int e = sched_setaffinity(pid, sizeof(mask), &mask);
SG_LOG(SG_VIEW, SG_ALERT, "Called sched_setaffinity()"
<< " pid=" << pid << " => e=" << e << " mask=" << mask);
//assert(!e);
}
m_thread_masks.clear();
m_state = s;
} else {
SG_LOG(SG_VIEW, SG_ALERT, "Unrecognised m_node=" << s);
}
#endif
}
SGPropertyNode_ptr m_node;
std::string m_state;
#ifdef __linux__
std::map<int, cpu_set_t> m_thread_masks;
#endif
};
int fgOSMainLoop()
{
AffinityControl affinity_control;
osgViewer::ViewerBase* viewer_base = globals->get_renderer()->getViewerBase();
viewer_base->setReleaseContextAtEndOfFrameHint(false);
if (!viewer_base->isRealized()) {
viewer_base->realize();
std::string affinity = fgGetString("/sim/thread-cpu-affinity");
SG_LOG(SG_VIEW, SG_ALERT, "affinity=" << affinity);
if (affinity != "") {
ShowAffinities();
if (affinity == "osg") {
SG_LOG(SG_VIEW, SG_ALERT, "Resetting affinity of current thread getpid()=" << getpid());
OpenThreads::Affinity affinity;
OpenThreads::SetProcessorAffinityOfCurrentThread(affinity);
ShowAffinities();
}
}
}
while (!viewer_base->done()) {
fgIdleHandler idleFunc = globals->get_renderer()->getEventHandler()->getIdleHandler();
if (idleFunc) {
_lastUpdate.stamp();
(*idleFunc)();
if (fgGetBool("/sim/position-finalized", false)) {
if (simHost && simFrameCount && simTotalHostTime && simFrameResetCount) {
int curFrameCount = simFrameCount->getIntValue();
double totalSimTime = simTotalHostTime->getDoubleValue();
if (simFrameResetCount->getBoolValue()) {
curFrameCount = 0;
totalSimTime = 0;
simFrameResetCount->setBoolValue(false);
}
double lastSimFrame_ms = _lastUpdate.elapsedMSec();
double idle_wait = 0;
if (frameWait)
idle_wait = frameWait->getDoubleValue();
if (lastSimFrame_ms > 0) {
totalSimTime += lastSimFrame_ms - idle_wait;
simTotalHostTime->setDoubleValue(totalSimTime);
curFrameCount++;
simFrameCount->setIntValue(curFrameCount);
simHost->setDoubleValue(totalSimTime / curFrameCount);
}
}
}
}
globals->get_renderer()->update();
#ifdef ENABLE_OSGXR
VRManager::instance()->update();
#endif
viewer_base->frame(globals->get_sim_time_sec());
}
flightgear::addSentryBreadcrumb("main loop exited", "info");
return status;
}
int fgGetKeyModifiers()
{
FGRenderer* r = globals->get_renderer();
if (!r || !r->getEventHandler()) { // happens during shutdown
return 0;
}
return r->getEventHandler()->getCurrentModifiers();
}
void fgWarpMouse(int x, int y)
{
warpGUIPointer(CameraGroup::getDefault(), x, y);
}
void fgOSInit(int* argc, char** argv)
{
// stock OSG windows are not Hi-DPI aware
fgSetDouble("/sim/rendering/gui-pixel-ratio", 1.0);
#if defined(SG_MAC)
cocoaRegisterTerminateHandler();
#endif
globals->get_renderer()->init();
WindowSystemAdapter::setWSA(new WindowSystemAdapter);
}
void fgOSCloseWindow()
{
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();
}
}
#ifdef ENABLE_OSGXR
VRManager::instance()->destroyAndWait();
#endif
FGScenery::resetPagerSingleton();
flightgear::addSentryBreadcrumb("fgOSCloseWindow, clearing camera group", "info");
flightgear::CameraGroup::setDefault(NULL);
WindowSystemAdapter::setWSA(NULL);
viewer = NULL;
}
void fgOSFullScreen()
{
osgViewer::ViewerBase* viewer_base = globals->get_renderer()->getViewerBase();
std::vector<osgViewer::GraphicsWindow*> windows;
viewer_base->getWindows(windows);
if (windows.empty())
return; // Huh?!?
/* Toggling window fullscreen is only supported for the main GUI window.
* The other windows should use fixed setup from the camera.xml file anyway. */
osgViewer::GraphicsWindow* window = windows[0];
osg::GraphicsContext::WindowingSystemInterface* wsi = osg::GraphicsContext::getWindowingSystemInterface();
if (wsi == NULL) {
SG_LOG(SG_VIEW, SG_ALERT, "ERROR: No WindowSystemInterface available. Cannot toggle window fullscreen.");
return;
}
static int previous_x = 0;
static int previous_y = 0;
static int previous_width = 800;
static int previous_height = 600;
unsigned int screenWidth;
unsigned int screenHeight;
wsi->getScreenResolution(*(window->getTraits()), screenWidth, screenHeight);
int x;
int y;
int width;
int height;
window->getWindowRectangle(x, y, width, height);
/* Note: the simple "is window size == screen size" check to detect full screen state doesn't work with
* X screen servers in Xinerama mode, since the reported screen width (or height) exceeds the maximum width
* (or height) usable by a single window (Xserver automatically shrinks/moves the full screen window to fit a
* single display) - so we detect full screen mode using "WindowDecoration" state instead.
* "false" - even when a single window is display in fullscreen */
//bool isFullScreen = x == 0 && y == 0 && width == (int)screenWidth && height == (int)screenHeight;
bool isFullScreen = !window->getWindowDecoration();
SG_LOG(SG_VIEW, SG_DEBUG, "Toggling fullscreen. Previous window rectangle (" << x << ", " << y << ") x (" << width << ", " << height << "), fullscreen: " << isFullScreen << ", number of screens: " << wsi->getNumScreens());
if (isFullScreen) {
// limit x,y coordinates and window size to screen area
if (previous_x + previous_width > (int)screenWidth)
previous_x = 0;
if (previous_y + previous_height > (int)screenHeight)
previous_y = 0;
// disable fullscreen mode, restore previous window size/coordinates
x = previous_x;
y = previous_y;
width = previous_width;
height = previous_height;
} else {
// remember previous setting
previous_x = x;
previous_y = y;
previous_width = width;
previous_height = height;
// enable fullscreen mode, set new width/height
x = 0;
y = 0;
width = screenWidth;
height = screenHeight;
}
// set xsize/ysize properties to adapt GUI planes
fgSetInt("/sim/startup/xsize", width);
fgSetInt("/sim/startup/ysize", height);
fgSetBool("/sim/startup/fullscreen", !isFullScreen);
// reconfigure window
window->setWindowDecoration(isFullScreen);
window->setWindowRectangle(x, y, width, height);
window->grabFocusIfPointerInWindow();
}
static void setMouseCursor(osgViewer::GraphicsWindow* gw, int cursor)
{
if (!gw) {
return;
}
osgViewer::GraphicsWindow::MouseCursor mouseCursor;
mouseCursor = osgViewer::GraphicsWindow::InheritCursor;
if (cursor == MOUSE_CURSOR_NONE)
mouseCursor = osgViewer::GraphicsWindow::NoCursor;
else if (cursor == MOUSE_CURSOR_POINTER)
#ifdef SG_MAC
// osgViewer-Cocoa lacks RightArrowCursor, use Left
mouseCursor = osgViewer::GraphicsWindow::LeftArrowCursor;
#else
mouseCursor = osgViewer::GraphicsWindow::RightArrowCursor;
#endif
else if (cursor == MOUSE_CURSOR_WAIT)
mouseCursor = osgViewer::GraphicsWindow::WaitCursor;
else if (cursor == MOUSE_CURSOR_CROSSHAIR)
mouseCursor = osgViewer::GraphicsWindow::CrosshairCursor;
else if (cursor == MOUSE_CURSOR_LEFTRIGHT)
mouseCursor = osgViewer::GraphicsWindow::LeftRightCursor;
else if (cursor == MOUSE_CURSOR_TOPSIDE)
mouseCursor = osgViewer::GraphicsWindow::TopSideCursor;
else if (cursor == MOUSE_CURSOR_BOTTOMSIDE)
mouseCursor = osgViewer::GraphicsWindow::BottomSideCursor;
else if (cursor == MOUSE_CURSOR_LEFTSIDE)
mouseCursor = osgViewer::GraphicsWindow::LeftSideCursor;
else if (cursor == MOUSE_CURSOR_RIGHTSIDE)
mouseCursor = osgViewer::GraphicsWindow::RightSideCursor;
else if (cursor == MOUSE_CURSOR_TOPLEFT)
mouseCursor = osgViewer::GraphicsWindow::TopLeftCorner;
else if (cursor == MOUSE_CURSOR_TOPRIGHT)
mouseCursor = osgViewer::GraphicsWindow::TopRightCorner;
else if (cursor == MOUSE_CURSOR_BOTTOMLEFT)
mouseCursor = osgViewer::GraphicsWindow::BottomLeftCorner;
else if (cursor == MOUSE_CURSOR_BOTTOMRIGHT)
mouseCursor = osgViewer::GraphicsWindow::BottomRightCorner;
gw->setCursor(mouseCursor);
}
static int _cursor = -1;
void fgSetMouseCursor(int cursor)
{
_cursor = cursor;
if (!globals || !globals->get_renderer())
return;
osgViewer::ViewerBase* viewer_base = globals->get_renderer()->getViewerBase();
if (!viewer_base)
return;
std::vector<osgViewer::GraphicsWindow*> windows;
viewer_base->getWindows(windows);
for (osgViewer::GraphicsWindow* gw : windows) {
setMouseCursor(gw, cursor);
}
}
int fgGetMouseCursor()
{
return _cursor;
}

267
src/Viewer/fgviewer.cxx Normal file
View File

@@ -0,0 +1,267 @@
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <iostream>
#include <cstdlib>
#include <osg/ArgumentParser>
#include <osg/Fog>
#include <osgDB/ReadFile>
#include <osgDB/Registry>
#include <osgDB/WriteFile>
#include <osgViewer/Renderer>
#include <osgViewer/Viewer>
#include <osgViewer/ViewerEventHandlers>
#include <osgGA/KeySwitchMatrixManipulator>
#include <osgGA/TrackballManipulator>
#include <osgGA/FlightManipulator>
#include <osgGA/DriveManipulator>
#include <osgGA/TerrainManipulator>
#include <osgGA/StateSetManipulator>
#include <simgear/props/props.hxx>
#include <simgear/props/props_io.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/scene/material/EffectCullVisitor.hxx>
#include <simgear/scene/material/matlib.hxx>
#include <simgear/scene/util/SGReaderWriterOptions.hxx>
#include <simgear/scene/tgdb/userdata.hxx>
#include <simgear/scene/model/ModelRegistry.hxx>
#include <simgear/scene/model/modellib.hxx>
#include <simgear/structure/exception.hxx>
#include <Scenery/scenery.hxx>
#include <Navaids/NavDataCache.hxx>
#include <Viewer/renderer.hxx>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include <Main/options.hxx>
#include <Main/fg_init.hxx>
class GraphDumpHandler : public osgGA::GUIEventHandler
{
public:
GraphDumpHandler() : _keyDump('d') {}
void setKeyDump(int key) { _keyDump = key; }
int getKeyDump() const { return _keyDump; }
bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa);
/** Get the keyboard and mouse usage of this manipulator.*/
virtual void getUsage(osg::ApplicationUsage& usage) const;
protected:
int _keyDump;
};
static void dumpOut(osg::Node* node)
{
char filename[24];
static int count = 1;
while (count < 1000) {
FILE *fp;
snprintf(filename, 24, "fgviewer-%03d.osg", count++);
if ( (fp = fopen(filename, "r")) == NULL )
break;
fclose(fp);
}
if (osgDB::writeNodeFile(*node, filename))
std::cerr << "Entire scene graph saved to \"" << filename << "\".\n";
else
std::cerr << "Failed to save to \"" << filename << "\".\n";
}
bool GraphDumpHandler::handle(const osgGA::GUIEventAdapter& ea,
osgGA::GUIActionAdapter& aa)
{
osgViewer::View* view = dynamic_cast<osgViewer::View*>(&aa);
if (!view)
return false;
if (ea.getHandled())
return false;
switch(ea.getEventType()) {
case osgGA::GUIEventAdapter::KEYUP:
if (ea.getKey() == _keyDump) {
dumpOut(view->getScene()->getSceneData());
return true;
}
break;
default:
return false;
}
return false;
}
void GraphDumpHandler::getUsage(osg::ApplicationUsage& usage) const
{
std::ostringstream ostr;
ostr << char(_keyDump);
usage.addKeyboardMouseBinding(ostr.str(),
"Dump scene graph to file");
}
int
fgviewerMain(int argc, char** argv)
{
sgUserDataInit(0);
// use an ArgumentParser object to manage the program arguments.
osg::ArgumentParser arguments(&argc, argv);
// construct the viewer.
FGRenderer* fgrenderer = new FGRenderer();
osgViewer::Viewer* viewer = new osgViewer::Viewer(arguments);
fgrenderer->setView(viewer);
osg::Camera* camera = viewer->getCamera();
osgViewer::Renderer* renderer
= static_cast<osgViewer::Renderer*>(camera->getRenderer());
for (int i = 0; i < 2; ++i) {
osgUtil::SceneView* sceneView = renderer->getSceneView(i);
sceneView->setCullVisitor(new simgear::EffectCullVisitor);
}
// Shaders expect valid fog
osg::StateSet* cameraSS = camera->getOrCreateStateSet();
osg::Fog* fog = new osg::Fog;
fog->setMode(osg::Fog::EXP2);
fog->setColor(osg::Vec4(1.0, 1.0, 1.0, 1.0));
fog->setDensity(.0000001);
cameraSS->setAttributeAndModes(fog);
// ... for some reason, get rid of that FIXME!
viewer->setThreadingModel(osgViewer::Viewer::SingleThreaded);
// set up the camera manipulators.
osgGA::KeySwitchMatrixManipulator* keyswitchManipulator;
keyswitchManipulator = new osgGA::KeySwitchMatrixManipulator;
keyswitchManipulator->addMatrixManipulator('1', "Trackball",
new osgGA::TrackballManipulator);
keyswitchManipulator->addMatrixManipulator('2', "Flight",
new osgGA::FlightManipulator);
keyswitchManipulator->addMatrixManipulator('3', "Drive",
new osgGA::DriveManipulator);
keyswitchManipulator->addMatrixManipulator('4', "Terrain",
new osgGA::TerrainManipulator);
viewer->setCameraManipulator(keyswitchManipulator);
// Usefull stats
viewer->addEventHandler(new osgViewer::HelpHandler);
viewer->addEventHandler(new osgViewer::StatsHandler);
viewer->addEventHandler( new osgGA::StateSetManipulator(viewer->getCamera()->getOrCreateStateSet()) );
// Same FIXME ...
// viewer->addEventHandler(new osgViewer::ThreadingHandler);
viewer->addEventHandler(new osgViewer::LODScaleHandler);
viewer->addEventHandler(new osgViewer::ScreenCaptureHandler);
viewer->addEventHandler(new GraphDumpHandler);
// Extract files to load from arguments now; this way fgInitConfig
// won't choke on them.
string_list dataFiles;
for (int i = arguments.argc() - 1; i >= 0; --i) {
if (arguments.isOption(i)) {
break;
} else {
dataFiles.insert(dataFiles.begin(), arguments[i]);
arguments.remove(i);
}
}
// A subset of full flightgear initialization.
// Allocate global data structures. This needs to happen before
// we parse command line options
globals = new FGGlobals;
globals->set_renderer(fgrenderer);
SGPath dataPath = fgHomePath();
globals->set_fg_home(dataPath);
std::string s;
if (arguments.read("--fg-scenery", s)) {
globals->append_fg_scenery(SGPath::fromLocal8Bit(s.c_str()));
}
if (std::getenv("FG_SCENERY")) {
globals->append_fg_scenery(SGPath::fromEnv("FG_SCENERY"));
}
int configResult = fgInitConfig(arguments.argc(), arguments.argv(), false);
if (configResult == flightgear::FG_OPTIONS_ERROR) {
return EXIT_FAILURE;
} else if (configResult == flightgear::FG_OPTIONS_EXIT) {
return EXIT_SUCCESS;
}
osgDB::FilePathList filePathList
= osgDB::Registry::instance()->getDataFilePathList();
filePathList.push_back(globals->get_fg_root().local8BitStr());
const PathList& path_list = globals->get_fg_scenery();
for (unsigned i = 0; i < path_list.size(); ++i) {
filePathList.push_back(path_list[i].local8BitStr());
}
globals->set_matlib( new SGMaterialLib );
simgear::SGModelLib::init(globals->get_fg_root().local8BitStr(), globals->get_props());
// Initialize the material property subsystem.
SGPath mpath( globals->get_fg_root() );
mpath.append( fgGetString("/sim/rendering/materials-file") );
if ( ! globals->get_matlib()->load(globals->get_fg_root().local8BitStr(),
mpath.local8BitStr(),
globals->get_props()) ) {
throw sg_io_exception("Error loading materials file", mpath);
}
// The file path list must be set in the registry.
osgDB::Registry::instance()->getDataFilePathList() = filePathList;
simgear::SGReaderWriterOptions* options = new simgear::SGReaderWriterOptions;
options->getDatabasePathList() = filePathList;
options->setMaterialLib(globals->get_matlib());
options->setPropertyNode(globals->get_props());
options->setPluginStringData("SimGear::PREVIEW", "ON");
// Now init the renderer, as we've got all the options, globals etc.
fgrenderer->init();
FGScenery* scenery = globals->add_new_subsystem<FGScenery>(SGSubsystemMgr::DISPLAY);
scenery->init();
scenery->bind();
if (! flightgear::NavDataCache::instance()) {
flightgear::NavDataCache* cache = flightgear::NavDataCache::createInstance();
cache->updateListsOfDatFiles();
if (cache->isRebuildRequired()) {
while (cache->rebuild() != flightgear::NavDataCache::REBUILD_DONE) {
SGTimeStamp::sleepForMSec(1000);
std::cerr << "." << std::flush;
}
}
}
// read the scene from the list of file specified command line args.
osg::ref_ptr<osg::Node> loadedModel;
loadedModel = osgDB::readNodeFiles(dataFiles, options);
// if no model has been successfully loaded report failure.
if (!loadedModel.valid()) {
std::cerr << arguments.getApplicationName()
<< ": No data loaded" << std::endl;
return EXIT_FAILURE;
}
// pass the loaded scene graph to the viewer->
viewer->setSceneData(loadedModel.get());
int result = viewer->run();
// clear cache now, since it contains SimGear objects. Otherwise SG_LOG
// calls during shutdown will cause crashes.
osgDB::Registry::instance()->clearObjectCache();
return result;
}

5
src/Viewer/fgviewer.hxx Normal file
View File

@@ -0,0 +1,5 @@
#ifndef __FG_FGVIEWER_HXX
#define __FG_FGVIEWER_HXX 1
int fgviewerMain(int argc, char** argv);
#endif

1401
src/Viewer/renderer.cxx Normal file

File diff suppressed because it is too large Load Diff

151
src/Viewer/renderer.hxx Normal file
View File

@@ -0,0 +1,151 @@
#ifndef __FG_RENDERER_HXX
#define __FG_RENDERER_HXX 1
#include <simgear/scene/util/SGPickCallback.hxx>
#include <simgear/props/props.hxx>
#include <simgear/timing/timestamp.hxx>
#include <osg/ref_ptr>
#include <osg/Matrix>
#include <osg/Vec2>
#include <osg/Vec3>
#include <osgViewer/CompositeViewer>
namespace osg
{
class Camera;
class Group;
class GraphicsContext;
class FrameStamp;
}
namespace osgGA
{
class GUIEventAdapter;
}
namespace osgViewer
{
class Viewer;
}
namespace flightgear
{
class FGEventHandler;
class CameraGroup;
class PUICamera;
}
class SGSky;
class SGUpdateVisitor;
class SplashScreen;
class QQuickDrawable;
typedef std::vector<SGSceneryPick> PickList;
class FGRenderer {
public:
FGRenderer();
FGRenderer(osg::ref_ptr<osgViewer::CompositeViewer> composite_viewer);
~FGRenderer();
void preinit();
void init();
void setupView();
void resize(int width, int height);
void resize(int width, int height, int x, int y);
void update();
/** 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();
/** For handling reset. */
osg::ref_ptr<osgViewer::CompositeViewer> getCompositeViewer();
/** Get and set the OSG Viewer object, if any.
*/
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(); }
const flightgear::FGEventHandler* getEventHandler() const { return eventHandler.get(); }
void setEventHandler(flightgear::FGEventHandler* manipulator);
/** Add a top level camera.
*/
void addCamera(osg::Camera* camera, bool useSceneData);
void removeCamera(osg::Camera* camera);
SGSky* getSky() const { return _sky; }
void setPlanes( double zNear, double zFar );
SplashScreen* getSplash();
protected:
int composite_viewer_enabled = -1;
osg::ref_ptr<osgViewer::Viewer> viewer;
osg::ref_ptr<osgViewer::CompositeViewer> composite_viewer;
osg::ref_ptr<flightgear::FGEventHandler> eventHandler;
osg::ref_ptr<osg::FrameStamp> _frameStamp;
osg::ref_ptr<SGUpdateVisitor> _updateVisitor;
osg::ref_ptr<osg::Group> _viewerSceneRoot;
osg::ref_ptr<osg::Group> _root;
SGPropertyNode_ptr _scenery_loaded, _position_finalized;
SGPropertyNode_ptr _splash_alpha;
SGPropertyNode_ptr _enhanced_lighting;
SGPropertyNode_ptr _textures;
SGPropertyNode_ptr _cloud_status, _visibility_m;
SGPropertyNode_ptr _xsize, _ysize;
SGPropertyNode_ptr _xpos, _ypos;
SGPropertyNode_ptr _panel_hotspots, _sim_delta_sec, _horizon_effect, _altitude_ft;
SGPropertyNode_ptr _virtual_cockpit;
SGTimeStamp _splash_time;
SGSky* _sky;
int MaximumTextureSize;
typedef std::vector<SGPropertyChangeListener*> SGPropertyChangeListenerVec;
SGPropertyChangeListenerVec _listeners;
void addChangeListener(SGPropertyChangeListener* l, const char* path);
void updateSky();
void setupRoot();
osg::ref_ptr<SplashScreen> _splash;
QQuickDrawable* _quickDrawable = nullptr;
flightgear::PUICamera* _puiCamera = nullptr;
};
bool fgDumpSceneGraphToFile(const char* filename);
bool fgDumpTerrainBranchToFile(const char* filename);
namespace flightgear
{
bool printVisibleSceneInfo(FGRenderer* renderer);
}
#endif

916
src/Viewer/splash.cxx Normal file
View File

@@ -0,0 +1,916 @@
// splash.cxx -- draws the initial splash screen
//
// Written by Curtis Olson, started July 1998. (With a little looking
// at Freidemann's panel code.) :-)
//
// Copyright (C) 1997 Michele F. America - nomimarketing@mail.telepac.pt
//
// 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.
//
// $Id$
#include <config.h>
#include <osg/BlendFunc>
#include <osg/Camera>
#include <osg/Depth>
#include <osg/Geometry>
#include <osg/Node>
#include <osg/NodeCallback>
#include <osg/NodeVisitor>
#include <osg/StateSet>
#include <osg/Switch>
#include <osg/Texture2D>
#include <osg/TextureRectangle>
#include <osg/Version>
#include <osgText/Text>
#include <osgText/String>
#include <osgDB/ReadFile>
#include <simgear/compiler.h>
#include <simgear/debug/logstream.hxx>
#include <simgear/math/sg_random.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/misc/sg_dir.hxx>
#include <simgear/scene/util/SGReaderWriterOptions.hxx>
#include <simgear/structure/OSGUtils.hxx>
#include <simgear/props/condition.hxx>
#include "VRManager.hxx"
#include "renderer.hxx"
#include "splash.hxx"
#include <GUI/gui.h>
#include <Main/fg_os.hxx>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include <Main/locale.hxx>
#include <Main/util.hxx>
#include <Viewer/CameraGroup.hxx>
#include <sstream>
static const char* LICENSE_URL_TEXT = "Licensed under the GNU GPL. See https://www.flightgear.org for more information";
using namespace std::string_literals;
using namespace simgear;
class SplashScreenUpdateCallback : public osg::NodeCallback {
public:
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
{
SplashScreen* screen = static_cast<SplashScreen*>(node);
screen->doUpdate();
traverse(node, nv);
}
};
SplashScreen::SplashScreen() :
_splashAlphaNode(fgGetNode("/sim/startup/splash-alpha", true))
{
#ifdef ENABLE_OSGXR
uint32_t splashW = 1920, splashH = 1080;
float aspect = (float)splashW / splashH;
_splashSwapchain = new osgXR::Swapchain(splashW, splashH);
_splashSwapchain->setAlphaBits(8);
_splashSwapchain->allowRGBEncoding(osgXR::Swapchain::Encoding::ENCODING_SRGB);
_splashLayer = new osgXR::CompositionLayerQuad(flightgear::VRManager::instance());
_splashLayer->setSubImage(_splashSwapchain);
_splashLayer->setSize(osg::Vec2f(aspect, 1.0f));
_splashLayer->setPosition(osg::Vec3f(0, 0, -2.0f));
_splashLayer->setAlphaMode(osgXR::CompositionLayer::BLEND_ALPHA_UNPREMULT);
#endif
setName("splashGroup");
setUpdateCallback(new SplashScreenUpdateCallback);
}
SplashScreen::~SplashScreen()
{
}
void SplashScreen::createNodes()
{
// The splash FBO is rendered as sRGB, but for VR it needs to be in an sRGB
// pixel format for it to be handled as such by OpenXR.
bool useSRGB = false;
#if !defined(SG_MAC)
// SRGB does detect on macOS, but doesn't actually work, so we
// disable the check there.
osg::Camera* guiCamera = flightgear::getGUICamera(flightgear::CameraGroup::getDefault());
if (guiCamera) {
osg::GraphicsContext* gc = guiCamera->getGraphicsContext();
osg::GLExtensions* glext = gc->getState()->get<osg::GLExtensions>();
if (glext) {
SG_LOG(SG_VIEW, SG_WARN, "GL version " << glext->glVersion);
SG_LOG(SG_VIEW, SG_WARN, "GL_EXT_texture_sRGB " << osg::isGLExtensionSupported(glext->contextID, "GL_EXT_texture_sRGB"));
SG_LOG(SG_VIEW, SG_WARN, "GL_EXT_framebuffer_sRGB " << osg::isGLExtensionSupported(glext->contextID, "GL_EXT_framebuffer_sRGB"));
useSRGB = osg::isGLExtensionOrVersionSupported(glext->contextID, "GL_EXT_texture_sRGB", 2.1f) &&
osg::isGLExtensionOrVersionSupported(glext->contextID, "GL_EXT_framebuffer_sRGB", 3.0f);
}
}
SG_LOG(SG_VIEW, SG_INFO, "Splash: useSRGB " << useSRGB);
#endif
// setup the base geometry
_splashFBOTexture = new osg::Texture2D;
_splashFBOTexture->setInternalFormat(useSRGB ? GL_SRGB8 : GL_RGB);
_splashFBOTexture->setResizeNonPowerOfTwoHint(false);
_splashFBOTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
_splashFBOTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
_splashFBOCamera = createFBOCamera();
addChild(_splashFBOCamera);
osg::Geometry* geometry = new osg::Geometry;
geometry->setSupportsDisplayList(false);
osg::Geode* geode = new osg::Geode;
_splashFBOCamera->addChild(geode);
geode->addDrawable(geometry);
// get localized GPL licence text to be displayed at splash screen startup
std::string licenseUrlText = globals->get_locale()->getLocalizedString("license-url", "sys", LICENSE_URL_TEXT);
// add the splash image
std::string splashImageName = selectSplashImage();
addImage(splashImageName, true, 0, 0, 1, 1, nullptr, true);
// parse the content from the tree
// there can be many <content> <model-content> and <image> nodes
// <content> is reserved for use in defaults.xml and is the basic
// text; model-content and images are for use in the model
// to present model related information.
auto root = globals->get_props()->getNode("/sim/startup");
bool legacySplashLogoMode = false;
// firstly add all image nodes.
std::vector<SGPropertyNode_ptr> images = root->getChildren("image");
if (!images.empty()) {
for (const auto& image : images) {
addImage(image->getStringValue("path", ""),
false,
image->getDoubleValue("x", 0.025f),
image->getDoubleValue("y", 0.935f),
image->getDoubleValue("width", 0.1),
image->getDoubleValue("height", 0.1),
image->getNode("condition"),
false);
}
} else {
// if there are no image nodes then revert to the legacy (2020.3 or before) way of doing things
auto splashLogoImage = fgGetString("/sim/startup/splash-logo-image");
if (!splashLogoImage.empty())
{
float logoX = fgGetDouble("/sim/startup/splash-logo-x-norm", 0.0);
float logoY = 1.0 - fgGetDouble("/sim/startup/splash-logo-y-norm", 0.065);
float logoWidth = fgGetDouble("/sim/startup/splash-logo-width", 0.6);
auto img = addImage(splashLogoImage, false, logoX, logoY, logoWidth, 0, nullptr, false);
if (img != nullptr)
legacySplashLogoMode = true;
}
}
// put this information into the property tree so the defaults or model can pull it in.
fgSetString("/sim/startup/splash-authors", flightgear::getAircraftAuthorsText());
fgSetString("/sim/startup/description", fgGetString("/sim/description"));
fgSetString("/sim/startup/title", "FlightGear "s + fgGetString("/sim/version/flightgear"));
if (!strcmp(FG_BUILD_TYPE, "Nightly")) {
fgSetString("sim/build-warning", globals->get_locale()->getLocalizedString("unstable-warning", "sys", "unstable!"));
fgSetBool("sim/build-warning-active", true);
}
// the licence node serves a dual purpose; both the licence URL and then after a delay
// (currently 5 seconds) it will display helpful information.
fgSetString("/sim/startup/licence", licenseUrlText);
fgSetString("/sim/startup/tip", licenseUrlText);
#ifndef NDEBUG
fgSetBool("/sim/startup/build-type-debug", true);
#else
fgSetBool("/sim/startup/build-type-debug", false);
#endif
fgSetBool("/sim/startup/legacy-splash-screen", _legacySplashScreenMode);
fgSetBool("/sim/startup/legacy-splash-logo", legacySplashLogoMode);
// load all model content first.
for (const auto& content : root->getChildren("model-content")) {
CreateTextFromNode(content, geode, true);
}
// default content comes in second; and has the ability to be overriden by the model
for (const auto& content : root->getChildren("content")) {
if (content->getIndex()) { // Skip 0 element - reserved for future usage.
// default content can be hidden by the model. By hidden it will never be
// added (there is also the possibility to use a condition to dynamically hide content)
if (!content->getBoolValue("hide"))
CreateTextFromNode(content, geode, false);
}
}
// add main title last so it is atop all.
addText(geode, osg::Vec2(0.025f, 0.02f), 0.08, "FlightGear "s + fgGetString("/sim/version/flightgear"), osgText::Text::LEFT_TOP);
///////////
geometry = new osg::Geometry;
geometry->setSupportsDisplayList(false);
_splashSpinnerVertexArray = new osg::Vec3Array;
for (int i=0; i < 8; ++i) {
_splashSpinnerVertexArray->push_back(osg::Vec3(0.0f, 0.0f, -0.1f));
}
geometry->setVertexArray(_splashSpinnerVertexArray);
// QColor buttonColor(27, 122, 211);
osg::Vec4Array* colorArray = new osg::Vec4Array;
colorArray->push_back(osg::Vec4(27 / 255.0f, 122 / 255.0f, 211 / 255.0f, 0.75f));
geometry->setColorArray(colorArray);
geometry->setColorBinding(osg::Geometry::BIND_OVERALL);
geometry->addPrimitiveSet(new osg::DrawArrays(GL_QUADS, 0, 8));
geode->addDrawable(geometry);
//// Full screen quad setup ////////////////////
_splashQuadCamera = new osg::Camera;
_splashQuadCamera->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
_splashQuadCamera->setViewMatrix(osg::Matrix::identity());
_splashQuadCamera->setProjectionMatrixAsOrtho2D(0.0, 1.0, 0.0, 1.0);
_splashQuadCamera->setAllowEventFocus(false);
_splashQuadCamera->setCullingActive(false);
_splashQuadCamera->setRenderOrder(osg::Camera::NESTED_RENDER);
osg::StateSet* stateSet = _splashQuadCamera->getOrCreateStateSet();
stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
stateSet->setAttribute(new osg::BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA), osg::StateAttribute::ON);
stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
stateSet->setRenderBinDetails(1000, "RenderBin");
stateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
if (useSRGB) {
stateSet->setMode(GL_FRAMEBUFFER_SRGB, osg::StateAttribute::ON);
}
geometry = osg::createTexturedQuadGeometry(osg::Vec3(0.0, 0.0, 0.0),
osg::Vec3(1.0, 0.0, 0.0),
osg::Vec3(0.0, 1.0, 0.0));
geometry->setSupportsDisplayList(false);
_splashFSQuadColor = new osg::Vec4Array;
_splashFSQuadColor->push_back(osg::Vec4(1, 1.0f, 1, 1));
geometry->setColorArray(_splashFSQuadColor);
geometry->setColorBinding(osg::Geometry::BIND_OVERALL);
stateSet = geometry->getOrCreateStateSet();
stateSet->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON);
stateSet->setTextureAttribute(0, _splashFBOTexture);
geode = new osg::Geode;
geode->addDrawable(geometry);
#ifdef ENABLE_OSGXR
_splashSwapchain->attachToMirror(stateSet);
#endif
_splashQuadCamera->addChild(geode);
addChild(_splashQuadCamera);
}
// creates a text element from a property node;
// <text> defines the text
// <text-prop> overrides the text with the contents of a property
// <dynamic-text> will link to a property and update the screen when the property changes
// <condition> Visibility Expression
// <color> <r><g><b> </color> define colour to be used
// <font> specifies the font alignment, size and typeface
// <max-width> [optional] the max normalized width of the element
// <max-height> [optional] the max normalized height of the element.
// <max-lines> [optional] the max number of lines this text can be wrapped over
// wrapping takes place at max-width
//
void SplashScreen::CreateTextFromNode(const SGPropertyNode_ptr& content, osg::Geode* geode, bool modelContent)
{
auto text = content->getStringValue("text", "");
std::string textFromProperty = content->getStringValue("text-prop", "");
if (!textFromProperty.empty())
text = fgGetString(textFromProperty);
SGPropertyNode* dynamicValueNode = nullptr;
std::string dynamicProperty = content->getStringValue("dynamic-text");
if (!dynamicProperty.empty())
dynamicValueNode = fgGetNode(dynamicProperty, true);
auto conditionNode = content->getChild("condition");
SGCondition* condition = nullptr;
if (conditionNode != nullptr) {
condition = sgReadCondition(fgGetNode("/"), conditionNode);
}
auto x = content->getDoubleValue("x", 0.5);
auto y = content->getDoubleValue("y", 0.5);
if (modelContent) {
// the top 0.2 of the screen is for system usage
if (y < 0.2) {
SG_LOG(SG_VIEW, SG_ALERT, "model content cannot be above 0.2 y");
y = 0.2;
}
}
auto textItem = addText(geode, osg::Vec2(x, y),
content->getDoubleValue("font/size", 0.06),
text,
osgutils::mapAlignment(content->getStringValue("font/alignment", "left-top")),
dynamicValueNode,
content->getDoubleValue("max-width", -1.0),
osg::Vec4(content->getDoubleValue("color/r", 1), content->getDoubleValue("color/g", 1), content->getDoubleValue("color/b", 1), content->getDoubleValue("color/a", 1)),
content->getStringValue("font/face", "Fonts/LiberationFonts/LiberationSans-BoldItalic.ttf"));
textItem->condition = condition;
if (textItem->condition != nullptr && !textItem->condition->test())
textItem->textNode->setDrawMode(0);
auto maxHeight = content->getDoubleValue("max-height", -1.0);
auto maxLineCount = content->getIntValue("max-line-count", -1);
if (maxLineCount > 0)
textItem->maxLineCount = maxLineCount;
if (maxHeight > 0)
textItem->maxHeightFraction = maxHeight;
}
osg::ref_ptr<osg::Camera> SplashScreen::createFBOCamera()
{
osg::ref_ptr<osg::Camera> c = new osg::Camera;
c->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
c->setViewMatrix(osg::Matrix::identity());
c->setClearMask( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
c->setClearColor( osg::Vec4( 0., 0., 0., 0. ) );
c->setAllowEventFocus(false);
c->setCullingActive(false);
c->setRenderTargetImplementation( osg::Camera::FRAME_BUFFER_OBJECT );
c->setRenderOrder(osg::Camera::PRE_RENDER);
c->attach(osg::Camera::COLOR_BUFFER, _splashFBOTexture);
#ifdef ENABLE_OSGXR
_splashSwapchain->attachToCamera(c);
#endif
osg::StateSet* stateSet = c->getOrCreateStateSet();
stateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::OFF);
stateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF);
stateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
stateSet->setAttribute(new osg::Depth(osg::Depth::ALWAYS, 0, 1, false));
stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
return c;
}
// Load an image for display
// - path
// - x,y,width,height (normalized coordinates)
// - isBackground flag to indicate that this image is the background image. Only set during one call to this method when loading the splash image
//
const SplashScreen::ImageItem *SplashScreen::addImage(const std::string &path, bool isAbsolutePath, double x, double y, double width, double height, SGPropertyNode*
conditionNode, bool isBackground)
{
if (path.empty())
return nullptr;
SGPath imagePath;
if (!isAbsolutePath)
imagePath = globals->resolve_maybe_aircraft_path(path);
else
imagePath = path;
if (!imagePath.exists() || !imagePath.isFile()) {
SG_LOG(SG_VIEW, SG_INFO, "Splash Image " << path << " not be found");
return nullptr;
}
ImageItem item;
item.name = path;
item.x = x;
item.y = y;
item.height = height;
item.width = width;
item.isBackground = isBackground;
if (conditionNode != nullptr)
item.condition = sgReadCondition(fgGetNode("/"), conditionNode);
else
item.condition = nullptr;
osg::ref_ptr<simgear::SGReaderWriterOptions> staticOptions = simgear::SGReaderWriterOptions::copyOrCreate(osgDB::Registry::instance()->getOptions());
staticOptions->setLoadOriginHint(simgear::SGReaderWriterOptions::LoadOriginHint::ORIGIN_SPLASH_SCREEN);
item.Image = osgDB::readRefImageFile(imagePath.utf8Str(), staticOptions);
if (!item.Image) {
SG_LOG(SG_VIEW, SG_INFO, "Splash Image " << imagePath << " failed to load");
return nullptr;
}
item.imageWidth = item.Image->s();
item.imageHeight = item.Image->t();
item.aspectRatio = static_cast<double>(item.imageWidth) / item.imageHeight;
if (item.height == 0 && item.imageWidth != 0)
item.height = item.imageHeight * (item.width / item.imageWidth);
osg::Texture2D* imageTexture = new osg::Texture2D(item.Image);
imageTexture->setResizeNonPowerOfTwoHint(false);
imageTexture->setInternalFormat(GL_RGBA);
imageTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
imageTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
osg::Geometry* geometry = new osg::Geometry;
geometry->setSupportsDisplayList(false);
item.vertexArray = new osg::Vec3Array;
for (int i=0; i < 4; ++i) {
item.vertexArray->push_back(osg::Vec3(0.0, 0.0, 0.0));
}
geometry->setVertexArray(item.vertexArray);
osg::Vec2Array* imageTextureCoordinates = new osg::Vec2Array;
imageTextureCoordinates->push_back(osg::Vec2(0, 0));
imageTextureCoordinates->push_back(osg::Vec2(1.0, 0));
imageTextureCoordinates->push_back(osg::Vec2(1.0, 1.0));
imageTextureCoordinates->push_back(osg::Vec2(0, 1.0));
geometry->setTexCoordArray(0, imageTextureCoordinates);
osg::Vec4Array* imageColorArray = new osg::Vec4Array;
imageColorArray->push_back(osg::Vec4(1, 1, 1, 1));
geometry->setColorArray(imageColorArray);
geometry->setColorBinding(osg::Geometry::BIND_OVERALL);
geometry->addPrimitiveSet(new osg::DrawArrays(GL_POLYGON, 0, 4));
osg::StateSet* stateSet = geometry->getOrCreateStateSet();
stateSet->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON);
stateSet->setTextureAttribute(0, imageTexture);
stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
osg::Geode* geode = new osg::Geode;
_splashFBOCamera->addChild(geode);
geode->addDrawable(geometry);
item.geode = geode;
item.nodeMask = geode->getNodeMask();
if (item.condition != nullptr && !item.condition->test())
item.geode->setNodeMask(0);
_imageItems.push_back(item);
return &_imageItems.back();
}
SplashScreen::TextItem *SplashScreen::addText(osg::Geode* geode ,
const osg::Vec2& pos, double size, const std::string& text,
const osgText::Text::AlignmentType alignment,
SGPropertyNode* dynamicValue,
double maxWidthFraction,
const osg::Vec4& textColor,
const std::string &fontFace )
{
SGPath path = globals->resolve_maybe_aircraft_path(fontFace);
TextItem item;
osg::ref_ptr<osgText::Text> t = new osgText::Text;
item.textNode = t;
t->setFont(path.utf8Str());
t->setColor(textColor);
t->setFontResolution(64, 64);
t->setText(text, osgText::String::Encoding::ENCODING_UTF8);
t->setBackdropType(osgText::Text::OUTLINE);
t->setBackdropColor(osg::Vec4(0.2, 0.2, 0.2, 1));
t->setBackdropOffset(0.04);
item.fractionalCharSize = size;
item.fractionalPosition = pos;
item.dynamicContent = dynamicValue;
item.textNode->setAlignment(alignment);
item.maxWidthFraction = maxWidthFraction;
item.condition = nullptr; // default to always display.
item.drawMode = t->getDrawMode();
geode->addDrawable(item.textNode);
_items.push_back(item);
return &_items.back();
}
void SplashScreen::TextItem::reposition(int width, int height) const
{
const int halfWidth = width >> 1;
const int halfHeight = height >> 1;
osg::Vec3 pixelPos(fractionalPosition.x() * width - halfWidth,
(1.0 - fractionalPosition.y()) * height - halfHeight,
-0.1);
textNode->setPosition(pixelPos);
textNode->setCharacterSize(fractionalCharSize * height);
if (maxWidthFraction > 0.0) {
textNode->setMaximumWidth(maxWidthFraction * width);
}
recomputeSize(height);
}
void SplashScreen::TextItem::recomputeSize(int height) const
{
if ((maxLineCount == 0) && (maxHeightFraction < 0.0)) {
return;
}
double heightFraction = maxHeightFraction;
if (heightFraction < 0.0) {
heightFraction = 9999.0;
}
double baseSize = fractionalCharSize;
textNode->update();
while ((textNode->getLineCount() > maxLineCount) ||
(baseSize * textNode->getLineCount() > heightFraction)) {
baseSize *= 0.8; // 20% shrink each time
textNode->setCharacterSize(baseSize * height);
textNode->update();
}
}
std::string SplashScreen::selectSplashImage()
{
sg_srandom_time(); // init random seed
simgear::PropertyList previewNodes = fgGetNode("/sim/previews", true)->getChildren("preview");
std::vector<SGPath> paths;
for (auto n : previewNodes) {
if (!n->getBoolValue("splash", false)) {
continue;
}
SGPath tpath = globals->resolve_maybe_aircraft_path(n->getStringValue("path"));
if (tpath.exists()) {
paths.push_back(tpath);
}
}
if (paths.empty()) {
// look for a legacy aircraft splash
simgear::PropertyList nodes = fgGetNode("/sim/startup", true)->getChildren("splash-texture");
for (auto n : nodes) {
SGPath tpath = globals->resolve_maybe_aircraft_path(n->getStringValue());
if (tpath.exists()) {
paths.push_back(tpath);
_legacySplashScreenMode = true;
}
}
}
if (!paths.empty()) {
// Select a random useable texture
const int index = (int)(sg_random() * paths.size());
return paths.at(index).utf8Str();
}
// no splash screen specified - use one of the default ones
SGPath tpath = globals->get_fg_root() / "Textures";
paths = simgear::Dir(tpath).children(simgear::Dir::TYPE_FILE);
paths.erase(std::remove_if(paths.begin(), paths.end(), [](const SGPath& p) {
const auto f = p.file();
if (f.find("Splash") != 0) return true;
const auto ext = p.extension();
return ext != "png" && ext != "jpg";
}), paths.end());
if (!paths.empty()) {
// Select a random useable texture
const int index = (int)(sg_random() * paths.size());
return paths.at(index).utf8Str();
}
SG_LOG(SG_GUI, SG_ALERT, "Couldn't find any splash screens at all");
return {};
}
void SplashScreen::doUpdate()
{
if (!guiInit()) {
// don't createNodes until OSG init operations have completed
return;
}
double alpha = _splashAlphaNode->getDoubleValue();
if (alpha <= 0 || !fgGetBool("/sim/startup/splash-screen")) {
removeChild(0, getNumChildren());
_splashFBOCamera = nullptr;
_splashQuadCamera = nullptr;
#ifdef ENABLE_OSGXR
_splashLayer->setVisible(false);
#endif
} else if (getNumChildren() == 0) {
createNodes();
_splashStartTime.stamp();
resize(fgGetInt("/sim/startup/xsize"),
fgGetInt("/sim/startup/ysize"));
#ifdef ENABLE_OSGXR
_splashLayer->setVisible(true);
_splashSwapchain->setForcedAlpha(alpha);
#endif
} else {
(*_splashFSQuadColor)[0] = osg::Vec4(1.0, 1.0, 1.0, _splashAlphaNode->getFloatValue());
_splashFSQuadColor->dirty();
for (const TextItem& item : _items) {
if (item.condition != nullptr) {
if (item.condition->test())
item.textNode->setDrawMode(item.drawMode);
else
item.textNode->setDrawMode(0);
}
if (item.dynamicContent) {
item.textNode->setText(
item.dynamicContent->getStringValue(),
osgText::String::Encoding::ENCODING_UTF8);
}
}
for (const ImageItem& image : _imageItems) {
if (image.condition) {
if (!image.condition->test())
image.geode->setNodeMask(0);
else
image.geode->setNodeMask(image.nodeMask);
}
}
updateSplashSpinner();
updateTipText();
#ifdef ENABLE_OSGXR
_splashSwapchain->setForcedAlpha(alpha);
#endif
}
}
float scaleAndOffset(float v, float halfWidth)
{
return halfWidth * ((v * 2.0) - 1.0);
}
void SplashScreen::updateSplashSpinner()
{
const int elapsedMsec = _splashStartTime.elapsedMSec();
float splashSpinnerPos = (elapsedMsec % 2000) / 2000.0f;
float endPos = splashSpinnerPos + 0.25f;
float wrapStartPos = 0.0f;
float wrapEndPos = 0.0f; // no wrapped quad
if (endPos > 1.0f) {
wrapEndPos = endPos - 1.0f;
}
const float halfWidth = _width * 0.5f;
const float halfHeight = _height * 0.5f;
const float bottomY = -halfHeight;
const float topY = bottomY + 8;
const float z = -0.05f;
splashSpinnerPos = scaleAndOffset(splashSpinnerPos, halfWidth);
endPos = scaleAndOffset(endPos, halfWidth);
wrapStartPos = scaleAndOffset(wrapStartPos, halfWidth);
wrapEndPos = scaleAndOffset(wrapEndPos, halfWidth);
osg::Vec3 positions[8] = {
osg::Vec3(splashSpinnerPos, bottomY, z),
osg::Vec3(endPos, bottomY, z),
osg::Vec3(endPos,topY, z),
osg::Vec3(splashSpinnerPos, topY, z),
osg::Vec3(wrapStartPos, bottomY, z),
osg::Vec3(wrapEndPos, bottomY, z),
osg::Vec3(wrapEndPos,topY, z),
osg::Vec3(wrapStartPos, topY, z)
};
for (int i=0; i<8; ++i) {
(*_splashSpinnerVertexArray)[i] = positions[i];
}
_splashSpinnerVertexArray->dirty();
}
void SplashScreen::updateTipText()
{
// after 5 seconds change the tip; but only do this once.
// the tip will be set into a property and this in turn will be
// displayed by the content using a dynamic-text element
if (!_haveSetStartupTip && (_splashStartTime.elapsedMSec() > 5000)) {
_haveSetStartupTip = true;
FGLocale* locale = globals->get_locale();
const int tipCount = locale->getLocalizedStringCount("tip", "tips");
if (tipCount == 0) {
return;
}
int tipIndex = globals->get_props()->getIntValue("/sim/session",0) % tipCount;
std::string tipText = locale->getLocalizedStringWithIndex("tip", "tips", tipIndex);
fgSetString("/sim/startup/tip", tipText);
}
}
void SplashScreen::resize( int width, int height )
{
if (getNumChildren() == 0) {
return;
}
_width = width;
_height = height;
_splashQuadCamera->setViewport(0, 0, width, height);
_splashFBOCamera->resizeAttachments(width, height);
_splashFBOCamera->setViewport(0, 0, width, height);
_splashFBOCamera->setProjectionMatrixAsOrtho2D(-width * 0.5, width * 0.5,
-height * 0.5, height * 0.5);
#ifdef ENABLE_OSGXR
float aspect = (float)width / height;
_splashSwapchain->setSize(width, height);
_splashLayer->setSize(osg::Vec2f(aspect, 1.0f));
#endif
const double screenAspectRatio = static_cast<double>(width) / height;
// resize all of the images on the splash screen (including the background)
for (const auto& _imageItem : _imageItems) {
if (_imageItem.isBackground) {
// background is based around the centre of the screen
// and adjusted so that the largest of width,height is used
// to fill the screen so that the image fits without distortion
double halfWidth = width * 0.5;
double halfHeight = height * 0.5;
// if this is the background image and we are in legacy mode then
// resize to keep the image scaled to fit in the centre
if (_legacySplashScreenMode) {
halfWidth = width * 0.35;
halfHeight = height * 0.35;
if (screenAspectRatio > _imageItem.aspectRatio) {
// screen is wider than our image
halfWidth = halfHeight;
}
else {
// screen is taller than our image
halfHeight = halfWidth;
}
}
else {
// adjust vertex positions; image covers entire area
if (screenAspectRatio > _imageItem.aspectRatio) {
// screen is wider than our image
halfHeight = halfWidth / _imageItem.aspectRatio;
}
else {
// screen is taller than our image
halfWidth = halfHeight * _imageItem.aspectRatio;
}
}
(*_imageItem.vertexArray)[0] = osg::Vec3(-halfWidth, -halfHeight, 0.0);
(*_imageItem.vertexArray)[1] = osg::Vec3(halfWidth, -halfHeight, 0.0);
(*_imageItem.vertexArray)[2] = osg::Vec3(halfWidth, halfHeight, 0.0);
(*_imageItem.vertexArray)[3] = osg::Vec3(-halfWidth, halfHeight, 0.0);
}
else {
float imageWidth = _imageItem.width * width;
float imageHeight = _imageItem.imageHeight * (imageWidth / _imageItem.imageWidth);
float imageX = _imageItem.x * (width - imageWidth);
float imageY = (1.0 - _imageItem.y) * (height - imageHeight);
float originX = imageX - (width * 0.5);
float originY = imageY - (height * 0.5);
(*_imageItem.vertexArray)[0] = osg::Vec3(originX, originY, 0.0);
(*_imageItem.vertexArray)[1] = osg::Vec3(originX + imageWidth, originY, 0.0);
(*_imageItem.vertexArray)[2] = osg::Vec3(originX + imageWidth, originY + imageHeight, 0.0);
(*_imageItem.vertexArray)[3] = osg::Vec3(originX, originY + imageHeight, 0.0);
}
_imageItem.vertexArray->dirty();
}
// adjust all text positions.
for (const TextItem& item : _items) {
item.reposition(width, height);
}
}
void fgSplashProgress( const char *identifier, unsigned int percent )
{
fgSetString("/sim/startup/splash-progress-spinner", "");
std::string text;
if (identifier[0] != 0)
{
text = globals->get_locale()->getLocalizedString(identifier, "sys");
if( text.empty() )
text = "<incomplete language resource>: "s + identifier;
}
if (!strcmp(identifier,"downloading-scenery")) {
// get localized texts for units
std::string kbytesUnitText = globals->get_locale()->getLocalizedString("units-kbytes", "sys", "KB");
std::string mbytesUnitText = globals->get_locale()->getLocalizedString("units-mbytes", "sys", "MB");
std::string kbytesPerSecUnitText = globals->get_locale()->getLocalizedString("units-kbytes-per-sec", "sys", "KB/s");
std::string mbytesPerSecUnitText = globals->get_locale()->getLocalizedString("units-mbytes-per-sec", "sys", "MB/s");
std::ostringstream oss;
unsigned int kbytesPerSec = fgGetInt("/sim/terrasync/transfer-rate-bytes-sec") / 1024;
unsigned int kbytesPending = fgGetInt("/sim/terrasync/pending-kbytes");
unsigned int kbytesPendingExtract = fgGetInt("/sim/terrasync/extract-pending-kbytes");
if (kbytesPending > 0) {
if (kbytesPending > 1024) {
int mBytesPending = kbytesPending >> 10;
oss << " " << mBytesPending << " "s << mbytesUnitText;
} else {
oss << " " << kbytesPending << " "s << kbytesUnitText;
}
}
if (kbytesPerSec > 100) {
double mbytesPerSec = kbytesPerSec / 1024.0;
oss << " - " << std::fixed << std::setprecision(1) << mbytesPerSec << " "s << mbytesPerSecUnitText;
} else if (kbytesPerSec > 0) {
oss << " - " << kbytesPerSec << " "s << kbytesPerSecUnitText;
} else if (kbytesPendingExtract > 0) {
const string extractText = globals->get_locale()->getLocalizedString("scenery-extract", "sys");
std::ostringstream os2;
if (kbytesPendingExtract > 1024) {
int mBytesPendingExtract = kbytesPendingExtract >> 10;
os2 << mBytesPendingExtract << " "s << mbytesUnitText;
} else {
os2 << kbytesPendingExtract << " "s << kbytesUnitText;
}
auto finalText = simgear::strutils::replace(extractText, "[VALUE]", os2.str());
oss << " - " << finalText;
}
fgSetString("/sim/startup/splash-progress-spinner", oss.str());
}
if (!strcmp(identifier, "loading-scenery")) {
// get localized texts for units
std::string kbytesUnitText = globals->get_locale()->getLocalizedString("units-kbytes", "sys", "KB");
std::string mbytesUnitText = globals->get_locale()->getLocalizedString("units-mbytes", "sys", "MB");
unsigned int kbytesPendingExtract = fgGetInt("/sim/terrasync/extract-pending-kbytes");
if (kbytesPendingExtract > 0) {
const string extractText = globals->get_locale()->getLocalizedString("scenery-extract", "sys");
std::ostringstream oss;
if (kbytesPendingExtract > 1024) {
int mBytesPendingExtract = kbytesPendingExtract >> 10;
oss << mBytesPendingExtract << " "s << mbytesUnitText;
} else {
oss << kbytesPendingExtract << " "s << kbytesUnitText;
}
auto finalText = simgear::strutils::replace(extractText, "[VALUE]", oss.str());
fgSetString("/sim/startup/splash-progress-spinner", finalText);
} else {
fgSetString("/sim/startup/splash-progress-spinner", "");
}
}
// over-write the spinner
if (!strncmp(identifier, "navdata-", 8)) {
const string percentText = globals->get_locale()->getLocalizedString("navdata-load-percent", "sys");
auto finalText = simgear::strutils::replace(percentText, "[VALUE]", to_string(percent));
fgSetString("/sim/startup/splash-progress-spinner", finalText);
}
if( fgGetString("/sim/startup/splash-progress-text") == text )
return;
SG_LOG( SG_VIEW, SG_INFO, "Splash screen progress " << identifier );
fgSetString("/sim/startup/splash-progress-text", text);
}

147
src/Viewer/splash.hxx Normal file
View File

@@ -0,0 +1,147 @@
// splash.hxx -- draws the initial splash screen
//
// Written by Curtis Olson, started July 1998. (With a little looking
// at Freidemann's panel code.) :-)
//
// Copyright (C) 1997 Michele F. America - nomimarketing@mail.telepac.pt
//
// 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.
//
// $Id$
#ifndef _SPLASH_HXX
#define _SPLASH_HXX
#include <config.h>
#include <osg/Group>
#include <osgText/Text>
#ifdef ENABLE_OSGXR
#include <osgXR/CompositionLayerQuad>
#include <osgXR/Swapchain>
#endif
#include <simgear/props/props.hxx>
#include <simgear/timing/timestamp.hxx>
#include <vector>
namespace osg
{
class Texture2D;
class Image;
class Camera;
}
class SplashScreen : public osg::Group
{
public:
SplashScreen();
~SplashScreen();
void resize(int width, int height);
private:
// model-content or content
struct TextItem
{
osg::ref_ptr<osgText::Text> textNode;
SGPropertyNode_ptr dynamicContent;
osg::Vec2 fractionalPosition; // position in the 0.0 .. 1.0 range
double fractionalCharSize;
double maxWidthFraction = -1.0;
unsigned int maxLineCount = 0;
double maxHeightFraction = -1.0;
SGCondition* condition;
unsigned int drawMode;
void recomputeSize(int height) const;
void reposition(int width, int height) const;
};
// used to manage the displayed images on the splash screen
struct ImageItem
{
std::string name;
double x;
double y;
double width;
double height;
bool isBackground;
double aspectRatio;
int imageWidth;
int imageHeight;
osg::ref_ptr<osg::Vec3Array> vertexArray = nullptr;
osg::ref_ptr<osg::Image> Image = nullptr;
SGCondition* condition;
osg::Node::NodeMask nodeMask;
osg::Geode* geode;
};
friend class SplashScreenUpdateCallback;
void createNodes();
void CreateTextFromNode(const SGPropertyNode_ptr& content, osg::Geode* geode, bool modelContent);
void doUpdate();
void updateSplashSpinner();
void updateTipText();
std::string selectSplashImage();
TextItem* addText(osg::Geode* geode, const osg::Vec2& pos, double size, const std::string& text,
const osgText::Text::AlignmentType alignment,
SGPropertyNode* dynamicValue = nullptr,
double maxWidthFraction = -1.0,
const osg::Vec4& textColor = osg::Vec4(1, 1, 1, 1),
const std::string &fontFace = "Fonts/LiberationFonts/LiberationSans-BoldItalic.ttf");
osg::ref_ptr<osg::Camera> createFBOCamera();
void manuallyResizeFBO(int width, int height);
bool _legacySplashScreenMode = false;
SGPropertyNode_ptr _splashAlphaNode;
osg::ref_ptr<osg::Camera> _splashFBOCamera;
osg::Vec3Array* _splashSpinnerVertexArray = nullptr;
int _width, _height;
osg::Texture2D* _splashFBOTexture;
osg::Vec4Array* _splashFSQuadColor;
osg::ref_ptr<osg::Camera> _splashQuadCamera;
std::vector<ImageItem> _imageItems;
#ifdef ENABLE_OSGXR
// Splash as OpenXR composition layer
osg::ref_ptr<osgXR::Swapchain> _splashSwapchain;
osg::ref_ptr<osgXR::CompositionLayerQuad> _splashLayer;
#endif
std::vector<TextItem> _items;
SGTimeStamp _splashStartTime;
bool _haveSetStartupTip = false;
const ImageItem* addImage(const std::string& path, bool isAbsolutePath, double x, double y, double width, double height, SGPropertyNode* condition, bool isSplash);
};
/** Set progress information.
* "identifier" references an element of the language resource. */
void fgSplashProgress ( const char *identifier, unsigned int percent = 0 );
#endif // _SPLASH_HXX

1907
src/Viewer/sview.cxx Normal file

File diff suppressed because it is too large Load Diff

235
src/Viewer/sview.hxx Normal file
View File

@@ -0,0 +1,235 @@
#pragma once
/*
Support for extra view windows using 'Step' views system.
Requires that composite-viewer is enabled at startup with --composite-viewer=1.
*/
#include <simgear/scene/util/SGReaderWriterOptions.hxx>
#include <simgear/props/props.hxx>
#include <osgViewer/View>
/* 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<simgear::SGReaderWriterOptions> 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;
/*
Creates a new SviewView in a new top-level window. It will be updated as
required by SviewUpdate() and can be dragged, resized
and closed by the user.
As of 2020-12-09, if not specified in *config, the new window will be half
width and height of the main window, and will have top-left corner at (100,
100).
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 key event handling, so no zooming with x/X.
config:
width, height:
Size of new window. Defaults are half main window width and height.
x, y:
Position of new window.
type:
"sview" or not specified:
List of sview-step's - see below.
"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.
"legacy":
view:
A legacy <view>...</view> tree, e.g. deep copy of /sim/view[]
or /ai/models/multiplayer[]/set/sim/view[].
Note that agl damping_time is calculated as log(10) /
view/config/lookat-agl-damping, for backwards compatibility
with legacy view code.
callsign:
"" if user aircraft, else multiplayer aircraft's callsign.
view-number-raw:
From /sim/current-view/view-number-raw. Used to examine
/sim/view[]/config/eye-*-deg-path to decide which of aircraft's
heading, pitch and roll to preserve in helicopter and chase
views etc.
direction-delta: initial modifications to direction.
heading
pitch
roll
zoom-delta: modification to default zoom.
Chase view seems to be slightly different from the real legacy
view, possibly because the viewpoint correction config/y-offset-m
is applied after rotation instead of before.
An sview-step is:
step:
type:
"aircraft"
callsign:
"": The user's aircraft.
Otherwise specifies a multiplayer aircraft.
"move":
forward
up
right
Fixed values defining the move relative to current
direction.
"direction-multiply":
heading, pitch, roll:
Values to multiply into direction. This can be used to
preserve/not preserve heading, pitch and roll, which allows
implementation of Helicopter and Chase views etc.
"copy-to-target":
Copy current position into target. This creates an implicit
final step that rotates the view to point at this target.
"mouse-drag":
Modify heading and pitch in response to mouse drags.
heading-scale:
pitch-scale:
Defaults to 1. For example use zero to disable or -1 to
reverse response.
"nearest-tower":
Move to position of the nearest tower to the aircraft.
"rotate":
heading, pitch, roll:
Values used to rotate direction.
damping-time-heading, damping-time-pitch, damping-time-roll:
Damping times for heading, pitch and roll; zero gives no
damping. See 'agl' below for details of damping.
"rotate-current-view":
Rotate view direction by current /sim/current-view/....
"double":
A double view, with eye in foreground and target in background.
chase-distance:
Distance to move from eye.
angle:
Angle to maintain between eye and target.
"agl":
Tilt and zoom to keep ground immediately below the aircraft
visible.
callsign:
Use chase distance of specified aircraft as a measure of
its size, to ensure entire aircraft is visible.
damping-time:
Ground level is damped as dx/dt =
(x_actual-x)/damping_time, and damping_time is the time in
seconds for the damped value to change by a factor of e
(2.71...).
Examples:
Note that as of 2020-12-09 these examples are untested.
Tower view of user aircraft:
<window-width>300</window-width>
<window-height>200</window-height>
<window-x>100</window-x>
<window-y>100</window-y>
<step> <!-- Move to aircraft. -->
<type>aircraft</type>
<callsign></callsign>
</step>
<step> <!-- Move to centre of aircraft. -->
<type>move</type>
<right>0</right>
<up>0.5</up>
<forward>-3.85</forward>
</step>
<step> <!-- Copy current position to target. -->
<type>copy-to-target</type>
</step>
<step> <!-- Move to nearest tower. -->
<type>nearest-tower</type>
</step>
Helicopter view:
<step> <!-- Move to aircraft. -->
<type>aircraft</type>
<callsign>foobar</callsign>
</step>
<step> <!-- Move to centre of aircraft. -->
<type>move</type>
<right>0</right>
<up>0.5</up>
<forward>-3.85</forward>
</step>
<step> <!-- Force some direction values to zero. -->
<type>direction-multiply</type>
<heading>1</heading> <!-- Preserve aircraft heading. -->
<pitch>0</pitch> <!-- Don't follow aircraft pitch. -->
<roll>0</roll> <!-- Don't follow aircraft roll. -->
</step>
<step> <!-- Add constants to heading, pitch and roll. -->
<type>rotate</type>
<heading>123</heading> <!-- initial view heading -->
<pitch>-10</pitch> <!-- initial view pitch -->
</step>
<step> <!-- Move back from aircraft. -->
<type>move</type>
<forward>-25</forward>
</step>
*/
std::shared_ptr<SviewView> SviewCreate(const SGPropertyNode* config);
#include <simgear/scene/viewer/Compositor.hxx>
/* If event is for an Sview window, returns the window's Compositor; otherwise
returns nullptr. */
simgear::compositor::Compositor* SviewGetEventViewport(const osgGA::GUIEventAdapter& ea);
/* If event is for an Sview window, handles it and returns true. Otherwise
returns false. */
bool SviewMouseMotion(int x, int y, const osgGA::GUIEventAdapter& ea);

1513
src/Viewer/view.cxx Normal file

File diff suppressed because it is too large Load Diff

459
src/Viewer/view.hxx Normal file
View File

@@ -0,0 +1,459 @@
// view.hxx -- class for managing a view in the flightgear world.
//
// Written by Curtis Olson, started August 1997.
// overhaul started October 2000.
// partially rewritten by Jim Wilson jim@kelcomaine.com using interface
// by David Megginson March 2002
//
// Copyright (C) 1997 - 2000 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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.
//
// $Id$
#ifndef _VIEWER_HXX
#define _VIEWER_HXX
#include <simgear/compiler.h>
#include <simgear/constants.h>
#include <simgear/props/props.hxx>
#include <simgear/props/tiedpropertylist.hxx>
#include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/math/SGMath.hxx>
#define FG_FOV_MIN 0.1
#define FG_FOV_MAX 179.9
namespace flightgear
{
// Define a structure containing view information
class View : public SGSubsystem
{
public:
enum ScalingType { // nominal Field Of View actually applies to ...
FG_SCALING_WIDTH, // window width
FG_SCALING_MAX // max(width, height)
// FG_SCALING_G_MEAN, // geometric_mean(width, height)
// FG_SCALING_INDEPENDENT // whole screen
};
enum ViewType {
FG_LOOKFROM = 0,
FG_LOOKAT = 1
};
// view_index is to allow us to look up corresponding view information for
// multiplayer aircraft.
static View* createFromProperties(SGPropertyNode_ptr props, int view_index=-1);
// Destructor
virtual ~View();
//////////////////////////////////////////////////////////////////////
// Part 1: standard SGSubsystem implementation.
//////////////////////////////////////////////////////////////////////
void init() override;
void bind() override;
void unbind() override;
void update(double dt) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "view"; }
//////////////////////////////////////////////////////////////////////
// Part 2: user settings.
//////////////////////////////////////////////////////////////////////
void resetOffsetsAndFOV();
ViewType getType() const { return _type; }
void setType( int type );
bool getInternal() const { return _internal; }
void setInternal( bool internal );
const std::string& getName() const { return _name; }
// Reference geodetic position of view from position...
// These are the actual aircraft position (pilot in
// pilot view, model in model view).
// FIXME: the model view position (ie target positions)
// should be in the model class.
const SGGeod& getPosition() const { return _position; }
// Reference geodetic target position...
const SGGeod& getTargetPosition() const { return _target; }
// Position offsets from reference
// These offsets position they "eye" in the scene according to a given
// location. For example in pilot view they are used to position the
// head inside the aircraft.
// Note that in pilot view these are applied "before" the orientation
// rotations (see below) so that the orientation rotations have the
// effect of the pilot staying in his seat and "looking out" in
// different directions.
// In chase view these are applied "after" the application of the
// orientation rotations listed below. This has the effect of the
// eye moving around and "looking at" the object (model) from
// different angles.
SGVec3d getOffset_m () const { return _offset_m; }
double getXOffset_m () const { return _offset_m.x(); }
double getYOffset_m () const { return _offset_m.y(); }
double getZOffset_m () const { return _offset_m.z(); }
double getTargetXOffset_m () const { return _target_offset_m.x(); }
double getTargetYOffset_m () const { return _target_offset_m.y(); }
double getTargetZOffset_m () const { return _target_offset_m.z(); }
void setXOffset_m (double x_offset_m);
void setYOffset_m (double y_offset_m);
void setZOffset_m (double z_offset_m);
void setTargetXOffset_m (double x_offset_m);
void setTargetYOffset_m (double y_offset_m);
void setTargetZOffset_m (double z_offset_m);
void setPositionOffsets (double x_offset_m,
double y_offset_m,
double z_offset_m);
double getAdjustXOffset_m () const { return _adjust_offset_m.x(); }
double getAdjustYOffset_m () const { return _adjust_offset_m.y(); }
double getAdjustZOffset_m () const { return _adjust_offset_m.z(); }
void setAdjustXOffset_m (double x_adjust_offset_m);
void setAdjustYOffset_m (double y_adjust_offset_m);
void setAdjustZOffset_m (double z_adjust_offset_m);
// Reference orientation rotations...
// These are rotations that represent the plane attitude effect on
// the view (in Pilot view). IE The view frustrum rotates as the plane
// turns, pitches, and rolls.
// In model view (lookat/chaseview) these end up changing the angle that
// the eye is looking at the ojbect (ie the model).
// FIXME: the FGModel class should have its own version of these so that
// it can generate it's own model rotations.
double getHeading_deg () const {return _heading_deg; }
// Orientation offsets rotations from reference orientation.
// Goal settings are for smooth transition from prior
// offset when changing view direction.
// These offsets are in ADDITION to the orientation rotations listed
// above.
// In pilot view they are applied after the position offsets in order to
// give the effect of the pilot looking around.
// In lookat view they are applied before the position offsets so that
// the effect is the eye moving around looking at the object (ie the model)
// from different angles.
double getRollOffset_deg () const { return _roll_offset_deg; }
double getPitchOffset_deg () const { return _pitch_offset_deg; }
double getHeadingOffset_deg () const { return _heading_offset_deg; }
void setGoalHeadingOffset_deg (double goal_heading_offset_deg);
void setHeadingOffset_deg (double heading_offset_deg);
//////////////////////////////////////////////////////////////////////
// Part 3: output vectors and matrices in FlightGear coordinates.
//////////////////////////////////////////////////////////////////////
// Vectors and positions...
const SGVec3d& getViewPosition() { if ( _dirty ) { recalc(); } return _absolute_view_pos; }
const SGQuatd& getViewOrientation() { if ( _dirty ) { recalc(); } return mViewOrientation; }
const SGQuatd& getViewOrientationOffset() { if ( _dirty ) { recalc(); } return mViewOffsetOr; }
//////////////////////////////////////////////////////////////////////
// Part 4: View and frustrum data setters and getters
//////////////////////////////////////////////////////////////////////
double get_fov() const { return _fov_deg; }
double get_h_fov(); // Get horizontal fov, in degrees.
double get_v_fov(); // Get vertical fov, in degrees.
// this is currently just a wrapper for the default CameraGroups' aspect
double get_aspect_ratio() const;
//////////////////////////////////////////////////////////////////////
// Part 5: misc setters and getters
//////////////////////////////////////////////////////////////////////
void set_dirty() { _dirty = true; }
private:
// Constructor
View( ViewType Type, bool from_model, int from_model_index,
bool at_model, int at_model_index,
double damp_roll, double damp_pitch, double damp_heading,
double x_offset_m, double y_offset_m, double z_offset_m,
double heading_offset_deg, double pitch_offset_deg,
double roll_offset_deg,
double fov_deg, double aspect_ratio_multiplier,
double target_x_offset_m, double target_y_offset_m,
double target_z_offset_m, double near_m, bool internal,
bool lookat_agl, double lookat_agl_damping, int view_index );
void set_clean() { _dirty = false; }
void setHeadingOffset_deg_property (double heading_offset_deg);
void setPitchOffset_deg_property(double pitch_offset_deg);
void setRollOffset_deg_property(double roll_offset_deg);
void setPosition (const SGGeod& geod);
void setTargetPosition (const SGGeod& geod);
double getAbsolutePosition_x() const;
double getAbsolutePosition_y() const;
double getAbsolutePosition_z() const;
double getRawOrientation_w() const;
double getRawOrientation_x() const;
double getRawOrientation_y() const;
double getRawOrientation_z() const;
// quaternion accessors, for debugging:
double getFrame_w() const;
double getFrame_x() const;
double getFrame_y() const;
double getFrame_z() const;
double getOrientation_w() const;
double getOrientation_x() const;
double getOrientation_y() const;
double getOrientation_z() const;
double getOrOffset_w() const;
double getOrOffset_x() const;
double getOrOffset_y() const;
double getOrOffset_z() const;
double getLon_deg() const;
double getLat_deg() const;
double getElev_ft() const;
// Reference orientation rotations...
// These are rotations that represent the plane attitude effect on
// the view (in Pilot view). IE The view frustrum rotates as the plane
// turns, pitches, and rolls.
// In model view (lookat/chaseview) these end up changing the angle that
// the eye is looking at the ojbect (ie the model).
// FIXME: the FGModel class should have its own version of these so that
// it can generate it's own model rotations.
double getRoll_deg () const { return _roll_deg; }
double getPitch_deg () const {return _pitch_deg; }
void setRoll_deg (double roll_deg);
void setPitch_deg (double pitch_deg);
void setHeading_deg (double heading_deg);
void setOrientation (double roll_deg, double pitch_deg, double heading_deg);
double getTargetRoll_deg () const { return _target_roll_deg; }
double getTargetPitch_deg () const {return _target_pitch_deg; }
double getTargetHeading_deg () const {return _target_heading_deg; }
void setTargetRoll_deg (double roll_deg);
void setTargetPitch_deg (double pitch_deg);
void setTargetHeading_deg (double heading_deg);
void setTargetOrientation (double roll_deg, double pitch_deg, double heading_deg);
void handleAGL();
// Orientation offsets rotations from reference orientation.
// Goal settings are for smooth transition from prior
// offset when changing view direction.
// These offsets are in ADDITION to the orientation rotations listed
// above.
// In pilot view they are applied after the position offsets in order to
// give the effect of the pilot looking around.
// In lookat view they are applied before the position offsets so that
// the effect is the eye moving around looking at the object (ie the model)
// from different angles.
double getGoalRollOffset_deg () const { return _goal_roll_offset_deg; }
double getGoalPitchOffset_deg () const { return _goal_pitch_offset_deg; }
double getGoalHeadingOffset_deg () const {return _goal_heading_offset_deg; }
void setRollOffset_deg (double roll_offset_deg);
void setPitchOffset_deg (double pitch_offset_deg);
void setGoalRollOffset_deg (double goal_roll_offset_deg);
void setGoalPitchOffset_deg (double goal_pitch_offset_deg);
void setOrientationOffsets (double roll_offset_deg,
double heading_offset_deg,
double pitch_offset_deg);
void set_aspect_ratio_multiplier( double m ) {
_aspect_ratio_multiplier = m;
}
double get_aspect_ratio_multiplier() const {
return _aspect_ratio_multiplier;
}
double getNear_m () const { return _ground_level_nearplane_m; }
void setNear_m (double near_m) {
_ground_level_nearplane_m = near_m;
}
void set_fov( double fov_deg ) {
_fov_deg = fov_deg;
}
double get_fov_user() const { return _fov_user_deg; }
void set_fov_user( double fov_deg ) { _fov_user_deg = fov_deg; }
//////////////////////////////////////////////////////////////////
// private data //
//////////////////////////////////////////////////////////////////
SGPropertyNode_ptr _config;
std::string _name, _typeString;
// flag forcing a recalc of derived view parameters
bool _dirty;
simgear::TiedPropertyList _tiedProperties;
SGQuatd mViewOrientation;
SGQuatd mViewOffsetOr;
SGVec3d _absolute_view_pos;
SGGeod _position;
SGGeod _target;
double _roll_deg;
double _pitch_deg;
double _heading_deg;
double _target_roll_deg;
double _target_pitch_deg;
double _target_heading_deg;
double _configRollOffsetDeg,
_configHeadingOffsetDeg,
_configPitchOffsetDeg;
SGVec3d _dampTarget; ///< current target value we are damping towards
SGVec3d _dampOutput; ///< current output of damping filter
SGVec3d _dampFactor; ///< weighting of the damping filter
/* Generic damping support. */
struct Damping {
Damping(double factor, double min, double max);
void setTarget(double target);
void update(double dt, void* id);
double get();
void updateTarget(double& io);
void reset(double target);
private:
void* _id;
double _min;
double _max;
double _target;
double _factor;
double _current;
};
Damping _lookat_agl_damping;
double _lookat_agl_ground_altitude;
// Position offsets from FDM origin. The X axis is positive
// out the tail, Y is out the right wing, and Z is positive up.
// distance in meters
SGVec3d _offset_m;
SGVec3d _configOffset_m;
SGVec3d _adjust_offset_m;
// Target offsets from FDM origin (for "lookat" targets) The X
// axis is positive out the tail, Y is out the right wing, and Z
// is positive up. distance in meters
SGVec3d _target_offset_m;
SGVec3d _configTargetOffset_m;
// orientation offsets from reference (_goal* are for smoothed transitions)
double _roll_offset_deg;
double _pitch_offset_deg;
double _heading_offset_deg;
double _goal_roll_offset_deg;
double _goal_pitch_offset_deg;
double _goal_heading_offset_deg;
// used to set nearplane when at ground level for this view
double _ground_level_nearplane_m;
ViewType _type;
ScalingType _scaling_type;
// internal view (e.g. cockpit) flag
bool _internal;
// Dynamically update view angle and field of view so that we always
// include the target and the ground below it.
bool _lookat_agl;
int _view_index;
// view is looking from a model
bool _from_model;
int _from_model_index; // number of model (for multi model)
// view is looking at a model
bool _at_model;
int _at_model_index; // number of model (for multi model)
// Field of view as requested by user. Usually copied directly into the
// actual field of view, except for Tower AGL view.
double _fov_user_deg;
// the nominal field of view (angle, in degrees)
double _fov_deg;
double _configFOV_deg;
// default = 1.0, this value is user configurable and is
// multiplied into the aspect_ratio to get the actual vertical fov
double _aspect_ratio_multiplier;
//////////////////////////////////////////////////////////////////
// private functions //
//////////////////////////////////////////////////////////////////
void recalc ();
void recalcLookFrom();
void recalcLookAt();
void setDampTarget(double h, double p, double r);
void getDampOutput(double& roll, double& pitch, double& heading);
void updateDampOutput(double dt);
// add to _heading_offset_deg
inline void incHeadingOffset_deg( double amt ) {
set_dirty();
_heading_offset_deg += amt;
}
// add to _pitch_offset_deg
inline void incPitchOffset_deg( double amt ) {
set_dirty();
_pitch_offset_deg += amt;
}
// add to _roll_offset_deg
inline void incRollOffset_deg( double amt ) {
set_dirty();
_roll_offset_deg += amt;
}
}; // of class View
} // of namespace flightgear
#endif // _VIEWER_HXX

574
src/Viewer/viewmgr.cxx Normal file
View File

@@ -0,0 +1,574 @@
// viewmgr.cxx -- class for managing all the views in the flightgear world.
//
// Written by Curtis Olson, started October 2000.
// partially rewritten by Jim Wilson March 2002
//
// Copyright (C) 2000 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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.
//
// $Id$
#include "config.h"
#include <algorithm> // for std::clamp
#include "viewmgr.hxx"
#include "ViewPropertyEvaluator.hxx"
#include <simgear/compiler.h>
#include <simgear/scene/util/OsgMath.hxx>
#include <simgear/props/props_io.hxx>
#include <simgear/screen/video-encoder.hxx>
#include <simgear/structure/commands.hxx>
#include <Main/fg_props.hxx>
#include "view.hxx"
#include "sview.hxx"
#include "renderer.hxx"
#include "CameraGroup.hxx"
#include "Scenery/scenery.hxx"
// Constructor
FGViewMgr::FGViewMgr(void)
{
}
// Destructor
FGViewMgr::~FGViewMgr( void )
{
}
void
FGViewMgr::init ()
{
if (_inited) {
SG_LOG(SG_VIEW, SG_WARN, "duplicate init of view manager");
return;
}
_inited = true;
// Ensure that /sim/chase-distance-m is negative if it is specified.
// E.g. see https://sourceforge.net/p/flightgear/codetickets/2454/
//
SGPropertyNode* chase_distance_node = fgGetNode("/sim/chase-distance-m");
if (chase_distance_node) {
double chase_distance = chase_distance_node->getDoubleValue();
if (chase_distance > 0) {
chase_distance = -chase_distance;
SG_LOG(SG_VIEW, SG_ALERT, "sim/chase-distance-m is positive; correcting to " << chase_distance);
chase_distance_node->setDoubleValue(chase_distance);
}
}
config_list = fgGetNode("/sim", true)->getChildren("view");
_current = fgGetInt("/sim/current-view/view-number");
if (_current != 0 && (_current < 0 || _current >= (int) views.size())) {
SG_LOG(SG_VIEW, SG_ALERT,
"Invalid /sim/current-view/view-number=" << _current
<< ". views.size()=" << views.size()
<< ". Will use zero."
);
_current = 0;
}
for (unsigned int i = 0; i < config_list.size(); i++) {
SGPropertyNode* n = config_list[i];
SGPropertyNode* config = n->getChild("config", 0, true);
flightgear::View* v = flightgear::View::createFromProperties(config, n->getIndex());
if (v) {
add_view(v);
} else {
SG_LOG(SG_VIEW, SG_DEV_WARN, "Failed to create view from:" << config->getPath());
}
}
if (get_current_view()) {
get_current_view()->bind();
} else {
SG_LOG(SG_VIEW, SG_DEV_WARN, "FGViewMgr::init: current view " << _current << " failed to create");
}
}
void
FGViewMgr::postinit()
{
// force update now so many properties of the current view are valid,
// eg view position and orientation (as exposed via globals)
update(0.0);
}
void
FGViewMgr::shutdown()
{
if (!_inited) {
return;
}
_inited = false;
views.clear();
}
void
FGViewMgr::reinit ()
{
viewer_list::iterator it;
for (it = views.begin(); it != views.end(); ++it) {
(*it)->resetOffsetsAndFOV();
}
}
void
FGViewMgr::bind()
{
// these are bound to the current view properties
_tiedProperties.setRoot(fgGetNode("/sim/current-view", true));
_tiedProperties.Tie("view-number", this,
&FGViewMgr::getCurrentViewIndex,
&FGViewMgr::setCurrentViewIndex, false);
_viewNumberProp = _tiedProperties.getRoot()->getNode("view-number");
_viewNumberProp->setAttribute(SGPropertyNode::ARCHIVE, false);
_viewNumberProp->setAttribute(SGPropertyNode::PRESERVE, true);
_viewNumberProp->setAttribute(SGPropertyNode::LISTENER_SAFE, true);
}
void
FGViewMgr::unbind ()
{
flightgear::View* v = get_current_view();
if (v) {
v->unbind();
}
_tiedProperties.Untie();
_viewNumberProp.clear();
ViewPropertyEvaluator::clear();
SviewClear();
}
static void videoEncodingPopup(const std::string& message, int delay)
{
SGPropertyNode_ptr args(new SGPropertyNode);
args->setStringValue("label", message);
args->setIntValue("delay", (delay) ? delay : 15);
SG_LOG(SG_GENERAL, SG_ALERT, message);
globals->get_commands()->execute("show-message", args);
}
static void vidoEncodingUpdateStatus(const std::string& path)
{
fgSetString("/sim/video/encoding-path", path);
bool encoding = (path != "");
SGPropertyNode* node = fgGetNode("/sim/menubar/default");
/* Enable/disable menu items 'File/Video encode start' and 'File/Video
encode stop'. */
for (auto a: node->getChildren("menu"))
{
std::string name = a->getStringValue("name");
if (name == "file")
{
for (auto b: a->getChildren("item"))
{
std::string name = b->getStringValue("name");
if (name == "video-start")
{
b->setBoolValue("enabled", !encoding);
}
if (name == "video-stop")
{
b->setBoolValue("enabled", encoding);
}
}
break;
}
}
}
static void videoEncodingError(const std::string& message)
{
globals->get_props()->setIntValue("/sim/video/error", 1);
vidoEncodingUpdateStatus("");
videoEncodingPopup(message, 15);
}
void
FGViewMgr::update (double dt)
{
flightgear::View* currentView = get_current_view();
if (!currentView) {
return;
}
// Update the current view
currentView->update(dt);
// update the camera now
osg::ref_ptr<flightgear::CameraGroup> cameraGroup = flightgear::CameraGroup::getDefault();
if (cameraGroup) {
cameraGroup->setCameraParameters(currentView->get_v_fov(),
cameraGroup->getMasterAspectRatio());
cameraGroup->update(toOsg(currentView->getViewPosition()),
toOsg(currentView->getViewOrientation()));
}
SviewUpdate(dt);
if (_video_encoder)
{
flightgear::CameraGroup* camera_group = flightgear::CameraGroup::getDefault();
for (auto& camera_info : camera_group->getCameras())
{
if (camera_info->flags & flightgear::CameraInfo::GUI) continue;
osg::GraphicsContext* gc = camera_info->compositor->getGraphicsContext();
try
{
_video_encoder->encode(dt, gc);
}
catch (std::exception& e)
{
videoEncodingError(e.what());
_video_encoder.reset();
}
break;
}
}
std::string callsign = globals->get_props()->getStringValue("/sim/log-multiplayer-callsign");
if (callsign != "")
{
auto multiplayers = globals->get_props()->getNode("/ai/models")->getChildren("multiplayer");
for (auto mutiplayer: multiplayers)
{
std::string callsign2 = mutiplayer->getStringValue("callsign");
if (callsign2 == callsign)
{
static SGVec3d pos_prev;
static double t = 0;
SGGeod pos_geod = SGGeod::fromDegFt(
mutiplayer->getDoubleValue("position/longitude-deg"),
mutiplayer->getDoubleValue("position/latitude-deg"),
mutiplayer->getDoubleValue("position/altitude-ft")
);
SGVec3d pos = SGVec3d::fromGeod(pos_geod);
double distance = length(pos - pos_prev);
double speed = distance / dt;
SGPropertyNode* item = fgGetNode("/sim/log-multiplayer", true /*create*/)->addChild("mp");
item->setDoubleValue("distance", distance);
item->setDoubleValue("speed", speed);
item->setDoubleValue("dt", dt);
item->setDoubleValue("t", t);
item->setDoubleValue("ubody", mutiplayer->getDoubleValue("velocities/uBody-fps"));
item->setDoubleValue("vbody", mutiplayer->getDoubleValue("velocities/vBody-fps"));
item->setDoubleValue("wbody", mutiplayer->getDoubleValue("velocities/wBody-fps"));
pos_prev = pos;
t += dt;
break;
}
}
}
}
void FGViewMgr::clear()
{
views.clear();
}
flightgear::View*
FGViewMgr::get_current_view()
{
if (views.empty())
return nullptr;
if (_current < 0 || _current >= (int) views.size()) {
SG_LOG(SG_VIEW, SG_ALERT, "Invalid _current=" << _current
<< ". views.size()=" << views.size()
<< ". Will use zero."
);
_current = 0;
}
return views[_current];
}
const flightgear::View*
FGViewMgr::get_current_view() const
{
return const_cast<FGViewMgr*>(this)->get_current_view();
}
flightgear::View*
FGViewMgr::get_view( int i )
{
const auto lastView = static_cast<int>(views.size()) - 1;
const int c = std::clamp(i, 0, lastView);
return views.at(c);
}
const flightgear::View*
FGViewMgr::get_view( int i ) const
{
const auto lastView = static_cast<int>(views.size()) - 1;
const int c = std::clamp(i, 0, lastView);
return views.at(c);
}
flightgear::View*
FGViewMgr::next_view()
{
const auto numViews = static_cast<int>(views.size());
setCurrentViewIndex((_current + 1) % numViews);
_viewNumberProp->fireValueChanged();
return get_current_view();
}
flightgear::View*
FGViewMgr::prev_view()
{
const auto numViews = static_cast<int>(views.size());
// subtract 1, but add a full numViews, to ensure the integer
// modulo returns a +ve result; negative values mean something
// else to setCurrentViewIndex
setCurrentViewIndex((_current - 1 + numViews) % numViews);
_viewNumberProp->fireValueChanged();
return get_current_view();
}
void FGViewMgr::view_push()
{
SviewPush();
}
void s_clone_internal(const SGPropertyNode* config, const std::string& type)
{
SGPropertyNode_ptr config2 = new SGPropertyNode;
copyProperties(config, config2);
config2->setStringValue("type", type);
SviewCreate(config2);
}
void FGViewMgr::clone_current_view(const SGPropertyNode* config)
{
s_clone_internal(config, "current");
}
void FGViewMgr::clone_last_pair(const SGPropertyNode* config)
{
s_clone_internal(config, "last_pair");
}
void FGViewMgr::clone_last_pair_double(const SGPropertyNode* config)
{
s_clone_internal(config, "last_pair_double");
}
void FGViewMgr::view_new(const SGPropertyNode* config)
{
SviewCreate(config);
}
#include <simgear/scene/util/SGReaderWriterOptions.hxx>
void
FGViewMgr::add_view( flightgear::View * v )
{
views.push_back(v);
v->init();
}
bool FGViewMgr::video_start(
const std::string& name_in,
const std::string& codec_in,
double quality,
double speed,
int bitrate
)
{
SG_LOG(SG_GENERAL, SG_ALERT, "FGViewMgr::video_start():"
<< " name_in=" << name_in
<< " codec_in=" << codec_in
<< " quality=" << quality
<< " speed=" << speed
<< " bitrate=" << bitrate
);
globals->get_props()->setIntValue("/sim/video/error", 0);
std::string name;
std::string name_link;
if (name_in == "")
{
/* Use a default name containing aircraft-name, current date and time
and the configured video container. */
time_t calendar_time = time(NULL);
struct tm* local_tm = localtime(&calendar_time);
char time_string[256];
strftime(time_string, sizeof(time_string), "-%Y%m%d-%H%M%S", local_tm);
std::string suffix = "." + fgGetString("/sim/video/container", "mpeg");
name = std::string("fgvideo-") + fgGetString("/sim/aircraft");
name_link = name + suffix;
name += time_string + suffix;
}
else
{
/* We assume <name_in> already has the desired suffix. We leave
name_link empty so we don't attempt to create a link. */
name = name_in;
}
std::string codec = codec_in;
string directory = fgGetString("/sim/video/directory");
SGPath path = SGPath(directory);
path.append(name);
if (path.exists())
{
videoEncodingError("Video encoding failure, output file already exists: " + path.str());
return false;
}
SGPath path_link;
if (name_link != "")
{
path_link = SGPath(directory);
path_link.append(name_link);
path_link.remove();
bool ok = path_link.makeLink(path.file());
if (!ok)
{
SG_LOG(SG_SYSTEMS, SG_ALERT, "Failed to create link "
<< path_link.c_str() << " => " << path.file()
);
}
}
if (codec == "") codec = fgGetString("/sim/video/codec");
if (quality == -1) quality = fgGetDouble("/sim/video/quality");
if (speed == -1) speed = fgGetDouble("/sim/video/speed");
if (bitrate == 0) bitrate = fgGetInt("/sim/video/bitrate");
std::string warning;
if (quality != -1 && (quality < 0 || quality > 1))
{
warning += "Ignoring quality=" + std::to_string(quality) + " because should be -1 or in range 0-1.\n";
quality = -1;
}
if (speed != -1 && (speed < 0 || speed > 1))
{
warning += "Ignoring speed=" + std::to_string(speed) + " because should be -1 or in range 0-1.\n";
speed = -1;
}
if (bitrate < 0)
{
warning += "Ignoring bitrate=" + std::to_string(bitrate) + " because should be >= 0.\n";
bitrate = 0;
}
if (warning != "")
{
videoEncodingPopup(warning, 10);
}
SG_LOG(SG_SYSTEMS, SG_ALERT, "Video encoding starting."
<< " codec=" << codec
<< " quality=" << quality
<< " speed=" << speed
<< " bitrate=" << bitrate
<< " path=" << path
<< " path_link=" << path_link
);
bool log_sws_scale_stats = globals->get_props()->getNode("/sim/video/log_sws_scale_stats", true /*create*/)->getBoolValue();
try
{
_video_encoder.reset(
new simgear::VideoEncoder(path.str(), codec, quality, speed, bitrate, log_sws_scale_stats)
);
}
catch (std::exception& e)
{
videoEncodingError(e.what());
return false;
}
vidoEncodingUpdateStatus(path.str());
return true;
}
void FGViewMgr::video_stop()
{
if (_video_encoder)
{
_video_encoder.reset();
vidoEncodingUpdateStatus("");
videoEncodingPopup("Video encoding stopped.", 5);
}
else
{
videoEncodingPopup("[Video encoding already stopped]", 5);
}
}
int FGViewMgr::getCurrentViewIndex() const
{
return _current;
}
void FGViewMgr::setCurrentViewIndex(int newview)
{
if (newview == _current) {
return;
}
// negative numbers -> set view with node index -newview
if (newview < 0) {
for (int i = 0; i < (int)config_list.size(); i++) {
int index = -config_list[i]->getIndex();
if (index == newview)
newview = i;
}
if (newview < 0) {
SG_LOG(SG_VIEW, SG_ALERT, "Failed to find -ve newview=" << newview);
return;
}
}
if (newview < 0 || newview >= (int) views.size()) {
SG_LOG(SG_VIEW, SG_ALERT, "Ignoring invalid newview=" << newview
<< ". views.size()=" << views.size()
);
return;
}
if (get_current_view()) {
get_current_view()->unbind();
}
_current = newview;
if (get_current_view()) {
get_current_view()->bind();
}
}
// Register the subsystem.
SGSubsystemMgr::Registrant<FGViewMgr> registrantFGViewMgr(
SGSubsystemMgr::DISPLAY);

154
src/Viewer/viewmgr.hxx Normal file
View File

@@ -0,0 +1,154 @@
// viewmgr.hxx -- class for managing all the views in the flightgear world.
//
// Written by Curtis Olson, started October 2000.
//
// Copyright (C) 2000 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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.
//
// $Id$
#ifndef _VIEWMGR_HXX
#define _VIEWMGR_HXX
#include <vector>
#include <simgear/compiler.h>
#include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/props/props.hxx>
#include <simgear/props/tiedpropertylist.hxx>
#include <simgear/math/SGMath.hxx>
#include <simgear/screen/video-encoder.hxx>
// forward decls
namespace flightgear
{
class View;
typedef SGSharedPtr<flightgear::View> ViewPtr;
}
// Define a structure containing view information
class FGViewMgr : public SGSubsystem
{
public:
// Constructor
FGViewMgr( void );
// Destructor
~FGViewMgr( void );
// Subsystem API.
void bind() override;
void init() override;
void postinit() override;
void reinit() override;
void shutdown() override;
void unbind() override;
void update(double dt) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "view-manager"; }
// getters
inline int size() const { return views.size(); }
int getCurrentViewIndex() const;
flightgear::View* get_current_view();
const flightgear::View* get_current_view() const;
flightgear::View* get_view( int i );
const flightgear::View* get_view( int i ) const;
flightgear::View* next_view();
flightgear::View* prev_view();
// Support for extra view windows. This only works if --compositer-viewer=1 was specified.
//
// Calls SviewPush().
void view_push();
// These all end up calling SviewCreate().
void clone_current_view(const SGPropertyNode* config);
void clone_last_pair(const SGPropertyNode* config);
void clone_last_pair_double(const SGPropertyNode* config);
void view_new(const SGPropertyNode* config);
// setters
void clear();
void add_view( flightgear::View * v );
// Start video encoding of main window.
//
// name
// If "", we generate a name containing aircraft name, date and
// time and with suffix /sim/video/container, and we also create a
// softlink with the same name excluding date and time. Both names are
// converted into paths by prepending with /sim/video/directory.
//
// Otherwise we prepend with /sim/video/directory, and do not create
// a softlink. In this case, <name> should end with a name that is a
// recognised video container, such as '.mp4'.
//
// List of supported containers can be found with 'ffmpeg -formats'.
// codec_name
// Name of codec or "" to use /sim/video/codec. Passed to
// avcodec_find_encoder_by_name(). List of supported codecs can be
// found with 'ffmpeg -codecs'.
// quality
// Encoding quality in range 0..1 or -1 to use /sim/video/quality.
// speed:
// Encoding speed in range 0..1 or -1 to use /sim/video/speed.
// bitrate
// Target bitratae in bits/sec or -1 to use /sim/video/bitrate.
//
// We show popup warning if values are out of range - quality and speed
// must be -1 or 0-1, bitrate must be >= 0.
//
// Returns false if we fail to start video encoding.
//
bool video_start(
const std::string& name="",
const std::string& codec="",
double quality=-1,
double speed=-1,
int bitrate=0
);
// Stop video encoding of main window.
//
void video_stop();
private:
simgear::TiedPropertyList _tiedProperties;
void setCurrentViewIndex(int newview);
bool _inited = false;
std::vector<SGPropertyNode_ptr> config_list;
SGPropertyNode_ptr _viewNumberProp;
typedef std::vector<flightgear::ViewPtr> viewer_list;
viewer_list views;
int _current = 0;
std::unique_ptr<simgear::VideoEncoder> _video_encoder;
};
#endif // _VIEWMGR_HXX