first commit
This commit is contained in:
45
src/Viewer/CMakeLists.txt
Normal file
45
src/Viewer/CMakeLists.txt
Normal 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
1200
src/Viewer/CameraGroup.cxx
Normal file
File diff suppressed because it is too large
Load Diff
290
src/Viewer/CameraGroup.hxx
Normal file
290
src/Viewer/CameraGroup.hxx
Normal 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
|
||||
547
src/Viewer/FGEventHandler.cxx
Normal file
547
src/Viewer/FGEventHandler.cxx
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
155
src/Viewer/FGEventHandler.hxx
Normal file
155
src/Viewer/FGEventHandler.hxx
Normal 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
|
||||
550
src/Viewer/GraphicsPresets.cxx
Normal file
550
src/Viewer/GraphicsPresets.cxx
Normal 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
|
||||
92
src/Viewer/GraphicsPresets.hxx
Normal file
92
src/Viewer/GraphicsPresets.hxx
Normal 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
385
src/Viewer/PUICamera.cxx
Normal 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
70
src/Viewer/PUICamera.hxx
Normal 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
298
src/Viewer/VRManager.cxx
Normal 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
171
src/Viewer/VRManager.hxx
Normal 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
|
||||
744
src/Viewer/ViewPropertyEvaluator.cxx
Normal file
744
src/Viewer/ViewPropertyEvaluator.cxx
Normal 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";
|
||||
}
|
||||
}
|
||||
132
src/Viewer/ViewPropertyEvaluator.hxx
Normal file
132
src/Viewer/ViewPropertyEvaluator.hxx
Normal 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();
|
||||
|
||||
}
|
||||
334
src/Viewer/WindowBuilder.cxx
Normal file
334
src/Viewer/WindowBuilder.cxx
Normal 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
|
||||
87
src/Viewer/WindowBuilder.hxx
Normal file
87
src/Viewer/WindowBuilder.hxx
Normal 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
|
||||
68
src/Viewer/WindowSystemAdapter.cxx
Normal file
68
src/Viewer/WindowSystemAdapter.cxx
Normal 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;
|
||||
}
|
||||
}
|
||||
134
src/Viewer/WindowSystemAdapter.hxx
Normal file
134
src/Viewer/WindowSystemAdapter.hxx
Normal 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
694
src/Viewer/fg_os_osgviewer.cxx
Executable 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
267
src/Viewer/fgviewer.cxx
Normal 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
5
src/Viewer/fgviewer.hxx
Normal 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
1401
src/Viewer/renderer.cxx
Normal file
File diff suppressed because it is too large
Load Diff
151
src/Viewer/renderer.hxx
Normal file
151
src/Viewer/renderer.hxx
Normal 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
916
src/Viewer/splash.cxx
Normal 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
147
src/Viewer/splash.hxx
Normal 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
1907
src/Viewer/sview.cxx
Normal file
File diff suppressed because it is too large
Load Diff
235
src/Viewer/sview.hxx
Normal file
235
src/Viewer/sview.hxx
Normal 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
1513
src/Viewer/view.cxx
Normal file
File diff suppressed because it is too large
Load Diff
459
src/Viewer/view.hxx
Normal file
459
src/Viewer/view.hxx
Normal 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
574
src/Viewer/viewmgr.cxx
Normal 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
154
src/Viewer/viewmgr.hxx
Normal 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
|
||||
Reference in New Issue
Block a user