1 //
2 //  SuperTuxKart - a fun racing game with go-kart
3 //  Copyright (C) 2004-2015 Steve Baker <sjbaker1@airmail.net>
4 //  Copyright (C) 2006-2015 Joerg Henrichs, SuperTuxKart-Team, Steve Baker
5 //
6 //  This program is free software; you can redistribute it and/or
7 //  modify it under the terms of the GNU General Public License
8 //  as published by the Free Software Foundation; either version 3
9 //  of the License, or (at your option) any later version.
10 //
11 //  This program is distributed in the hope that it will be useful,
12 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //  GNU General Public License for more details.
15 //
16 //  You should have received a copy of the GNU General Public License
17 //  along with this program; if not, write to the Free Software
18 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19 
20 #include "states_screens/race_gui.hpp"
21 
22 using namespace irr;
23 
24 #include <algorithm>
25 #include <limits>
26 
27 #include "challenges/story_mode_timer.hpp"
28 #include "challenges/unlock_manager.hpp"
29 #include "config/user_config.hpp"
30 #include "font/font_drawer.hpp"
31 #include "graphics/camera.hpp"
32 #include "graphics/2dutils.hpp"
33 #ifndef SERVER_ONLY
34 #include "graphics/glwrap.hpp"
35 #endif
36 #include "graphics/irr_driver.hpp"
37 #include "graphics/material.hpp"
38 #include "graphics/material_manager.hpp"
39 #include "guiengine/engine.hpp"
40 #include "guiengine/modaldialog.hpp"
41 #include "guiengine/scalable_font.hpp"
42 #include "io/file_manager.hpp"
43 #include "items/powerup_manager.hpp"
44 #include "items/projectile_manager.hpp"
45 #include "karts/abstract_kart.hpp"
46 #include "karts/controller/controller.hpp"
47 #include "karts/controller/spare_tire_ai.hpp"
48 #include "karts/kart_properties.hpp"
49 #include "karts/kart_properties_manager.hpp"
50 #include "modes/capture_the_flag.hpp"
51 #include "modes/follow_the_leader.hpp"
52 #include "modes/linear_world.hpp"
53 #include "modes/world.hpp"
54 #include "modes/soccer_world.hpp"
55 #include "network/protocols/client_lobby.hpp"
56 #include "race/race_manager.hpp"
57 #include "states_screens/race_gui_multitouch.hpp"
58 #include "tracks/track.hpp"
59 #include "tracks/track_object_manager.hpp"
60 #include "utils/constants.hpp"
61 #include "utils/string_utils.hpp"
62 #include "utils/translation.hpp"
63 
64 /** The constructor is called before anything is attached to the scene node.
65  *  So rendering to a texture can be done here. But world is not yet fully
66  *  created, so only the race manager can be accessed safely.
67  */
RaceGUI()68 RaceGUI::RaceGUI()
69 {
70     m_enabled = true;
71 
72     if (UserConfigParams::m_artist_debug_mode && UserConfigParams::m_hide_gui)
73         m_enabled = false;
74 
75     initSize();
76     bool multitouch_enabled = (UserConfigParams::m_multitouch_active == 1 &&
77                                irr_driver->getDevice()->supportsTouchDevice()) ||
78                                UserConfigParams::m_multitouch_active > 1;
79 
80     if (multitouch_enabled && UserConfigParams::m_multitouch_draw_gui &&
81         RaceManager::get()->getNumLocalPlayers() == 1)
82     {
83         m_multitouch_gui = new RaceGUIMultitouch(this);
84     }
85 
86     calculateMinimapSize();
87 
88     m_is_tutorial = (RaceManager::get()->getTrackName() == "tutorial");
89 
90     // Load speedmeter texture before rendering the first frame
91     m_speed_meter_icon = material_manager->getMaterial("speedback.png");
92     m_speed_meter_icon->getTexture(false,false);
93     m_speed_bar_icon   = material_manager->getMaterial("speedfore.png");
94     m_speed_bar_icon->getTexture(false,false);
95     //createMarkerTexture();
96 
97     // Load icon textures for later reuse
98     m_red_team = irr_driver->getTexture(FileManager::GUI_ICON, "soccer_ball_red.png");
99     m_blue_team = irr_driver->getTexture(FileManager::GUI_ICON, "soccer_ball_blue.png");
100     m_red_flag = irr_driver->getTexture(FileManager::GUI_ICON, "red_flag.png");
101     m_blue_flag = irr_driver->getTexture(FileManager::GUI_ICON, "blue_flag.png");
102     m_soccer_ball = irr_driver->getTexture(FileManager::GUI_ICON, "soccer_ball_normal.png");
103     m_heart_icon = irr_driver->getTexture(FileManager::GUI_ICON, "heart.png");
104     m_basket_ball_icon = irr_driver->getTexture(FileManager::GUI_ICON, "rubber_ball-icon.png");
105     m_champion = irr_driver->getTexture(FileManager::GUI_ICON, "cup_gold.png");
106 }   // RaceGUI
107 
108 // ----------------------------------------------------------------------------
109 /** Called when loading the race gui or screen resized. */
initSize()110 void RaceGUI::initSize()
111 {
112     RaceGUIBase::initSize();
113     // Determine maximum length of the rank/lap text, in order to
114     // align those texts properly on the right side of the viewport.
115     gui::ScalableFont* font = GUIEngine::getHighresDigitFont();
116     core::dimension2du area = font->getDimension(L"99:99.999");
117     m_timer_width = area.Width;
118     m_font_height = area.Height;
119 
120     area = font->getDimension(L"99.999");
121     m_small_precise_timer_width = area.Width;
122 
123     area = font->getDimension(L"99:99.999");
124     m_big_precise_timer_width = area.Width;
125 
126     area = font->getDimension(L"-");
127     m_negative_timer_additional_width = area.Width;
128 
129     if (RaceManager::get()->getMinorMode()==RaceManager::MINOR_MODE_FOLLOW_LEADER ||
130         RaceManager::get()->isBattleMode()     ||
131         RaceManager::get()->getNumLaps() > 9)
132         m_lap_width = font->getDimension(L"99/99").Width;
133     else
134         m_lap_width = font->getDimension(L"9/9").Width;
135 }   // initSize
136 
137 //-----------------------------------------------------------------------------
~RaceGUI()138 RaceGUI::~RaceGUI()
139 {
140     delete m_multitouch_gui;
141 }   // ~Racegui
142 
143 
144 //-----------------------------------------------------------------------------
init()145 void RaceGUI::init()
146 {
147     RaceGUIBase::init();
148     // Technically we only need getNumLocalPlayers, but using the
149     // global kart id to find the data for a specific kart.
150     int n = RaceManager::get()->getNumberOfKarts();
151 
152     m_animation_states.resize(n);
153     m_rank_animation_duration.resize(n);
154     m_last_ranks.resize(n);
155 }   // init
156 
157 //-----------------------------------------------------------------------------
158 /** Reset the gui before a race. It initialised all rank animation related
159  *  values back to the default.
160  */
reset()161 void RaceGUI::reset()
162 {
163     RaceGUIBase::reset();
164     for(unsigned int i=0; i<RaceManager::get()->getNumberOfKarts(); i++)
165     {
166         m_animation_states[i] = AS_NONE;
167         m_last_ranks[i]       = i+1;
168     }
169 }  // reset
170 
171 //-----------------------------------------------------------------------------
calculateMinimapSize()172 void RaceGUI::calculateMinimapSize()
173 {
174     float map_size_splitscreen = 1.0f;
175 
176     // If there are four players or more in splitscreen
177     // and the map is in a player view, scale down the map
178     if (RaceManager::get()->getNumLocalPlayers() >= 4 && !RaceManager::get()->getIfEmptyScreenSpaceExists())
179     {
180         // If the resolution is wider than 4:3, we don't have to scaledown the minimap as much
181         // Uses some margin, in case the game's screen is not exactly 4:3
182         if ( ((float) irr_driver->getFrameSize().Width / (float) irr_driver->getFrameSize().Height) >
183              (4.1f/3.0f))
184         {
185             if (RaceManager::get()->getNumLocalPlayers() == 4)
186                 map_size_splitscreen = 0.75f;
187             else
188                 map_size_splitscreen = 0.5f;
189         }
190         else
191             map_size_splitscreen = 0.5f;
192     }
193 
194     // Originally m_map_height was 100, and we take 480 as minimum res
195     float scaling = std::min(irr_driver->getFrameSize().Height,
196                              irr_driver->getFrameSize().Width) / 480.0f;
197     const float map_size = stk_config->m_minimap_size * map_size_splitscreen;
198     const float top_margin = 3.5f * m_font_height;
199 
200     // Check if we have enough space for minimap when touch steering is enabled
201     if (m_multitouch_gui != NULL  && !m_multitouch_gui->isSpectatorMode())
202     {
203         const float map_bottom = (float)(irr_driver->getActualScreenSize().Height -
204                                          m_multitouch_gui->getHeight());
205 
206         if ((map_size + 20.0f) * scaling > map_bottom - top_margin)
207         {
208             scaling = (map_bottom - top_margin) / (map_size + 20.0f);
209         }
210 
211         // Use some reasonable minimum scale, because minimap size can be
212         // changed during the race
213         scaling = std::max(scaling,
214                            irr_driver->getActualScreenSize().Height * 0.15f /
215                            (map_size + 20.0f));
216     }
217 
218     // Marker texture has to be power-of-two for (old) OpenGL compliance
219     //m_marker_rendered_size  =  2 << ((int) ceil(1.0 + log(32.0 * scaling)));
220     m_minimap_ai_size       = (int)( stk_config->m_minimap_ai_icon     * scaling);
221     m_minimap_player_size   = (int)( stk_config->m_minimap_player_icon * scaling);
222     m_map_width             = (int)(map_size * scaling);
223     m_map_height            = (int)(map_size * scaling);
224 
225     if ((UserConfigParams::m_minimap_display == 1 && /*map on the right side*/
226        RaceManager::get()->getNumLocalPlayers() == 1) || m_multitouch_gui)
227     {
228         m_map_left          = (int)(irr_driver->getActualScreenSize().Width -
229                                                         m_map_width - 10.0f*scaling);
230         m_map_bottom        = (int)(3*irr_driver->getActualScreenSize().Height/4 -
231                                                         m_map_height);
232     }
233     else if ((UserConfigParams::m_minimap_display == 3 && /*map on the center of the screen*/
234        RaceManager::get()->getNumLocalPlayers() == 1) || m_multitouch_gui)
235     {
236         m_map_left          = (int)(irr_driver->getActualScreenSize().Width / 2);
237         if (m_map_left + m_map_width > (int)irr_driver->getActualScreenSize().Width)
238           m_map_left        = (int)(irr_driver->getActualScreenSize().Width - m_map_width);
239         m_map_bottom        = (int)( 10.0f * scaling);
240     }
241     else // default, map in the bottom-left corner
242     {
243         m_map_left          = (int)( 10.0f * scaling);
244         m_map_bottom        = (int)( 10.0f * scaling);
245     }
246 
247     // Minimap is also rendered bigger via OpenGL, so find power-of-two again
248     const int map_texture   = 2 << ((int) ceil(1.0 + log(128.0 * scaling)));
249     m_map_rendered_width    = map_texture;
250     m_map_rendered_height   = map_texture;
251 
252 
253     // special case : when 3 players play, use available 4th space for such things
254     if (RaceManager::get()->getIfEmptyScreenSpaceExists())
255     {
256         m_map_left = irr_driver->getActualScreenSize().Width -
257                      m_map_width - (int)( 10.0f * scaling);
258         m_map_bottom        = (int)( 10.0f * scaling);
259     }
260     else if (m_multitouch_gui != NULL  && !m_multitouch_gui->isSpectatorMode())
261     {
262         m_map_left = (int)((irr_driver->getActualScreenSize().Width -
263                                                         m_map_width) * 0.95f);
264         m_map_bottom = (int)(irr_driver->getActualScreenSize().Height -
265                                                     top_margin - m_map_height);
266     }
267 }  // calculateMinimapSize
268 
269 //-----------------------------------------------------------------------------
270 /** Render all global parts of the race gui, i.e. things that are only
271  *  displayed once even in splitscreen.
272  *  \param dt Timestep sized.
273  */
renderGlobal(float dt)274 void RaceGUI::renderGlobal(float dt)
275 {
276 #ifndef SERVER_ONLY
277     RaceGUIBase::renderGlobal(dt);
278     cleanupMessages(dt);
279 
280     // Special case : when 3 players play, use 4th window to display such
281     // stuff (but we must clear it)
282     if (RaceManager::get()->getIfEmptyScreenSpaceExists() &&
283         !GUIEngine::ModalDialog::isADialogActive())
284     {
285         static video::SColor black = video::SColor(255,0,0,0);
286 
287         GL32_draw2DRectangle(black, irr_driver->getSplitscreenWindow(
288             RaceManager::get()->getNumLocalPlayers()));
289     }
290 
291     World *world = World::getWorld();
292     assert(world != NULL);
293     if(world->getPhase() >= WorldStatus::WAIT_FOR_SERVER_PHASE &&
294        world->getPhase() <= WorldStatus::GO_PHASE      )
295     {
296         drawGlobalReadySetGo();
297     }
298     else if (world->isGoalPhase())
299         drawGlobalGoal();
300 
301     if (!m_enabled) return;
302 
303     // Display the story mode timer if not in speedrun mode
304     // If in speedrun mode, it is taken care of in GUI engine
305     // as it must be displayed in all the game's screens
306     if (UserConfigParams::m_display_story_mode_timer &&
307         !UserConfigParams::m_speedrun_mode &&
308         RaceManager::get()->raceWasStartedFromOverworld())
309         irr_driver->displayStoryModeTimer();
310 
311     // MiniMap is drawn when the players wait for the start countdown to end
312     drawGlobalMiniMap();
313 
314     // Timer etc. are not displayed unless the game is actually started.
315     if(!world->isRacePhase()) return;
316 
317     //drawGlobalTimer checks if it should display in the current phase/mode
318     FontDrawer::startBatching();
319     drawGlobalTimer();
320 
321     if (!m_is_tutorial)
322     {
323         if (RaceManager::get()->isLinearRaceMode() &&
324             RaceManager::get()->hasGhostKarts() &&
325             RaceManager::get()->getNumberOfKarts() >= 2 )
326             drawLiveDifference();
327 
328         if(world->getPhase() == WorldStatus::GO_PHASE ||
329            world->getPhase() == WorldStatus::MUSIC_PHASE)
330         {
331             drawGlobalMusicDescription();
332         }
333     }
334 
335     if (!m_is_tutorial)
336     {
337         if (m_multitouch_gui != NULL)
338         {
339             drawGlobalPlayerIcons(m_multitouch_gui->getHeight());
340         }
341         else if (UserConfigParams::m_minimap_display == 0 || /*map in the bottom-left*/
342                 (UserConfigParams::m_minimap_display == 1 &&
343                 RaceManager::get()->getNumLocalPlayers() >= 2))
344         {
345             drawGlobalPlayerIcons(m_map_height + m_map_bottom);
346         }
347         else // map hidden or on the right side
348         {
349             drawGlobalPlayerIcons(0);
350         }
351     }
352     FontDrawer::endBatching();
353 #endif
354 }   // renderGlobal
355 
356 //-----------------------------------------------------------------------------
357 /** Render the details for a single player, i.e. speed, energy,
358  *  collectibles, ...
359  *  \param kart Pointer to the kart for which to render the view.
360  */
renderPlayerView(const Camera * camera,float dt)361 void RaceGUI::renderPlayerView(const Camera *camera, float dt)
362 {
363 #ifndef SERVER_ONLY
364     if (!m_enabled) return;
365 
366     RaceGUIBase::renderPlayerView(camera, dt);
367 
368     const core::recti &viewport = camera->getViewport();
369 
370     core::vector2df scaling = camera->getScaling();
371     const AbstractKart *kart = camera->getKart();
372     if(!kart) return;
373 
374     bool isSpectatorCam = Camera::getActiveCamera()->isSpectatorMode();
375 
376     if (!isSpectatorCam) drawPlungerInFace(camera, dt);
377 
378     if (viewport.getWidth() != (int)irr_driver->getActualScreenSize().Width)
379     {
380         scaling *= float(viewport.getWidth()) / float(irr_driver->getActualScreenSize().Width); // scale race GUI along screen size
381     }
382     else
383     {
384         scaling *= float(viewport.getWidth()) / 800.0f; // scale race GUI along screen size
385     }
386 
387     drawAllMessages(kart, viewport, scaling);
388 
389     if(!World::getWorld()->isRacePhase()) return;
390 
391     FontDrawer::startBatching();
392     if (!isSpectatorCam)
393     {
394         if (m_multitouch_gui == NULL || m_multitouch_gui->isSpectatorMode())
395         {
396             drawPowerupIcons(kart, viewport, scaling);
397             drawSpeedEnergyRank(kart, viewport, scaling, dt);
398         }
399     }
400 
401     if (!m_is_tutorial)
402         drawLap(kart, viewport, scaling);
403     FontDrawer::endBatching();
404 #endif
405 }   // renderPlayerView
406 
407 //-----------------------------------------------------------------------------
408 /** Displays the racing time on the screen.
409  */
drawGlobalTimer()410 void RaceGUI::drawGlobalTimer()
411 {
412     assert(World::getWorld() != NULL);
413 
414     if (!World::getWorld()->shouldDrawTimer())
415     {
416         return;
417     }
418 
419     core::stringw sw;
420     video::SColor time_color = video::SColor(255, 255, 255, 255);
421     int dist_from_right = 10 + m_timer_width;
422 
423     bool use_digit_font = true;
424 
425     float elapsed_time = World::getWorld()->getTime();
426     if (!RaceManager::get()->hasTimeTarget() ||
427         RaceManager::get()->getMinorMode() ==RaceManager::MINOR_MODE_SOCCER ||
428         RaceManager::get()->getMinorMode() == RaceManager::MINOR_MODE_FREE_FOR_ALL ||
429         RaceManager::get()->getMinorMode() == RaceManager::MINOR_MODE_CAPTURE_THE_FLAG)
430     {
431         sw = core::stringw (
432             StringUtils::timeToString(elapsed_time).c_str() );
433     }
434     else
435     {
436         float time_target = RaceManager::get()->getTimeTarget();
437         if (elapsed_time < time_target)
438         {
439             sw = core::stringw (
440               StringUtils::timeToString(time_target - elapsed_time).c_str());
441         }
442         else
443         {
444             sw = _("Challenge Failed");
445             int string_width =
446                 GUIEngine::getFont()->getDimension(sw.c_str()).Width;
447             dist_from_right = 10 + string_width;
448             time_color = video::SColor(255,255,0,0);
449             use_digit_font = false;
450         }
451     }
452 
453     core::rect<s32> pos(irr_driver->getActualScreenSize().Width - dist_from_right,
454                         irr_driver->getActualScreenSize().Height*2/100,
455                         irr_driver->getActualScreenSize().Width,
456                         irr_driver->getActualScreenSize().Height*6/100);
457 
458     // special case : when 3 players play, use available 4th space for such things
459     if (RaceManager::get()->getIfEmptyScreenSpaceExists())
460     {
461         pos -= core::vector2d<s32>(0, pos.LowerRightCorner.Y / 2);
462         pos += core::vector2d<s32>(0, irr_driver->getActualScreenSize().Height - irr_driver->getSplitscreenWindow(0).getHeight());
463     }
464 
465     gui::ScalableFont* font = (use_digit_font ? GUIEngine::getHighresDigitFont() : GUIEngine::getFont());
466     font->setScale(1.0f);
467     font->setBlackBorder(true);
468     font->draw(sw, pos, time_color, false, false, NULL,
469                true /* ignore RTL */);
470     font->setBlackBorder(false);
471 
472 }   // drawGlobalTimer
473 
474 
475 //-----------------------------------------------------------------------------
476 /** Displays the live difference with a ghost on screen.
477  */
drawLiveDifference()478 void RaceGUI::drawLiveDifference()
479 {
480     assert(World::getWorld() != NULL);
481 
482     if (!World::getWorld()->shouldDrawTimer())
483     {
484         return;
485     }
486 
487     const LinearWorld *linearworld = dynamic_cast<LinearWorld*>(World::getWorld());
488     assert(linearworld != NULL);
489 
490     // Don't display the live difference timer if its time is wrong
491     // (before crossing the start line at start or after crossing it at end)
492     if (!linearworld->hasValidTimeDifference())
493         return;
494 
495     float live_difference = linearworld->getLiveTimeDifference();
496 
497     int timer_width = m_small_precise_timer_width;
498 
499     // 59.9995 is the smallest number of seconds that could get rounded to 1 minute
500     // when rounding at the closest ms
501     if (fabsf(live_difference) >= 59.9995f)
502         timer_width = m_big_precise_timer_width;
503 
504     if (live_difference < 0.0f)
505         timer_width += m_negative_timer_additional_width;
506 
507     core::stringw sw;
508     video::SColor time_color;
509 
510     // Change color depending on value
511     if (live_difference > 1.0f)
512         time_color = video::SColor(255, 255, 0, 0);
513     else if (live_difference > 0.0f)
514         time_color = video::SColor(255, 255, 160, 0);
515     else if (live_difference > -1.0f)
516         time_color = video::SColor(255, 160, 255, 0);
517     else
518         time_color = video::SColor(255, 0, 255, 0);
519 
520     int dist_from_right = 10 + timer_width;
521 
522     sw = core::stringw (StringUtils::timeToString(live_difference,3,
523                         /* display_minutes_if_zero */ false).c_str() );
524 
525     core::rect<s32> pos(irr_driver->getActualScreenSize().Width - dist_from_right,
526                         irr_driver->getActualScreenSize().Height*7/100,
527                         irr_driver->getActualScreenSize().Width,
528                         irr_driver->getActualScreenSize().Height*11/100);
529 
530     gui::ScalableFont* font = GUIEngine::getHighresDigitFont();
531     font->setScale(1.0f);
532     font->setBlackBorder(true);
533     font->draw(sw.c_str(), pos, time_color, false, false, NULL,
534                true /* ignore RTL */);
535     font->setBlackBorder(false);
536 }   // drawLiveDifference
537 
538 //-----------------------------------------------------------------------------
539 /** Draws the mini map and the position of all karts on it.
540  */
drawGlobalMiniMap()541 void RaceGUI::drawGlobalMiniMap()
542 {
543 #ifndef SERVER_ONLY
544     //TODO : exception for some game modes ? Another option "Hidden in race, shown in battle ?"
545     if (UserConfigParams::m_minimap_display == 2) /*map hidden*/
546         return;
547 
548     if (m_multitouch_gui != NULL && !m_multitouch_gui->isSpectatorMode())
549     {
550         float max_scale = 1.3f;
551 
552         if (UserConfigParams::m_multitouch_scale > max_scale)
553             return;
554     }
555 
556     // draw a map when arena has a navigation mesh.
557     Track *track = Track::getCurrentTrack();
558     if ( (track->isArena() || track->isSoccer()) && !(track->hasNavMesh()) )
559         return;
560 
561     int upper_y = irr_driver->getActualScreenSize().Height - m_map_bottom - m_map_height;
562     int lower_y = irr_driver->getActualScreenSize().Height - m_map_bottom;
563 
564     core::rect<s32> dest(m_map_left, upper_y,
565                          m_map_left + m_map_width, lower_y);
566 
567     track->drawMiniMap(dest);
568 
569     World* world = World::getWorld();
570     CaptureTheFlag *ctf_world = dynamic_cast<CaptureTheFlag*>(World::getWorld());
571     SoccerWorld *soccer_world = dynamic_cast<SoccerWorld*>(World::getWorld());
572 
573     if (ctf_world)
574     {
575         Vec3 draw_at;
576         if (!ctf_world->isRedFlagInBase())
577         {
578             track->mapPoint2MiniMap(Track::getCurrentTrack()->getRedFlag().getOrigin(),
579                 &draw_at);
580             core::rect<s32> rs(core::position2di(0, 0), m_red_flag->getSize());
581             core::rect<s32> rp(m_map_left+(int)(draw_at.getX()-(m_minimap_player_size/1.4f)),
582                 lower_y   -(int)(draw_at.getY()+(m_minimap_player_size/2.2f)),
583                 m_map_left+(int)(draw_at.getX()+(m_minimap_player_size/1.4f)),
584                 lower_y   -(int)(draw_at.getY()-(m_minimap_player_size/2.2f)));
585             draw2DImage(m_red_flag, rp, rs, NULL, NULL, true, true);
586         }
587         Vec3 pos = ctf_world->getRedHolder() == -1 ? ctf_world->getRedFlag() :
588             ctf_world->getKart(ctf_world->getRedHolder())->getSmoothedTrans().getOrigin();
589 
590         track->mapPoint2MiniMap(pos, &draw_at);
591         core::rect<s32> rs(core::position2di(0, 0), m_red_flag->getSize());
592         core::rect<s32> rp(m_map_left+(int)(draw_at.getX()-(m_minimap_player_size/1.4f)),
593                                  lower_y   -(int)(draw_at.getY()+(m_minimap_player_size/2.2f)),
594                                  m_map_left+(int)(draw_at.getX()+(m_minimap_player_size/1.4f)),
595                                  lower_y   -(int)(draw_at.getY()-(m_minimap_player_size/2.2f)));
596         draw2DImage(m_red_flag, rp, rs, NULL, NULL, true);
597 
598         if (!ctf_world->isBlueFlagInBase())
599         {
600             track->mapPoint2MiniMap(Track::getCurrentTrack()->getBlueFlag().getOrigin(),
601                 &draw_at);
602             core::rect<s32> rs(core::position2di(0, 0), m_blue_flag->getSize());
603             core::rect<s32> rp(m_map_left+(int)(draw_at.getX()-(m_minimap_player_size/1.4f)),
604                 lower_y   -(int)(draw_at.getY()+(m_minimap_player_size/2.2f)),
605                 m_map_left+(int)(draw_at.getX()+(m_minimap_player_size/1.4f)),
606                 lower_y   -(int)(draw_at.getY()-(m_minimap_player_size/2.2f)));
607             draw2DImage(m_blue_flag, rp, rs, NULL, NULL, true, true);
608         }
609 
610         pos = ctf_world->getBlueHolder() == -1 ? ctf_world->getBlueFlag() :
611             ctf_world->getKart(ctf_world->getBlueHolder())->getSmoothedTrans().getOrigin();
612 
613         track->mapPoint2MiniMap(pos, &draw_at);
614         core::rect<s32> bs(core::position2di(0, 0), m_blue_flag->getSize());
615         core::rect<s32> bp(m_map_left+(int)(draw_at.getX()-(m_minimap_player_size/1.4f)),
616                                  lower_y   -(int)(draw_at.getY()+(m_minimap_player_size/2.2f)),
617                                  m_map_left+(int)(draw_at.getX()+(m_minimap_player_size/1.4f)),
618                                  lower_y   -(int)(draw_at.getY()-(m_minimap_player_size/2.2f)));
619         draw2DImage(m_blue_flag, bp, bs, NULL, NULL, true);
620     }
621 
622     AbstractKart* target_kart = NULL;
623     Camera* cam = Camera::getActiveCamera();
624     auto cl = LobbyProtocol::get<ClientLobby>();
625     bool is_nw_spectate = cl && cl->isSpectator();
626     // For network spectator highlight
627     if (RaceManager::get()->getNumLocalPlayers() == 1 && cam && is_nw_spectate)
628         target_kart = cam->getKart();
629 
630     // Move AI/remote players to the beginning, so that local players icons
631     // are drawn above them
632     World::KartList karts = world->getKarts();
633     std::partition(karts.begin(), karts.end(), [target_kart, is_nw_spectate]
634         (const std::shared_ptr<AbstractKart>& k)->bool
635     {
636         if (is_nw_spectate)
637             return k.get() != target_kart;
638         else
639             return !k->getController()->isLocalPlayerController();
640     });
641 
642     for (unsigned int i = 0; i < karts.size(); i++)
643     {
644         const AbstractKart *kart = karts[i].get();
645         const SpareTireAI* sta =
646             dynamic_cast<const SpareTireAI*>(kart->getController());
647 
648         // don't draw eliminated kart
649         if (kart->isEliminated() && !(sta && sta->isMoving()))
650             continue;
651         if (!kart->isVisible())
652             continue;
653         const Vec3& xyz = kart->getSmoothedTrans().getOrigin();
654         Vec3 draw_at;
655         track->mapPoint2MiniMap(xyz, &draw_at);
656 
657         video::ITexture* icon = sta ? m_heart_icon :
658             kart->getKartProperties()->getMinimapIcon();
659         if (icon == NULL)
660         {
661             continue;
662         }
663         bool is_local = is_nw_spectate ? kart == target_kart :
664             kart->getController()->isLocalPlayerController();
665         // int marker_height = m_marker->getSize().Height;
666         core::rect<s32> source(core::position2di(0, 0), icon->getSize());
667         int marker_half_size = (is_local
668                                 ? m_minimap_player_size
669                                 : m_minimap_ai_size                        )>>1;
670         core::rect<s32> position(m_map_left+(int)(draw_at.getX()-marker_half_size),
671                                  lower_y   -(int)(draw_at.getY()+marker_half_size),
672                                  m_map_left+(int)(draw_at.getX()+marker_half_size),
673                                  lower_y   -(int)(draw_at.getY()-marker_half_size));
674 
675         bool has_teams = (ctf_world || soccer_world);
676 
677         // Highlight the player icons with some background image.
678         if ((has_teams || is_local) && m_icons_frame != NULL)
679         {
680             video::SColor color = kart->getKartProperties()->getColor();
681 
682             if (has_teams)
683             {
684                 KartTeam team = world->getKartTeam(kart->getWorldKartId());
685 
686                 if (team == KART_TEAM_RED)
687                 {
688                     color = video::SColor(255, 200, 0, 0);
689                 }
690                 else if (team == KART_TEAM_BLUE)
691                 {
692                     color = video::SColor(255, 0, 0, 200);
693                 }
694             }
695 
696             video::SColor colors[4] = {color, color, color, color};
697 
698             const core::rect<s32> rect(core::position2d<s32>(0,0),
699                                         m_icons_frame->getSize());
700 
701             // show kart direction in soccer
702             if (soccer_world)
703             {
704                 // Find the direction a kart is moving in
705                 btTransform trans = kart->getTrans();
706                 Vec3 direction(trans.getBasis().getColumn(2));
707                 // Get the rotation to rotate the icon frame
708                 float rotation = atan2f(direction.getZ(),direction.getX());
709                 if (track->getMinimapInvert())
710                 {   // correct the direction due to invert minimap for blue
711                     rotation = rotation + M_PI;
712                 }
713                 rotation = -1.0f * rotation + 0.25f * M_PI; // icons-frame_arrow.png was rotated by 45 degrees
714                 draw2DImage(m_icons_frame, position, rect, NULL, colors, true, false, rotation);
715             }
716             else
717             {
718                 draw2DImage(m_icons_frame, position, rect, NULL, colors, true);
719             }
720         }   // if isPlayerController
721 
722         draw2DImage(icon, position, source, NULL, NULL, true);
723 
724     }   // for i<getNumKarts
725 
726     // Draw the basket-ball icons on the minimap
727     std::vector<Vec3> basketballs = ProjectileManager::get()->getBasketballPositions();
728     for(unsigned int i = 0; i != basketballs.size(); i++)
729     {
730         Vec3 draw_at;
731         track->mapPoint2MiniMap(basketballs[i], &draw_at);
732 
733         video::ITexture* icon = m_basket_ball_icon;
734 
735         core::rect<s32> source(core::position2di(0, 0), icon->getSize());
736         int marker_half_size = m_minimap_player_size / 2;
737         core::rect<s32> position(m_map_left+(int)(draw_at.getX()-marker_half_size),
738                                  lower_y   -(int)(draw_at.getY()+marker_half_size),
739                                  m_map_left+(int)(draw_at.getX()+marker_half_size),
740                                  lower_y   -(int)(draw_at.getY()-marker_half_size));
741 
742         draw2DImage(icon, position, source, NULL, NULL, true);
743     }
744 
745     // Draw the soccer ball icon
746     if (soccer_world)
747     {
748         Vec3 draw_at;
749         track->mapPoint2MiniMap(soccer_world->getBallPosition(), &draw_at);
750 
751         core::rect<s32> source(core::position2di(0, 0), m_soccer_ball->getSize());
752         core::rect<s32> position(m_map_left+(int)(draw_at.getX()-(m_minimap_player_size/2.5f)),
753                                  lower_y   -(int)(draw_at.getY()+(m_minimap_player_size/2.5f)),
754                                  m_map_left+(int)(draw_at.getX()+(m_minimap_player_size/2.5f)),
755                                  lower_y   -(int)(draw_at.getY()-(m_minimap_player_size/2.5f)));
756         draw2DImage(m_soccer_ball, position, source, NULL, NULL, true);
757     }
758 #endif
759 }   // drawGlobalMiniMap
760 
761 //-----------------------------------------------------------------------------
762 /** Energy meter that gets filled with nitro. This function is called from
763  *  drawSpeedEnergyRank, which defines the correct position of the energy
764  *  meter.
765  *  \param x X position of the meter.
766  *  \param y Y position of the meter.
767  *  \param kart Kart to display the data for.
768  *  \param scaling Scaling applied (in case of split screen)
769  */
drawEnergyMeter(int x,int y,const AbstractKart * kart,const core::recti & viewport,const core::vector2df & scaling)770 void RaceGUI::drawEnergyMeter(int x, int y, const AbstractKart *kart,
771                               const core::recti &viewport,
772                               const core::vector2df &scaling)
773 {
774 #ifndef SERVER_ONLY
775     float min_ratio        = std::min(scaling.X, scaling.Y);
776     const int GAUGEWIDTH   = 94;//same inner radius as the inner speedometer circle
777     int gauge_width        = (int)(GAUGEWIDTH*min_ratio);
778     int gauge_height       = (int)(GAUGEWIDTH*min_ratio);
779 
780     float state = (float)(kart->getEnergy())
781                 / kart->getKartProperties()->getNitroMax();
782     if (state < 0.0f) state = 0.0f;
783     else if (state > 1.0f) state = 1.0f;
784 
785     core::vector2df offset;
786     offset.X = (float)(x-gauge_width) - 9.5f*scaling.X;
787     offset.Y = (float)y-11.5f*scaling.Y;
788 
789 
790     // Background
791     draw2DImage(m_gauge_empty, core::rect<s32>((int)offset.X,
792                                                (int)offset.Y-gauge_height,
793                                                (int)offset.X + gauge_width,
794                                                (int)offset.Y) /* dest rect */,
795                 core::rect<s32>(core::position2d<s32>(0,0),
796                                 m_gauge_empty->getSize()) /* source rect */,
797                 NULL /* clip rect */, NULL /* colors */,
798                 true /* alpha */);
799 
800     // The positions for A to G are defined here.
801     // They are calculated from gauge_full.png
802     // They are further than the nitrometer farther position because
803     // the lines between them would otherwise cut through the outside circle.
804 
805     const int vertices_count = 9;
806 
807     core::vector2df position[vertices_count];
808     position[0].X = 0.324f;//A
809     position[0].Y = 0.35f;//A
810     position[1].X = 0.01f;//B1 (margin for gauge goal)
811     position[1].Y = 0.88f;//B1
812     position[2].X = 0.029f;//B2
813     position[2].Y = 0.918f;//B2
814     position[3].X = 0.307f;//C
815     position[3].Y = 0.99f;//C
816     position[4].X = 0.589f;//D
817     position[4].Y = 0.932f;//D
818     position[5].X = 0.818f;//E
819     position[5].Y = 0.755f;//E
820     position[6].X = 0.945f;//F
821     position[6].Y = 0.497f;//F
822     position[7].X = 0.948f;//G1
823     position[7].Y = 0.211f;//G1
824     position[8].X = 0.94f;//G2 (margin for gauge goal)
825     position[8].Y = 0.17f;//G2
826 
827     // The states at which different polygons must be used.
828 
829     float threshold[vertices_count-2];
830     threshold[0] = 0.0001f; //for gauge drawing
831     threshold[1] = 0.2f;
832     threshold[2] = 0.4f;
833     threshold[3] = 0.6f;
834     threshold[4] = 0.8f;
835     threshold[5] = 0.9999f;
836     threshold[6] = 1.0f;
837 
838     // Filling (current state)
839 
840     if (state > 0.0f)
841     {
842         video::S3DVertex vertices[vertices_count];
843 
844         //3D effect : wait for the full border to appear before drawing
845         for (int i=0;i<5;i++)
846         {
847             if ((state-0.2f*i < 0.006f && state-0.2f*i >= 0.0f) || (0.2f*i-state < 0.003f && 0.2f*i-state >= 0.0f) )
848             {
849                 state = 0.2f*i-0.003f;
850                 break;
851             }
852         }
853 
854         unsigned int count = computeVerticesForMeter(position, threshold, vertices, vertices_count,
855                                                      state, gauge_width, gauge_height, offset);
856 
857         if(kart->getControls().getNitro() || kart->isOnMinNitroTime())
858             drawMeterTexture(m_gauge_full_bright, vertices, count);
859         else
860             drawMeterTexture(m_gauge_full, vertices, count);
861     }
862 
863     // Target
864 
865     if (RaceManager::get()->getCoinTarget() > 0)
866     {
867         float coin_target = (float)RaceManager::get()->getCoinTarget()
868                           / kart->getKartProperties()->getNitroMax();
869 
870         video::S3DVertex vertices[vertices_count];
871 
872         unsigned int count = computeVerticesForMeter(position, threshold, vertices, vertices_count,
873                                                      coin_target, gauge_width, gauge_height, offset);
874 
875         drawMeterTexture(m_gauge_goal, vertices, count);
876     }
877 #endif
878 }   // drawEnergyMeter
879 
880 //-----------------------------------------------------------------------------
881 /** Draws the rank of a player.
882  *  \param kart The kart of the player.
883  *  \param offset Offset of top left corner for this display (for splitscreen).
884  *  \param min_ratio Scaling of the screen (for splitscreen).
885  *  \param meter_width Width of the meter (inside which the rank is shown).
886  *  \param meter_height Height of the meter (inside which the rank is shown).
887  *  \param dt Time step size.
888  */
drawRank(const AbstractKart * kart,const core::vector2df & offset,float min_ratio,int meter_width,int meter_height,float dt)889 void RaceGUI::drawRank(const AbstractKart *kart,
890                       const core::vector2df &offset,
891                       float min_ratio, int meter_width,
892                       int meter_height, float dt)
893 {
894     static video::SColor color = video::SColor(255, 255, 255, 255);
895 
896     // Draw rank
897     WorldWithRank *world = dynamic_cast<WorldWithRank*>(World::getWorld());
898     if (!world || !world->displayRank())
899         return;
900 
901     int id = kart->getWorldKartId();
902 
903     if (m_animation_states[id] == AS_NONE)
904     {
905         if (m_last_ranks[id] != kart->getPosition())
906         {
907             m_rank_animation_duration[id] = 0.0f;
908             m_animation_states[id] = AS_SMALLER;
909         }
910     }
911     else
912     {
913         m_rank_animation_duration[id] += dt;
914     }
915 
916     float scale = 1.0f;
917     int rank = kart->getPosition();
918     const float DURATION = 0.4f;
919     const float MIN_SHRINK = 0.3f;
920     if (m_animation_states[id] == AS_SMALLER)
921     {
922         scale = 1.0f - m_rank_animation_duration[id]/ DURATION;
923         rank = m_last_ranks[id];
924         if (scale < MIN_SHRINK)
925         {
926             m_animation_states[id] = AS_BIGGER;
927             m_rank_animation_duration[id] = 0.0f;
928             // Store the new rank
929             m_last_ranks[id] = kart->getPosition();
930             scale = MIN_SHRINK;
931         }
932     }
933     else if (m_animation_states[id] == AS_BIGGER)
934     {
935         scale = m_rank_animation_duration[id] / DURATION + MIN_SHRINK;
936         rank = m_last_ranks[id];
937         if (scale > 1.0f)
938         {
939             m_animation_states[id] = AS_NONE;
940             scale = 1.0f;
941         }
942 
943     }
944     else
945     {
946         m_last_ranks[id] = kart->getPosition();
947     }
948 
949     gui::ScalableFont* font = GUIEngine::getHighresDigitFont();
950 
951     int font_height = font->getDimension(L"X").Height;
952     font->setScale((float)meter_height / font_height * 0.4f * scale);
953     std::ostringstream oss;
954     oss << rank; // the current font has no . :(   << ".";
955 
956     core::recti pos;
957     pos.LowerRightCorner = core::vector2di(int(offset.X + 0.64f*meter_width),
958                                            int(offset.Y - 0.49f*meter_height));
959     pos.UpperLeftCorner = core::vector2di(int(offset.X + 0.64f*meter_width),
960                                           int(offset.Y - 0.49f*meter_height));
961 
962     font->setBlackBorder(true);
963     font->draw(oss.str().c_str(), pos, color, true, true);
964     font->setBlackBorder(false);
965     font->setScale(1.0f);
966 }   // drawRank
967 
968 //-----------------------------------------------------------------------------
969 /** Draws the speedometer, the display of available nitro, and
970  *  the rank of the kart (inside the speedometer).
971  *  \param kart The kart for which to show the data.
972  *  \param viewport The viewport to use.
973  *  \param scaling Which scaling to apply to the speedometer.
974  *  \param dt Time step size.
975  */
drawSpeedEnergyRank(const AbstractKart * kart,const core::recti & viewport,const core::vector2df & scaling,float dt)976 void RaceGUI::drawSpeedEnergyRank(const AbstractKart* kart,
977                                  const core::recti &viewport,
978                                  const core::vector2df &scaling,
979                                  float dt)
980 {
981 #ifndef SERVER_ONLY
982     float min_ratio         = std::min(scaling.X, scaling.Y);
983     const int SPEEDWIDTH   = 128;
984     int meter_width        = (int)(SPEEDWIDTH*min_ratio);
985     int meter_height       = (int)(SPEEDWIDTH*min_ratio);
986 
987     drawEnergyMeter(viewport.LowerRightCorner.X ,
988                     (int)(viewport.LowerRightCorner.Y),
989                     kart, viewport, scaling);
990 
991     // First draw the meter (i.e. the background )
992     // -------------------------------------------------------------------------
993     core::vector2df offset;
994     offset.X = (float)(viewport.LowerRightCorner.X-meter_width) - 24.0f*scaling.X;
995     offset.Y = viewport.LowerRightCorner.Y-10.0f*scaling.Y;
996 
997     const core::rect<s32> meter_pos((int)offset.X,
998                                     (int)(offset.Y-meter_height),
999                                     (int)(offset.X+meter_width),
1000                                     (int)offset.Y);
1001     video::ITexture *meter_texture = m_speed_meter_icon->getTexture();
1002     const core::rect<s32> meter_texture_coords(core::position2d<s32>(0,0),
1003                                                meter_texture->getSize());
1004     draw2DImage(meter_texture, meter_pos, meter_texture_coords, NULL,
1005                        NULL, true);
1006     // TODO: temporary workaround, shouldn't have to use
1007     // draw2DVertexPrimitiveList to render a simple rectangle
1008 
1009     const float speed =  kart->getSpeed();
1010 
1011     drawRank(kart, offset, min_ratio, meter_width, meter_height, dt);
1012 
1013 
1014     if(speed <=0) return;  // Nothing to do if speed is negative.
1015 
1016     // Draw the actual speed bar (if the speed is >0)
1017     // ----------------------------------------------
1018     float speed_ratio = speed/40.0f; //max displayed speed of 40
1019     if(speed_ratio>1) speed_ratio = 1;
1020 
1021     // see computeVerticesForMeter for the detail of the drawing
1022     // If increasing this, update drawMeterTexture
1023 
1024     const int vertices_count = 12;
1025 
1026     video::S3DVertex vertices[vertices_count];
1027 
1028     // The positions for A to J2 are defined here.
1029 
1030     // They are calculated from speedometer.png
1031     // A is the center of the speedometer's circle
1032     // B2, C, D, E, F, G, H, I and J1 are points on the line
1033     // from A to their respective 1/8th threshold division
1034     // B2 is 36,9° clockwise from the vertical (on bottom-left)
1035     // J1 s 70,7° clockwise from the vertical (on upper-right)
1036     // B1 and J2 are used for correct display of the 3D effect
1037     // They are 1,13* further than the speedometer farther position because
1038     // the lines between them would otherwise cut through the outside circle.
1039 
1040     core::vector2df position[vertices_count];
1041 
1042     position[0].X = 0.546f;//A
1043     position[0].Y = 0.566f;//A
1044     position[1].X = 0.216f;//B1
1045     position[1].Y = 1.036f;//B1
1046     position[2].X = 0.201f;//B2
1047     position[2].Y = 1.023f;//B2
1048     position[3].X = 0.036f;//C
1049     position[3].Y = 0.831f;//C
1050     position[4].X = -0.029f;//D
1051     position[4].Y = 0.589f;//D
1052     position[5].X = 0.018f;//E
1053     position[5].Y = 0.337f;//E
1054     position[6].X = 0.169f;//F
1055     position[6].Y = 0.134f;//F
1056     position[7].X = 0.391f;//G
1057     position[7].Y = 0.014f;//G
1058     position[8].X = 0.642f;//H
1059     position[8].Y = 0.0f;//H
1060     position[9].X = 0.878f;//I
1061     position[9].Y = 0.098f;//I
1062     position[10].X = 1.046f;//J1
1063     position[10].Y = 0.285f;//J1
1064     position[11].X = 1.052f;//J2
1065     position[11].Y = 0.297f;//J2
1066 
1067     // The speed ratios at which different triangles must be used.
1068 
1069     float threshold[vertices_count-2];
1070     threshold[0] = 0.00001f;//for the 3D margin
1071     threshold[1] = 0.125f;
1072     threshold[2] = 0.25f;
1073     threshold[3] = 0.375f;
1074     threshold[4] = 0.50f;
1075     threshold[5] = 0.625f;
1076     threshold[6] = 0.750f;
1077     threshold[7] = 0.875f;
1078     threshold[8] = 0.99999f;//for the 3D margin
1079     threshold[9] = 1.0f;
1080 
1081     //3D effect : wait for the full border to appear before drawing
1082     for (int i=0;i<8;i++)
1083     {
1084         if ((speed_ratio-0.125f*i < 0.00625f && speed_ratio-0.125f*i >= 0.0f) || (0.125f*i-speed_ratio < 0.0045f && 0.125f*i-speed_ratio >= 0.0f) )
1085         {
1086             speed_ratio = 0.125f*i-0.0045f;
1087             break;
1088         }
1089     }
1090 
1091     unsigned int count = computeVerticesForMeter(position, threshold, vertices, vertices_count,
1092                                                      speed_ratio, meter_width, meter_height, offset);
1093 
1094 
1095     drawMeterTexture(m_speed_bar_icon->getTexture(), vertices, count);
1096 #endif
1097 }   // drawSpeedEnergyRank
1098 
drawMeterTexture(video::ITexture * meter_texture,video::S3DVertex vertices[],unsigned int count)1099 void RaceGUI::drawMeterTexture(video::ITexture *meter_texture, video::S3DVertex vertices[], unsigned int count)
1100 {
1101 #ifndef SERVER_ONLY
1102     //Should be greater or equal than the greatest vertices_count used by the meter functions
1103     if (count < 2)
1104         return;
1105     short int index[12];
1106     for(unsigned int i=0; i<count; i++)
1107     {
1108         index[i]=i;
1109         vertices[i].Color = video::SColor(255, 255, 255, 255);
1110     }
1111 
1112     video::SMaterial m;
1113     m.setTexture(0, meter_texture);
1114     m.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
1115     irr_driver->getVideoDriver()->setMaterial(m);
1116     glEnable(GL_BLEND);
1117     glDisable(GL_CULL_FACE);
1118 
1119     glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
1120     draw2DVertexPrimitiveList(m.getTexture(0), vertices, count,
1121         index, count-2, video::EVT_STANDARD, scene::EPT_TRIANGLE_FAN);
1122     glDisable(GL_BLEND);
1123     glEnable(GL_CULL_FACE);
1124 
1125 #endif
1126 }   // drawMeterTexture
1127 
1128 
1129 
1130 //-----------------------------------------------------------------------------
1131 /** This function computes a polygon used for drawing the measure for a meter (speedometer, etc.)
1132  *  The variable measured by the meter is compared to the thresholds, and is then used to
1133  *  compute a point between the two points associated with the lower and upper threshold
1134  *  Then, a polygon is calculated linking all the previous points and the variable point
1135  *  which link back to the first point. This polygon is used for drawing.
1136  *
1137  *  Consider the following example :
1138  *
1139  *      A                E
1140  *                      -|
1141  *                      x
1142  *                      |
1143  *                   -D-|
1144  *                -w-|
1145  *           |-C--|
1146  *     -B--v-|
1147  *
1148  *  If the measure is inferior to the first threshold, the function will create a triangle ABv
1149  *  with the position of v varying proportionally on a line between B and C ;
1150  *  at B with 0 and at C when it reaches the first threshold.
1151  *  If the measure is between the first and second thresholds, the function will create a quad ABCw,
1152  *  with w varying in the same way than v.
1153  *  If the measure exceds the higher threshold, the function will return the poly ABCDE.
1154  *
1155  *  \param position The relative positions of the vertices.
1156  *  \param threshold The thresholds at which the variable point switch from a segment to the next.
1157  *                   The size of this array should be smaller by two than the position array.
1158  *                   The last threshold determines the measure over which the meter is full
1159  *  \param vertices Where the results of the computation are put, for use by the calling function.
1160  *  \param vertices_count The maximum number of vertices to use. Should be superior or equal to the
1161  *                       size of the arrays.
1162  *  \param measure The value of the variable measured by the meter.
1163  *  \param gauge_width The width of the meter
1164  *  \param gauge_height The height of the meter
1165  *  \param offset The offset to position the meter
1166  */
computeVerticesForMeter(core::vector2df position[],float threshold[],video::S3DVertex vertices[],unsigned int vertices_count,float measure,int gauge_width,int gauge_height,core::vector2df offset)1167 unsigned int RaceGUI::computeVerticesForMeter(core::vector2df position[], float threshold[], video::S3DVertex vertices[], unsigned int vertices_count,
1168                                      float measure, int gauge_width, int gauge_height, core::vector2df offset)
1169 {
1170     //Nothing to draw ; we need at least three points to draw a triangle
1171     if (vertices_count <= 2 || measure < 0)
1172     {
1173         return 0;
1174     }
1175 
1176     unsigned int count=2;
1177     float f = 1.0f;
1178 
1179     for (unsigned int i=2 ; i < vertices_count ; i++)
1180     {
1181         count++;
1182 
1183         //Stop when we have found between which thresholds the measure is
1184         if (measure < threshold[i-2])
1185         {
1186             if (i-2 == 0)
1187             {
1188                 f = measure/threshold[i-2];
1189             }
1190             else
1191             {
1192                 f = (measure - threshold[i-3])/(threshold[i-2]-threshold[i-3]);
1193             }
1194 
1195             break;
1196         }
1197     }
1198 
1199     for (unsigned int i=0 ; i < count ; i++)
1200     {
1201         //if the measure don't fall in this segment, use the next predefined point
1202         if (i<count-1 || (count == vertices_count && f == 1.0f))
1203         {
1204             vertices[i].TCoords = core::vector2df(position[i].X, position[i].Y);
1205             vertices[i].Pos     = core::vector3df(offset.X+position[i].X*gauge_width,
1206                                   offset.Y-(1-position[i].Y)*gauge_height, 0);
1207         }
1208         //if the measure fall in this segment, compute the variable position
1209         else
1210         {
1211             //f : the proportion of the next point. 1-f : the proportion of the previous point
1212             vertices[i].TCoords = core::vector2df(position[i].X*(f)+position[i-1].X*(1.0f-f),
1213                                                   position[i].Y*(f)+position[i-1].Y*(1.0f-f));
1214             vertices[i].Pos = core::vector3df(offset.X+ ((position[i].X*(f)+position[i-1].X*(1.0f-f))*gauge_width),
1215                                               offset.Y-(((1-position[i].Y)*(f)+(1-position[i-1].Y)*(1.0f-f))*gauge_height),0);
1216         }
1217     }
1218 
1219     //the count is used in the drawing functions
1220     return count;
1221 } //computeVerticesForMeter
1222 
1223 //-----------------------------------------------------------------------------
1224 /** Displays the lap of the kart.
1225  *  \param info Info object c
1226 */
drawLap(const AbstractKart * kart,const core::recti & viewport,const core::vector2df & scaling)1227 void RaceGUI::drawLap(const AbstractKart* kart,
1228                       const core::recti &viewport,
1229                       const core::vector2df &scaling)
1230 {
1231 #ifndef SERVER_ONLY
1232     // Don't display laps if the kart has already finished the race.
1233     if (kart->hasFinishedRace()) return;
1234 
1235     World *world = World::getWorld();
1236 
1237     core::recti pos;
1238 
1239     pos.UpperLeftCorner.Y = viewport.UpperLeftCorner.Y + m_font_height;
1240 
1241     // If the time display in the top right is in this viewport,
1242     // move the lap/rank display down a little bit so that it is
1243     // displayed under the time.
1244     if (viewport.UpperLeftCorner.Y == 0 &&
1245         viewport.LowerRightCorner.X == (int)(irr_driver->getActualScreenSize().Width) &&
1246         !RaceManager::get()->getIfEmptyScreenSpaceExists())
1247     {
1248         pos.UpperLeftCorner.Y = irr_driver->getActualScreenSize().Height*12/100;
1249     }
1250     pos.LowerRightCorner.Y  = viewport.LowerRightCorner.Y+20;
1251     pos.UpperLeftCorner.X   = viewport.LowerRightCorner.X
1252                             - m_lap_width - 10;
1253     pos.LowerRightCorner.X  = viewport.LowerRightCorner.X;
1254 
1255     // Draw CTF / soccer scores with red score - blue score (score limit)
1256     CaptureTheFlag* ctf = dynamic_cast<CaptureTheFlag*>(World::getWorld());
1257     SoccerWorld* sw = dynamic_cast<SoccerWorld*>(World::getWorld());
1258     FreeForAll* ffa = dynamic_cast<FreeForAll*>(World::getWorld());
1259 
1260     static video::SColor color = video::SColor(255, 255, 255, 255);
1261     int hit_capture_limit =
1262         (RaceManager::get()->getHitCaptureLimit() != std::numeric_limits<int>::max()
1263          && RaceManager::get()->getHitCaptureLimit() != 0)
1264         ? RaceManager::get()->getHitCaptureLimit() : -1;
1265     int score_limit = sw && !RaceManager::get()->hasTimeTarget() ?
1266         RaceManager::get()->getMaxGoal() : ctf ? hit_capture_limit : -1;
1267     if (!ctf && ffa && hit_capture_limit != -1)
1268     {
1269         int icon_width = irr_driver->getActualScreenSize().Height/19;
1270         core::rect<s32> indicator_pos(viewport.LowerRightCorner.X - (icon_width+10),
1271                                     pos.UpperLeftCorner.Y,
1272                                     viewport.LowerRightCorner.X - 10,
1273                                     pos.UpperLeftCorner.Y + icon_width);
1274         core::rect<s32> source_rect(core::position2d<s32>(0,0),
1275                                                 m_champion->getSize());
1276         draw2DImage(m_champion, indicator_pos, source_rect,
1277             NULL, NULL, true);
1278 
1279         gui::ScalableFont* font = GUIEngine::getHighresDigitFont();
1280         font->setBlackBorder(true);
1281         pos.UpperLeftCorner.X += 30;
1282         font->draw(StringUtils::toWString(hit_capture_limit).c_str(), pos, color);
1283         font->setBlackBorder(false);
1284         font->setScale(1.0f);
1285         return;
1286     }
1287 
1288     if (ctf || sw)
1289     {
1290         int red_score = ctf ? ctf->getRedScore() : sw->getScore(KART_TEAM_RED);
1291         int blue_score = ctf ? ctf->getBlueScore() : sw->getScore(KART_TEAM_BLUE);
1292         gui::ScalableFont* font = GUIEngine::getHighresDigitFont();
1293         font->setBlackBorder(true);
1294         font->setScale(1.0f);
1295         core::dimension2du d;
1296         if (score_limit != -1)
1297         {
1298              d = font->getDimension(
1299                 (StringUtils::toWString(red_score) + L"-"
1300                 + StringUtils::toWString(blue_score) + L"     "
1301                 + StringUtils::toWString(score_limit)).c_str());
1302             pos.UpperLeftCorner.X -= d.Width / 2;
1303             int icon_width = irr_driver->getActualScreenSize().Height/19;
1304             core::rect<s32> indicator_pos(viewport.LowerRightCorner.X - (icon_width+10),
1305                                         pos.UpperLeftCorner.Y,
1306                                         viewport.LowerRightCorner.X - 10,
1307                                         pos.UpperLeftCorner.Y + icon_width);
1308             core::rect<s32> source_rect(core::position2d<s32>(0,0),
1309                                                     m_champion->getSize());
1310             draw2DImage(m_champion, indicator_pos, source_rect,
1311                 NULL, NULL, true);
1312         }
1313 
1314         core::stringw text = StringUtils::toWString(red_score);
1315         font->draw(text, pos, video::SColor(255, 255, 0, 0));
1316         d = font->getDimension(text.c_str());
1317         pos += core::position2di(d.Width, 0);
1318         text = L"-";
1319         font->draw(text, pos, video::SColor(255, 255, 255, 255));
1320         d = font->getDimension(text.c_str());
1321         pos += core::position2di(d.Width, 0);
1322         text = StringUtils::toWString(blue_score);
1323         font->draw(text, pos, video::SColor(255, 0, 0, 255));
1324         pos += core::position2di(d.Width, 0);
1325         if (score_limit != -1)
1326         {
1327             text = L"     ";
1328             text += StringUtils::toWString(score_limit);
1329             font->draw(text, pos, video::SColor(255, 255, 255, 255));
1330         }
1331         font->setBlackBorder(false);
1332         return;
1333     }
1334 
1335     if (!world->raceHasLaps()) return;
1336     int lap = world->getFinishedLapsOfKart(kart->getWorldKartId());
1337     // Network race has larger lap than getNumLaps near finish line
1338     // due to waiting for final race result from server
1339     if (lap + 1> RaceManager::get()->getNumLaps())
1340         lap--;
1341     // don't display 'lap 0/..' at the start of a race
1342     if (lap < 0 ) return;
1343 
1344     // Display lap flag
1345 
1346 
1347     int icon_width = irr_driver->getActualScreenSize().Height/19;
1348     core::rect<s32> indicator_pos(viewport.LowerRightCorner.X - (icon_width+10),
1349                                   pos.UpperLeftCorner.Y,
1350                                   viewport.LowerRightCorner.X - 10,
1351                                   pos.UpperLeftCorner.Y + icon_width);
1352     core::rect<s32> source_rect(core::position2d<s32>(0,0),
1353                                                m_lap_flag->getSize());
1354     draw2DImage(m_lap_flag,indicator_pos,source_rect,
1355         NULL,NULL,true);
1356 
1357     pos.UpperLeftCorner.X -= icon_width;
1358     pos.LowerRightCorner.X -= icon_width;
1359 
1360     std::ostringstream out;
1361     out << lap + 1 << "/" << RaceManager::get()->getNumLaps();
1362 
1363     gui::ScalableFont* font = GUIEngine::getHighresDigitFont();
1364     font->setBlackBorder(true);
1365     font->draw(out.str().c_str(), pos, color);
1366     font->setBlackBorder(false);
1367     font->setScale(1.0f);
1368 #endif
1369 } // drawLap
1370 
1371