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