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