From 918db84ac4ec8fa72fcdf94586162d7a732e82b4 Mon Sep 17 00:00:00 2001 From: James Turner Date: Fri, 18 Nov 2016 17:53:23 +0100 Subject: [PATCH] New splash screen system. Uses TTF fonts, and displays more information textually including the application version and current aircraft. Also rename FGRenderer::splashinit to preinit, as was suggested a long time ago. --- src/Main/fg_init.cxx | 4 +- src/Main/main.cxx | 3 +- src/Viewer/renderer.cxx | 15 +- src/Viewer/renderer.hxx | 5 +- src/Viewer/splash.cxx | 847 ++++++++++++++++++++++++---------------- src/Viewer/splash.hxx | 78 +++- 6 files changed, 589 insertions(+), 363 deletions(-) diff --git a/src/Main/fg_init.cxx b/src/Main/fg_init.cxx index 0ee86e29d..d0ca9f5ae 100644 --- a/src/Main/fg_init.cxx +++ b/src/Main/fg_init.cxx @@ -1111,9 +1111,9 @@ void fgStartNewReset() viewer->getDatabasePager()->setUpThreads(1, 1); - // must do this before splashinit for Rembrandt + // must do this before preinit for Rembrandt flightgear::CameraGroup::buildDefaultGroup(viewer.get()); - render->splashinit(); + render->preinit(); viewer->startThreading(); fgOSResetProperties(); diff --git a/src/Main/main.cxx b/src/Main/main.cxx index 2432e99d2..0b0db0954 100644 --- a/src/Main/main.cxx +++ b/src/Main/main.cxx @@ -534,9 +534,8 @@ int fgMainInit( int argc, char **argv ) fgOSOpenWindow(true /* request stencil buffer */); fgOSResetProperties(); - // Initialize the splash screen right away fntInit(); - fgSplashInit(); + globals->get_renderer()->preinit(); fgOutputSettings(); diff --git a/src/Viewer/renderer.cxx b/src/Viewer/renderer.cxx index e25cc417e..6689d426d 100644 --- a/src/Viewer/renderer.cxx +++ b/src/Viewer/renderer.cxx @@ -433,10 +433,8 @@ FGRenderer::~FGRenderer() } // Initialize various GL/view parameters -// XXX This should be called "preinit" or something, as it initializes -// critical parts of the scene graph in addition to the splash screen. void -FGRenderer::splashinit( void ) +FGRenderer::preinit( void ) { // important that we reset the viewer sceneData here, to ensure the reference // time for everything is in sync; otherwise on reset the Viewer and @@ -446,10 +444,10 @@ FGRenderer::splashinit( void ) _viewerSceneRoot = new osg::Group; _viewerSceneRoot->setName("viewerSceneRoot"); viewer->setSceneData(_viewerSceneRoot); - - ref_ptr splashNode = fgCreateSplashNode(); + + _splash = new SplashScreen; if (_classicalRenderer) { - _viewerSceneRoot->addChild(splashNode.get()); + _viewerSceneRoot->addChild(_splash); } else { for ( CameraGroup::CameraIterator ii = CameraGroup::getDefault()->camerasBegin(); ii != CameraGroup::getDefault()->camerasEnd(); @@ -459,7 +457,7 @@ FGRenderer::splashinit( void ) Camera* camera = info->getCamera(DISPLAY_CAMERA); if (camera == 0) continue; - camera->addChild(splashNode.get()); + camera->addChild(_splash); } } @@ -1727,6 +1725,9 @@ FGRenderer::resize( int width, int height ) _xsize->setIntValue(width); _ysize->setIntValue(height); } + + // update splash node if present ? + _splash->resize(width, height); } typedef osgUtil::LineSegmentIntersector::Intersection Intersection; diff --git a/src/Viewer/renderer.hxx b/src/Viewer/renderer.hxx index d203df94f..74fd03777 100644 --- a/src/Viewer/renderer.hxx +++ b/src/Viewer/renderer.hxx @@ -43,6 +43,7 @@ class CameraGroup; class SGSky; class SGUpdateVisitor; +class SplashScreen; typedef std::vector PickList; @@ -53,7 +54,7 @@ public: FGRenderer(); ~FGRenderer(); - void splashinit(); + void preinit(); void init(); void setupView(); @@ -188,6 +189,8 @@ protected: void updateSky(); void setupRoot(); + + SplashScreen* _splash; }; bool fgDumpSceneGraphToFile(const char* filename); diff --git a/src/Viewer/splash.cxx b/src/Viewer/splash.cxx index 21ec44399..07cfb0cef 100644 --- a/src/Viewer/splash.cxx +++ b/src/Viewer/splash.cxx @@ -21,10 +21,7 @@ // // $Id$ - -#ifdef HAVE_CONFIG_H -# include -#endif +#include #include #include @@ -36,7 +33,8 @@ #include #include #include -#include +#include + #include #include @@ -46,11 +44,6 @@ #include #include -#include - -#include "GUI/FGFontCache.hxx" -#include "GUI/FGColor.hxx" - #include
#include
#include
@@ -58,358 +51,530 @@ #include "splash.hxx" #include "renderer.hxx" -class FGSplashUpdateCallback : public osg::Drawable::UpdateCallback { +#include + +const char* LICENSE_URL_TEXT = "Licensed under the GNU GPL. See http://www.flightgear.org for more information"; + +class SplashScreenUpdateCallback : public osg::NodeCallback { public: - FGSplashUpdateCallback(osg::Vec4Array* colorArray, SGPropertyNode* prop) : - _colorArray(colorArray), - _colorProperty(prop), - _alphaProperty(fgGetNode("/sim/startup/splash-alpha", true)) - { } - virtual void update(osg::NodeVisitor*, osg::Drawable*) - { - FGColor c(0, 0, 0); - if (_colorProperty) { - c.merge(_colorProperty); - (*_colorArray)[0][0] = c.red(); - (*_colorArray)[0][1] = c.green(); - (*_colorArray)[0][2] = c.blue(); + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + SplashScreen* screen = static_cast(node); + screen->doUpdate(); + traverse(node, nv); } - (*_colorArray)[0][3] = _alphaProperty->getFloatValue(); - _colorArray->dirty(); - } -private: - osg::ref_ptr _colorArray; - SGSharedPtr _colorProperty; - SGSharedPtr _alphaProperty; }; -class FGSplashTextUpdateCallback : public osg::Drawable::UpdateCallback { -public: - FGSplashTextUpdateCallback(const SGPropertyNode* prop) : - _textProperty(prop), - _alphaProperty(fgGetNode("/sim/startup/splash-alpha", true)), - _styleProperty(fgGetNode("/sim/gui/style[0]", true)) - {} - virtual void update(osg::NodeVisitor*, osg::Drawable* drawable) - { - assert(dynamic_cast(drawable)); - osgText::Text* text = static_cast(drawable); - - FGColor c(1.0, 0.9, 0.0); - c.merge(_styleProperty->getNode("colors/splash-font")); - float alpha = _alphaProperty->getFloatValue(); - text->setColor(osg::Vec4(c.red(), c.green(), c.blue(), alpha)); - - const char* s = _textProperty->getStringValue(); - if (s && fgGetBool("/sim/startup/splash-progress", true)) - text->setText(s); - else - text->setText(""); - } -private: - SGSharedPtr _textProperty; - SGSharedPtr _alphaProperty; - SGSharedPtr _styleProperty; -}; - - - -class FGSplashContentProjectionCalback : public osg::NodeCallback { -public: - virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) - { - assert(dynamic_cast(nv)); - osgUtil::CullVisitor* cullVisitor = static_cast(nv); - - // adjust the projection matrix in a way that preserves the aspect ratio - // of the content ... - const osg::Viewport* viewport = cullVisitor->getViewport(); - float viewportAspect = float(viewport->height())/float(viewport->width()); - - float height, width; - if (viewportAspect < 1) { - height = 1; - width = 1/viewportAspect; - } else { - height = viewportAspect; - width = 1; - } - - osg::RefMatrix* matrix = new osg::RefMatrix; - matrix->makeOrtho2D(-width, width, -height, height); - - // The trick is to have the projection matrix adapted independent - // of the scenegraph but dependent on the viewport of this current - // camera we cull for. Therefore we do not put that projection matrix into - // an additional camera rather than from within that cull callback. - cullVisitor->pushProjectionMatrix(matrix); - traverse(node, nv); - cullVisitor->popProjectionMatrix(); - } -}; - -char *genNameString() +SplashScreen::SplashScreen() : + _splashAlphaNode(fgGetNode("/sim/startup/splash-alpha", true)) { - std::string website = "http://www.flightgear.org"; - std::string programName = "FlightGear"; - char *name = new char[26]; - name[20] = 114; - name[8] = 119; - name[5] = 47; - name[12] = 108; - name[2] = 116; - name[1] = 116; - name[16] = 116; - name[13] = 105; - name[17] = 103; - name[19] = 97; - name[25] = 0; - name[0] = 104; - name[24] = 103; - name[21] = 46; - name[15] = 104; - name[3] = 112; - name[22] = 111; - name[18] = 101; - name[7] = 119; - name[14] = 103; - name[23] = 114; - name[4] = 58; - name[11] = 102; - name[9] = 119; - name[10] = 46; - name[6] = 47; - return name; + setName("splashGroup"); + setUpdateCallback(new SplashScreenUpdateCallback); } -static osg::Node* fgCreateSplashCamera() +SplashScreen::~SplashScreen() { - char *namestring = genNameString(); - fgSetString("/sim/startup/program-name", namestring); - delete[] namestring; +} - simgear::PropertyList textures = fgGetNode("/sim/startup", true)->getChildren("splash-texture"); - std::vector useable_textures; - SGPath tpath; +void SplashScreen::createNodes() +{ + _splashImage = osgDB::readImageFile(selectSplashImage()); - // Build a list of textures that are usable; those whose path can be resolved - for (simgear::PropertyList::iterator it = textures.begin(); it != textures.end(); it++) { - std::string value = (*it)->getStringValue(); - if (!value.empty()) { - tpath = globals->resolve_maybe_aircraft_path(value); - if (!tpath.isNull()) { - useable_textures.push_back(tpath); - } - else { - SG_LOG(SG_VIEW, SG_ALERT, "Cannot find splash screen file '" << value << "'"); + int width = _splashImage->s(); + int height = _splashImage->t(); + _splashImageAspectRatio = static_cast(width) / height; + + osg::TextureRectangle* splashTexture = new osg::TextureRectangle(_splashImage); + splashTexture->setTextureSize(width, height); + splashTexture->setInternalFormat(GL_RGB); + splashTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + splashTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + splashTexture->setImage(_splashImage); + + _splashFBOCamera = new osg::Camera; + _splashFBOCamera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); + _splashFBOCamera->setViewMatrix(osg::Matrix::identity()); + _splashFBOCamera->setClearMask( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); + _splashFBOCamera->setClearColor( osg::Vec4( 0., 0., 0., 0. ) ); + _splashFBOCamera->setAllowEventFocus(false); + _splashFBOCamera->setCullingActive(false); + + _splashFBOTexture = new osg::Texture2D; + _splashFBOTexture->setInternalFormat(GL_RGB); + _splashFBOTexture->setResizeNonPowerOfTwoHint(false); + _splashFBOTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + _splashFBOTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + _splashFBOCamera->setRenderTargetImplementation( osg::Camera::FRAME_BUFFER_OBJECT ); + _splashFBOCamera->setRenderOrder(osg::Camera::PRE_RENDER); + _splashFBOCamera->attach(osg::Camera::COLOR_BUFFER, _splashFBOTexture); + + addChild(_splashFBOCamera); + + osg::StateSet* stateSet = _splashFBOCamera->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); + + osg::Geometry* geometry = new osg::Geometry; + geometry->setSupportsDisplayList(false); + + _splashImageVertexArray = new osg::Vec3Array; + for (int i=0; i < 4; ++i) { + _splashImageVertexArray->push_back(osg::Vec3(0.0, 0.0, 0.0)); + } + geometry->setVertexArray(_splashImageVertexArray); + + osg::Vec2Array* imageTCs = new osg::Vec2Array; + imageTCs->push_back(osg::Vec2(0, 0)); + imageTCs->push_back(osg::Vec2(width, 0)); + imageTCs->push_back(osg::Vec2(width, height)); + imageTCs->push_back(osg::Vec2(0, height)); + geometry->setTexCoordArray(0, imageTCs); + + osg::Vec4Array* colorArray = new osg::Vec4Array; + colorArray->push_back(osg::Vec4(1, 1, 1, 1)); + geometry->setColorArray(colorArray); + geometry->setColorBinding(osg::Geometry::BIND_OVERALL); + geometry->addPrimitiveSet(new osg::DrawArrays(GL_POLYGON, 0, 4)); + + stateSet = geometry->getOrCreateStateSet(); + stateSet->setTextureMode(0, GL_TEXTURE_RECTANGLE, osg::StateAttribute::ON); + stateSet->setTextureAttribute(0, splashTexture); + + osg::Geode* geode = new osg::Geode; + _splashFBOCamera->addChild(geode); + geode->addDrawable(geometry); + + if (_legacySplashScreenMode) { + addText(geode, osg::Vec2(0.025, 0.025), 0.03, + std::string("FlightGear ") + fgGetString("/sim/version/flightgear") + + std::string(" ") + std::string(LICENSE_URL_TEXT), + osgText::Text::LEFT_TOP, + nullptr, + 0.9); + } else { + setupLogoImage(); + + addText(geode, osg::Vec2(0.025, 0.025), 0.10, std::string("FlightGear ") + fgGetString("/sim/version/flightgear"), osgText::Text::LEFT_TOP); + addText(geode, osg::Vec2(0.025, 0.15), 0.03, LICENSE_URL_TEXT, + osgText::Text::LEFT_TOP, + nullptr, + 0.6); + + if (!_aircraftLogoVertexArray) { + addText(geode, osg::Vec2(0.025, 0.935), 0.10, + fgGetString("/sim/description"), + osgText::Text::LEFT_BOTTOM, + nullptr, + 0.6); + _items.back().maxLineCount = 1; + } + + addText(geode, osg::Vec2(0.025, 0.975), 0.03, + fgGetString("/sim/author"), + osgText::Text::LEFT_BOTTOM, + nullptr, + 0.6); + _items.back().maxLineCount = 1; + } + + addText(geode, osg::Vec2(0.975, 0.935), 0.03, + "loading status", + osgText::Text::RIGHT_BOTTOM, + fgGetNode("/sim/startup/splash-progress-text", true), + 0.4); + + addText(geode, osg::Vec2(0.975, 0.975), 0.03, + "spinner", + osgText::Text::RIGHT_BOTTOM, + fgGetNode("/sim/startup/splash-progress-spinner", true)); + + /////////// + + geometry = new osg::Geometry; + geometry->setSupportsDisplayList(false); + + _splashSpinnerVertexArray = new osg::Vec3Array; + for (int i=0; i < 8; ++i) { + _splashSpinnerVertexArray->push_back(osg::Vec3(0.0, 0.0, -0.1)); + } + geometry->setVertexArray(_splashSpinnerVertexArray); + + // QColor buttonColor(27, 122, 211); + colorArray = new osg::Vec4Array; + colorArray->push_back(osg::Vec4(27 / 255.0, 122 / 255.0, 211 / 255.0, 0.75)); + 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->setClearMask( 0 ); + _splashQuadCamera->setAllowEventFocus(false); + _splashQuadCamera->setCullingActive(false); + _splashQuadCamera->setRenderOrder(osg::Camera::POST_RENDER, 20000); + + 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(1, "RenderBin"); + + geometry = new osg::Geometry; + geometry->setSupportsDisplayList(false); + + osg::Vec3Array* splashFullscreenQuadVertices = new osg::Vec3Array; + splashFullscreenQuadVertices->push_back(osg::Vec3(0.0, 0.0, 0.0)); + splashFullscreenQuadVertices->push_back(osg::Vec3(1.0, 0.0, 0.0)); + splashFullscreenQuadVertices->push_back(osg::Vec3(1.0, 1.0, 0.0)); + splashFullscreenQuadVertices->push_back(osg::Vec3(0.0, 1.0, 0.0)); + geometry->setVertexArray(splashFullscreenQuadVertices); + + osg::Vec2Array* quadTexCoords = new osg::Vec2Array; + quadTexCoords->push_back(osg::Vec2(0, 0)); + quadTexCoords->push_back(osg::Vec2(1.0f, 0)); + quadTexCoords->push_back(osg::Vec2(1.0f, 1.0f)); + quadTexCoords->push_back(osg::Vec2(0, 1.0f)); + geometry->setTexCoordArray(0, quadTexCoords); + + _splashFSQuadColor = new osg::Vec4Array; + _splashFSQuadColor->push_back(osg::Vec4(1, 1.0f, 1, 1)); + geometry->setColorArray(_splashFSQuadColor); + geometry->setColorBinding(osg::Geometry::BIND_OVERALL); + geometry->addPrimitiveSet(new osg::DrawArrays(GL_POLYGON, 0, 4)); + + stateSet = geometry->getOrCreateStateSet(); + stateSet->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON); + stateSet->setTextureAttribute(0, _splashFBOTexture); + + geode = new osg::Geode; + geode->addDrawable(geometry); + + _splashQuadCamera->addChild(geode); + addChild(_splashQuadCamera); +} + +void SplashScreen::setupLogoImage() +{ + // check for a logo image, add underneath other text + SGPath logoPath = globals->resolve_maybe_aircraft_path(fgGetString("/sim/startup/splash-logo-image")); + if (!logoPath.exists()) { + return; + } + + _logoImage = osgDB::readImageFile(logoPath.utf8Str()); + if (!_logoImage) { + return; + } + + osg::Texture2D* logoTexture = new osg::Texture2D(_logoImage); + logoTexture->setResizeNonPowerOfTwoHint(false); + logoTexture->setInternalFormat(GL_RGBA); + logoTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + logoTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + + osg::Geometry* geometry = new osg::Geometry; + geometry->setSupportsDisplayList(false); + + _aircraftLogoVertexArray = new osg::Vec3Array; + for (int i=0; i < 4; ++i) { + _aircraftLogoVertexArray->push_back(osg::Vec3(0.0, 0.0, 0.0)); + } + geometry->setVertexArray(_aircraftLogoVertexArray); + + osg::Vec2Array* logoTCs = new osg::Vec2Array; + logoTCs->push_back(osg::Vec2(0, 0)); + logoTCs->push_back(osg::Vec2(1.0, 0)); + logoTCs->push_back(osg::Vec2(1.0, 1.0)); + logoTCs->push_back(osg::Vec2(0, 1.0)); + geometry->setTexCoordArray(0, logoTCs); + + osg::Vec4Array* colorArray = new osg::Vec4Array; + colorArray->push_back(osg::Vec4(1, 1, 1, 1)); + geometry->setColorArray(colorArray); + 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, logoTexture); + stateSet->setMode(GL_BLEND, osg::StateAttribute::ON); + + osg::Geode* geode = new osg::Geode; + _splashFBOCamera->addChild(geode); + geode->addDrawable(geometry); + +} + +void SplashScreen::addText(osg::Geode* geode , + const osg::Vec2& pos, double size, const std::string& text, + const osgText::Text::AlignmentType alignment, + SGPropertyNode* dynamicValue, + double maxWidthFraction) +{ + SGPath path = globals->resolve_resource_path("Fonts/LiberationFonts/LiberationSans-BoldItalic.ttf"); + + TextItem item; + osg::ref_ptr t = new osgText::Text; + item.textNode = t; + t->setFont(path.utf8Str()); + t->setColor(osg::Vec4(1, 1, 1, 1)); + t->setFontResolution(64, 64); + t->setText(text); + 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; + geode->addDrawable(item.textNode); + + _items.push_back(item); +} + +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) { + return; + } + + double baseSize = fractionalCharSize; + textNode->update(); + while (textNode->getLineCount() > maxLineCount) { + 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 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; } } } - // Seed the RNG in order to get randomness - sg_srandom_time(); - - if (!useable_textures.empty()) { + if (!paths.empty()) { // Select a random useable texture - const int index = (int)(sg_random() * useable_textures.size()); - tpath = useable_textures[index]; + const int index = (int)(sg_random() * paths.size()); + return paths.at(index).utf8Str(); } - if (tpath.isNull()) { // no splash screen specified - select random image - tpath = globals->get_fg_root(); - // load in the texture data + SGPath tpath = globals->get_fg_root() / "Textures"; int num = (int)(sg_random() * 5.0 + 1.0); - char num_str[5]; - snprintf(num_str, 4, "%d", num); - - tpath.append( "Textures/Splash" ); - tpath.concat( num_str ); - tpath.concat( ".png" ); - } - - SGSharedPtr style = fgGetNode("/sim/gui/style[0]", true); - - osg::Texture2D* splashTexture = new osg::Texture2D; - splashTexture->setImage(osgDB::readImageFile(tpath.local8BitStr())); - - osg::Camera* camera = new osg::Camera; - camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); - camera->setProjectionMatrix(osg::Matrix::ortho2D(-1, 1, -1, 1)); - camera->setViewMatrix(osg::Matrix::identity()); - camera->setRenderOrder(osg::Camera::POST_RENDER, 10000); - camera->setClearMask(0); - camera->setAllowEventFocus(false); - camera->setCullingActive(false); - - osg::StateSet* stateSet = camera->getOrCreateStateSet(); - stateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::OFF); - stateSet->setMode(GL_BLEND, osg::StateAttribute::ON); - stateSet->setAttribute(new osg::BlendFunc); - 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); - - - osg::Geometry* geometry = new osg::Geometry; - geometry->setSupportsDisplayList(false); - - osg::Vec3Array* vertexArray = new osg::Vec3Array; - vertexArray->push_back(osg::Vec3(-1, -1, 0)); - vertexArray->push_back(osg::Vec3( 1, -1, 0)); - vertexArray->push_back(osg::Vec3( 1, 1, 0)); - vertexArray->push_back(osg::Vec3(-1, 1, 0)); - geometry->setVertexArray(vertexArray); - osg::Vec4Array* colorArray = new osg::Vec4Array; - colorArray->push_back(osg::Vec4(0, 0, 0, 1)); - geometry->setColorArray(colorArray); - geometry->setColorBinding(osg::Geometry::BIND_OVERALL); - geometry->addPrimitiveSet(new osg::DrawArrays(GL_POLYGON, 0, 4)); - geometry->setUpdateCallback(new FGSplashUpdateCallback(colorArray, - style->getNode("colors/splash-screen"))); - - osg::Geode* geode = new osg::Geode; - geode->addDrawable(geometry); - - stateSet = geode->getOrCreateStateSet(); - stateSet->setRenderBinDetails(1, "RenderBin"); - camera->addChild(geode); - - - // The group is needed because of osg is handling the cull callbacks in a - // different way for groups than for a geode. It does not hurt here ... - osg::Group* group = new osg::Group; - group->setCullCallback(new FGSplashContentProjectionCalback); - camera->addChild(group); - - geode = new osg::Geode; - stateSet = geode->getOrCreateStateSet(); - stateSet->setRenderBinDetails(2, "RenderBin"); - group->addChild(geode); - - - geometry = new osg::Geometry; - geometry->setSupportsDisplayList(false); - - vertexArray = new osg::Vec3Array; - vertexArray->push_back(osg::Vec3(-0.84, -0.84, 0)); - vertexArray->push_back(osg::Vec3( 0.84, -0.84, 0)); - vertexArray->push_back(osg::Vec3( 0.84, 0.84, 0)); - vertexArray->push_back(osg::Vec3(-0.84, 0.84, 0)); - geometry->setVertexArray(vertexArray); - osg::Vec2Array* texCoordArray = new osg::Vec2Array; - texCoordArray->push_back(osg::Vec2(0, 0)); - texCoordArray->push_back(osg::Vec2(1, 0)); - texCoordArray->push_back(osg::Vec2(1, 1)); - texCoordArray->push_back(osg::Vec2(0, 1)); - geometry->setTexCoordArray(0, texCoordArray); - colorArray = new osg::Vec4Array; - colorArray->push_back(osg::Vec4(1, 1, 1, 1)); - geometry->setColorArray(colorArray); - geometry->setColorBinding(osg::Geometry::BIND_OVERALL); - geometry->addPrimitiveSet(new osg::DrawArrays(GL_POLYGON, 0, 4)); - geometry->setUpdateCallback(new FGSplashUpdateCallback(colorArray, 0)); - stateSet = geometry->getOrCreateStateSet(); - stateSet->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON); - stateSet->setTextureAttribute(0, splashTexture); - geode->addDrawable(geometry); - - FGFontCache* fontCache = FGFontCache::instance(); - osgText::Text* text = new osgText::Text; - std::string fn = style->getStringValue("fonts/splash", ""); - text->setFont(fontCache->getfntpath(fn).local8BitStr()); - text->setCharacterSize(0.06); - text->setColor(osg::Vec4(1, 1, 1, 1)); - text->setPosition(osg::Vec3(0, -0.92, 0)); - text->setAlignment(osgText::Text::CENTER_CENTER); - SGPropertyNode* prop = fgGetNode("/sim/startup/splash-progress-text", true); - prop->setStringValue(""); - text->setUpdateCallback(new FGSplashTextUpdateCallback(prop)); - geode->addDrawable(text); - - osgText::Text* spinnertext = new osgText::Text; - spinnertext->setFont(fontCache->getfntpath(fn).local8BitStr()); - spinnertext->setCharacterSize(0.06); - spinnertext->setColor(osg::Vec4(1, 1, 1, 1)); - spinnertext->setPosition(osg::Vec3(0, -0.97, 0)); - spinnertext->setAlignment(osgText::Text::CENTER_CENTER); - prop = fgGetNode("/sim/startup/splash-progress-spinner", true); - prop->setStringValue(""); - spinnertext->setUpdateCallback(new FGSplashTextUpdateCallback(prop)); - geode->addDrawable(spinnertext); - - text = new osgText::Text; - text->setFont(fontCache->getfntpath(fn).local8BitStr()); - text->setCharacterSize(0.08); - text->setColor(osg::Vec4(1, 1, 1, 1)); - text->setPosition(osg::Vec3(0, 0.92, 0)); - text->setAlignment(osgText::Text::CENTER_CENTER); - prop = fgGetNode("/sim/startup/program-name", "FlightGear"); - text->setUpdateCallback(new FGSplashTextUpdateCallback(prop)); - geode->addDrawable(text); - - - text = new osgText::Text; - text->setFont(fontCache->getfntpath(fn).local8BitStr()); - text->setCharacterSize(0.06); - text->setColor(osg::Vec4(1, 1, 1, 1)); - text->setPosition(osg::Vec3(0, 0.82, 0)); - text->setAlignment(osgText::Text::CENTER_CENTER); - prop = fgGetNode("/sim/startup/splash-title", true); - text->setUpdateCallback(new FGSplashTextUpdateCallback(prop)); - geode->addDrawable(text); - - fgSplashProgress("init"); - - return camera; + std::ostringstream oss; + oss << "Splash" << num << ".png"; + tpath.append(oss.str()); + return tpath.utf8Str(); } -// update callback for the switch node guarding that splash -class FGSplashGroupUpdateCallback : public osg::NodeCallback { -public: - FGSplashGroupUpdateCallback() : - _splashAlphaNode(fgGetNode("/sim/startup/splash-alpha", true)) - { } - virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) - { - assert(dynamic_cast(node)); - osg::Group* group = static_cast(node); - - double alpha = _splashAlphaNode->getDoubleValue(); - if (alpha <= 0 || !fgGetBool("/sim/startup/splash-screen")) - group->removeChild(0, group->getNumChildren()); - else if (group->getNumChildren() == 0) - group->addChild(fgCreateSplashCamera()); - - traverse(node, nv); - } -private: - SGSharedPtr _splashAlphaNode; -}; - -osg::Node* fgCreateSplashNode() { - osg::Group* group = new osg::Group; - group->setName("splashGroup"); - group->setUpdateCallback(new FGSplashGroupUpdateCallback); - return group; -} - -// Initialize the splash screen -void fgSplashInit () +void SplashScreen::doUpdate() { - globals->get_renderer()->splashinit(); + double alpha = _splashAlphaNode->getDoubleValue(); + + if (alpha <= 0 || !fgGetBool("/sim/startup/splash-screen")) { + removeChild(0, getNumChildren()); + _splashFBOCamera = nullptr; + _splashQuadCamera = nullptr; + } else if (getNumChildren() == 0) { + createNodes(); + _splashStartTime.stamp(); + resize(fgGetInt("/sim/startup/xsize"), + fgGetInt("/sim/startup/ysize")); + } else { + (*_splashFSQuadColor)[0] = osg::Vec4(1.0, 1.0, 1.0, _splashAlphaNode->getFloatValue()); + _splashFSQuadColor->dirty(); + + for (const TextItem& item : _items) { + if (item.dynamicContent) { + item.textNode->setText(item.dynamicContent->getStringValue()); + } + } + + updateSplashSpinner(); + } } -void fgSplashProgress( const char *identifier, unsigned int percent ) { - const char* spinChars = "-\\|/"; - static int spin_count = 0; - std::string spin_status = std::string(""); +float scaleAndOffset(float v, float halfWidth) +{ + return halfWidth * ((v * 2.0) - 1.0); +} - if (identifier[0] != 0) - spin_status += spinChars[spin_count++ % 4]; +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; + } - fgSetString("/sim/startup/splash-progress-spinner", spin_status); + 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::resize( int width, int height ) +{ + if (getNumChildren() == 0) { + return; + } + + _width = width; + _height = height; + _splashFBOCamera->setViewport(0, 0, width, height); + _splashFBOCamera->setProjectionMatrixAsOrtho2D(-width * 0.5, width * 0.5, + -height * 0.5, height * 0.5); + + _splashQuadCamera->setViewport(0, 0, width, height); + _splashFBOCamera->resizeAttachments(width, height); + + double halfWidth = width * 0.5; + double halfHeight = height * 0.5; + const double screenAspectRatio = static_cast(width) / height; + + if (_legacySplashScreenMode) { + halfWidth = width * 0.4; + halfHeight = height * 0.4; + + if (screenAspectRatio > _splashImageAspectRatio) { + // 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 > _splashImageAspectRatio) { + // screen is wider than our image + halfHeight = halfWidth / _splashImageAspectRatio; + } else { + // screen is taller than our image + halfWidth = halfHeight * _splashImageAspectRatio; + } + } + + // adjust vertex positions and mark as dirty + osg::Vec3 positions[4] = { + osg::Vec3(-halfWidth, -halfHeight, 0.0), + osg::Vec3(halfWidth, -halfHeight, 0.0), + osg::Vec3(halfWidth, halfHeight, 0.0), + osg::Vec3(-halfWidth, halfHeight, 0.0) + }; + + for (int i=0; i<4; ++i) { + (*_splashImageVertexArray)[i] = positions[i]; + } + + _splashImageVertexArray->dirty(); + + if (_aircraftLogoVertexArray) { + float logoWidth = fgGetDouble("/sim/startup/splash-logo-width", 0.6) * width; + float logoHeight = _logoImage->t() * (logoWidth / _logoImage->s()); + float originX = width * -0.5; + float originY = height * ((1.0 - 0.935) - 0.5); + osg::Vec3 positions[4] = { + osg::Vec3(originX, originY, 0.0), + osg::Vec3(originX + logoWidth, originY, 0.0), + osg::Vec3(originX + logoWidth, originY + logoHeight, 0.0), + osg::Vec3(originX, originY + logoHeight, 0.0) + }; + + for (int i=0; i<4; ++i) { + (*_aircraftLogoVertexArray)[i] = positions[i]; + } + + _aircraftLogoVertexArray->dirty(); + } + + 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) @@ -420,12 +585,11 @@ void fgSplashProgress( const char *identifier, unsigned int percent ) { if( text.empty() ) text = ": " + id; } - + if (!strcmp(identifier,"downloading-scenery")) { std::ostringstream oss; unsigned int kbytesPerSec = fgGetInt("/sim/terrasync/transfer-rate-bytes-sec") / 1024; unsigned int kbytesPending = fgGetInt("/sim/terrasync/pending-kbytes"); - oss << text; if (kbytesPending > 0) { if (kbytesPending > 1024) { int mBytesPending = kbytesPending >> 10; @@ -442,8 +606,7 @@ void fgSplashProgress( const char *identifier, unsigned int percent ) { oss << " - " << kbytesPerSec << " Kb/sec"; } } - fgSetString("/sim/startup/splash-progress-text", oss.str()); - return; + fgSetString("/sim/startup/splash-progress-spinner", oss.str()); } // over-write the spinner @@ -452,10 +615,10 @@ void fgSplashProgress( const char *identifier, unsigned int percent ) { oss << percent << "% complete"; fgSetString("/sim/startup/splash-progress-spinner", oss.str()); } - + 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); } diff --git a/src/Viewer/splash.hxx b/src/Viewer/splash.hxx index cb1c219c5..01ecc4fab 100644 --- a/src/Viewer/splash.hxx +++ b/src/Viewer/splash.hxx @@ -25,22 +25,82 @@ #ifndef _SPLASH_HXX #define _SPLASH_HXX -#include +#include +#include -#ifndef __cplusplus -# error This library requires C++ -#endif +#include +#include -/** Initialize the splash screen */ -void fgSplashInit (); +#include + +namespace osg +{ + class Texture2D; + class Image; + class Camera; +} + +class SplashScreen : public osg::Group +{ +public: + SplashScreen(); + ~SplashScreen(); + + void resize(int width, int height); + +private: + friend class SplashScreenUpdateCallback; + + void createNodes(); + void setupLogoImage(); + + void doUpdate(); + void updateSplashSpinner(); + + std::string selectSplashImage(); + + void 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); + + bool _legacySplashScreenMode = false; + SGPropertyNode_ptr _splashAlphaNode; + osg::ref_ptr _splashFBOCamera; + double _splashImageAspectRatio; // stores width/height of the splash image we loaded + osg::Image* _splashImage = nullptr; + osg::Image* _logoImage = nullptr; + osg::Vec3Array* _splashImageVertexArray = nullptr; + osg::Vec3Array* _splashSpinnerVertexArray = nullptr; + osg::Vec3Array* _aircraftLogoVertexArray = nullptr; + + int _width, _height; + + osg::Texture2D* _splashFBOTexture; + osg::Vec4Array* _splashFSQuadColor; + osg::ref_ptr _splashQuadCamera; + + struct TextItem + { + osg::ref_ptr 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; + + void recomputeSize(int height) const; + void reposition(int width, int height) const; + }; + + std::vector _items; + SGTimeStamp _splashStartTime; +}; /** Set progress information. * "identifier" references an element of the language resource. */ void fgSplashProgress ( const char *identifier, unsigned int percent = 0 ); -/** Retrieve the splash screen node */ -osg::Node* fgCreateSplashNode(); - #endif // _SPLASH_HXX