1
0
Fork 0
flightgear/src/Viewer/PUICamera.cxx
2019-01-22 10:50:55 +01:00

412 lines
14 KiB
C++

// 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 <osgUtil/CullVisitor>
#include <osgGA/GUIEventHandler>
#include <osgGA/GUIEventAdapter>
#include <plib/pu.h>
#include <simgear/scene/util/SGNodeMasks.hxx>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include <Main/locale.hxx>
#include <Viewer/CameraGroup.hxx>
#include <Viewer/FGEventHandler.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;
#if OSG_VERSION_LESS_THAN(3,4,0)
// this class kindly provided by Wojtek Lewandowsk on ths OpenSceneGraph-users
// mailing list from his code-archive, it's needed to poke the camera setup
// after resizing the FBO backing storage, on OSG 3.2.x
class PUICamera::UpdateViewportAndFBOAfterTextureResizeCallback : public osg::NodeCallback
{
public:
UpdateViewportAndFBOAfterTextureResizeCallback(bool dirty = false) : _dirty(dirty) {}
void setDirty(bool dirty) { _dirty = dirty; }
bool getDirty() { return _dirty; }
void operator()(osg::Node *node, osg::NodeVisitor *nv)
{
if (_dirty)
{
osgUtil::CullVisitor *cv = static_cast<osgUtil::CullVisitor *>(nv);
if (cv && node == cv->getCurrentRenderStage()->getCamera())
{
cv->getCurrentRenderStage()->setCameraRequiresSetUp(true);
_dirty = false;
}
}
traverse(node, nv);
}
protected:
bool _dirty;
};
#endif
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):
{
FGMouseInput* mouseSubsystem = globals->get_subsystem<FGInput>()->get_subsystem<FGMouseInput>();
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;
} else {
return PU_SCROLL_DOWN_BUTTON;
}
return PU_NOBUTTON;
}
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()
{
}
void PUICamera::init(osg::Group* parent)
{
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);
#if OSG_VERSION_LESS_THAN(3,4,0)
_resizeCullCallback = new UpdateViewportAndFBOAfterTextureResizeCallback;
setCullCallback(_resizeCullCallback);
#endif
// 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);
// set the event callback on the Geode; if we set it on the Drawable,
// osgGA calls it twice (as a NodeCallback and also a Drawable::EventCallback)
fsQuadGeode->setEventCallback(new PUIEventHandler(this));
parent->addChild(this);
parent->addChild(fsQuadGeode);
osg::Camera* camera = getGUICamera(CameraGroup::getDefault());
if (camera) {
osg::Viewport* vport = camera->getViewport();
resizeUi(vport->width(), vport->height());
}
}
// 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);
#if OSG_VERSION_LESS_THAN(3,4,0)
manuallyResizeFBO(scaledWidth, scaledHeight);
_resizeCullCallback->setDirty(true);
#else
osg::Camera::resize(scaledWidth, scaledHeight);
resizeAttachments(scaledWidth, scaledHeight);
#endif
// resize the full-screen quad
osg::Vec3Array* fsQuadVertices = static_cast<osg::Vec3Array*>(_fullScreenQuad->getVertexArray());
(*fsQuadVertices)[0] = osg::Vec3(0.0, height, 0.0);
(*fsQuadVertices)[1] = osg::Vec3(0.0, 0.0, 0.0);
(*fsQuadVertices)[2] = osg::Vec3(width, 0.0, 0.0);
(*fsQuadVertices)[3] = osg::Vec3(width, height, 0.0);
fsQuadVertices->dirty();
}