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>
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.
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.
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.
22 #include "client/hud.h"
25 #include "util/numeric.h"
28 #include "inventory.h"
30 #include "client/tile.h"
31 #include "localplayer.h"
34 #include "fontengine.h"
35 #include "guiscalingfilter.h"
37 #include "wieldmesh.h"
38 #include "client/renderingengine.h"
39 #include "client/minimap.h"
41 #ifdef HAVE_TOUCHSCREENGUI
42 #include "gui/touchscreengui.h"
45 #define OBJECT_CROSSHAIR_LINE_SIZE 8
46 #define CROSSHAIR_LINE_SIZE 10
48 Hud::Hud(Client *client, LocalPlayer *player,
51 driver = RenderingEngine::get_video_driver();
52 this->client = client;
53 this->player = player;
54 this->inventory = inventory;
56 m_hud_scaling = g_settings->getFloat("hud_scaling");
57 m_scale_factor = m_hud_scaling * RenderingEngine::getDisplayDensity();
58 m_hotbar_imagesize = std::floor(HOTBAR_IMAGE_SIZE *
59 RenderingEngine::getDisplayDensity() + 0.5f);
60 m_hotbar_imagesize *= m_hud_scaling;
61 m_padding = m_hotbar_imagesize / 12;
63 for (auto &hbar_color : hbar_colors)
64 hbar_color = video::SColor(255, 255, 255, 255);
66 tsrc = client->getTextureSource();
68 v3f crosshair_color = g_settings->getV3F("crosshair_color");
69 u32 cross_r = rangelim(myround(crosshair_color.X), 0, 255);
70 u32 cross_g = rangelim(myround(crosshair_color.Y), 0, 255);
71 u32 cross_b = rangelim(myround(crosshair_color.Z), 0, 255);
72 u32 cross_a = rangelim(g_settings->getS32("crosshair_alpha"), 0, 255);
73 crosshair_argb = video::SColor(cross_a, cross_r, cross_g, cross_b);
75 v3f selectionbox_color = g_settings->getV3F("selectionbox_color");
76 u32 sbox_r = rangelim(myround(selectionbox_color.X), 0, 255);
77 u32 sbox_g = rangelim(myround(selectionbox_color.Y), 0, 255);
78 u32 sbox_b = rangelim(myround(selectionbox_color.Z), 0, 255);
79 selectionbox_argb = video::SColor(255, sbox_r, sbox_g, sbox_b);
81 use_crosshair_image = tsrc->isKnownSourceImage("crosshair.png");
82 use_object_crosshair_image = tsrc->isKnownSourceImage("object_crosshair.png");
84 m_selection_boxes.clear();
87 std::string mode_setting = g_settings->get("node_highlighting");
89 if (mode_setting == "halo") {
90 m_mode = HIGHLIGHT_HALO;
91 } else if (mode_setting == "none") {
92 m_mode = HIGHLIGHT_NONE;
94 m_mode = HIGHLIGHT_BOX;
97 m_selection_material.Lighting = false;
99 if (g_settings->getBool("enable_shaders")) {
100 IShaderSource *shdrsrc = client->getShaderSource();
101 u16 shader_id = shdrsrc->getShader(
102 m_mode == HIGHLIGHT_HALO ? "selection_shader" : "default_shader", TILE_MATERIAL_ALPHA);
103 m_selection_material.MaterialType = shdrsrc->getShaderInfo(shader_id).material;
105 m_selection_material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
108 if (m_mode == HIGHLIGHT_BOX) {
109 m_selection_material.Thickness =
110 rangelim(g_settings->getS16("selectionbox_width"), 1, 5);
111 } else if (m_mode == HIGHLIGHT_HALO) {
112 m_selection_material.setTexture(0, tsrc->getTextureForMesh("halo.png"));
113 m_selection_material.setFlag(video::EMF_BACK_FACE_CULLING, true);
115 m_selection_material.MaterialType = video::EMT_SOLID;
118 // Prepare mesh for compass drawing
119 m_rotation_mesh_buffer.Vertices.set_used(4);
120 m_rotation_mesh_buffer.Indices.set_used(6);
122 video::SColor white(255, 255, 255, 255);
123 v3f normal(0.f, 0.f, 1.f);
125 m_rotation_mesh_buffer.Vertices[0] = video::S3DVertex(v3f(-1.f, -1.f, 0.f), normal, white, v2f(0.f, 1.f));
126 m_rotation_mesh_buffer.Vertices[1] = video::S3DVertex(v3f(-1.f, 1.f, 0.f), normal, white, v2f(0.f, 0.f));
127 m_rotation_mesh_buffer.Vertices[2] = video::S3DVertex(v3f( 1.f, 1.f, 0.f), normal, white, v2f(1.f, 0.f));
128 m_rotation_mesh_buffer.Vertices[3] = video::S3DVertex(v3f( 1.f, -1.f, 0.f), normal, white, v2f(1.f, 1.f));
130 m_rotation_mesh_buffer.Indices[0] = 0;
131 m_rotation_mesh_buffer.Indices[1] = 1;
132 m_rotation_mesh_buffer.Indices[2] = 2;
133 m_rotation_mesh_buffer.Indices[3] = 2;
134 m_rotation_mesh_buffer.Indices[4] = 3;
135 m_rotation_mesh_buffer.Indices[5] = 0;
137 m_rotation_mesh_buffer.getMaterial().Lighting = false;
138 m_rotation_mesh_buffer.getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
143 if (m_selection_mesh)
144 m_selection_mesh->drop();
147 void Hud::drawItem(const ItemStack &item, const core::rect<s32>& rect,
151 /* draw hihlighting around selected item */
152 if (use_hotbar_selected_image) {
153 core::rect<s32> imgrect2 = rect;
154 imgrect2.UpperLeftCorner.X -= (m_padding*2);
155 imgrect2.UpperLeftCorner.Y -= (m_padding*2);
156 imgrect2.LowerRightCorner.X += (m_padding*2);
157 imgrect2.LowerRightCorner.Y += (m_padding*2);
158 video::ITexture *texture = tsrc->getTexture(hotbar_selected_image);
159 core::dimension2di imgsize(texture->getOriginalSize());
160 draw2DImageFilterScaled(driver, texture, imgrect2,
161 core::rect<s32>(core::position2d<s32>(0,0), imgsize),
162 NULL, hbar_colors, true);
164 video::SColor c_outside(255,255,0,0);
165 //video::SColor c_outside(255,0,0,0);
166 //video::SColor c_inside(255,192,192,192);
167 s32 x1 = rect.UpperLeftCorner.X;
168 s32 y1 = rect.UpperLeftCorner.Y;
169 s32 x2 = rect.LowerRightCorner.X;
170 s32 y2 = rect.LowerRightCorner.Y;
171 // Black base borders
172 driver->draw2DRectangle(c_outside,
174 v2s32(x1 - m_padding, y1 - m_padding),
175 v2s32(x2 + m_padding, y1)
177 driver->draw2DRectangle(c_outside,
179 v2s32(x1 - m_padding, y2),
180 v2s32(x2 + m_padding, y2 + m_padding)
182 driver->draw2DRectangle(c_outside,
184 v2s32(x1 - m_padding, y1),
187 driver->draw2DRectangle(c_outside,
190 v2s32(x2 + m_padding, y2)
192 /*// Light inside borders
193 driver->draw2DRectangle(c_inside,
195 v2s32(x1 - padding/2, y1 - padding/2),
196 v2s32(x2 + padding/2, y1)
198 driver->draw2DRectangle(c_inside,
200 v2s32(x1 - padding/2, y2),
201 v2s32(x2 + padding/2, y2 + padding/2)
203 driver->draw2DRectangle(c_inside,
205 v2s32(x1 - padding/2, y1),
208 driver->draw2DRectangle(c_inside,
211 v2s32(x2 + padding/2, y2)
217 video::SColor bgcolor2(128, 0, 0, 0);
218 if (!use_hotbar_image)
219 driver->draw2DRectangle(bgcolor2, rect, NULL);
220 drawItemStack(driver, g_fontengine->getFont(), item, rect, NULL,
221 client, selected ? IT_ROT_SELECTED : IT_ROT_NONE);
224 //NOTE: selectitem = 0 -> no selected; selectitem 1-based
225 void Hud::drawItems(v2s32 upperleftpos, v2s32 screen_offset, s32 itemcount,
226 s32 inv_offset, InventoryList *mainlist, u16 selectitem, u16 direction)
228 #ifdef HAVE_TOUCHSCREENGUI
229 if (g_touchscreengui && inv_offset == 0)
230 g_touchscreengui->resetHud();
233 s32 height = m_hotbar_imagesize + m_padding * 2;
234 s32 width = (itemcount - inv_offset) * (m_hotbar_imagesize + m_padding * 2);
236 if (direction == HUD_DIR_TOP_BOTTOM || direction == HUD_DIR_BOTTOM_TOP) {
242 // Position of upper left corner of bar
243 v2s32 pos = screen_offset * m_scale_factor;
246 // Store hotbar_image in member variable, used by drawItem()
247 if (hotbar_image != player->hotbar_image) {
248 hotbar_image = player->hotbar_image;
249 use_hotbar_image = !hotbar_image.empty();
252 // Store hotbar_selected_image in member variable, used by drawItem()
253 if (hotbar_selected_image != player->hotbar_selected_image) {
254 hotbar_selected_image = player->hotbar_selected_image;
255 use_hotbar_selected_image = !hotbar_selected_image.empty();
258 // draw customized item background
259 if (use_hotbar_image) {
260 core::rect<s32> imgrect2(-m_padding/2, -m_padding/2,
261 width+m_padding/2, height+m_padding/2);
262 core::rect<s32> rect2 = imgrect2 + pos;
263 video::ITexture *texture = tsrc->getTexture(hotbar_image);
264 core::dimension2di imgsize(texture->getOriginalSize());
265 draw2DImageFilterScaled(driver, texture, rect2,
266 core::rect<s32>(core::position2d<s32>(0,0), imgsize),
267 NULL, hbar_colors, true);
271 core::rect<s32> imgrect(0, 0, m_hotbar_imagesize, m_hotbar_imagesize);
272 for (s32 i = inv_offset; i < itemcount && (size_t)i < mainlist->getSize(); i++) {
273 s32 fullimglen = m_hotbar_imagesize + m_padding * 2;
277 case HUD_DIR_RIGHT_LEFT:
278 steppos = v2s32(-(m_padding + (i - inv_offset) * fullimglen), m_padding);
280 case HUD_DIR_TOP_BOTTOM:
281 steppos = v2s32(m_padding, m_padding + (i - inv_offset) * fullimglen);
283 case HUD_DIR_BOTTOM_TOP:
284 steppos = v2s32(m_padding, -(m_padding + (i - inv_offset) * fullimglen));
287 steppos = v2s32(m_padding + (i - inv_offset) * fullimglen, m_padding);
291 drawItem(mainlist->getItem(i), (imgrect + pos + steppos), (i + 1) == selectitem);
293 #ifdef HAVE_TOUCHSCREENGUI
294 if (g_touchscreengui)
295 g_touchscreengui->registerHudItem(i, (imgrect + pos + steppos));
300 bool Hud::hasElementOfType(HudElementType type)
302 for (size_t i = 0; i != player->maxHudId(); i++) {
303 HudElement *e = player->getHud(i);
312 // Calculates screen position of waypoint. Returns true if waypoint is visible (in front of the player), else false.
313 bool Hud::calculateScreenPos(const v3s16 &camera_offset, HudElement *e, v2s32 *pos)
315 v3f w_pos = e->world_pos * BS;
316 scene::ICameraSceneNode* camera =
317 client->getSceneManager()->getActiveCamera();
318 w_pos -= intToFloat(camera_offset, BS);
319 core::matrix4 trans = camera->getProjectionMatrix();
320 trans *= camera->getViewMatrix();
321 f32 transformed_pos[4] = { w_pos.X, w_pos.Y, w_pos.Z, 1.0f };
322 trans.multiplyWith1x4Matrix(transformed_pos);
323 if (transformed_pos[3] < 0)
325 f32 zDiv = transformed_pos[3] == 0.0f ? 1.0f :
326 core::reciprocal(transformed_pos[3]);
327 pos->X = m_screensize.X * (0.5 * transformed_pos[0] * zDiv + 0.5);
328 pos->Y = m_screensize.Y * (0.5 - transformed_pos[1] * zDiv * 0.5);
332 void Hud::drawLuaElements(const v3s16 &camera_offset)
334 u32 text_height = g_fontengine->getTextHeight();
335 irr::gui::IGUIFont* font = g_fontengine->getFont();
337 // Reorder elements by z_index
338 std::vector<HudElement*> elems;
339 elems.reserve(player->maxHudId());
341 for (size_t i = 0; i != player->maxHudId(); i++) {
342 HudElement *e = player->getHud(i);
346 auto it = elems.begin();
347 while (it != elems.end() && (*it)->z_index <= e->z_index)
353 for (HudElement *e : elems) {
355 v2s32 pos(floor(e->pos.X * (float) m_screensize.X + 0.5),
356 floor(e->pos.Y * (float) m_screensize.Y + 0.5));
358 case HUD_ELEM_TEXT: {
359 irr::gui::IGUIFont *textfont = font;
360 unsigned int font_size = g_fontengine->getDefaultFontSize();
363 font_size *= e->size.X;
365 if (font_size != g_fontengine->getDefaultFontSize())
366 textfont = g_fontengine->getFont(font_size);
368 video::SColor color(255, (e->number >> 16) & 0xFF,
369 (e->number >> 8) & 0xFF,
370 (e->number >> 0) & 0xFF);
371 std::wstring text = unescape_translate(utf8_to_wide(e->text));
372 core::dimension2d<u32> textsize = textfont->getDimension(text.c_str());
374 // The text size on Android is not proportional with the actual scaling
375 irr::gui::IGUIFont *font_scaled = font_size <= 3 ?
376 textfont : g_fontengine->getFont(font_size - 3);
377 if (e->offset.X < -20)
378 textsize = font_scaled->getDimension(text.c_str());
380 v2s32 offset((e->align.X - 1.0) * (textsize.Width / 2),
381 (e->align.Y - 1.0) * (textsize.Height / 2));
382 core::rect<s32> size(0, 0, e->scale.X * m_scale_factor,
383 text_height * e->scale.Y * m_scale_factor);
384 v2s32 offs(e->offset.X * m_scale_factor,
385 e->offset.Y * m_scale_factor);
387 if (e->offset.X < -20)
388 font_scaled->draw(text.c_str(), size + pos + offset + offs, color);
392 textfont->draw(text.c_str(), size + pos + offset + offs, color);
395 case HUD_ELEM_STATBAR: {
396 v2s32 offs(e->offset.X, e->offset.Y);
397 drawStatbar(pos, HUD_CORNER_UPPER, e->dir, e->text, e->text2,
398 e->number, e->item, offs, e->size);
400 case HUD_ELEM_INVENTORY: {
401 InventoryList *inv = inventory->getList(e->text);
402 drawItems(pos, v2s32(e->offset.X, e->offset.Y), e->number, 0,
403 inv, e->item, e->dir);
405 case HUD_ELEM_WAYPOINT: {
406 if (!calculateScreenPos(camera_offset, e, &pos))
408 v3f p_pos = player->getPosition() / BS;
409 pos += v2s32(e->offset.X, e->offset.Y);
410 video::SColor color(255, (e->number >> 16) & 0xFF,
411 (e->number >> 8) & 0xFF,
412 (e->number >> 0) & 0xFF);
413 std::wstring text = unescape_translate(utf8_to_wide(e->name));
414 const std::string &unit = e->text;
415 // waypoints reuse the item field to store precision, item = precision + 1
417 float precision = (item == 0) ? 10.0f : (item - 1.f);
418 bool draw_precision = precision > 0;
420 core::rect<s32> bounds(0, 0, font->getDimension(text.c_str()).Width, (draw_precision ? 2:1) * text_height);
421 pos.Y += (e->align.Y - 1.0) * bounds.getHeight() / 2;
423 font->draw(text.c_str(), bounds + v2s32((e->align.X - 1.0) * bounds.getWidth() / 2, 0), color);
424 if (draw_precision) {
425 std::ostringstream os;
426 float distance = std::floor(precision * p_pos.getDistanceFrom(e->world_pos)) / precision;
427 os << distance << unit;
428 text = unescape_translate(utf8_to_wide(os.str()));
429 bounds.LowerRightCorner.X = bounds.UpperLeftCorner.X + font->getDimension(text.c_str()).Width;
430 font->draw(text.c_str(), bounds + v2s32((e->align.X - 1.0f) * bounds.getWidth() / 2, text_height), color);
433 case HUD_ELEM_IMAGE_WAYPOINT: {
434 if (!calculateScreenPos(camera_offset, e, &pos))
437 case HUD_ELEM_IMAGE: {
438 video::ITexture *texture = tsrc->getTexture(e->text);
442 const video::SColor color(255, 255, 255, 255);
443 const video::SColor colors[] = {color, color, color, color};
444 core::dimension2di imgsize(texture->getOriginalSize());
445 v2s32 dstsize(imgsize.Width * e->scale.X * m_scale_factor,
446 imgsize.Height * e->scale.Y * m_scale_factor);
448 dstsize.X = m_screensize.X * (e->scale.X * -0.01);
450 dstsize.Y = m_screensize.Y * (e->scale.Y * -0.01);
451 v2s32 offset((e->align.X - 1.0) * dstsize.X / 2,
452 (e->align.Y - 1.0) * dstsize.Y / 2);
453 core::rect<s32> rect(0, 0, dstsize.X, dstsize.Y);
454 rect += pos + offset + v2s32(e->offset.X * m_scale_factor,
455 e->offset.Y * m_scale_factor);
456 draw2DImageFilterScaled(driver, texture, rect,
457 core::rect<s32>(core::position2d<s32>(0,0), imgsize),
460 case HUD_ELEM_COMPASS: {
461 video::ITexture *texture = tsrc->getTexture(e->text);
466 v2s32 dstsize(e->size.X, e->size.Y);
468 dstsize.X = m_screensize.X * (e->size.X * -0.01);
470 dstsize.Y = m_screensize.Y * (e->size.Y * -0.01);
472 if (dstsize.X <= 0 || dstsize.Y <= 0)
473 return; // Avoid zero divides
475 // Angle according to camera view
476 v3f fore(0.f, 0.f, 1.f);
477 scene::ICameraSceneNode *cam = client->getSceneManager()->getActiveCamera();
478 cam->getAbsoluteTransformation().rotateVect(fore);
479 int angle = - fore.getHorizontalAngle().Y;
481 // Limit angle and ajust with given offset
482 angle = (angle + (int)e->number) % 360;
484 core::rect<s32> dstrect(0, 0, dstsize.X, dstsize.Y);
485 dstrect += pos + v2s32(
486 (e->align.X - 1.0) * dstsize.X / 2,
487 (e->align.Y - 1.0) * dstsize.Y / 2) +
488 v2s32(e->offset.X * m_hud_scaling, e->offset.Y * m_hud_scaling);
491 case HUD_COMPASS_ROTATE:
492 drawCompassRotate(e, texture, dstrect, angle);
494 case HUD_COMPASS_ROTATE_REVERSE:
495 drawCompassRotate(e, texture, dstrect, -angle);
497 case HUD_COMPASS_TRANSLATE:
498 drawCompassTranslate(e, texture, dstrect, angle);
500 case HUD_COMPASS_TRANSLATE_REVERSE:
501 drawCompassTranslate(e, texture, dstrect, -angle);
507 case HUD_ELEM_MINIMAP: {
508 if (e->size.X <= 0 || e->size.Y <= 0)
510 if (!client->getMinimap())
512 // Draw a minimap of size "size"
513 v2s32 dstsize(e->size.X * m_scale_factor,
514 e->size.Y * m_scale_factor);
515 // (no percent size as minimap would likely be anamorphosed)
516 v2s32 offset((e->align.X - 1.0) * dstsize.X / 2,
517 (e->align.Y - 1.0) * dstsize.Y / 2);
518 core::rect<s32> rect(0, 0, dstsize.X, dstsize.Y);
519 rect += pos + offset + v2s32(e->offset.X * m_scale_factor,
520 e->offset.Y * m_scale_factor);
521 client->getMinimap()->drawMinimap(rect);
524 infostream << "Hud::drawLuaElements: ignoring drawform " << e->type
525 << " due to unrecognized type" << std::endl;
530 void Hud::drawCompassTranslate(HudElement *e, video::ITexture *texture,
531 const core::rect<s32> &rect, int angle)
533 const video::SColor color(255, 255, 255, 255);
534 const video::SColor colors[] = {color, color, color, color};
536 // Compute source image scaling
537 core::dimension2di imgsize(texture->getOriginalSize());
538 core::rect<s32> srcrect(0, 0, imgsize.Width, imgsize.Height);
540 v2s32 dstsize(rect.getHeight() * e->scale.X * imgsize.Width / imgsize.Height,
541 rect.getHeight() * e->scale.Y);
543 // Avoid infinite loop
544 if (dstsize.X <= 0 || dstsize.Y <= 0)
547 core::rect<s32> tgtrect(0, 0, dstsize.X, dstsize.Y);
549 (rect.getWidth() - dstsize.X) / 2,
550 (rect.getHeight() - dstsize.Y) / 2) +
551 rect.UpperLeftCorner;
553 int offset = angle * dstsize.X / 360;
555 tgtrect += v2s32(offset, 0);
557 // Repeat image as much as needed
558 while (tgtrect.UpperLeftCorner.X > rect.UpperLeftCorner.X)
559 tgtrect -= v2s32(dstsize.X, 0);
561 draw2DImageFilterScaled(driver, texture, tgtrect, srcrect, &rect, colors, true);
562 tgtrect += v2s32(dstsize.X, 0);
564 while (tgtrect.UpperLeftCorner.X < rect.LowerRightCorner.X) {
565 draw2DImageFilterScaled(driver, texture, tgtrect, srcrect, &rect, colors, true);
566 tgtrect += v2s32(dstsize.X, 0);
570 void Hud::drawCompassRotate(HudElement *e, video::ITexture *texture,
571 const core::rect<s32> &rect, int angle)
573 core::rect<s32> oldViewPort = driver->getViewPort();
574 core::matrix4 oldProjMat = driver->getTransform(video::ETS_PROJECTION);
575 core::matrix4 oldViewMat = driver->getTransform(video::ETS_VIEW);
577 core::matrix4 Matrix;
578 Matrix.makeIdentity();
579 Matrix.setRotationDegrees(v3f(0.f, 0.f, angle));
581 driver->setViewPort(rect);
582 driver->setTransform(video::ETS_PROJECTION, core::matrix4());
583 driver->setTransform(video::ETS_VIEW, core::matrix4());
584 driver->setTransform(video::ETS_WORLD, Matrix);
586 video::SMaterial &material = m_rotation_mesh_buffer.getMaterial();
587 material.TextureLayer[0].Texture = texture;
588 driver->setMaterial(material);
589 driver->drawMeshBuffer(&m_rotation_mesh_buffer);
591 driver->setTransform(video::ETS_WORLD, core::matrix4());
592 driver->setTransform(video::ETS_VIEW, oldViewMat);
593 driver->setTransform(video::ETS_PROJECTION, oldProjMat);
595 // restore the view area
596 driver->setViewPort(oldViewPort);
599 void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir,
600 const std::string &texture, const std::string &bgtexture,
601 s32 count, s32 maxcount, v2s32 offset, v2s32 size)
603 const video::SColor color(255, 255, 255, 255);
604 const video::SColor colors[] = {color, color, color, color};
606 video::ITexture *stat_texture = tsrc->getTexture(texture);
610 video::ITexture *stat_texture_bg = nullptr;
611 if (!bgtexture.empty()) {
612 stat_texture_bg = tsrc->getTexture(bgtexture);
615 core::dimension2di srcd(stat_texture->getOriginalSize());
616 core::dimension2di dstd;
617 if (size == v2s32()) {
619 dstd.Height *= m_scale_factor;
620 dstd.Width *= m_scale_factor;
621 offset.X *= m_scale_factor;
622 offset.Y *= m_scale_factor;
624 dstd.Height = size.Y * m_scale_factor;
625 dstd.Width = size.X * m_scale_factor;
626 offset.X *= m_scale_factor;
627 offset.Y *= m_scale_factor;
631 if (corner & HUD_CORNER_LOWER)
638 case HUD_DIR_RIGHT_LEFT:
639 steppos = v2s32(-1, 0);
641 case HUD_DIR_TOP_BOTTOM:
642 steppos = v2s32(0, 1);
644 case HUD_DIR_BOTTOM_TOP:
645 steppos = v2s32(0, -1);
648 // From left to right
649 steppos = v2s32(1, 0);
653 auto calculate_clipping_rect = [] (core::dimension2di src,
654 v2s32 steppos) -> core::rect<s32> {
656 // Create basic rectangle
657 core::rect<s32> rect(0, 0,
658 src.Width - std::abs(steppos.X) * src.Width / 2,
659 src.Height - std::abs(steppos.Y) * src.Height / 2
661 // Move rectangle left or down
663 rect += v2s32(src.Width / 2, 0);
665 rect += v2s32(0, src.Height / 2);
668 // Rectangles for 1/2 the actual value to display
669 core::rect<s32> srchalfrect, dsthalfrect;
670 // Rectangles for 1/2 the "off state" texture
671 core::rect<s32> srchalfrect2, dsthalfrect2;
673 if (count % 2 == 1) {
674 // Need to draw halves: Calculate rectangles
675 srchalfrect = calculate_clipping_rect(srcd, steppos);
676 dsthalfrect = calculate_clipping_rect(dstd, steppos);
677 srchalfrect2 = calculate_clipping_rect(srcd, steppos * -1);
678 dsthalfrect2 = calculate_clipping_rect(dstd, steppos * -1);
681 steppos.X *= dstd.Width;
682 steppos.Y *= dstd.Height;
684 // Draw full textures
685 for (s32 i = 0; i < count / 2; i++) {
686 core::rect<s32> srcrect(0, 0, srcd.Width, srcd.Height);
687 core::rect<s32> dstrect(0, 0, dstd.Width, dstd.Height);
690 draw2DImageFilterScaled(driver, stat_texture,
691 dstrect, srcrect, NULL, colors, true);
695 if (count % 2 == 1) {
696 // Draw half a texture
697 draw2DImageFilterScaled(driver, stat_texture,
698 dsthalfrect + p, srchalfrect, NULL, colors, true);
700 if (stat_texture_bg && maxcount > count) {
701 draw2DImageFilterScaled(driver, stat_texture_bg,
702 dsthalfrect2 + p, srchalfrect2,
708 if (stat_texture_bg && maxcount > count / 2) {
709 // Draw "off state" textures
712 start_offset = count / 2 + 1;
714 start_offset = count / 2;
715 for (s32 i = start_offset; i < maxcount / 2; i++) {
716 core::rect<s32> srcrect(0, 0, srcd.Width, srcd.Height);
717 core::rect<s32> dstrect(0, 0, dstd.Width, dstd.Height);
720 draw2DImageFilterScaled(driver, stat_texture_bg,
726 if (maxcount % 2 == 1) {
727 draw2DImageFilterScaled(driver, stat_texture_bg,
728 dsthalfrect + p, srchalfrect,
735 void Hud::drawHotbar(u16 playeritem) {
737 v2s32 centerlowerpos(m_displaycenter.X, m_screensize.Y);
739 InventoryList *mainlist = inventory->getList("main");
740 if (mainlist == NULL) {
741 //silently ignore this we may not be initialized completely
745 s32 hotbar_itemcount = player->hud_hotbar_itemcount;
746 s32 width = hotbar_itemcount * (m_hotbar_imagesize + m_padding * 2);
747 v2s32 pos = centerlowerpos - v2s32(width / 2, m_hotbar_imagesize + m_padding * 3);
749 const v2u32 &window_size = RenderingEngine::getWindowSize();
750 if ((float) width / (float) window_size.X <=
751 g_settings->getFloat("hud_hotbar_max_width")) {
752 if (player->hud_flags & HUD_FLAG_HOTBAR_VISIBLE) {
753 drawItems(pos, v2s32(0, 0), hotbar_itemcount, 0, mainlist, playeritem + 1, 0);
758 v2s32 secondpos = pos;
759 pos = pos - v2s32(0, m_hotbar_imagesize + m_padding);
761 if (player->hud_flags & HUD_FLAG_HOTBAR_VISIBLE) {
762 drawItems(pos, v2s32(0, 0), hotbar_itemcount / 2, 0,
763 mainlist, playeritem + 1, 0);
764 drawItems(secondpos, v2s32(0, 0), hotbar_itemcount,
765 hotbar_itemcount / 2, mainlist, playeritem + 1, 0);
771 void Hud::drawCrosshair()
773 if (pointing_at_object) {
774 if (use_object_crosshair_image) {
775 video::ITexture *object_crosshair = tsrc->getTexture("object_crosshair.png");
776 v2u32 size = object_crosshair->getOriginalSize();
777 v2s32 lsize = v2s32(m_displaycenter.X - (size.X / 2),
778 m_displaycenter.Y - (size.Y / 2));
779 driver->draw2DImage(object_crosshair, lsize,
780 core::rect<s32>(0, 0, size.X, size.Y),
781 nullptr, crosshair_argb, true);
784 m_displaycenter - v2s32(OBJECT_CROSSHAIR_LINE_SIZE,
785 OBJECT_CROSSHAIR_LINE_SIZE),
786 m_displaycenter + v2s32(OBJECT_CROSSHAIR_LINE_SIZE,
787 OBJECT_CROSSHAIR_LINE_SIZE), crosshair_argb);
789 m_displaycenter + v2s32(OBJECT_CROSSHAIR_LINE_SIZE,
790 -OBJECT_CROSSHAIR_LINE_SIZE),
791 m_displaycenter + v2s32(-OBJECT_CROSSHAIR_LINE_SIZE,
792 OBJECT_CROSSHAIR_LINE_SIZE), crosshair_argb);
798 if (use_crosshair_image) {
799 video::ITexture *crosshair = tsrc->getTexture("crosshair.png");
800 v2u32 size = crosshair->getOriginalSize();
801 v2s32 lsize = v2s32(m_displaycenter.X - (size.X / 2),
802 m_displaycenter.Y - (size.Y / 2));
803 driver->draw2DImage(crosshair, lsize,
804 core::rect<s32>(0, 0, size.X, size.Y),
805 nullptr, crosshair_argb, true);
807 driver->draw2DLine(m_displaycenter - v2s32(CROSSHAIR_LINE_SIZE, 0),
808 m_displaycenter + v2s32(CROSSHAIR_LINE_SIZE, 0), crosshair_argb);
809 driver->draw2DLine(m_displaycenter - v2s32(0, CROSSHAIR_LINE_SIZE),
810 m_displaycenter + v2s32(0, CROSSHAIR_LINE_SIZE), crosshair_argb);
814 void Hud::setSelectionPos(const v3f &pos, const v3s16 &camera_offset)
816 m_camera_offset = camera_offset;
817 m_selection_pos = pos;
818 m_selection_pos_with_offset = pos - intToFloat(camera_offset, BS);
821 void Hud::drawSelectionMesh()
823 if (m_mode == HIGHLIGHT_BOX) {
824 // Draw 3D selection boxes
825 video::SMaterial oldmaterial = driver->getMaterial2D();
826 driver->setMaterial(m_selection_material);
827 for (auto & selection_box : m_selection_boxes) {
829 selection_box.MinEdge + m_selection_pos_with_offset,
830 selection_box.MaxEdge + m_selection_pos_with_offset);
832 u32 r = (selectionbox_argb.getRed() *
833 m_selection_mesh_color.getRed() / 255);
834 u32 g = (selectionbox_argb.getGreen() *
835 m_selection_mesh_color.getGreen() / 255);
836 u32 b = (selectionbox_argb.getBlue() *
837 m_selection_mesh_color.getBlue() / 255);
838 driver->draw3DBox(box, video::SColor(255, r, g, b));
840 driver->setMaterial(oldmaterial);
841 } else if (m_mode == HIGHLIGHT_HALO && m_selection_mesh) {
842 // Draw selection mesh
843 video::SMaterial oldmaterial = driver->getMaterial2D();
844 driver->setMaterial(m_selection_material);
845 setMeshColor(m_selection_mesh, m_selection_mesh_color);
846 video::SColor face_color(0,
847 MYMIN(255, m_selection_mesh_color.getRed() * 1.5),
848 MYMIN(255, m_selection_mesh_color.getGreen() * 1.5),
849 MYMIN(255, m_selection_mesh_color.getBlue() * 1.5));
850 setMeshColorByNormal(m_selection_mesh, m_selected_face_normal,
852 scene::IMesh* mesh = cloneMesh(m_selection_mesh);
853 translateMesh(mesh, m_selection_pos_with_offset);
854 u32 mc = m_selection_mesh->getMeshBufferCount();
855 for (u32 i = 0; i < mc; i++) {
856 scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
857 driver->drawMeshBuffer(buf);
860 driver->setMaterial(oldmaterial);
864 void Hud::toggleBlockBounds()
866 m_block_bounds_mode = static_cast<BlockBoundsMode>(m_block_bounds_mode + 1);
868 if (m_block_bounds_mode >= BLOCK_BOUNDS_MAX) {
869 m_block_bounds_mode = BLOCK_BOUNDS_OFF;
873 void Hud::disableBlockBounds()
875 m_block_bounds_mode = BLOCK_BOUNDS_OFF;
878 void Hud::drawBlockBounds()
880 if (m_block_bounds_mode == BLOCK_BOUNDS_OFF) {
884 video::SMaterial old_material = driver->getMaterial2D();
885 driver->setMaterial(m_selection_material);
887 v3s16 pos = player->getStandingNodePos();
890 floorf((float) pos.X / MAP_BLOCKSIZE),
891 floorf((float) pos.Y / MAP_BLOCKSIZE),
892 floorf((float) pos.Z / MAP_BLOCKSIZE)
895 v3f offset = intToFloat(client->getCamera()->getOffset(), BS);
897 s8 radius = m_block_bounds_mode == BLOCK_BOUNDS_ALL ? 2 : 0;
899 v3f halfNode = v3f(BS, BS, BS) / 2.0f;
901 for (s8 x = -radius; x <= radius; x++)
902 for (s8 y = -radius; y <= radius; y++)
903 for (s8 z = -radius; z <= radius; z++) {
904 v3s16 blockOffset(x, y, z);
907 intToFloat((blockPos + blockOffset) * MAP_BLOCKSIZE, BS) - offset - halfNode,
908 intToFloat(((blockPos + blockOffset) * MAP_BLOCKSIZE) + (MAP_BLOCKSIZE - 1), BS) - offset + halfNode
911 driver->draw3DBox(box, video::SColor(255, 255, 0, 0));
914 driver->setMaterial(old_material);
917 void Hud::updateSelectionMesh(const v3s16 &camera_offset)
919 m_camera_offset = camera_offset;
920 if (m_mode != HIGHLIGHT_HALO)
923 if (m_selection_mesh) {
924 m_selection_mesh->drop();
925 m_selection_mesh = NULL;
928 if (m_selection_boxes.empty()) {
933 // New pointed object, create new mesh.
935 // Texture UV coordinates for selection boxes
936 static f32 texture_uv[24] = {
945 // Use single halo box instead of multiple overlapping boxes.
946 // Temporary solution - problem can be solved with multiple
947 // rendering targets, or some method to remove inner surfaces.
948 // Thats because of halo transparency.
950 aabb3f halo_box(100.0, 100.0, 100.0, -100.0, -100.0, -100.0);
951 m_halo_boxes.clear();
953 for (const auto &selection_box : m_selection_boxes) {
954 halo_box.addInternalBox(selection_box);
957 m_halo_boxes.push_back(halo_box);
958 m_selection_mesh = convertNodeboxesToMesh(
959 m_halo_boxes, texture_uv, 0.5);
962 void Hud::resizeHotbar() {
963 const v2u32 &window_size = RenderingEngine::getWindowSize();
965 if (m_screensize != window_size) {
966 m_hotbar_imagesize = floor(HOTBAR_IMAGE_SIZE *
967 RenderingEngine::getDisplayDensity() + 0.5);
968 m_hotbar_imagesize *= m_hud_scaling;
969 m_padding = m_hotbar_imagesize / 12;
970 m_screensize = window_size;
971 m_displaycenter = v2s32(m_screensize.X/2,m_screensize.Y/2);
975 struct MeshTimeInfo {
977 scene::IMesh *mesh = nullptr;
981 video::IVideoDriver *driver,
983 const ItemStack &item,
984 const core::rect<s32> &rect,
985 const core::rect<s32> *clip,
987 ItemRotationKind rotation_kind,
989 const v3s16 &rotation_speed)
991 static MeshTimeInfo rotation_time_infos[IT_ROT_NONE];
994 if (rotation_kind < IT_ROT_NONE && rotation_kind != IT_ROT_OTHER) {
995 rotation_time_infos[rotation_kind].mesh = NULL;
1000 const static thread_local bool enable_animations =
1001 g_settings->getBool("inventory_items_animations");
1003 const ItemDefinition &def = item.getDefinition(client->idef());
1005 bool draw_overlay = false;
1007 // Render as mesh if animated or no inventory image
1008 if ((enable_animations && rotation_kind < IT_ROT_NONE) || def.inventory_image.empty()) {
1009 ItemMesh *imesh = client->idef()->getWieldMesh(def.name, client);
1010 if (!imesh || !imesh->mesh)
1012 scene::IMesh *mesh = imesh->mesh;
1013 driver->clearBuffers(video::ECBF_DEPTH);
1015 if (rotation_kind < IT_ROT_NONE) {
1016 MeshTimeInfo &ti = rotation_time_infos[rotation_kind];
1017 if (mesh != ti.mesh && rotation_kind != IT_ROT_OTHER) {
1019 ti.time = porting::getTimeMs();
1021 delta = porting::getDeltaMs(ti.time, porting::getTimeMs()) % 100000;
1024 core::rect<s32> oldViewPort = driver->getViewPort();
1025 core::matrix4 oldProjMat = driver->getTransform(video::ETS_PROJECTION);
1026 core::matrix4 oldViewMat = driver->getTransform(video::ETS_VIEW);
1027 core::rect<s32> viewrect = rect;
1029 viewrect.clipAgainst(*clip);
1031 core::matrix4 ProjMatrix;
1032 ProjMatrix.buildProjectionMatrixOrthoLH(2.0f, 2.0f, -1.0f, 100.0f);
1034 core::matrix4 ViewMatrix;
1035 ViewMatrix.buildProjectionMatrixOrthoLH(
1036 2.0f * viewrect.getWidth() / rect.getWidth(),
1037 2.0f * viewrect.getHeight() / rect.getHeight(),
1040 ViewMatrix.setTranslation(core::vector3df(
1041 1.0f * (rect.LowerRightCorner.X + rect.UpperLeftCorner.X -
1042 viewrect.LowerRightCorner.X - viewrect.UpperLeftCorner.X) /
1043 viewrect.getWidth(),
1044 1.0f * (viewrect.LowerRightCorner.Y + viewrect.UpperLeftCorner.Y -
1045 rect.LowerRightCorner.Y - rect.UpperLeftCorner.Y) /
1046 viewrect.getHeight(),
1049 driver->setTransform(video::ETS_PROJECTION, ProjMatrix);
1050 driver->setTransform(video::ETS_VIEW, ViewMatrix);
1052 core::matrix4 matrix;
1053 matrix.makeIdentity();
1055 if (enable_animations) {
1056 float timer_f = (float) delta / 5000.f;
1057 matrix.setRotationDegrees(v3f(
1058 angle.X + rotation_speed.X * 3.60f * timer_f,
1059 angle.Y + rotation_speed.Y * 3.60f * timer_f,
1060 angle.Z + rotation_speed.Z * 3.60f * timer_f)
1064 driver->setTransform(video::ETS_WORLD, matrix);
1065 driver->setViewPort(viewrect);
1067 video::SColor basecolor =
1068 client->idef()->getItemstackColor(item, client);
1070 u32 mc = mesh->getMeshBufferCount();
1071 for (u32 j = 0; j < mc; ++j) {
1072 scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
1073 // we can modify vertices relatively fast,
1074 // because these meshes are not buffered.
1075 assert(buf->getHardwareMappingHint_Vertex() == scene::EHM_NEVER);
1076 video::SColor c = basecolor;
1078 if (imesh->buffer_colors.size() > j) {
1079 ItemPartColor *p = &imesh->buffer_colors[j];
1080 if (p->override_base)
1084 if (imesh->needs_shading)
1085 colorizeMeshBuffer(buf, &c);
1087 setMeshBufferColor(buf, c);
1089 video::SMaterial &material = buf->getMaterial();
1090 material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
1091 material.Lighting = false;
1092 driver->setMaterial(material);
1093 driver->drawMeshBuffer(buf);
1096 driver->setTransform(video::ETS_VIEW, oldViewMat);
1097 driver->setTransform(video::ETS_PROJECTION, oldProjMat);
1098 driver->setViewPort(oldViewPort);
1100 draw_overlay = def.type == ITEM_NODE && def.inventory_image.empty();
1101 } else { // Otherwise just draw as 2D
1102 video::ITexture *texture = client->idef()->getInventoryTexture(def.name, client);
1105 video::SColor color =
1106 client->idef()->getItemstackColor(item, client);
1107 const video::SColor colors[] = { color, color, color, color };
1109 draw2DImageFilterScaled(driver, texture, rect,
1110 core::rect<s32>({0, 0}, core::dimension2di(texture->getOriginalSize())),
1111 clip, colors, true);
1113 draw_overlay = true;
1116 // draw the inventory_overlay
1117 if (!def.inventory_overlay.empty() && draw_overlay) {
1118 ITextureSource *tsrc = client->getTextureSource();
1119 video::ITexture *overlay_texture = tsrc->getTexture(def.inventory_overlay);
1120 core::dimension2d<u32> dimens = overlay_texture->getOriginalSize();
1121 core::rect<s32> srcrect(0, 0, dimens.Width, dimens.Height);
1122 draw2DImageFilterScaled(driver, overlay_texture, rect, srcrect, clip, 0, true);
1125 if (def.type == ITEM_TOOL && item.wear != 0) {
1126 // Draw a progressbar
1127 float barheight = static_cast<float>(rect.getHeight()) / 16;
1128 float barpad_x = static_cast<float>(rect.getWidth()) / 16;
1129 float barpad_y = static_cast<float>(rect.getHeight()) / 16;
1131 core::rect<s32> progressrect(
1132 rect.UpperLeftCorner.X + barpad_x,
1133 rect.LowerRightCorner.Y - barpad_y - barheight,
1134 rect.LowerRightCorner.X - barpad_x,
1135 rect.LowerRightCorner.Y - barpad_y);
1137 // Shrink progressrect by amount of tool damage
1138 float wear = item.wear / 65535.0f;
1140 wear * progressrect.UpperLeftCorner.X +
1141 (1 - wear) * progressrect.LowerRightCorner.X;
1143 // Compute progressbar color
1144 // wear = 0.0: green
1145 // wear = 0.5: yellow
1147 video::SColor color(255, 255, 255, 255);
1148 int wear_i = MYMIN(std::floor(wear * 600), 511);
1149 wear_i = MYMIN(wear_i + 10, 511);
1152 color.set(255, wear_i, 255, 0);
1154 color.set(255, 255, 511 - wear_i, 0);
1156 core::rect<s32> progressrect2 = progressrect;
1157 progressrect2.LowerRightCorner.X = progressmid;
1158 driver->draw2DRectangle(color, progressrect2, clip);
1160 color = video::SColor(255, 0, 0, 0);
1161 progressrect2 = progressrect;
1162 progressrect2.UpperLeftCorner.X = progressmid;
1163 driver->draw2DRectangle(color, progressrect2, clip);
1166 if (font != NULL && item.count >= 2) {
1167 // Get the item count as a string
1168 std::string text = itos(item.count);
1169 v2u32 dim = font->getDimension(utf8_to_wide(text).c_str());
1170 v2s32 sdim(dim.X, dim.Y);
1172 core::rect<s32> rect2(
1173 /*rect.UpperLeftCorner,
1174 core::dimension2d<u32>(rect.getWidth(), 15)*/
1175 rect.LowerRightCorner - sdim,
1179 video::SColor bgcolor(128, 0, 0, 0);
1180 driver->draw2DRectangle(bgcolor, rect2, clip);
1182 video::SColor color(255, 255, 255, 255);
1183 font->draw(text.c_str(), rect2, color, false, false, clip);
1188 video::IVideoDriver *driver,
1189 gui::IGUIFont *font,
1190 const ItemStack &item,
1191 const core::rect<s32> &rect,
1192 const core::rect<s32> *clip,
1194 ItemRotationKind rotation_kind)
1196 drawItemStack(driver, font, item, rect, clip, client, rotation_kind,
1197 v3s16(0, 0, 0), v3s16(0, 100, 0));