// Viewer.cxx -- alternative flightgear viewer application // // Copyright (C) 2009 - 2012 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 HAVE_CONFIG_H #include #endif #include "Viewer.hxx" #include #include #include #include #ifdef __linux__ #include #include #endif #include "MEncoderCaptureOperation.hxx" #include namespace fgviewer { Viewer::Viewer(osg::ArgumentParser& arguments) : osgViewer::Viewer(arguments), _sceneDataGroup(new osg::Group), _timeIncrement(SGTimeStamp::fromSec(0)), _simTime(SGTimeStamp::fromSec(0)) { /// Careful: this method really assigns the sceneDataGroup to all cameras! /// FIXME the 'useMasterScene' flag at the slave is able to get around that!!! osgViewer::Viewer::setSceneData(_sceneDataGroup.get()); /// The only changed default that is renderer independent ... getCamera()->setClearColor(osg::Vec4(0, 0, 0, 0)); } Viewer::~Viewer() { stopThreading(); #if FG_HAVE_HLA if (_viewerFederate.valid()) _viewerFederate->shutdown(); _viewerFederate = 0; #endif } bool Viewer::readCameraConfig(const SGPropertyNode& viewerNode) { // Collect and realize all windows for (int i = 0; i < viewerNode.nChildren(); ++i) { // FIXME support window, fullscreen, offscreen const SGPropertyNode* windowNode = viewerNode.getChild(i); if (!windowNode || windowNode->getNameString() != "window") continue; std::string name = windowNode->getStringValue("name", ""); if (name.empty()) { SG_LOG(SG_VIEW, SG_ALERT, "Ignoring unnamed window!"); return false; } Drawable* drawable = getOrCreateDrawable(name); osg::GraphicsContext::ScreenIdentifier screenIdentifier; screenIdentifier = getScreenIdentifier(windowNode->getStringValue("display", "")); drawable->setScreenIdentifier(screenIdentifier.displayName()); if (windowNode->getBoolValue("fullscreen", false)) { osg::GraphicsContext::ScreenSettings screenSettings; screenSettings = getScreenSettings(screenIdentifier); drawable->setPosition(SGVec2i(0, 0)); drawable->setSize(SGVec2i(screenSettings.width, screenSettings.height)); drawable->setFullscreen(true); drawable->setOffscreen(false); } else if (windowNode->getBoolValue("video", false)) { drawable->setPosition(SGVec2i(0, 0)); SGVec2i size; size[0] = windowNode->getIntValue("geometry/width", 1366); size[1] = windowNode->getIntValue("geometry/height", 768); drawable->setSize(size); drawable->setFullscreen(true); drawable->setOffscreen(true); std::string outputFile = windowNode->getStringValue("output-file", "fgviewer.avi"); unsigned fps = windowNode->getIntValue("frames-per-second", 30); /// This is the case for the video writers, have a fixed time increment _timeIncrement = SGTimeStamp::fromSec(1.0/fps); MEncoderCaptureOperation* captureOperation; captureOperation = new MEncoderCaptureOperation(outputFile, fps); osgViewer::ScreenCaptureHandler* captureHandler; captureHandler = new osgViewer::ScreenCaptureHandler(captureOperation, -1); addEventHandler(captureHandler); captureHandler->startCapture(); } else { SGVec2i position; position[0] = windowNode->getIntValue("geometry/x", 0); position[1] = windowNode->getIntValue("geometry/y", 0); drawable->setPosition(position); SGVec2i size; size[0] = windowNode->getIntValue("geometry/width", 1366); size[1] = windowNode->getIntValue("geometry/height", 768); drawable->setSize(size); drawable->setFullscreen(false); drawable->setOffscreen(false); } } for (int i = 0; i < viewerNode.nChildren(); ++i) { const SGPropertyNode* cameraNode = viewerNode.getChild(i); if (!cameraNode || cameraNode->getNameString() != "camera") continue; std::string name = cameraNode->getStringValue("name", ""); if (name.empty()) { SG_LOG(SG_VIEW, SG_ALERT, "Camera configuration needs a name!"); return false; } SlaveCamera* slaveCamera = getOrCreateSlaveCamera(name); std::string drawableName = cameraNode->getStringValue("window", ""); if (drawableName.empty()) { SG_LOG(SG_VIEW, SG_ALERT, "Camera configuration needs an assigned window!"); return false; } Drawable* drawable = getDrawable(drawableName); if (!drawable) { SG_LOG(SG_VIEW, SG_ALERT, "Camera configuration \"" << name << "\" needs a drawable configured!"); return false; } slaveCamera->setDrawableName(drawableName); drawable->attachSlaveCamera(slaveCamera); SGVec2i size = drawable->getSize(); SGVec4i viewport(0, 0, size[0], size[1]); viewport[0] = cameraNode->getIntValue("viewport/x", viewport[0]); viewport[1] = cameraNode->getIntValue("viewport/y", viewport[1]); viewport[2] = cameraNode->getIntValue("viewport/width", viewport[2]); viewport[3] = cameraNode->getIntValue("viewport/height", viewport[3]); slaveCamera->setViewport(viewport); double headingDeg = cameraNode->getDoubleValue("view-offset/heading-deg", 0); double pitchDeg = cameraNode->getDoubleValue("view-offset/pitch-deg", 0); double rollDeg = cameraNode->getDoubleValue("view-offset/roll-deg", 0); slaveCamera->setViewOffsetDeg(headingDeg, pitchDeg, rollDeg); // Care for the reference points if (const SGPropertyNode* referencePointsNode = cameraNode->getNode("reference-points")) { for (int j = 0; j < referencePointsNode->nChildren(); ++j) { const SGPropertyNode* referencePointNode = cameraNode->getNode("reference-point"); if (!referencePointNode) continue; std::string name = referencePointNode->getStringValue("name", ""); if (name.empty()) continue; osg::Vec2 point; point[0] = referencePointNode->getDoubleValue("x", 0); point[1] = referencePointNode->getDoubleValue("y", 0); slaveCamera->setProjectionReferencePoint(name, point); } } // Define 4 reference points by monitor dimensions else if (const SGPropertyNode* physicalDimensionsNode = cameraNode->getNode("physical-dimensions")) { double physicalWidth = physicalDimensionsNode->getDoubleValue("width", viewport[2]); double physicalHeight = physicalDimensionsNode->getDoubleValue("height", viewport[3]); if (const SGPropertyNode* bezelNode = physicalDimensionsNode->getNode("bezel")) { double bezelHeightTop = bezelNode->getDoubleValue("top", 0); double bezelHeightBottom = bezelNode->getDoubleValue("bottom", 0); double bezelWidthLeft = bezelNode->getDoubleValue("left", 0); double bezelWidthRight = bezelNode->getDoubleValue("right", 0); slaveCamera->setMonitorProjectionReferences(physicalWidth, physicalHeight, bezelHeightTop, bezelHeightBottom, bezelWidthLeft, bezelWidthRight); } } // The frustum node takes precedence, as this is the most explicit one. if (const SGPropertyNode* frustumNode = cameraNode->getNode("frustum")) { Frustum frustum(slaveCamera->getAspectRatio()); frustum._near = frustumNode->getDoubleValue("near", frustum._near); frustum._left = frustumNode->getDoubleValue("left", frustum._left); frustum._right = frustumNode->getDoubleValue("right", frustum._right); frustum._bottom = frustumNode->getDoubleValue("bottom", frustum._bottom); frustum._top = frustumNode->getDoubleValue("top", frustum._top); slaveCamera->setFrustum(frustum); } else if (const SGPropertyNode* perspectiveNode = cameraNode->getNode("perspective")) { double fieldOfViewDeg = perspectiveNode->getDoubleValue("field-of-view-deg", 55); slaveCamera->setFustumByFieldOfViewDeg(fieldOfViewDeg); } else if (const SGPropertyNode* monitorNode = cameraNode->getNode("monitor")) { std::string referenceCameraName; std::string names[2]; std::string referenceNames[2]; // FIXME??!! if (const SGPropertyNode* leftOfNode = monitorNode->getNode("left-of")) { referenceCameraName = leftOfNode->getStringValue(""); names[0] = "lowerRight"; referenceNames[0] = "lowerLeft"; names[1] = "upperRight"; referenceNames[1] = "upperLeft"; } else if (const SGPropertyNode* rightOfNode = monitorNode->getNode("right-of")) { referenceCameraName = rightOfNode->getStringValue(""); names[0] = "lowerLeft"; referenceNames[0] = "lowerRight"; names[1] = "upperLeft"; referenceNames[1] = "upperRight"; } else if (const SGPropertyNode* aboveNode = monitorNode->getNode("above")) { referenceCameraName = aboveNode->getStringValue(""); names[0] = "lowerLeft"; referenceNames[0] = "upperLeft"; names[1] = "lowerRight"; referenceNames[1] = "upperRight"; } else if (const SGPropertyNode* belowNode = monitorNode->getNode("below")) { referenceCameraName = belowNode->getStringValue(""); names[0] = "upperLeft"; referenceNames[0] = "lowerLeft"; names[1] = "upperRight"; referenceNames[1] = "lowerRight"; } else { // names[0] = ; // referenceNames[0] = ; // names[1] = ; // referenceNames[1] = ; } // If we finally found a set of reference points that should match, // then create a relative frustum matching these references if (SlaveCamera* referenceSlaveCamera = getSlaveCamera(referenceCameraName)) { slaveCamera->setRelativeFrustum(names, *referenceSlaveCamera, referenceNames); } else { SG_LOG(SG_VIEW, SG_ALERT, "Unable to find reference camera \"" << referenceCameraName << "\" for camera \"" << name << "\"!"); } } else { // Set a proper default taking the current aspect ratio into account slaveCamera->setFustumByFieldOfViewDeg(55); } } return true; } void Viewer::setupDefaultCameraConfigIfUnset() { if (getNumDrawables() || getNumSlaveCameras()) return; osg::GraphicsContext::ScreenIdentifier screenIdentifier; screenIdentifier = getDefaultScreenIdentifier(); Drawable* drawable = getOrCreateDrawable("fgviewer"); drawable->setScreenIdentifier(screenIdentifier.displayName()); drawable->setPosition(SGVec2i(0, 0)); SGVec2i size(800, 600); drawable->setSize(size); drawable->setFullscreen(false); drawable->setOffscreen(false); SlaveCamera* slaveCamera = getOrCreateSlaveCamera(drawable->getName()); slaveCamera->setDrawableName(drawable->getName()); drawable->attachSlaveCamera(slaveCamera); slaveCamera->setViewport(SGVec4i(0, 0, size[0], size[1])); slaveCamera->setViewOffset(osg::Matrix::identity()); slaveCamera->setFustumByFieldOfViewDeg(55); } bool Viewer::readConfiguration(const std::string&) { return false; } void Viewer::setRenderer(Renderer* renderer) { if (!renderer) { SG_LOG(SG_VIEW, SG_ALERT, "Viewer::setRenderer(): Setting the renderer to zero is not supported!"); return; } if (_renderer.valid()) { SG_LOG(SG_VIEW, SG_ALERT, "Viewer::setRenderer(): Setting the renderer twice is not supported!"); return; } _renderer = renderer; } Renderer* Viewer::getRenderer() { return _renderer.get(); } Drawable* Viewer::getOrCreateDrawable(const std::string& name) { Drawable* drawable = getDrawable(name); if (drawable) return drawable; if (!_renderer.valid()) return 0; drawable = _renderer->createDrawable(*this, name); if (!drawable) return 0; _drawableVector.push_back(drawable); return drawable; } Drawable* Viewer::getDrawable(const std::string& name) { return getDrawable(getDrawableIndex(name)); } unsigned Viewer::getDrawableIndex(const std::string& name) { for (DrawableVector::size_type i = 0; i < _drawableVector.size(); ++i) { if (_drawableVector[i]->getName() == name) return i; } return ~0u; } Drawable* Viewer::getDrawable(unsigned index) { if (_drawableVector.size() <= index) return 0; return _drawableVector[index].get(); } unsigned Viewer::getNumDrawables() const { return _drawableVector.size(); } SlaveCamera* Viewer::getOrCreateSlaveCamera(const std::string& name) { SlaveCamera* slaveCamera = getSlaveCamera(name); if (slaveCamera) return slaveCamera; if (!_renderer.valid()) return 0; slaveCamera = _renderer->createSlaveCamera(*this, name); if (!slaveCamera) return 0; _slaveCameraVector.push_back(slaveCamera); return slaveCamera; } SlaveCamera* Viewer::getSlaveCamera(const std::string& name) { return getSlaveCamera(getSlaveCameraIndex(name)); } unsigned Viewer::getSlaveCameraIndex(const std::string& name) { for (SlaveCameraVector::size_type i = 0; i < _slaveCameraVector.size(); ++i) { if (_slaveCameraVector[i]->getName() == name) return i; } return ~0u; } SlaveCamera* Viewer::getSlaveCamera(unsigned index) { if (_slaveCameraVector.size() <= index) return 0; return _slaveCameraVector[index].get(); } unsigned Viewer::getNumSlaveCameras() const { return _slaveCameraVector.size(); } void Viewer::realize() { if (isRealized()) return; if (!_renderer.valid()) return; // Setup a default config if there is none setupDefaultCameraConfigIfUnset(); // Realize if (!_renderer->realize(*this)) { SG_LOG(SG_VIEW, SG_ALERT, "Renderer::realize() failed!"); return; } osgViewer::Viewer::realize(); } bool Viewer::realizeDrawables() { for (DrawableVector::iterator i = _drawableVector.begin(); i != _drawableVector.end(); ++i) { if (!(*i)->realize(*this)) { SG_LOG(SG_VIEW, SG_ALERT, "Unable to realize drawable \"" << (*i)->getName() << "\"!"); return false; } } return true; } bool Viewer::realizeSlaveCameras() { for (SlaveCameraVector::iterator i = _slaveCameraVector.begin(); i != _slaveCameraVector.end(); ++i) { if (!(*i)->realize(*this)) { SG_LOG(SG_VIEW, SG_ALERT, "Unable to realize camera \"" << (*i)->getName() << "\"!"); return false; } } return true; } void Viewer::advance(double) { if (_timeIncrement == SGTimeStamp::fromSec(0)) { // Flightgears current scheme - could be improoved _simTime = SGTimeStamp::now(); } else { // Giving an explicit time increment makes sense in presence // of the video capture where we need deterministic // frame times and object positions for each picture. _simTime += _timeIncrement; } // This sets the frame stamps simulation time to simTime // and schedules a frame event osgViewer::Viewer::advance(_simTime.toSecs()); } void Viewer::updateTraversal() { #if FG_HAVE_HLA if (_viewerFederate.valid()) { if (_timeIncrement == SGTimeStamp::fromSec(0)) { if (!_viewerFederate->timeAdvanceAvailable()) { SG_LOG(SG_NETWORK, SG_ALERT, "Got error from federate update!"); _viewerFederate->shutdown(); _viewerFederate = 0; } } else { osg::FrameStamp* frameStamp = getViewerFrameStamp(); SGTimeStamp timeStamp = SGTimeStamp::fromSec(frameStamp->getSimulationTime()); if (!_viewerFederate->timeAdvance(timeStamp)) { SG_LOG(SG_NETWORK, SG_ALERT, "Got error from federate update!"); _viewerFederate->shutdown(); _viewerFederate = 0; } } } #endif osgViewer::Viewer::updateTraversal(); if (!_renderer->update(*this)) { SG_LOG(SG_VIEW, SG_ALERT, "Renderer::update() failed!"); } } bool Viewer::updateSlaveCameras() { for (SlaveCameraVector::iterator i = _slaveCameraVector.begin(); i != _slaveCameraVector.end(); ++i) { if (!(*i)->update(*this)) { SG_LOG(SG_VIEW, SG_ALERT, "SlaveCamera::update() failed!"); return false; } } return true; } void Viewer::setReaderWriterOptions(simgear::SGReaderWriterOptions* readerWriterOptions) { _readerWriterOptions = readerWriterOptions; } simgear::SGReaderWriterOptions* Viewer::getReaderWriterOptions() { return _readerWriterOptions.get(); } void Viewer::setSceneData(osg::Node* node) { _sceneDataGroup->removeChildren(0, _sceneDataGroup->getNumChildren()); insertSceneData(node); } void Viewer::insertSceneData(osg::Node* node) { _sceneDataGroup->addChild(node); } bool Viewer::insertSceneData(const std::string& fileName, const osgDB::Options* options) { #if 0 osg::ProxyNode* proxyNode = new osg::ProxyNode; if (options) proxyNode->setDatabaseOptions(options->clone(osg::CopyOp())); else proxyNode->setDatabaseOptions(_readerWriterOptions->clone(osg::CopyOp())); proxyNode->setFileName(0, fileName); insertSceneData(proxyNode); return true; #else #if OSG_VERSION_LESS_THAN(3,4,0) osg::ref_ptr node = osgDB::readNodeFile(fileName, options); #else osg::ref_ptr node = osgDB::readRefNodeFile(fileName, options); #endif if (!node.valid()) return false; insertSceneData(node.get()); return true; #endif } osg::Group* Viewer::getSceneDataGroup() { return _sceneDataGroup.get(); } class Viewer::_PurgeLevelOfDetailNodesVisitor : public osg::NodeVisitor { public: _PurgeLevelOfDetailNodesVisitor() : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) { } virtual ~_PurgeLevelOfDetailNodesVisitor() { } virtual void apply(osg::ProxyNode& node) { for (unsigned i = 0; i < node.getNumChildren(); ++i) { if (node.getFileName(i).empty()) continue; node.removeChildren(i, node.getNumChildren() - i); break; } osg::NodeVisitor::apply(static_cast(node)); } virtual void apply(osg::PagedLOD& node) { for (unsigned i = 0; i < node.getNumChildren(); ++i) { if (node.getFileName(i).empty()) continue; node.removeChildren(i, node.getNumChildren() - i); break; } osg::NodeVisitor::apply(static_cast(node)); } }; void Viewer::purgeLevelOfDetailNodes() { _PurgeLevelOfDetailNodesVisitor purgeLevelOfDetailNodesVisitor; _sceneDataGroup->accept(purgeLevelOfDetailNodesVisitor); } osg::GraphicsContext::ScreenIdentifier Viewer::getDefaultScreenIdentifier() { osg::GraphicsContext::ScreenIdentifier screenIdentifier; screenIdentifier.readDISPLAY(); if (screenIdentifier.displayNum < 0) screenIdentifier.displayNum = 0; if (screenIdentifier.screenNum < 0) screenIdentifier.screenNum = 0; return screenIdentifier; } osg::GraphicsContext::ScreenIdentifier Viewer::getScreenIdentifier(const std::string& display) { osg::GraphicsContext::ScreenIdentifier screenIdentifier; screenIdentifier.setScreenIdentifier(display); osg::GraphicsContext::ScreenIdentifier defaultScreenIdentifier; defaultScreenIdentifier = getDefaultScreenIdentifier(); if (screenIdentifier.hostName.empty()) screenIdentifier.hostName = defaultScreenIdentifier.hostName; if (screenIdentifier.displayNum < 0) screenIdentifier.displayNum = defaultScreenIdentifier.displayNum; if (screenIdentifier.screenNum < 0) screenIdentifier.screenNum = defaultScreenIdentifier.screenNum; return screenIdentifier; } osg::GraphicsContext::ScreenSettings Viewer::getScreenSettings(const osg::GraphicsContext::ScreenIdentifier& screenIdentifier) { osg::GraphicsContext::ScreenSettings screenSettings; osg::GraphicsContext::WindowingSystemInterface* wsi; wsi = osg::GraphicsContext::getWindowingSystemInterface(); if (!wsi) { SG_LOG(SG_VIEW, SG_ALERT, "No windowing system interface defined!"); return screenSettings; } wsi->getScreenSettings(screenIdentifier, screenSettings); return screenSettings; } osg::GraphicsContext::Traits* Viewer::getTraits(const osg::GraphicsContext::ScreenIdentifier& screenIdentifier) { osg::DisplaySettings* ds = _displaySettings.get(); if (!ds) ds = osg::DisplaySettings::instance().get(); osg::GraphicsContext::Traits* traits = new osg::GraphicsContext::Traits(ds); traits->hostName = screenIdentifier.hostName; traits->displayNum = screenIdentifier.displayNum; traits->screenNum = screenIdentifier.screenNum; // not seriously consider something different traits->doubleBuffer = true; osg::GraphicsContext::ScreenSettings screenSettings; screenSettings = getScreenSettings(screenIdentifier); traits->x = 0; traits->y = 0; traits->width = screenSettings.width; traits->height = screenSettings.height; return traits; } #ifdef __linux__ class Viewer::_ResetScreenSaverSwapCallback : public osg::GraphicsContext::SwapCallback { public: _ResetScreenSaverSwapCallback() : _timeStamp(SGTimeStamp::fromSec(0)) { } virtual ~_ResetScreenSaverSwapCallback() { } virtual void swapBuffersImplementation(osg::GraphicsContext* graphicsContext) { graphicsContext->swapBuffersImplementation(); // This callback must be attached to this type of graphics context assert(dynamic_cast(graphicsContext)); // Reset the screen saver every 10 seconds SGTimeStamp timeStamp = SGTimeStamp::now(); if (timeStamp < _timeStamp) return; _timeStamp = timeStamp + SGTimeStamp::fromSec(10); // Obviously runs in the draw thread. Thus, use the draw display. XResetScreenSaver(static_cast(graphicsContext)->getDisplay()); } private: SGTimeStamp _timeStamp; }; #endif osg::GraphicsContext* Viewer::createGraphicsContext(osg::GraphicsContext::Traits* traits) { osg::GraphicsContext::WindowingSystemInterface* wsi; wsi = osg::GraphicsContext::getWindowingSystemInterface(); if (!wsi) { SG_LOG(SG_VIEW, SG_ALERT, "No windowing system interface defined!"); return 0; } osg::GraphicsContext* graphicsContext = wsi->createGraphicsContext(traits); if (!graphicsContext) { SG_LOG(SG_VIEW, SG_ALERT, "Unable to create window \"" << traits->windowName << "\"!"); return 0; } #ifdef __linux__ if (dynamic_cast(graphicsContext)) graphicsContext->setSwapCallback(new _ResetScreenSaverSwapCallback); #endif return graphicsContext; } #if FG_HAVE_HLA const HLAViewerFederate* Viewer::getViewerFederate() const { return _viewerFederate.get(); } HLAViewerFederate* Viewer::getViewerFederate() { return _viewerFederate.get(); } void Viewer::setViewerFederate(HLAViewerFederate* viewerFederate) { if (!viewerFederate) { SG_LOG(SG_VIEW, SG_ALERT, "Viewer::setViewerFederate(): Setting the viewer federate to zero is not supported!"); return; } if (_viewerFederate.valid()) { SG_LOG(SG_VIEW, SG_ALERT, "Viewer::setViewerFederate(): Setting the viewer federate twice is not supported!"); return; } _viewerFederate = viewerFederate; _viewerFederate->attachToViewer(this); } #endif } // namespace fgviewer