1 // This file is part of Dust Racing 2D.
2 // Copyright (C) 2014 Jussi Lind <jussi.lind@iki.fi>
3 //
4 // Dust Racing 2D is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 // Dust Racing 2D is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with Dust Racing 2D. If not, see <http://www.gnu.org/licenses/>.
15 
16 #include "scene.hpp"
17 
18 #include "ai.hpp"
19 #include "audioworker.hpp"
20 #include "bridge.hpp"
21 #include "car.hpp"
22 #include "carfactory.hpp"
23 #include "carsoundeffectmanager.hpp"
24 #include "checkeredflag.hpp"
25 #include "fadeanimation.hpp"
26 #include "game.hpp"
27 #include "inputhandler.hpp"
28 #include "intro.hpp"
29 #include "layers.hpp"
30 #include "mainmenu.hpp"
31 #include "messageoverlay.hpp"
32 #include "particlefactory.hpp"
33 #include "pit.hpp"
34 #include "race.hpp"
35 #include "renderer.hpp"
36 #include "settings.hpp"
37 #include "startlights.hpp"
38 #include "startlightsoverlay.hpp"
39 #include "statemachine.hpp"
40 #include "timingoverlay.hpp"
41 #include "track.hpp"
42 #include "trackdata.hpp"
43 #include "trackobject.hpp"
44 #include "tracktile.hpp"
45 
46 #include "../common/config.hpp"
47 #include "../common/targetnodebase.hpp"
48 
49 #include <MenuManager>
50 
51 #include <MCAssetManager>
52 #include <MCCamera>
53 #include <MCFrictionGenerator>
54 #include <MCGLAmbientLight>
55 #include <MCGLDiffuseLight>
56 #include <MCGLScene>
57 #include <MCGLShaderProgram>
58 #include <MCObject>
59 #include <MCObjectFactory>
60 #include <MCPhysicsComponent>
61 #include <MCShape>
62 #include <MCSurface>
63 #include <MCSurfaceView>
64 #include <MCTextureFont>
65 
66 #include <MCWorld>
67 #include <MCWorldRenderer>
68 
69 #include <QApplication>
70 #include <QObject>
71 
72 #include <algorithm>
73 #include <cassert>
74 #include <memory>
75 
76 using std::dynamic_pointer_cast;
77 
78 // Default visible scene size.
79 int Scene::m_width = 1024;
80 int Scene::m_height = 768;
81 
82 static const float METERS_PER_UNIT = 0.05f;
83 
Scene(Game & game,StateMachine & stateMachine,Renderer & renderer,MCWorld & world)84 Scene::Scene(Game & game, StateMachine & stateMachine, Renderer & renderer, MCWorld & world)
85   : m_game(game)
86   , m_stateMachine(stateMachine)
87   , m_renderer(renderer)
88   , m_messageOverlay(new MessageOverlay)
89   , m_race(std::make_shared<Race>(game, carCount()))
90   , m_activeTrack(nullptr)
91   , m_world(world)
92   , m_startlights(new Startlights)
93   , m_startlightsOverlay(new StartlightsOverlay(*m_startlights))
94   , m_checkeredFlag(new CheckeredFlag)
95   , m_intro(new Intro)
96   , m_particleFactory(new ParticleFactory)
97   , m_fadeAnimation(new FadeAnimation)
98 {
99     initializeComponents();
100 
101     connectComponents();
102 
103     createMenus();
104 }
105 
connectComponents()106 void Scene::connectComponents()
107 {
108     connect(m_startlights.get(), &Startlights::raceStarted, m_race.get(), &Race::start);
109     connect(m_startlights.get(), &Startlights::animationEnded, &m_stateMachine, &StateMachine::endStartlightAnimation);
110 
111     connect(&m_stateMachine, &StateMachine::startlightAnimationRequested, m_startlights.get(), &Startlights::beginAnimation);
112     connect(&m_stateMachine, &StateMachine::fadeInRequested, m_fadeAnimation.get(), &FadeAnimation::beginFadeIn);
113     connect(&m_stateMachine, &StateMachine::fadeOutRequested, m_fadeAnimation.get(), &FadeAnimation::beginFadeOut);
114     connect(&m_stateMachine, &StateMachine::fadeOutFlashRequested, m_fadeAnimation.get(), &FadeAnimation::beginFadeOutFlash);
115     connect(&m_stateMachine, &StateMachine::soundsStopped, m_race.get(), &Race::stopEngineSounds);
116 
117     connect(m_fadeAnimation.get(), &FadeAnimation::fadeValueChanged, &m_renderer, &Renderer::setFadeValue);
118     connect(m_fadeAnimation.get(), &FadeAnimation::fadeInFinished, &m_stateMachine, &StateMachine::endFadeIn);
119     connect(m_fadeAnimation.get(), &FadeAnimation::fadeOutFinished, &m_stateMachine, &StateMachine::endFadeOut);
120 
121     connect(m_race.get(), &Race::finished, &m_stateMachine, &StateMachine::finishRace);
122     connect(m_race.get(), &Race::messageRequested, m_messageOverlay.get(), static_cast<void (MessageOverlay::*)(QString)>(&MessageOverlay::addMessage));
123 
124     connect(m_startlights.get(), &Startlights::messageRequested, m_messageOverlay.get(), static_cast<void (MessageOverlay::*)(QString)>(&MessageOverlay::addMessage));
125     connect(this, &Scene::listenerLocationChanged, &m_game.audioWorker(), &AudioWorker::setListenerLocation);
126 
127     m_game.audioWorker().connectAudioSource(*m_race);
128 }
129 
initializeComponents()130 void Scene::initializeComponents()
131 {
132     for (size_t i = 0; i < 2; i++)
133     {
134         m_cameraOffset.at(i) = 0.0f;
135         m_timingOverlay.at(i).setRace(m_race);
136     }
137 
138     m_checkeredFlag->setDimensions(width(), height());
139     m_intro->setDimensions(width(), height());
140     m_startlightsOverlay->setDimensions(width(), height());
141     m_messageOverlay->setDimensions(width(), height());
142 
143     m_world.setMetersPerUnit(METERS_PER_UNIT);
144 
145     MCAssetManager::textureFontManager().font(m_game.fontName()).setShaderProgram(m_renderer.program("text"));
146     MCAssetManager::textureFontManager().font(m_game.fontName()).setShadowShaderProgram(m_renderer.program("textShadow"));
147 
148     const MCGLAmbientLight ambientLight(1.0f, 0.9f, 0.95f, 0.75f);
149     const MCGLDiffuseLight diffuseLight(MCVector3dF(1.0f, -1.0f, -1.0f), 1.0f, 0.9f, 0.5f, 0.75f);
150     const MCGLDiffuseLight specularLight(MCVector3dF(1.0f, -1.0f, -1.0f), 1.0f, 1.0f, 0.8f, 0.9f);
151 
152     auto & glScene = MCWorld::instance().renderer().glScene();
153     glScene.setAmbientLight(ambientLight);
154     glScene.setDiffuseLight(diffuseLight);
155     glScene.setSpecularLight(specularLight);
156 
157     m_renderer.setFadeValue(0.0);
158 }
159 
setupAudio(Car & car,size_t index)160 void Scene::setupAudio(Car & car, size_t index)
161 {
162     CarSoundEffectManager::MultiSoundHandles handles;
163 
164     const auto indexStr = std::to_string(index);
165     const auto engine = "carEngine" + indexStr;
166     handles.engineSoundHandle = engine.c_str();
167 
168     const auto hit = "carHit" + indexStr;
169     handles.hitSoundHandle = hit.c_str();
170 
171     const auto skid = "skid" + indexStr;
172     handles.skidSoundHandle = skid.c_str();
173 
174     const auto sfx = std::make_shared<CarSoundEffectManager>(car, handles);
175     m_game.audioWorker().connectAudioSource(*sfx);
176     car.setSoundEffectManager(sfx);
177 }
178 
createCars()179 void Scene::createCars()
180 {
181     m_race->removeCars();
182     m_cars.clear();
183     m_ai.clear();
184 
185     // Create and add cars.
186     for (size_t i = 0; i < carCount(); i++)
187     {
188         CarPtr car(CarFactory::buildCar(i, carCount(), m_game));
189         if (car)
190         {
191             if (!car->isHuman())
192             {
193                 m_ai.push_back(std::make_shared<AI>(*car, m_race));
194             }
195 
196             car->shape()->view()->setShaderProgram(m_renderer.program("car"));
197             car->shape()->view()->object()->material()->setDiffuseCoeff(3.4f);
198 
199             setupAudio(*car, i);
200 
201             m_cars.push_back(car);
202             m_race->addCar(*car);
203         }
204     }
205 
206     if (m_game.hasTwoHumanPlayers())
207     {
208         m_timingOverlay.at(1).setCarToFollow(*m_cars.at(1));
209         m_crashOverlay.at(1).setCarToFollow(*m_cars.at(1));
210     }
211 
212     m_timingOverlay.at(0).setCarToFollow(*m_cars.at(0));
213     m_crashOverlay.at(0).setCarToFollow(*m_cars.at(0));
214 }
215 
setupMinimaps()216 void Scene::setupMinimaps()
217 {
218     const auto minimapSize = static_cast<int>(m_width * 0.2f);
219     const auto minimapY = !m_game.hasTwoHumanPlayers() ? minimapSize : minimapSize / 2 + 10;
220 
221     for (size_t i = 0; i < std::min(static_cast<size_t>(2), m_cars.size()); i++)
222     {
223         m_minimap.at(i).initialize(*m_cars.at(i), m_activeTrack->trackData().map(), minimapSize / 2 + 10, minimapY, static_cast<size_t>(minimapSize));
224     }
225 }
226 
width()227 int Scene::width()
228 {
229     return Scene::m_width;
230 }
231 
height()232 int Scene::height()
233 {
234     return Scene::m_height;
235 }
236 
setSize(int width,int height)237 void Scene::setSize(int width, int height)
238 {
239     Scene::m_width = width;
240     Scene::m_height = height;
241 }
242 
carCount()243 size_t Scene::carCount()
244 {
245     return 12;
246 }
247 
createMenus()248 void Scene::createMenus()
249 {
250     m_menuManager.reset(new MTFH::MenuManager);
251 
252     m_mainMenu = std::make_shared<MainMenu>(*m_menuManager, *this, width(), height());
253     connect(
254       std::static_pointer_cast<MainMenu>(m_mainMenu).get(), &MainMenu::exitGameRequested, &m_game, &Game::exitGame);
255 
256     m_menuManager->addMenu(m_mainMenu);
257     m_menuManager->enterMenu(m_mainMenu);
258 }
259 
updateFrame(InputHandler & handler,int step)260 void Scene::updateFrame(InputHandler & handler, int step)
261 {
262     if (m_stateMachine.state() == StateMachine::State::GameTransitionIn || m_stateMachine.state() == StateMachine::State::GameTransitionOut || m_stateMachine.state() == StateMachine::State::DoStartlights || m_stateMachine.state() == StateMachine::State::Play)
263     {
264         if (m_activeTrack)
265         {
266             if (m_race->started())
267             {
268                 processUserInput(handler);
269                 updateAi();
270             }
271 
272             updateWorld(step);
273             updateRace();
274 
275             if (m_game.hasTwoHumanPlayers())
276             {
277                 for (size_t i = 0; i < 2; i++)
278                 {
279                     updateCameraLocation(m_camera.at(i), m_cameraOffset.at(i), *m_cars.at(i));
280                 }
281             }
282             else
283             {
284                 updateCameraLocation(m_camera.at(0), m_cameraOffset.at(0), *m_cars.at(0));
285             }
286         }
287     }
288     else if (m_stateMachine.state() == StateMachine::State::Menu)
289     {
290         m_menuManager->stepTime(step);
291     }
292 
293     if (m_fadeAnimation->isFading())
294     {
295         auto & glScene = MCWorld::instance().renderer().glScene();
296         glScene.setFadeValue(m_renderer.fadeValue());
297     }
298 }
299 
updateOverlays()300 void Scene::updateOverlays()
301 {
302     if (m_game.hasTwoHumanPlayers())
303     {
304         m_timingOverlay.at(1).update();
305         m_crashOverlay.at(1).update();
306     }
307 
308     m_timingOverlay.at(0).update();
309     m_crashOverlay.at(0).update();
310 
311     m_messageOverlay->update();
312 }
313 
updateWorld(int timeStep)314 void Scene::updateWorld(int timeStep)
315 {
316     // Step time
317     m_world.stepTime(timeStep);
318 }
319 
updateRace()320 void Scene::updateRace()
321 {
322     // Update race situation
323     m_race->update();
324 
325     emit listenerLocationChanged(m_cars.at(0)->location().i(), m_cars.at(0)->location().j());
326 }
327 
updateCameraLocation(MCCamera & camera,float & offset,MCObject & object)328 void Scene::updateCameraLocation(MCCamera & camera, float & offset, MCObject & object)
329 {
330     // Update camera location with respect to the car speed.
331     // Make changes a bit smoother so that an abrupt decrease
332     // in the speed won't look bad.
333     offset += (object.physicsComponent().velocity().lengthFast() - offset) * 0.2f;
334     const float offsetAmplification = m_game.hasTwoHumanPlayers() ? 9.6f : 13.8f;
335     const auto loc = MCVector2dF(object.location()) + object.direction() * offset * offsetAmplification;
336     camera.setPos(loc.i(), loc.j());
337 }
338 
processUserInput(InputHandler & handler)339 void Scene::processUserInput(InputHandler & handler)
340 {
341     for (size_t i = 0; i < (m_game.hasTwoHumanPlayers() ? 2 : 1); i++)
342     {
343         // Handle accelerating / braking
344         if (handler.getActionState(i, InputHandler::Action::Down))
345         {
346             if (!m_race->timing().raceCompleted(i))
347             {
348                 m_cars.at(i)->setBrakeEnabled(true);
349             }
350         }
351         else
352         {
353             m_cars.at(i)->setBrakeEnabled(false);
354         }
355 
356         if (handler.getActionState(i, InputHandler::Action::Up))
357         {
358             if (!m_race->timing().raceCompleted(i))
359             {
360                 m_cars.at(i)->setAcceleratorEnabled(true);
361             }
362         }
363         else
364         {
365             m_cars.at(i)->setAcceleratorEnabled(false);
366         }
367 
368         // Handle turning
369         if (handler.getActionState(i, InputHandler::Action::Left))
370         {
371             m_cars.at(i)->steer(Car::Steer::Left);
372         }
373         else if (handler.getActionState(i, InputHandler::Action::Right))
374         {
375             m_cars.at(i)->steer(Car::Steer::Right);
376         }
377         else
378         {
379             m_cars.at(i)->steer(Car::Steer::Neutral);
380         }
381     }
382 }
383 
updateAi()384 void Scene::updateAi()
385 {
386     for (auto && ai : m_ai)
387     {
388         const bool isRaceCompleted = m_race->timing().raceCompleted(ai->car().index());
389         ai->update(isRaceCompleted);
390     }
391 }
392 
setupCameras(Track & activeTrack)393 void Scene::setupCameras(Track & activeTrack)
394 {
395     m_world.renderer().removeParticleVisibilityCameras();
396     if (m_game.hasTwoHumanPlayers())
397     {
398         for (size_t i = 0; i < 2; i++)
399         {
400             if (m_game.splitType() == Game::SplitType::Vertical)
401             {
402                 m_camera.at(i).init(
403                   Scene::width() / 2, Scene::height(), 0, 0, activeTrack.width(), activeTrack.height());
404                 m_world.renderer().addParticleVisibilityCamera(m_camera.at(i));
405             }
406             else
407             {
408                 m_camera.at(i).init(
409                   Scene::width(), Scene::height() / 2, 0, 0, activeTrack.width(), activeTrack.height());
410                 m_world.renderer().addParticleVisibilityCamera(m_camera.at(i));
411             }
412         }
413     }
414     else
415     {
416         m_camera.at(0).init(
417           Scene::width(), Scene::height(), 0, 0, activeTrack.width(), activeTrack.height());
418         m_world.renderer().addParticleVisibilityCamera(m_camera.at(0));
419     }
420 }
421 
setupAI(std::shared_ptr<Track> activeTrack)422 void Scene::setupAI(std::shared_ptr<Track> activeTrack)
423 {
424     for (auto && ai : m_ai)
425     {
426         ai->setTrack(activeTrack);
427     }
428 }
429 
setActiveTrack(std::shared_ptr<Track> activeTrack)430 void Scene::setActiveTrack(std::shared_ptr<Track> activeTrack)
431 {
432     m_activeTrack = activeTrack;
433 
434     // Remove previous objects
435     m_world.clear();
436 
437     setupCameras(*activeTrack);
438 
439     setWorldDimensions();
440 
441     createCars();
442 
443     resizeOverlays();
444 
445     addCarsToWorld();
446 
447     addTrackObjectsToWorld();
448 
449     initializeRace();
450 
451     setupAI(activeTrack);
452 
453     setupMinimaps();
454 }
455 
setWorldDimensions()456 void Scene::setWorldDimensions()
457 {
458     assert(m_activeTrack);
459 
460     // Update world dimensions according to the
461     // active track.
462     const size_t minX = 0;
463     const size_t maxX = m_activeTrack->width();
464     const size_t minY = 0;
465     const size_t maxY = m_activeTrack->height();
466     const size_t minZ = 0;
467     const size_t maxZ = 1000;
468 
469     m_world.setDimensions(minX, maxX, minY, maxY, minZ, maxZ, METERS_PER_UNIT);
470 }
471 
addCarsToWorld()472 void Scene::addCarsToWorld()
473 {
474     // Add objects to the world
475     for (auto && car : m_cars)
476     {
477         car->addToWorld();
478     }
479 }
480 
addTrackObjectsToWorld()481 void Scene::addTrackObjectsToWorld()
482 {
483     createNormalObjects();
484 
485     createBridgeObjects();
486 }
487 
createNormalObjects()488 void Scene::createNormalObjects()
489 {
490     assert(m_activeTrack);
491 
492     for (size_t i = 0; i < m_activeTrack->trackData().objects().count(); i++)
493     {
494         const auto trackObject = dynamic_pointer_cast<TrackObject>(m_activeTrack->trackData().objects().object(i));
495         assert(trackObject);
496 
497         MCObject & object = trackObject->object();
498         object.addToWorld();
499 
500         // Set the base Z of mesh objects at ground level instead of at the object center
501         float baseZ = 0;
502         if (object.shape() && object.shape()->view() && object.shape()->view()->object())
503         {
504             if (dynamic_cast<MCMesh *>(object.shape()->view()->object()))
505             {
506                 baseZ = -object.shape()->view()->object()->minZ();
507             }
508         }
509 
510         object.translate(object.initialLocation() + MCVector3dF(0, 0, baseZ));
511         object.rotate(object.initialAngle());
512 
513         if (auto pit = dynamic_cast<Pit *>(&object))
514         {
515             pit->reset();
516             connect(pit, &Pit::pitStop, m_race.get(), &Race::pitStop);
517         }
518     }
519 }
520 
createBridgeObjects()521 void Scene::createBridgeObjects()
522 {
523     assert(m_activeTrack);
524 
525     const auto & map = m_activeTrack->trackData().map();
526     for (size_t j = 0; j <= map.rows(); j++)
527     {
528         for (size_t i = 0; i <= map.cols(); i++)
529         {
530             auto tile = dynamic_pointer_cast<TrackTile>(map.getTile(i, j));
531             if (tile && tile->tileTypeEnum() == TrackTile::TileType::Bridge)
532             {
533                 const auto bridge = std::make_shared<Bridge>();
534                 bridge->translate(MCVector3dF(i * TrackTile::width() + TrackTile::width() / 2, j * TrackTile::height() + TrackTile::height() / 2, 0));
535                 bridge->rotate(tile->rotation());
536                 bridge->addToWorld();
537                 m_bridges.push_back(bridge);
538             }
539         }
540     }
541 
542     Bridge::reset();
543 }
544 
resizeOverlays()545 void Scene::resizeOverlays()
546 {
547     if (m_game.hasTwoHumanPlayers())
548     {
549         if (m_game.splitType() == Game::SplitType::Vertical)
550         {
551             for (size_t i = 0; i < 2; i++)
552             {
553                 m_timingOverlay.at(i).setDimensions(width() / 2, height());
554                 m_crashOverlay.at(i).setDimensions(width() / 2, height());
555             }
556         }
557         else
558         {
559             for (size_t i = 0; i < 2; i++)
560             {
561                 m_timingOverlay.at(i).setDimensions(width(), height() / 2);
562                 m_crashOverlay.at(i).setDimensions(width(), height() / 2);
563             }
564         }
565     }
566     else
567     {
568         m_timingOverlay.at(0).setDimensions(width(), height());
569         m_crashOverlay.at(0).setDimensions(width(), height());
570     }
571 }
572 
initializeRace()573 void Scene::initializeRace()
574 {
575     assert(m_activeTrack);
576     m_race->init(m_activeTrack, static_cast<size_t>(m_game.lapCount()));
577 }
578 
activeTrack() const579 std::shared_ptr<Track> Scene::activeTrack() const
580 {
581     return m_activeTrack;
582 }
583 
trackSelectionMenu() const584 MTFH::MenuPtr Scene::trackSelectionMenu() const
585 {
586     return m_menuManager->getMenuById("trackSelection");
587 }
588 
getSplitPositions(MCGLScene::SplitType & p0,MCGLScene::SplitType & p1)589 void Scene::getSplitPositions(MCGLScene::SplitType & p0, MCGLScene::SplitType & p1)
590 {
591     if (m_game.splitType() == Game::SplitType::Vertical)
592     {
593         p1 = MCGLScene::ShowOnLeft;
594         p0 = MCGLScene::ShowOnRight;
595     }
596     else
597     {
598         p1 = MCGLScene::ShowOnTop;
599         p0 = MCGLScene::ShowOnBottom;
600     }
601 }
602 
renderTrack()603 void Scene::renderTrack()
604 {
605     switch (m_stateMachine.state())
606     {
607     case StateMachine::State::GameTransitionIn:
608     case StateMachine::State::GameTransitionOut:
609     case StateMachine::State::DoStartlights:
610     case StateMachine::State::Play:
611     {
612         if (m_game.hasTwoHumanPlayers())
613         {
614             MCGLScene::SplitType p1, p0;
615             getSplitPositions(p1, p0);
616 
617             auto & glScene = MCWorld::instance().renderer().glScene();
618 
619             glScene.setSplitType(p1);
620             m_activeTrack->render(m_camera.at(1));
621 
622             glScene.setSplitType(p0);
623             m_activeTrack->render(m_camera.at(0));
624 
625             glScene.setSplitType(MCGLScene::ShowFullScreen);
626         }
627         else
628         {
629             m_activeTrack->render(m_camera.at(0));
630         }
631 
632         break;
633     }
634     default:
635         break;
636     }
637 }
638 
renderMenu()639 void Scene::renderMenu()
640 {
641     switch (m_stateMachine.state())
642     {
643     case StateMachine::State::DoIntro:
644 
645         MCWorld::instance().renderer().glScene().setSplitType(MCGLScene::ShowFullScreen);
646 
647         m_intro->render();
648 
649         break;
650 
651     case StateMachine::State::Menu:
652     case StateMachine::State::MenuTransitionOut:
653     case StateMachine::State::MenuTransitionIn:
654 
655         MCWorld::instance().renderer().glScene().setSplitType(MCGLScene::ShowFullScreen);
656 
657         m_menuManager->render();
658 
659         break;
660 
661     default:
662         break;
663     }
664 }
665 
renderCommonHUD()666 void Scene::renderCommonHUD()
667 {
668     switch (m_stateMachine.state())
669     {
670     case StateMachine::State::GameTransitionIn:
671     case StateMachine::State::GameTransitionOut:
672     case StateMachine::State::DoStartlights:
673     case StateMachine::State::Play:
674     {
675         // Setup for common scene
676         MCWorld::instance().renderer().glScene().setSplitType(MCGLScene::ShowFullScreen);
677 
678         if (m_race->checkeredFlagEnabled() && !m_game.hasTwoHumanPlayers())
679         {
680             m_checkeredFlag->render();
681         }
682 
683         m_startlightsOverlay->render();
684         m_messageOverlay->render();
685         break;
686     }
687     default:
688         break;
689     }
690 }
691 
renderHUD()692 void Scene::renderHUD()
693 {
694     switch (m_stateMachine.state())
695     {
696     case StateMachine::State::GameTransitionIn:
697     case StateMachine::State::GameTransitionOut:
698     case StateMachine::State::DoStartlights:
699     case StateMachine::State::Play:
700     {
701         if (m_game.hasTwoHumanPlayers())
702         {
703             MCGLScene::SplitType p1, p0;
704             getSplitPositions(p1, p0);
705 
706             auto & glScene = MCWorld::instance().renderer().glScene();
707 
708             glScene.setSplitType(p1);
709             m_timingOverlay.at(1).render();
710             m_minimap.at(1).render(m_cars, *m_race);
711             m_crashOverlay.at(1).render();
712 
713             glScene.setSplitType(p0);
714             m_timingOverlay.at(0).render();
715             m_minimap.at(0).render(m_cars, *m_race);
716             m_crashOverlay.at(0).render();
717 
718             glScene.setSplitType(MCGLScene::ShowFullScreen);
719         }
720         else
721         {
722             m_timingOverlay.at(0).render();
723             m_minimap.at(0).render(m_cars, *m_race);
724             m_crashOverlay.at(0).render();
725         }
726 
727         break;
728     }
729     default:
730         break;
731     }
732 }
733 
renderWorld(MCRenderGroup renderGroup,bool prepareRendering)734 void Scene::renderWorld(MCRenderGroup renderGroup, bool prepareRendering)
735 {
736     switch (m_stateMachine.state())
737     {
738     case StateMachine::State::GameTransitionIn:
739     case StateMachine::State::GameTransitionOut:
740     case StateMachine::State::DoStartlights:
741     case StateMachine::State::Play:
742     {
743         if (m_game.hasTwoHumanPlayers())
744         {
745             MCGLScene::SplitType p1, p0;
746             getSplitPositions(p1, p0);
747 
748             if (prepareRendering)
749             {
750                 m_world.prepareRendering(&m_camera.at(1));
751                 m_world.prepareRendering(&m_camera.at(0));
752             }
753 
754             auto & glScene = MCWorld::instance().renderer().glScene();
755 
756             glScene.setSplitType(p1);
757             m_world.render(&m_camera.at(1), renderGroup);
758 
759             glScene.setSplitType(p0);
760             m_world.render(&m_camera.at(0), renderGroup);
761 
762             glScene.setSplitType(MCGLScene::ShowFullScreen);
763         }
764         else
765         {
766             if (prepareRendering)
767             {
768                 m_world.prepareRendering(&m_camera.at(0));
769             }
770 
771             m_world.render(&m_camera.at(0), renderGroup);
772         }
773 
774         break;
775     }
776     default:
777         break;
778     }
779 }
780 
781 Scene::~Scene() = default;
782