1 // splash.cxx -- draws the initial splash screen
2 //
3 // Written by Curtis Olson, started July 1998.  (With a little looking
4 // at Freidemann's panel code.) :-)
5 //
6 // Copyright (C) 1997  Michele F. America  - nomimarketing@mail.telepac.pt
7 //
8 // This program is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU General Public License as
10 // published by the Free Software Foundation; either version 2 of the
11 // License, or (at your option) any later version.
12 //
13 // This program is distributed in the hope that it will be useful, but
14 // WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 // General Public License for more details.
17 //
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software
20 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21 //
22 // $Id$
23 
24 #include <config.h>
25 
26 #include <osg/BlendFunc>
27 #include <osg/Camera>
28 #include <osg/Depth>
29 #include <osg/Geometry>
30 #include <osg/Node>
31 #include <osg/NodeCallback>
32 #include <osg/NodeVisitor>
33 #include <osg/StateSet>
34 #include <osg/Switch>
35 #include <osg/Texture2D>
36 #include <osg/TextureRectangle>
37 #include <osg/Version>
38 
39 #include <osgText/Text>
40 #include <osgText/String>
41 #include <osgDB/ReadFile>
42 
43 #include <simgear/compiler.h>
44 
45 #include <simgear/debug/logstream.hxx>
46 #include <simgear/math/sg_random.h>
47 #include <simgear/misc/sg_path.hxx>
48 #include <simgear/misc/sg_dir.hxx>
49 #include <simgear/scene/util/SGReaderWriterOptions.hxx>
50 
51 #include <Main/globals.hxx>
52 #include <Main/fg_props.hxx>
53 #include <Main/fg_os.hxx>
54 #include <Main/locale.hxx>
55 #include <Main/util.hxx>
56 #include "splash.hxx"
57 #include "renderer.hxx"
58 
59 #include <sstream>
60 
61 
62 static const char* LICENSE_URL_TEXT = "Licensed under the GNU GPL. See http://www.flightgear.org for more information";
63 
64 using namespace simgear;
65 
66 class SplashScreenUpdateCallback : public osg::NodeCallback {
67 public:
operator ()(osg::Node * node,osg::NodeVisitor * nv)68     virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
69     {
70         SplashScreen* screen = static_cast<SplashScreen*>(node);
71         screen->doUpdate();
72         traverse(node, nv);
73     }
74 };
75 
SplashScreen()76 SplashScreen::SplashScreen() :
77     _splashAlphaNode(fgGetNode("/sim/startup/splash-alpha", true))
78 {
79     setName("splashGroup");
80     setUpdateCallback(new SplashScreenUpdateCallback);
81 }
82 
~SplashScreen()83 SplashScreen::~SplashScreen()
84 {
85 }
86 
createNodes()87 void SplashScreen::createNodes()
88 {
89     std::string splashImage = selectSplashImage();
90 #if OSG_VERSION_LESS_THAN(3,4,0)
91     _splashImage = osgDB::readImageFile(splashImage);
92 #else
93     osg::ref_ptr<SGReaderWriterOptions> staticOptions = SGReaderWriterOptions::copyOrCreate(osgDB::Registry::instance()->getOptions());
94     staticOptions->setLoadOriginHint(SGReaderWriterOptions::LoadOriginHint::ORIGIN_SPLASH_SCREEN);
95     _splashImage = osgDB::readRefImageFile(splashImage, staticOptions);
96 #endif
97 
98     if (!_splashImage){
99         SG_LOG(SG_VIEW, SG_INFO, "Splash Image " << splashImage << " failed to load");
100         return;
101     }
102     int width               = _splashImage->s();
103     int height = _splashImage->t();
104     _splashImageAspectRatio = static_cast<double>(width) / height;
105 
106     osg::TextureRectangle* splashTexture = new osg::TextureRectangle(_splashImage);
107     splashTexture->setTextureSize(width, height);
108     splashTexture->setInternalFormat(GL_RGB);
109     splashTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
110     splashTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
111     splashTexture->setImage(_splashImage);
112 
113 
114     _splashFBOTexture = new osg::Texture2D;
115     _splashFBOTexture->setInternalFormat(GL_RGB);
116     _splashFBOTexture->setResizeNonPowerOfTwoHint(false);
117     _splashFBOTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
118     _splashFBOTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
119 
120     _splashFBOCamera = createFBOCamera();
121     addChild(_splashFBOCamera);
122 
123     osg::Geometry* geometry = new osg::Geometry;
124     geometry->setSupportsDisplayList(false);
125 
126     _splashImageVertexArray = new osg::Vec3Array;
127     for (int i=0; i < 4; ++i) {
128         _splashImageVertexArray->push_back(osg::Vec3(0.0, 0.0, 0.0));
129     }
130     geometry->setVertexArray(_splashImageVertexArray);
131 
132     osg::Vec2Array* imageTCs = new osg::Vec2Array;
133     imageTCs->push_back(osg::Vec2(0, 0));
134     imageTCs->push_back(osg::Vec2(width, 0));
135     imageTCs->push_back(osg::Vec2(width, height));
136     imageTCs->push_back(osg::Vec2(0, height));
137     geometry->setTexCoordArray(0, imageTCs);
138 
139     osg::Vec4Array* colorArray = new osg::Vec4Array;
140     colorArray->push_back(osg::Vec4(1, 1, 1, 1));
141     geometry->setColorArray(colorArray);
142     geometry->setColorBinding(osg::Geometry::BIND_OVERALL);
143     geometry->addPrimitiveSet(new osg::DrawArrays(GL_POLYGON, 0, 4));
144 
145     osg::StateSet* stateSet = geometry->getOrCreateStateSet();
146     stateSet->setTextureMode(0, GL_TEXTURE_RECTANGLE, osg::StateAttribute::ON);
147     stateSet->setTextureAttribute(0, splashTexture);
148 
149     osg::Geode* geode = new osg::Geode;
150     _splashFBOCamera->addChild(geode);
151     geode->addDrawable(geometry);
152 
153     if (_legacySplashScreenMode) {
154         addText(geode, osg::Vec2(0.025f, 0.025f), 0.03,
155                 std::string("FlightGear ") + fgGetString("/sim/version/flightgear") +
156                 std::string(" ") + std::string(LICENSE_URL_TEXT),
157                 osgText::Text::LEFT_TOP,
158                 nullptr,
159                 0.9);
160     } else {
161         setupLogoImage();
162 
163         // order here is important so we can re-write first item with the
164         // startup tip.
165         addText(geode, osg::Vec2(0.025f, 0.15f), 0.03, LICENSE_URL_TEXT,
166                 osgText::Text::LEFT_TOP,
167                 nullptr,
168                 0.6);
169 
170         addText(geode, osg::Vec2(0.025f, 0.025f), 0.10, std::string("FlightGear ") + fgGetString("/sim/version/flightgear"), osgText::Text::LEFT_TOP);
171 
172         if (!_aircraftLogoVertexArray) {
173             addText(geode, osg::Vec2(0.025f, 0.935f), 0.10,
174                     fgGetString("/sim/description"),
175                     osgText::Text::LEFT_BOTTOM,
176                     nullptr,
177                     0.6);
178             _items.back().maxLineCount = 1;
179         }
180 
181         const auto authors = flightgear::getAircraftAuthorsText();
182         addText(geode, osg::Vec2(0.025f, 0.940f), 0.03,
183                 authors,
184                 osgText::Text::LEFT_TOP,
185                 nullptr,
186                 0.6);
187         _items.back().maxLineCount = 3;
188         _items.back().maxHeightFraction = 0.055;
189     }
190 
191     addText(geode, osg::Vec2(0.975f, 0.935f), 0.03,
192             "loading status",
193             osgText::Text::RIGHT_BOTTOM,
194             fgGetNode("/sim/startup/splash-progress-text", true),
195             0.4);
196 
197     addText(geode, osg::Vec2(0.975f, 0.975f), 0.03,
198             "spinner",
199             osgText::Text::RIGHT_BOTTOM,
200             fgGetNode("/sim/startup/splash-progress-spinner", true));
201 
202     if (!strcmp(FG_BUILD_TYPE, "Nightly")) {
203         std::string unstableWarningText = globals->get_locale()->getLocalizedString("unstable-warning", "sys", "unstable!");
204         addText(geode, osg::Vec2(0.5, 0.5), 0.03,
205               unstableWarningText,
206               osgText::Text::CENTER_CENTER,
207               nullptr, -1.0, osg::Vec4(1.0, 0.0, 0.0, 1.0));
208     }
209 
210 #if defined(ENABLE_COMPOSITOR)
211     auto compositorText = globals->get_locale()->getLocalizedString("compositor-enabled", "sys", "Compositor");
212     addText(geode, osg::Vec2(0.5f, 0.55f), 0.03,
213             compositorText,
214             osgText::Text::CENTER_CENTER,
215             nullptr, -1.0, osg::Vec4(1.0, 0.0, 0.0, 1.0));
216 #endif
217 
218 
219     ///////////
220 
221     geometry = new osg::Geometry;
222     geometry->setSupportsDisplayList(false);
223 
224     _splashSpinnerVertexArray = new osg::Vec3Array;
225     for (int i=0; i < 8; ++i) {
226         _splashSpinnerVertexArray->push_back(osg::Vec3(0.0f, 0.0f, -0.1f));
227     }
228     geometry->setVertexArray(_splashSpinnerVertexArray);
229 
230     // QColor buttonColor(27, 122, 211);
231     colorArray = new osg::Vec4Array;
232     colorArray->push_back(osg::Vec4(27 / 255.0f, 122 / 255.0f, 211 / 255.0f, 0.75f));
233     geometry->setColorArray(colorArray);
234     geometry->setColorBinding(osg::Geometry::BIND_OVERALL);
235     geometry->addPrimitiveSet(new osg::DrawArrays(GL_QUADS, 0, 8));
236 
237     geode->addDrawable(geometry);
238 
239     //// Full screen quad setup ////////////////////
240 
241     _splashQuadCamera = new osg::Camera;
242     _splashQuadCamera->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
243     _splashQuadCamera->setViewMatrix(osg::Matrix::identity());
244     _splashQuadCamera->setProjectionMatrixAsOrtho2D(0.0, 1.0, 0.0, 1.0);
245     _splashQuadCamera->setAllowEventFocus(false);
246     _splashQuadCamera->setCullingActive(false);
247     _splashQuadCamera->setRenderOrder(osg::Camera::NESTED_RENDER);
248 
249     stateSet = _splashQuadCamera->getOrCreateStateSet();
250     stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
251     stateSet->setAttribute(new osg::BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA), osg::StateAttribute::ON);
252     stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
253     stateSet->setRenderBinDetails(1000, "RenderBin");
254     stateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
255 
256     geometry = osg::createTexturedQuadGeometry(osg::Vec3(0.0, 0.0, 0.0),
257                                                osg::Vec3(1.0, 0.0, 0.0),
258                                                osg::Vec3(0.0, 1.0, 0.0));
259     geometry->setSupportsDisplayList(false);
260 
261     _splashFSQuadColor = new osg::Vec4Array;
262     _splashFSQuadColor->push_back(osg::Vec4(1, 1.0f, 1, 1));
263     geometry->setColorArray(_splashFSQuadColor);
264     geometry->setColorBinding(osg::Geometry::BIND_OVERALL);
265 
266     stateSet = geometry->getOrCreateStateSet();
267     stateSet->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON);
268     stateSet->setTextureAttribute(0, _splashFBOTexture);
269 
270     geode = new osg::Geode;
271     geode->addDrawable(geometry);
272 
273     _splashQuadCamera->addChild(geode);
274     addChild(_splashQuadCamera);
275 }
276 
createFBOCamera()277 osg::ref_ptr<osg::Camera> SplashScreen::createFBOCamera()
278 {
279     osg::ref_ptr<osg::Camera> c = new osg::Camera;
280     c->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
281     c->setViewMatrix(osg::Matrix::identity());
282     c->setClearMask( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
283     c->setClearColor( osg::Vec4( 0., 0., 0., 0. ) );
284     c->setAllowEventFocus(false);
285     c->setCullingActive(false);
286     c->setRenderTargetImplementation( osg::Camera::FRAME_BUFFER_OBJECT );
287     c->setRenderOrder(osg::Camera::PRE_RENDER);
288     c->attach(osg::Camera::COLOR_BUFFER, _splashFBOTexture);
289 
290     osg::StateSet* stateSet = c->getOrCreateStateSet();
291     stateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::OFF);
292     stateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF);
293     stateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
294     stateSet->setAttribute(new osg::Depth(osg::Depth::ALWAYS, 0, 1, false));
295     stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
296 
297     return c;
298 }
299 
setupLogoImage()300 void SplashScreen::setupLogoImage()
301 {
302     // check for a logo image, add underneath other text
303     SGPath logoPath = globals->resolve_maybe_aircraft_path(fgGetString("/sim/startup/splash-logo-image"));
304     if (!logoPath.exists() || !logoPath.isFile()) {
305         return;
306     }
307 
308     osg::ref_ptr<simgear::SGReaderWriterOptions> staticOptions = simgear::SGReaderWriterOptions::copyOrCreate(osgDB::Registry::instance()->getOptions());
309     staticOptions->setLoadOriginHint(simgear::SGReaderWriterOptions::LoadOriginHint::ORIGIN_SPLASH_SCREEN);
310 
311 #if OSG_VERSION_LESS_THAN(3, 4, 0)
312     _logoImage = osgDB::readImageFile(logoPath.utf8Str(), staticOptions);
313 #else
314     _logoImage = osgDB::readRefImageFile(logoPath.utf8Str(), staticOptions);
315 #endif
316     if (!_logoImage) {
317         SG_LOG(SG_VIEW, SG_INFO, "Splash logo image " << logoPath << " failed to load");
318         return;
319     }
320 
321     osg::Texture2D* logoTexture = new osg::Texture2D(_logoImage);
322     logoTexture->setResizeNonPowerOfTwoHint(false);
323     logoTexture->setInternalFormat(GL_RGBA);
324     logoTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
325     logoTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
326 
327     osg::Geometry* geometry = new osg::Geometry;
328     geometry->setSupportsDisplayList(false);
329 
330     _aircraftLogoVertexArray = new osg::Vec3Array;
331     for (int i=0; i < 4; ++i) {
332         _aircraftLogoVertexArray->push_back(osg::Vec3(0.0, 0.0, 0.0));
333     }
334     geometry->setVertexArray(_aircraftLogoVertexArray);
335 
336     osg::Vec2Array* logoTCs = new osg::Vec2Array;
337     logoTCs->push_back(osg::Vec2(0, 0));
338     logoTCs->push_back(osg::Vec2(1.0, 0));
339     logoTCs->push_back(osg::Vec2(1.0, 1.0));
340     logoTCs->push_back(osg::Vec2(0, 1.0));
341     geometry->setTexCoordArray(0, logoTCs);
342 
343     osg::Vec4Array* colorArray = new osg::Vec4Array;
344     colorArray->push_back(osg::Vec4(1, 1, 1, 1));
345     geometry->setColorArray(colorArray);
346     geometry->setColorBinding(osg::Geometry::BIND_OVERALL);
347     geometry->addPrimitiveSet(new osg::DrawArrays(GL_POLYGON, 0, 4));
348 
349     osg::StateSet* stateSet = geometry->getOrCreateStateSet();
350     stateSet->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON);
351     stateSet->setTextureAttribute(0, logoTexture);
352     stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
353 
354     osg::Geode* geode = new osg::Geode;
355     _splashFBOCamera->addChild(geode);
356     geode->addDrawable(geometry);
357 
358 }
359 
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)360 void SplashScreen::addText(osg::Geode* geode ,
361                            const osg::Vec2& pos, double size, const std::string& text,
362                            const osgText::Text::AlignmentType alignment,
363                            SGPropertyNode* dynamicValue,
364                            double maxWidthFraction,
365                            const osg::Vec4& textColor)
366 {
367     SGPath path = globals->resolve_resource_path("Fonts/LiberationFonts/LiberationSans-BoldItalic.ttf");
368 
369     TextItem item;
370     osg::ref_ptr<osgText::Text> t = new osgText::Text;
371     item.textNode = t;
372     t->setFont(path.utf8Str());
373     t->setColor(textColor);
374     t->setFontResolution(64, 64);
375     t->setText(text, osgText::String::Encoding::ENCODING_UTF8);
376     t->setBackdropType(osgText::Text::OUTLINE);
377     t->setBackdropColor(osg::Vec4(0.2, 0.2, 0.2, 1));
378     t->setBackdropOffset(0.04);
379 
380     item.fractionalCharSize = size;
381     item.fractionalPosition = pos;
382     item.dynamicContent = dynamicValue;
383     item.textNode->setAlignment(alignment);
384     item.maxWidthFraction = maxWidthFraction;
385     geode->addDrawable(item.textNode);
386 
387     _items.push_back(item);
388 }
389 
reposition(int width,int height) const390 void SplashScreen::TextItem::reposition(int width, int height) const
391 {
392     const int halfWidth = width >> 1;
393     const int halfHeight = height >> 1;
394     osg::Vec3 pixelPos(fractionalPosition.x() * width - halfWidth,
395                        (1.0 - fractionalPosition.y()) * height - halfHeight,
396                        -0.1);
397     textNode->setPosition(pixelPos);
398     textNode->setCharacterSize(fractionalCharSize * height);
399 
400     if (maxWidthFraction > 0.0) {
401         textNode->setMaximumWidth(maxWidthFraction * width);
402     }
403 
404     recomputeSize(height);
405 }
406 
recomputeSize(int height) const407 void SplashScreen::TextItem::recomputeSize(int height) const
408 {
409     if ((maxLineCount == 0) && (maxHeightFraction < 0.0)) {
410         return;
411     }
412 
413     double heightFraction = maxHeightFraction;
414     if (heightFraction < 0.0) {
415         heightFraction = 9999.0;
416     }
417 
418     double baseSize = fractionalCharSize;
419     textNode->update();
420     while ((textNode->getLineCount() > maxLineCount) ||
421            (baseSize * textNode->getLineCount() > heightFraction)) {
422         baseSize *= 0.8; // 20% shrink each time
423         textNode->setCharacterSize(baseSize * height);
424         textNode->update();
425     }
426 }
427 
selectSplashImage()428 std::string SplashScreen::selectSplashImage()
429 {
430     sg_srandom_time(); // init random seed
431 
432     simgear::PropertyList previewNodes = fgGetNode("/sim/previews", true)->getChildren("preview");
433     std::vector<SGPath> paths;
434 
435     for (auto n : previewNodes) {
436         if (!n->getBoolValue("splash", false)) {
437             continue;
438         }
439 
440         SGPath tpath = globals->resolve_maybe_aircraft_path(n->getStringValue("path"));
441         if (tpath.exists()) {
442             paths.push_back(tpath);
443         }
444     }
445 
446     if (paths.empty()) {
447         // look for a legacy aircraft splash
448         simgear::PropertyList nodes = fgGetNode("/sim/startup", true)->getChildren("splash-texture");
449         for (auto n : nodes) {
450             SGPath tpath = globals->resolve_maybe_aircraft_path(n->getStringValue());
451             if (tpath.exists()) {
452                 paths.push_back(tpath);
453                 _legacySplashScreenMode = true;
454             }
455         }
456     }
457 
458     if (!paths.empty()) {
459         // Select a random useable texture
460         const int index = (int)(sg_random() * paths.size());
461         return paths.at(index).utf8Str();
462     }
463 
464     // no splash screen specified - use one of the default ones
465     SGPath tpath = globals->get_fg_root() / "Textures";
466     paths = simgear::Dir(tpath).children(simgear::Dir::TYPE_FILE);
467     paths.erase(std::remove_if(paths.begin(), paths.end(), [](const SGPath& p) {
468         const auto f = p.file();
469         if (f.find("Splash") != 0) return true;
470         const auto ext = p.extension();
471         return ext != "png" && ext != "jpg";
472     }), paths.end());
473 
474     if (!paths.empty()) {
475         // Select a random useable texture
476         const int index = (int)(sg_random() * paths.size());
477         return paths.at(index).utf8Str();
478     }
479 
480     SG_LOG(SG_GUI, SG_ALERT, "Couldn't find any splash screens at all");
481     return {};
482 }
483 
doUpdate()484 void SplashScreen::doUpdate()
485 {
486     double alpha = _splashAlphaNode->getDoubleValue();
487 
488     if (alpha <= 0 || !fgGetBool("/sim/startup/splash-screen")) {
489         removeChild(0, getNumChildren());
490         _splashFBOCamera = nullptr;
491         _splashQuadCamera = nullptr;
492     } else if (getNumChildren() == 0) {
493         createNodes();
494         _splashStartTime.stamp();
495         resize(fgGetInt("/sim/startup/xsize"),
496                fgGetInt("/sim/startup/ysize"));
497     } else {
498         (*_splashFSQuadColor)[0] = osg::Vec4(1.0, 1.0, 1.0, _splashAlphaNode->getFloatValue());
499         _splashFSQuadColor->dirty();
500 
501         for (const TextItem& item : _items) {
502             if (item.dynamicContent) {
503                 item.textNode->setText(
504                   item.dynamicContent->getStringValue(),
505                   osgText::String::Encoding::ENCODING_UTF8);
506             }
507         }
508 
509         updateSplashSpinner();
510         updateText();
511     }
512 }
513 
scaleAndOffset(float v,float halfWidth)514 float scaleAndOffset(float v, float halfWidth)
515 {
516     return halfWidth * ((v * 2.0) - 1.0);
517 }
518 
updateSplashSpinner()519 void SplashScreen::updateSplashSpinner()
520 {
521     const int elapsedMsec = _splashStartTime.elapsedMSec();
522     float splashSpinnerPos = (elapsedMsec % 2000) / 2000.0f;
523     float endPos = splashSpinnerPos + 0.25f;
524     float wrapStartPos = 0.0f;
525     float wrapEndPos = 0.0f; // no wrapped quad
526     if (endPos > 1.0f) {
527         wrapEndPos = endPos - 1.0f;
528     }
529 
530     const float halfWidth = _width * 0.5f;
531     const float halfHeight = _height * 0.5f;
532     const float bottomY = -halfHeight;
533     const float topY = bottomY + 8;
534     const float z = -0.05f;
535 
536     splashSpinnerPos = scaleAndOffset(splashSpinnerPos, halfWidth);
537     endPos = scaleAndOffset(endPos, halfWidth);
538     wrapStartPos = scaleAndOffset(wrapStartPos, halfWidth);
539     wrapEndPos = scaleAndOffset(wrapEndPos, halfWidth);
540 
541     osg::Vec3 positions[8] = {
542         osg::Vec3(splashSpinnerPos, bottomY, z),
543         osg::Vec3(endPos, bottomY, z),
544         osg::Vec3(endPos,topY, z),
545         osg::Vec3(splashSpinnerPos, topY, z),
546         osg::Vec3(wrapStartPos, bottomY, z),
547         osg::Vec3(wrapEndPos, bottomY, z),
548         osg::Vec3(wrapEndPos,topY, z),
549         osg::Vec3(wrapStartPos, topY, z)
550 
551     };
552 
553     for (int i=0; i<8; ++i) {
554         (*_splashSpinnerVertexArray)[i] = positions[i];
555     }
556 
557     _splashSpinnerVertexArray->dirty();
558 }
559 
updateText()560 void SplashScreen::updateText()
561 {
562     if (!_haveSetStartupTip && (_splashStartTime.elapsedMSec() > 5000)) {
563         // switch to show tooltip
564         _haveSetStartupTip = true;
565         FGLocale* locale = globals->get_locale();
566         const int tipCount = locale->getLocalizedStringCount("tip", "tips");
567         int tipIndex = globals->get_props()->getIntValue("/sim/session",0) % tipCount;
568 
569         std::string tipText = locale->getLocalizedStringWithIndex("tip", "tips", tipIndex);
570 
571         // find the item to switch
572         _items.front().textNode->setText(
573           tipText, osgText::String::Encoding::ENCODING_UTF8);
574     }
575 }
576 
577 // remove once we require OSG 3.4
manuallyResizeFBO(int width,int height)578 void SplashScreen::manuallyResizeFBO(int width, int height)
579 {
580     _splashFBOTexture->setTextureSize(width, height);
581     _splashFBOTexture->dirtyTextureObject();
582 
583     osg::ref_ptr<osg::Camera> newCam = createFBOCamera();
584 
585     // swap everything around
586     for (unsigned int i=0; i < _splashFBOCamera->getNumChildren(); ++i) {
587         newCam->addChild(_splashFBOCamera->getChild(i));
588     }
589 
590     addChild(newCam);
591     removeChild(_splashFBOCamera);
592     _splashFBOCamera = newCam;
593 }
594 
resize(int width,int height)595 void SplashScreen::resize( int width, int height )
596 {
597     if (getNumChildren() == 0) {
598         return;
599     }
600 
601     _width = width;
602     _height = height;
603 
604     _splashQuadCamera->setViewport(0, 0, width, height);
605 #if OSG_VERSION_LESS_THAN(3,4,0)
606     manuallyResizeFBO(width, height);
607 #else
608     _splashFBOCamera->resizeAttachments(width, height);
609 #endif
610     _splashFBOCamera->setViewport(0, 0, width, height);
611     _splashFBOCamera->setProjectionMatrixAsOrtho2D(-width * 0.5, width * 0.5,
612                                                    -height * 0.5, height * 0.5);
613 
614     double halfWidth = width * 0.5;
615     double halfHeight = height * 0.5;
616     const double screenAspectRatio = static_cast<double>(width) / height;
617 
618     if (_legacySplashScreenMode) {
619         halfWidth = width * 0.4;
620         halfHeight = height * 0.4;
621 
622         if (screenAspectRatio > _splashImageAspectRatio) {
623             // screen is wider than our image
624             halfWidth = halfHeight;
625         } else {
626             // screen is taller than our image
627             halfHeight = halfWidth;
628         }
629     } else {
630         // adjust vertex positions; image covers entire area
631         if (screenAspectRatio > _splashImageAspectRatio) {
632             // screen is wider than our image
633             halfHeight = halfWidth / _splashImageAspectRatio;
634         } else {
635             // screen is taller than our image
636             halfWidth = halfHeight * _splashImageAspectRatio;
637         }
638     }
639 
640     // adjust vertex positions and mark as dirty
641     osg::Vec3 positions[4] = {
642         osg::Vec3(-halfWidth, -halfHeight, 0.0),
643         osg::Vec3(halfWidth, -halfHeight, 0.0),
644         osg::Vec3(halfWidth, halfHeight, 0.0),
645         osg::Vec3(-halfWidth, halfHeight, 0.0)
646     };
647 
648     for (int i=0; i<4; ++i) {
649         (*_splashImageVertexArray)[i] = positions[i];
650     }
651 
652     _splashImageVertexArray->dirty();
653 
654     if (_aircraftLogoVertexArray) {
655         float logoWidth = fgGetDouble("/sim/startup/splash-logo-width", 0.6) * width;
656         float logoHeight = _logoImage->t() * (logoWidth / _logoImage->s());
657 
658         float logoX = fgGetDouble("/sim/startup/splash-logo-x-norm", 0.0) * (width - logoWidth);
659         float logoY = fgGetDouble("/sim/startup/splash-logo-y-norm", 1.0 - 0.935) * (height - logoHeight);
660 
661         float originX = logoX - (width * 0.5);
662         float originY = logoY - (height * 0.5);
663 
664         osg::Vec3 positions[4] = {
665             osg::Vec3(originX, originY, 0.0),
666             osg::Vec3(originX + logoWidth, originY, 0.0),
667             osg::Vec3(originX + logoWidth, originY + logoHeight, 0.0),
668             osg::Vec3(originX, originY + logoHeight, 0.0)
669         };
670 
671         for (int i=0; i<4; ++i) {
672             (*_aircraftLogoVertexArray)[i] = positions[i];
673         }
674 
675         _aircraftLogoVertexArray->dirty();
676     }
677 
678     for (const TextItem& item : _items) {
679         item.reposition(width, height);
680     }
681 }
682 
fgSplashProgress(const char * identifier,unsigned int percent)683 void fgSplashProgress( const char *identifier, unsigned int percent )
684 {
685   fgSetString("/sim/startup/splash-progress-spinner", "");
686 
687   std::string text;
688   if (identifier[0] != 0)
689   {
690     text = globals->get_locale()->getLocalizedString(identifier, "sys");
691 
692     if( text.empty() )
693       text = std::string("<incomplete language resource>: ") + identifier;
694   }
695 
696     if (!strcmp(identifier,"downloading-scenery")) {
697         std::ostringstream oss;
698         unsigned int kbytesPerSec = fgGetInt("/sim/terrasync/transfer-rate-bytes-sec") / 1024;
699         unsigned int kbytesPending = fgGetInt("/sim/terrasync/pending-kbytes");
700         unsigned int kbytesPendingExtract = fgGetInt("/sim/terrasync/extract-pending-kbytes");
701         if (kbytesPending > 0) {
702             if (kbytesPending > 1024) {
703                 int mBytesPending = kbytesPending >> 10;
704                 oss << " " << mBytesPending << "MB";
705             } else {
706                 oss << " " << kbytesPending << "KB";
707             }
708         }
709 
710         if (kbytesPerSec > 100) {
711             double mbytesPerSec = kbytesPerSec / 1024.0;
712             oss << " - " << std::fixed << std::setprecision(1) << mbytesPerSec << "MB/sec";
713         } else if (kbytesPerSec > 0) {
714             oss << " - " << kbytesPerSec << " KB/sec";
715         } else if (kbytesPendingExtract > 0) {
716             const string extractText = globals->get_locale()->getLocalizedString("scenery-extract", "sys");
717             std::ostringstream os2;
718 
719             if (kbytesPendingExtract > 1024) {
720                 int mBytesPendingExtract = kbytesPendingExtract >> 10;
721                 os2 << mBytesPendingExtract << "MB";
722             } else {
723                 os2 << kbytesPendingExtract << "KB";
724             }
725             auto finalText = simgear::strutils::replace(extractText, "[VALUE]", os2.str());
726             oss << " - " << finalText;
727         }
728         fgSetString("/sim/startup/splash-progress-spinner", oss.str());
729     }
730 
731     if (!strcmp(identifier, "loading-scenery")) {
732         unsigned int kbytesPendingExtract = fgGetInt("/sim/terrasync/extract-pending-kbytes");
733         if (kbytesPendingExtract > 0) {
734             const string extractText = globals->get_locale()->getLocalizedString("scenery-extract", "sys");
735             std::ostringstream oss;
736             if (kbytesPendingExtract > 1024) {
737                 int mBytesPendingExtract = kbytesPendingExtract >> 10;
738                 oss << mBytesPendingExtract << "MB";
739             } else {
740                 oss << kbytesPendingExtract << "KB";
741             }
742 
743             auto finalText = simgear::strutils::replace(extractText, "[VALUE]", oss.str());
744             fgSetString("/sim/startup/splash-progress-spinner", finalText);
745         } else {
746             fgSetString("/sim/startup/splash-progress-spinner", "");
747         }
748     }
749 
750     // over-write the spinner
751     if (!strncmp(identifier, "navdata-", 8)) {
752         const string percentText = globals->get_locale()->getLocalizedString("navdata-load-percent", "sys");
753         auto finalText = simgear::strutils::replace(percentText, "[VALUE]", to_string(percent));
754         fgSetString("/sim/startup/splash-progress-spinner", finalText);
755     }
756 
757     if( fgGetString("/sim/startup/splash-progress-text") == text )
758       return;
759 
760     SG_LOG( SG_VIEW, SG_INFO, "Splash screen progress " << identifier );
761     fgSetString("/sim/startup/splash-progress-text", text);
762 }
763