Translated using Weblate (Chinese (Simplified))
[oweals/minetest.git] / src / client / hud.cpp
1 /*
2 Minetest
3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4 Copyright (C) 2010-2013 blue42u, Jonathon Anderson <anderjon@umail.iu.edu>
5 Copyright (C) 2010-2013 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License along
18 with this program; if not, write to the Free Software Foundation, Inc.,
19 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 */
21
22 #include "client/hud.h"
23 #include <cmath>
24 #include "settings.h"
25 #include "util/numeric.h"
26 #include "log.h"
27 #include "client.h"
28 #include "inventory.h"
29 #include "shader.h"
30 #include "client/tile.h"
31 #include "localplayer.h"
32 #include "camera.h"
33 #include "porting.h"
34 #include "fontengine.h"
35 #include "guiscalingfilter.h"
36 #include "mesh.h"
37 #include "wieldmesh.h"
38 #include "client/renderingengine.h"
39
40 #ifdef HAVE_TOUCHSCREENGUI
41 #include "gui/touchscreengui.h"
42 #endif
43
44 Hud::Hud(gui::IGUIEnvironment *guienv, Client *client, LocalPlayer *player,
45                 Inventory *inventory)
46 {
47         driver            = RenderingEngine::get_video_driver();
48         this->guienv      = guienv;
49         this->client      = client;
50         this->player      = player;
51         this->inventory   = inventory;
52
53         m_hud_scaling      = g_settings->getFloat("hud_scaling");
54         m_scale_factor     = m_hud_scaling * RenderingEngine::getDisplayDensity();
55         m_hotbar_imagesize = std::floor(HOTBAR_IMAGE_SIZE *
56                 RenderingEngine::getDisplayDensity() + 0.5f);
57         m_hotbar_imagesize *= m_hud_scaling;
58         m_padding = m_hotbar_imagesize / 12;
59
60         for (auto &hbar_color : hbar_colors)
61                 hbar_color = video::SColor(255, 255, 255, 255);
62
63         tsrc = client->getTextureSource();
64
65         v3f crosshair_color = g_settings->getV3F("crosshair_color");
66         u32 cross_r = rangelim(myround(crosshair_color.X), 0, 255);
67         u32 cross_g = rangelim(myround(crosshair_color.Y), 0, 255);
68         u32 cross_b = rangelim(myround(crosshair_color.Z), 0, 255);
69         u32 cross_a = rangelim(g_settings->getS32("crosshair_alpha"), 0, 255);
70         crosshair_argb = video::SColor(cross_a, cross_r, cross_g, cross_b);
71
72         v3f selectionbox_color = g_settings->getV3F("selectionbox_color");
73         u32 sbox_r = rangelim(myround(selectionbox_color.X), 0, 255);
74         u32 sbox_g = rangelim(myround(selectionbox_color.Y), 0, 255);
75         u32 sbox_b = rangelim(myround(selectionbox_color.Z), 0, 255);
76         selectionbox_argb = video::SColor(255, sbox_r, sbox_g, sbox_b);
77
78         use_crosshair_image = tsrc->isKnownSourceImage("crosshair.png");
79
80         m_selection_boxes.clear();
81         m_halo_boxes.clear();
82
83         std::string mode_setting = g_settings->get("node_highlighting");
84
85         if (mode_setting == "halo") {
86                 m_mode = HIGHLIGHT_HALO;
87         } else if (mode_setting == "none") {
88                 m_mode = HIGHLIGHT_NONE;
89         } else {
90                 m_mode = HIGHLIGHT_BOX;
91         }
92
93         m_selection_material.Lighting = false;
94
95         if (g_settings->getBool("enable_shaders")) {
96                 IShaderSource *shdrsrc = client->getShaderSource();
97                 u16 shader_id = shdrsrc->getShader(
98                         m_mode == HIGHLIGHT_HALO ? "selection_shader" : "default_shader", 1, 1);
99                 m_selection_material.MaterialType = shdrsrc->getShaderInfo(shader_id).material;
100         } else {
101                 m_selection_material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
102         }
103
104         if (m_mode == HIGHLIGHT_BOX) {
105                 m_selection_material.Thickness =
106                         rangelim(g_settings->getS16("selectionbox_width"), 1, 5);
107         } else if (m_mode == HIGHLIGHT_HALO) {
108                 m_selection_material.setTexture(0, tsrc->getTextureForMesh("halo.png"));
109                 m_selection_material.setFlag(video::EMF_BACK_FACE_CULLING, true);
110         } else {
111                 m_selection_material.MaterialType = video::EMT_SOLID;
112         }
113 }
114
115 Hud::~Hud()
116 {
117         if (m_selection_mesh)
118                 m_selection_mesh->drop();
119 }
120
121 void Hud::drawItem(const ItemStack &item, const core::rect<s32>& rect,
122                 bool selected)
123 {
124         if (selected) {
125                 /* draw hihlighting around selected item */
126                 if (use_hotbar_selected_image) {
127                         core::rect<s32> imgrect2 = rect;
128                         imgrect2.UpperLeftCorner.X  -= (m_padding*2);
129                         imgrect2.UpperLeftCorner.Y  -= (m_padding*2);
130                         imgrect2.LowerRightCorner.X += (m_padding*2);
131                         imgrect2.LowerRightCorner.Y += (m_padding*2);
132                                 video::ITexture *texture = tsrc->getTexture(hotbar_selected_image);
133                                 core::dimension2di imgsize(texture->getOriginalSize());
134                         draw2DImageFilterScaled(driver, texture, imgrect2,
135                                         core::rect<s32>(core::position2d<s32>(0,0), imgsize),
136                                         NULL, hbar_colors, true);
137                 } else {
138                         video::SColor c_outside(255,255,0,0);
139                         //video::SColor c_outside(255,0,0,0);
140                         //video::SColor c_inside(255,192,192,192);
141                         s32 x1 = rect.UpperLeftCorner.X;
142                         s32 y1 = rect.UpperLeftCorner.Y;
143                         s32 x2 = rect.LowerRightCorner.X;
144                         s32 y2 = rect.LowerRightCorner.Y;
145                         // Black base borders
146                         driver->draw2DRectangle(c_outside,
147                                 core::rect<s32>(
148                                 v2s32(x1 - m_padding, y1 - m_padding),
149                                 v2s32(x2 + m_padding, y1)
150                                 ), NULL);
151                         driver->draw2DRectangle(c_outside,
152                                 core::rect<s32>(
153                                 v2s32(x1 - m_padding, y2),
154                                 v2s32(x2 + m_padding, y2 + m_padding)
155                                 ), NULL);
156                         driver->draw2DRectangle(c_outside,
157                                 core::rect<s32>(
158                                 v2s32(x1 - m_padding, y1),
159                                         v2s32(x1, y2)
160                                 ), NULL);
161                         driver->draw2DRectangle(c_outside,
162                                 core::rect<s32>(
163                                         v2s32(x2, y1),
164                                 v2s32(x2 + m_padding, y2)
165                                 ), NULL);
166                         /*// Light inside borders
167                         driver->draw2DRectangle(c_inside,
168                                 core::rect<s32>(
169                                         v2s32(x1 - padding/2, y1 - padding/2),
170                                         v2s32(x2 + padding/2, y1)
171                                 ), NULL);
172                         driver->draw2DRectangle(c_inside,
173                                 core::rect<s32>(
174                                         v2s32(x1 - padding/2, y2),
175                                         v2s32(x2 + padding/2, y2 + padding/2)
176                                 ), NULL);
177                         driver->draw2DRectangle(c_inside,
178                                 core::rect<s32>(
179                                         v2s32(x1 - padding/2, y1),
180                                         v2s32(x1, y2)
181                                 ), NULL);
182                         driver->draw2DRectangle(c_inside,
183                                 core::rect<s32>(
184                                         v2s32(x2, y1),
185                                         v2s32(x2 + padding/2, y2)
186                                 ), NULL);
187                         */
188                 }
189         }
190
191         video::SColor bgcolor2(128, 0, 0, 0);
192         if (!use_hotbar_image)
193                 driver->draw2DRectangle(bgcolor2, rect, NULL);
194         drawItemStack(driver, g_fontengine->getFont(), item, rect, NULL,
195                 client, selected ? IT_ROT_SELECTED : IT_ROT_NONE);
196 }
197
198 //NOTE: selectitem = 0 -> no selected; selectitem 1-based
199 void Hud::drawItems(v2s32 upperleftpos, v2s32 screen_offset, s32 itemcount,
200                 s32 inv_offset, InventoryList *mainlist, u16 selectitem, u16 direction)
201 {
202 #ifdef HAVE_TOUCHSCREENGUI
203         if (g_touchscreengui && inv_offset == 0)
204                 g_touchscreengui->resetHud();
205 #endif
206
207         s32 height  = m_hotbar_imagesize + m_padding * 2;
208         s32 width   = (itemcount - inv_offset) * (m_hotbar_imagesize + m_padding * 2);
209
210         if (direction == HUD_DIR_TOP_BOTTOM || direction == HUD_DIR_BOTTOM_TOP) {
211                 s32 tmp = height;
212                 height = width;
213                 width = tmp;
214         }
215
216         // Position of upper left corner of bar
217         v2s32 pos = screen_offset * m_scale_factor;
218         pos += upperleftpos;
219
220         // Store hotbar_image in member variable, used by drawItem()
221         if (hotbar_image != player->hotbar_image) {
222                 hotbar_image = player->hotbar_image;
223                 use_hotbar_image = !hotbar_image.empty();
224         }
225
226         // Store hotbar_selected_image in member variable, used by drawItem()
227         if (hotbar_selected_image != player->hotbar_selected_image) {
228                 hotbar_selected_image = player->hotbar_selected_image;
229                 use_hotbar_selected_image = !hotbar_selected_image.empty();
230         }
231
232         // draw customized item background
233         if (use_hotbar_image) {
234                 core::rect<s32> imgrect2(-m_padding/2, -m_padding/2,
235                         width+m_padding/2, height+m_padding/2);
236                 core::rect<s32> rect2 = imgrect2 + pos;
237                 video::ITexture *texture = tsrc->getTexture(hotbar_image);
238                 core::dimension2di imgsize(texture->getOriginalSize());
239                 draw2DImageFilterScaled(driver, texture, rect2,
240                         core::rect<s32>(core::position2d<s32>(0,0), imgsize),
241                         NULL, hbar_colors, true);
242         }
243
244         // Draw items
245         core::rect<s32> imgrect(0, 0, m_hotbar_imagesize, m_hotbar_imagesize);
246         for (s32 i = inv_offset; i < itemcount && (size_t)i < mainlist->getSize(); i++) {
247                 s32 fullimglen = m_hotbar_imagesize + m_padding * 2;
248
249                 v2s32 steppos;
250                 switch (direction) {
251                 case HUD_DIR_RIGHT_LEFT:
252                         steppos = v2s32(-(m_padding + (i - inv_offset) * fullimglen), m_padding);
253                         break;
254                 case HUD_DIR_TOP_BOTTOM:
255                         steppos = v2s32(m_padding, m_padding + (i - inv_offset) * fullimglen);
256                         break;
257                 case HUD_DIR_BOTTOM_TOP:
258                         steppos = v2s32(m_padding, -(m_padding + (i - inv_offset) * fullimglen));
259                         break;
260                 default:
261                         steppos = v2s32(m_padding + (i - inv_offset) * fullimglen, m_padding);
262                         break;
263                 }
264
265                 drawItem(mainlist->getItem(i), (imgrect + pos + steppos), (i + 1) == selectitem);
266
267 #ifdef HAVE_TOUCHSCREENGUI
268                 if (g_touchscreengui)
269                         g_touchscreengui->registerHudItem(i, (imgrect + pos + steppos));
270 #endif
271         }
272 }
273
274 // Calculates screen position of waypoint. Returns true if waypoint is visible (in front of the player), else false.
275 bool Hud::calculateScreenPos(const v3s16 &camera_offset, HudElement *e, v2s32 *pos)
276 {
277         v3f w_pos = e->world_pos * BS;
278         scene::ICameraSceneNode* camera =
279                 RenderingEngine::get_scene_manager()->getActiveCamera();
280         w_pos -= intToFloat(camera_offset, BS);
281         core::matrix4 trans = camera->getProjectionMatrix();
282         trans *= camera->getViewMatrix();
283         f32 transformed_pos[4] = { w_pos.X, w_pos.Y, w_pos.Z, 1.0f };
284         trans.multiplyWith1x4Matrix(transformed_pos);
285         if (transformed_pos[3] < 0)
286                 return false;
287         f32 zDiv = transformed_pos[3] == 0.0f ? 1.0f :
288                 core::reciprocal(transformed_pos[3]);
289         pos->X = m_screensize.X * (0.5 * transformed_pos[0] * zDiv + 0.5);
290         pos->Y = m_screensize.Y * (0.5 - transformed_pos[1] * zDiv * 0.5);
291         return true;
292 }
293
294 void Hud::drawLuaElements(const v3s16 &camera_offset)
295 {
296         u32 text_height = g_fontengine->getTextHeight();
297         irr::gui::IGUIFont* font = g_fontengine->getFont();
298
299         // Reorder elements by z_index
300         std::vector<size_t> ids;
301
302         for (size_t i = 0; i != player->maxHudId(); i++) {
303                 HudElement *e = player->getHud(i);
304                 if (!e)
305                         continue;
306
307                 auto it = ids.begin();
308                 while (it != ids.end() && player->getHud(*it)->z_index <= e->z_index)
309                         ++it;
310
311                 ids.insert(it, i);
312         }
313
314         for (size_t i : ids) {
315                 HudElement *e = player->getHud(i);
316
317                 v2s32 pos(floor(e->pos.X * (float) m_screensize.X + 0.5),
318                                 floor(e->pos.Y * (float) m_screensize.Y + 0.5));
319                 switch (e->type) {
320                         case HUD_ELEM_TEXT: {
321                                 irr::gui::IGUIFont *textfont = font;
322                                 unsigned int font_size = g_fontengine->getDefaultFontSize();
323
324                                 if (e->size.X > 0)
325                                         font_size *= e->size.X;
326
327                                 if (font_size != g_fontengine->getDefaultFontSize())
328                                         textfont = g_fontengine->getFont(font_size);
329
330                                 video::SColor color(255, (e->number >> 16) & 0xFF,
331                                                                                  (e->number >> 8)  & 0xFF,
332                                                                                  (e->number >> 0)  & 0xFF);
333                                 std::wstring text = unescape_translate(utf8_to_wide(e->text));
334                                 core::dimension2d<u32> textsize = textfont->getDimension(text.c_str());
335 #ifdef __ANDROID__
336                                 // The text size on Android is not proportional with the actual scaling
337                                 irr::gui::IGUIFont *font_scaled = font_size <= 3 ?
338                                         textfont : g_fontengine->getFont(font_size - 3);
339                                 if (e->offset.X < -20)
340                                         textsize = font_scaled->getDimension(text.c_str());
341 #endif
342                                 v2s32 offset((e->align.X - 1.0) * (textsize.Width / 2),
343                                              (e->align.Y - 1.0) * (textsize.Height / 2));
344                                 core::rect<s32> size(0, 0, e->scale.X * m_scale_factor,
345                                                      text_height * e->scale.Y * m_scale_factor);
346                                 v2s32 offs(e->offset.X * m_scale_factor,
347                                            e->offset.Y * m_scale_factor);
348 #ifdef __ANDROID__
349                                 if (e->offset.X < -20)
350                                         font_scaled->draw(text.c_str(), size + pos + offset + offs, color);
351                                 else
352 #endif
353                                 {
354                                         textfont->draw(text.c_str(), size + pos + offset + offs, color);
355                                 }
356                                 break; }
357                         case HUD_ELEM_STATBAR: {
358                                 v2s32 offs(e->offset.X, e->offset.Y);
359                                 drawStatbar(pos, HUD_CORNER_UPPER, e->dir, e->text, e->text2,
360                                         e->number, e->item, offs, e->size);
361                                 break; }
362                         case HUD_ELEM_INVENTORY: {
363                                 InventoryList *inv = inventory->getList(e->text);
364                                 drawItems(pos, v2s32(e->offset.X, e->offset.Y), e->number, 0,
365                                         inv, e->item, e->dir);
366                                 break; }
367                         case HUD_ELEM_WAYPOINT: {
368                                 if (!calculateScreenPos(camera_offset, e, &pos))
369                                         break;
370                                 v3f p_pos = player->getPosition() / BS;
371                                 pos += v2s32(e->offset.X, e->offset.Y);
372                                 video::SColor color(255, (e->number >> 16) & 0xFF,
373                                                                                  (e->number >> 8)  & 0xFF,
374                                                                                  (e->number >> 0)  & 0xFF);
375                                 std::wstring text = unescape_translate(utf8_to_wide(e->name));
376                                 const std::string &unit = e->text;
377                                 // waypoints reuse the item field to store precision, item = precision + 1
378                                 u32 item = e->item;
379                                 float precision = (item == 0) ? 10.0f : (item - 1.f);
380                                 bool draw_precision = precision > 0;
381
382                                 core::rect<s32> bounds(0, 0, font->getDimension(text.c_str()).Width, (draw_precision ? 2:1) * text_height);
383                                 pos.Y += (e->align.Y - 1.0) * bounds.getHeight() / 2;
384                                 bounds += pos;
385                                 font->draw(text.c_str(), bounds + v2s32((e->align.X - 1.0) * bounds.getWidth() / 2, 0), color);
386                                 if (draw_precision) {
387                                         std::ostringstream os;
388                                         float distance = std::floor(precision * p_pos.getDistanceFrom(e->world_pos)) / precision;
389                                         os << distance << unit;
390                                         text = unescape_translate(utf8_to_wide(os.str()));
391                                         bounds.LowerRightCorner.X = bounds.UpperLeftCorner.X + font->getDimension(text.c_str()).Width;
392                                         font->draw(text.c_str(), bounds + v2s32((e->align.X - 1.0f) * bounds.getWidth() / 2, text_height), color);
393                                 }
394                                 break; }
395                         case HUD_ELEM_IMAGE_WAYPOINT: {
396                                 if (!calculateScreenPos(camera_offset, e, &pos))
397                                         break;
398                         }
399                         case HUD_ELEM_IMAGE: {
400                                 video::ITexture *texture = tsrc->getTexture(e->text);
401                                 if (!texture)
402                                         continue;
403
404                                 const video::SColor color(255, 255, 255, 255);
405                                 const video::SColor colors[] = {color, color, color, color};
406                                 core::dimension2di imgsize(texture->getOriginalSize());
407                                 v2s32 dstsize(imgsize.Width * e->scale.X * m_scale_factor,
408                                               imgsize.Height * e->scale.Y * m_scale_factor);
409                                 if (e->scale.X < 0)
410                                         dstsize.X = m_screensize.X * (e->scale.X * -0.01);
411                                 if (e->scale.Y < 0)
412                                         dstsize.Y = m_screensize.Y * (e->scale.Y * -0.01);
413                                 v2s32 offset((e->align.X - 1.0) * dstsize.X / 2,
414                                              (e->align.Y - 1.0) * dstsize.Y / 2);
415                                 core::rect<s32> rect(0, 0, dstsize.X, dstsize.Y);
416                                 rect += pos + offset + v2s32(e->offset.X * m_scale_factor,
417                                                              e->offset.Y * m_scale_factor);
418                                 draw2DImageFilterScaled(driver, texture, rect,
419                                         core::rect<s32>(core::position2d<s32>(0,0), imgsize),
420                                         NULL, colors, true);
421                                 break; }
422                         default:
423                                 infostream << "Hud::drawLuaElements: ignoring drawform " << e->type <<
424                                         " of hud element ID " << i << " due to unrecognized type" << std::endl;
425                 }
426         }
427 }
428
429
430 void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir,
431                 const std::string &texture, const std::string &bgtexture,
432                 s32 count, s32 maxcount, v2s32 offset, v2s32 size)
433 {
434         const video::SColor color(255, 255, 255, 255);
435         const video::SColor colors[] = {color, color, color, color};
436
437         video::ITexture *stat_texture = tsrc->getTexture(texture);
438         if (!stat_texture)
439                 return;
440
441         video::ITexture *stat_texture_bg = nullptr;
442         if (!bgtexture.empty()) {
443                 stat_texture_bg = tsrc->getTexture(bgtexture);
444         }
445
446         core::dimension2di srcd(stat_texture->getOriginalSize());
447         core::dimension2di dstd;
448         if (size == v2s32()) {
449                 dstd = srcd;
450                 dstd.Height *= m_scale_factor;
451                 dstd.Width  *= m_scale_factor;
452                 offset.X *= m_scale_factor;
453                 offset.Y *= m_scale_factor;
454         } else {
455                 dstd.Height = size.Y * m_scale_factor;
456                 dstd.Width  = size.X * m_scale_factor;
457                 offset.X *= m_scale_factor;
458                 offset.Y *= m_scale_factor;
459         }
460
461         v2s32 p = pos;
462         if (corner & HUD_CORNER_LOWER)
463                 p -= dstd.Height;
464
465         p += offset;
466
467         v2s32 steppos;
468         switch (drawdir) {
469                 case HUD_DIR_RIGHT_LEFT:
470                         steppos = v2s32(-1, 0);
471                         break;
472                 case HUD_DIR_TOP_BOTTOM:
473                         steppos = v2s32(0, 1);
474                         break;
475                 case HUD_DIR_BOTTOM_TOP:
476                         steppos = v2s32(0, -1);
477                         break;
478                 default:
479                         // From left to right
480                         steppos = v2s32(1, 0);
481                         break;
482         }
483
484         auto calculate_clipping_rect = [] (core::dimension2di src,
485                         v2s32 steppos) -> core::rect<s32> {
486
487                 // Create basic rectangle
488                 core::rect<s32> rect(0, 0,
489                         src.Width  - std::abs(steppos.X) * src.Width / 2,
490                         src.Height - std::abs(steppos.Y) * src.Height / 2
491                 );
492                 // Move rectangle left or down
493                 if (steppos.X == -1)
494                         rect += v2s32(src.Width / 2, 0);
495                 if (steppos.Y == -1)
496                         rect += v2s32(0, src.Height / 2);
497                 return rect;
498         };
499         // Rectangles for 1/2 the actual value to display
500         core::rect<s32> srchalfrect, dsthalfrect;
501         // Rectangles for 1/2 the "off state" texture
502         core::rect<s32> srchalfrect2, dsthalfrect2;
503
504         if (count % 2 == 1) {
505                 // Need to draw halves: Calculate rectangles
506                 srchalfrect  = calculate_clipping_rect(srcd, steppos);
507                 dsthalfrect  = calculate_clipping_rect(dstd, steppos);
508                 srchalfrect2 = calculate_clipping_rect(srcd, steppos * -1);
509                 dsthalfrect2 = calculate_clipping_rect(dstd, steppos * -1);
510         }
511
512         steppos.X *= dstd.Width;
513         steppos.Y *= dstd.Height;
514
515         // Draw full textures
516         for (s32 i = 0; i < count / 2; i++) {
517                 core::rect<s32> srcrect(0, 0, srcd.Width, srcd.Height);
518                 core::rect<s32> dstrect(0, 0, dstd.Width, dstd.Height);
519
520                 dstrect += p;
521                 draw2DImageFilterScaled(driver, stat_texture,
522                         dstrect, srcrect, NULL, colors, true);
523                 p += steppos;
524         }
525
526         if (count % 2 == 1) {
527                 // Draw half a texture
528                 draw2DImageFilterScaled(driver, stat_texture,
529                         dsthalfrect + p, srchalfrect, NULL, colors, true);
530
531                 if (stat_texture_bg && maxcount > count) {
532                         draw2DImageFilterScaled(driver, stat_texture_bg,
533                                         dsthalfrect2 + p, srchalfrect2,
534                                         NULL, colors, true);
535                         p += steppos;
536                 }
537         }
538
539         if (stat_texture_bg && maxcount > count / 2) {
540                 // Draw "off state" textures
541                 s32 start_offset;
542                 if (count % 2 == 1)
543                         start_offset = count / 2 + 1;
544                 else
545                         start_offset = count / 2;
546                 for (s32 i = start_offset; i < maxcount / 2; i++) {
547                         core::rect<s32> srcrect(0, 0, srcd.Width, srcd.Height);
548                         core::rect<s32> dstrect(0, 0, dstd.Width, dstd.Height);
549
550                         dstrect += p;
551                         draw2DImageFilterScaled(driver, stat_texture_bg,
552                                         dstrect, srcrect,
553                                         NULL, colors, true);
554                         p += steppos;
555                 }
556
557                 if (maxcount % 2 == 1) {
558                         draw2DImageFilterScaled(driver, stat_texture_bg,
559                                         dsthalfrect + p, srchalfrect,
560                                         NULL, colors, true);
561                 }
562         }
563 }
564
565
566 void Hud::drawHotbar(u16 playeritem) {
567
568         v2s32 centerlowerpos(m_displaycenter.X, m_screensize.Y);
569
570         InventoryList *mainlist = inventory->getList("main");
571         if (mainlist == NULL) {
572                 //silently ignore this we may not be initialized completely
573                 return;
574         }
575
576         s32 hotbar_itemcount = player->hud_hotbar_itemcount;
577         s32 width = hotbar_itemcount * (m_hotbar_imagesize + m_padding * 2);
578         v2s32 pos = centerlowerpos - v2s32(width / 2, m_hotbar_imagesize + m_padding * 3);
579
580         const v2u32 &window_size = RenderingEngine::get_instance()->getWindowSize();
581         if ((float) width / (float) window_size.X <=
582                         g_settings->getFloat("hud_hotbar_max_width")) {
583                 if (player->hud_flags & HUD_FLAG_HOTBAR_VISIBLE) {
584                         drawItems(pos, v2s32(0, 0), hotbar_itemcount, 0, mainlist, playeritem + 1, 0);
585                 }
586         } else {
587                 pos.X += width/4;
588
589                 v2s32 secondpos = pos;
590                 pos = pos - v2s32(0, m_hotbar_imagesize + m_padding);
591
592                 if (player->hud_flags & HUD_FLAG_HOTBAR_VISIBLE) {
593                         drawItems(pos, v2s32(0, 0), hotbar_itemcount / 2, 0,
594                                 mainlist, playeritem + 1, 0);
595                         drawItems(secondpos, v2s32(0, 0), hotbar_itemcount,
596                                 hotbar_itemcount / 2, mainlist, playeritem + 1, 0);
597                 }
598         }
599 }
600
601
602 void Hud::drawCrosshair()
603 {
604         if (use_crosshair_image) {
605                 video::ITexture *crosshair = tsrc->getTexture("crosshair.png");
606                 v2u32 size  = crosshair->getOriginalSize();
607                 v2s32 lsize = v2s32(m_displaycenter.X - (size.X / 2),
608                                 m_displaycenter.Y - (size.Y / 2));
609                 driver->draw2DImage(crosshair, lsize,
610                                 core::rect<s32>(0, 0, size.X, size.Y),
611                                 0, crosshair_argb, true);
612         } else {
613                 driver->draw2DLine(m_displaycenter - v2s32(10, 0),
614                                 m_displaycenter + v2s32(10, 0), crosshair_argb);
615                 driver->draw2DLine(m_displaycenter - v2s32(0, 10),
616                                 m_displaycenter + v2s32(0, 10), crosshair_argb);
617         }
618 }
619
620 void Hud::setSelectionPos(const v3f &pos, const v3s16 &camera_offset)
621 {
622         m_camera_offset = camera_offset;
623         m_selection_pos = pos;
624         m_selection_pos_with_offset = pos - intToFloat(camera_offset, BS);
625 }
626
627 void Hud::drawSelectionMesh()
628 {
629         if (m_mode == HIGHLIGHT_BOX) {
630                 // Draw 3D selection boxes
631                 video::SMaterial oldmaterial = driver->getMaterial2D();
632                 driver->setMaterial(m_selection_material);
633                 for (auto & selection_box : m_selection_boxes) {
634                         aabb3f box = aabb3f(
635                                 selection_box.MinEdge + m_selection_pos_with_offset,
636                                 selection_box.MaxEdge + m_selection_pos_with_offset);
637
638                         u32 r = (selectionbox_argb.getRed() *
639                                         m_selection_mesh_color.getRed() / 255);
640                         u32 g = (selectionbox_argb.getGreen() *
641                                         m_selection_mesh_color.getGreen() / 255);
642                         u32 b = (selectionbox_argb.getBlue() *
643                                         m_selection_mesh_color.getBlue() / 255);
644                         driver->draw3DBox(box, video::SColor(255, r, g, b));
645                 }
646                 driver->setMaterial(oldmaterial);
647         } else if (m_mode == HIGHLIGHT_HALO && m_selection_mesh) {
648                 // Draw selection mesh
649                 video::SMaterial oldmaterial = driver->getMaterial2D();
650                 driver->setMaterial(m_selection_material);
651                 setMeshColor(m_selection_mesh, m_selection_mesh_color);
652                 video::SColor face_color(0,
653                         MYMIN(255, m_selection_mesh_color.getRed() * 1.5),
654                         MYMIN(255, m_selection_mesh_color.getGreen() * 1.5),
655                         MYMIN(255, m_selection_mesh_color.getBlue() * 1.5));
656                 setMeshColorByNormal(m_selection_mesh, m_selected_face_normal,
657                         face_color);
658                 scene::IMesh* mesh = cloneMesh(m_selection_mesh);
659                 translateMesh(mesh, m_selection_pos_with_offset);
660                 u32 mc = m_selection_mesh->getMeshBufferCount();
661                 for (u32 i = 0; i < mc; i++) {
662                         scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
663                         driver->drawMeshBuffer(buf);
664                 }
665                 mesh->drop();
666                 driver->setMaterial(oldmaterial);
667         }
668 }
669
670 void Hud::updateSelectionMesh(const v3s16 &camera_offset)
671 {
672         m_camera_offset = camera_offset;
673         if (m_mode != HIGHLIGHT_HALO)
674                 return;
675
676         if (m_selection_mesh) {
677                 m_selection_mesh->drop();
678                 m_selection_mesh = NULL;
679         }
680
681         if (m_selection_boxes.empty()) {
682                 // No pointed object
683                 return;
684         }
685
686         // New pointed object, create new mesh.
687
688         // Texture UV coordinates for selection boxes
689         static f32 texture_uv[24] = {
690                 0,0,1,1,
691                 0,0,1,1,
692                 0,0,1,1,
693                 0,0,1,1,
694                 0,0,1,1,
695                 0,0,1,1
696         };
697
698         // Use single halo box instead of multiple overlapping boxes.
699         // Temporary solution - problem can be solved with multiple
700         // rendering targets, or some method to remove inner surfaces.
701         // Thats because of halo transparency.
702
703         aabb3f halo_box(100.0, 100.0, 100.0, -100.0, -100.0, -100.0);
704         m_halo_boxes.clear();
705
706         for (const auto &selection_box : m_selection_boxes) {
707                 halo_box.addInternalBox(selection_box);
708         }
709
710         m_halo_boxes.push_back(halo_box);
711         m_selection_mesh = convertNodeboxesToMesh(
712                 m_halo_boxes, texture_uv, 0.5);
713 }
714
715 void Hud::resizeHotbar() {
716         const v2u32 &window_size = RenderingEngine::get_instance()->getWindowSize();
717
718         if (m_screensize != window_size) {
719                 m_hotbar_imagesize = floor(HOTBAR_IMAGE_SIZE *
720                         RenderingEngine::getDisplayDensity() + 0.5);
721                 m_hotbar_imagesize *= m_hud_scaling;
722                 m_padding = m_hotbar_imagesize / 12;
723                 m_screensize = window_size;
724                 m_displaycenter = v2s32(m_screensize.X/2,m_screensize.Y/2);
725         }
726 }
727
728 struct MeshTimeInfo {
729         u64 time;
730         scene::IMesh *mesh = nullptr;
731 };
732
733 void drawItemStack(
734                 video::IVideoDriver *driver,
735                 gui::IGUIFont *font,
736                 const ItemStack &item,
737                 const core::rect<s32> &rect,
738                 const core::rect<s32> *clip,
739                 Client *client,
740                 ItemRotationKind rotation_kind,
741                 const v3s16 &angle,
742                 const v3s16 &rotation_speed)
743 {
744         static MeshTimeInfo rotation_time_infos[IT_ROT_NONE];
745
746         if (item.empty()) {
747                 if (rotation_kind < IT_ROT_NONE && rotation_kind != IT_ROT_OTHER) {
748                         rotation_time_infos[rotation_kind].mesh = NULL;
749                 }
750                 return;
751         }
752
753         const ItemDefinition &def = item.getDefinition(client->idef());
754         ItemMesh *imesh = client->idef()->getWieldMesh(def.name, client);
755
756         if (imesh && imesh->mesh) {
757                 scene::IMesh *mesh = imesh->mesh;
758                 driver->clearZBuffer();
759                 s32 delta = 0;
760                 if (rotation_kind < IT_ROT_NONE) {
761                         MeshTimeInfo &ti = rotation_time_infos[rotation_kind];
762                         if (mesh != ti.mesh && rotation_kind != IT_ROT_OTHER) {
763                                 ti.mesh = mesh;
764                                 ti.time = porting::getTimeMs();
765                         } else {
766                                 delta = porting::getDeltaMs(ti.time, porting::getTimeMs()) % 100000;
767                         }
768                 }
769                 core::rect<s32> oldViewPort = driver->getViewPort();
770                 core::matrix4 oldProjMat = driver->getTransform(video::ETS_PROJECTION);
771                 core::matrix4 oldViewMat = driver->getTransform(video::ETS_VIEW);
772                 core::rect<s32> viewrect = rect;
773                 if (clip)
774                         viewrect.clipAgainst(*clip);
775
776                 core::matrix4 ProjMatrix;
777                 ProjMatrix.buildProjectionMatrixOrthoLH(2.0f, 2.0f, -1.0f, 100.0f);
778
779                 core::matrix4 ViewMatrix;
780                 ViewMatrix.buildProjectionMatrixOrthoLH(
781                         2.0f * viewrect.getWidth() / rect.getWidth(),
782                         2.0f * viewrect.getHeight() / rect.getHeight(),
783                         -1.0f,
784                         100.0f);
785                 ViewMatrix.setTranslation(core::vector3df(
786                         1.0f * (rect.LowerRightCorner.X + rect.UpperLeftCorner.X -
787                                         viewrect.LowerRightCorner.X - viewrect.UpperLeftCorner.X) /
788                                         viewrect.getWidth(),
789                         1.0f * (viewrect.LowerRightCorner.Y + viewrect.UpperLeftCorner.Y -
790                                         rect.LowerRightCorner.Y - rect.UpperLeftCorner.Y) /
791                                         viewrect.getHeight(),
792                         0.0f));
793
794                 driver->setTransform(video::ETS_PROJECTION, ProjMatrix);
795                 driver->setTransform(video::ETS_VIEW, ViewMatrix);
796
797                 core::matrix4 matrix;
798                 matrix.makeIdentity();
799
800                 static thread_local bool enable_animations =
801                         g_settings->getBool("inventory_items_animations");
802
803                 if (enable_animations) {
804                         float timer_f = (float) delta / 5000.f;
805                         matrix.setRotationDegrees(v3f(
806                                 angle.X + rotation_speed.X * 3.60f * timer_f,
807                                 angle.Y + rotation_speed.Y * 3.60f * timer_f,
808                                 angle.Z + rotation_speed.Z * 3.60f * timer_f)
809                         );
810                 }
811
812                 driver->setTransform(video::ETS_WORLD, matrix);
813                 driver->setViewPort(viewrect);
814
815                 video::SColor basecolor =
816                         client->idef()->getItemstackColor(item, client);
817
818                 u32 mc = mesh->getMeshBufferCount();
819                 for (u32 j = 0; j < mc; ++j) {
820                         scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
821                         // we can modify vertices relatively fast,
822                         // because these meshes are not buffered.
823                         assert(buf->getHardwareMappingHint_Vertex() == scene::EHM_NEVER);
824                         video::SColor c = basecolor;
825
826                         if (imesh->buffer_colors.size() > j) {
827                                 ItemPartColor *p = &imesh->buffer_colors[j];
828                                 if (p->override_base)
829                                         c = p->color;
830                         }
831
832                         if (imesh->needs_shading)
833                                 colorizeMeshBuffer(buf, &c);
834                         else
835                                 setMeshBufferColor(buf, c);
836
837                         video::SMaterial &material = buf->getMaterial();
838                         material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
839                         material.Lighting = false;
840                         driver->setMaterial(material);
841                         driver->drawMeshBuffer(buf);
842                 }
843
844                 driver->setTransform(video::ETS_VIEW, oldViewMat);
845                 driver->setTransform(video::ETS_PROJECTION, oldProjMat);
846                 driver->setViewPort(oldViewPort);
847
848                 // draw the inventory_overlay
849                 if (def.type == ITEM_NODE && def.inventory_image.empty() &&
850                                 !def.inventory_overlay.empty()) {
851                         ITextureSource *tsrc = client->getTextureSource();
852                         video::ITexture *overlay_texture = tsrc->getTexture(def.inventory_overlay);
853                         core::dimension2d<u32> dimens = overlay_texture->getOriginalSize();
854                         core::rect<s32> srcrect(0, 0, dimens.Width, dimens.Height);
855                         draw2DImageFilterScaled(driver, overlay_texture, rect, srcrect, clip, 0, true);
856                 }
857         }
858
859         if (def.type == ITEM_TOOL && item.wear != 0) {
860                 // Draw a progressbar
861                 float barheight = rect.getHeight() / 16;
862                 float barpad_x = rect.getWidth() / 16;
863                 float barpad_y = rect.getHeight() / 16;
864
865                 core::rect<s32> progressrect(
866                         rect.UpperLeftCorner.X + barpad_x,
867                         rect.LowerRightCorner.Y - barpad_y - barheight,
868                         rect.LowerRightCorner.X - barpad_x,
869                         rect.LowerRightCorner.Y - barpad_y);
870
871                 // Shrink progressrect by amount of tool damage
872                 float wear = item.wear / 65535.0f;
873                 int progressmid =
874                         wear * progressrect.UpperLeftCorner.X +
875                         (1 - wear) * progressrect.LowerRightCorner.X;
876
877                 // Compute progressbar color
878                 //   wear = 0.0: green
879                 //   wear = 0.5: yellow
880                 //   wear = 1.0: red
881                 video::SColor color(255, 255, 255, 255);
882                 int wear_i = MYMIN(std::floor(wear * 600), 511);
883                 wear_i = MYMIN(wear_i + 10, 511);
884
885                 if (wear_i <= 255)
886                         color.set(255, wear_i, 255, 0);
887                 else
888                         color.set(255, 255, 511 - wear_i, 0);
889
890                 core::rect<s32> progressrect2 = progressrect;
891                 progressrect2.LowerRightCorner.X = progressmid;
892                 driver->draw2DRectangle(color, progressrect2, clip);
893
894                 color = video::SColor(255, 0, 0, 0);
895                 progressrect2 = progressrect;
896                 progressrect2.UpperLeftCorner.X = progressmid;
897                 driver->draw2DRectangle(color, progressrect2, clip);
898         }
899
900         if (font != NULL && item.count >= 2) {
901                 // Get the item count as a string
902                 std::string text = itos(item.count);
903                 v2u32 dim = font->getDimension(utf8_to_wide(text).c_str());
904                 v2s32 sdim(dim.X, dim.Y);
905
906                 core::rect<s32> rect2(
907                         /*rect.UpperLeftCorner,
908                         core::dimension2d<u32>(rect.getWidth(), 15)*/
909                         rect.LowerRightCorner - sdim,
910                         sdim
911                 );
912
913                 video::SColor bgcolor(128, 0, 0, 0);
914                 driver->draw2DRectangle(bgcolor, rect2, clip);
915
916                 video::SColor color(255, 255, 255, 255);
917                 font->draw(text.c_str(), rect2, color, false, false, clip);
918         }
919 }
920
921 void drawItemStack(
922                 video::IVideoDriver *driver,
923                 gui::IGUIFont *font,
924                 const ItemStack &item,
925                 const core::rect<s32> &rect,
926                 const core::rect<s32> *clip,
927                 Client *client,
928                 ItemRotationKind rotation_kind)
929 {
930         drawItemStack(driver, font, item, rect, clip, client, rotation_kind,
931                 v3s16(0, 0, 0), v3s16(0, 100, 0));
932 }