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"
27 #include "util/numeric.h"
30 #include "inventory.h"
32 #include "client/tile.h"
33 #include "localplayer.h"
36 #include "fontengine.h"
37 #include "guiscalingfilter.h"
39 #include "wieldmesh.h"
40 #include "client/renderingengine.h"
41 #include "client/minimap.h"
43 #ifdef HAVE_TOUCHSCREENGUI
44 #include "gui/touchscreengui.h"
47 #define OBJECT_CROSSHAIR_LINE_SIZE 8
48 #define CROSSHAIR_LINE_SIZE 10
50 Hud::Hud(Client *client, LocalPlayer *player,
53 driver = RenderingEngine::get_video_driver();
54 this->client = client;
55 this->player = player;
56 this->inventory = inventory;
58 m_hud_scaling = g_settings->getFloat("hud_scaling");
59 m_scale_factor = m_hud_scaling * RenderingEngine::getDisplayDensity();
60 m_hotbar_imagesize = std::floor(HOTBAR_IMAGE_SIZE *
61 RenderingEngine::getDisplayDensity() + 0.5f);
62 m_hotbar_imagesize *= m_hud_scaling;
63 m_padding = m_hotbar_imagesize / 12;
65 for (auto &hbar_color : hbar_colors)
66 hbar_color = video::SColor(255, 255, 255, 255);
68 tsrc = client->getTextureSource();
70 v3f crosshair_color = g_settings->getV3F("crosshair_color");
71 u32 cross_r = rangelim(myround(crosshair_color.X), 0, 255);
72 u32 cross_g = rangelim(myround(crosshair_color.Y), 0, 255);
73 u32 cross_b = rangelim(myround(crosshair_color.Z), 0, 255);
74 u32 cross_a = rangelim(g_settings->getS32("crosshair_alpha"), 0, 255);
75 crosshair_argb = video::SColor(cross_a, cross_r, cross_g, cross_b);
77 v3f selectionbox_color = g_settings->getV3F("selectionbox_color");
78 u32 sbox_r = rangelim(myround(selectionbox_color.X), 0, 255);
79 u32 sbox_g = rangelim(myround(selectionbox_color.Y), 0, 255);
80 u32 sbox_b = rangelim(myround(selectionbox_color.Z), 0, 255);
81 selectionbox_argb = video::SColor(255, sbox_r, sbox_g, sbox_b);
83 use_crosshair_image = tsrc->isKnownSourceImage("crosshair.png");
84 use_object_crosshair_image = tsrc->isKnownSourceImage("object_crosshair.png");
86 m_selection_boxes.clear();
89 std::string mode_setting = g_settings->get("node_highlighting");
91 if (mode_setting == "halo") {
92 m_mode = HIGHLIGHT_HALO;
93 } else if (mode_setting == "none") {
94 m_mode = HIGHLIGHT_NONE;
96 m_mode = HIGHLIGHT_BOX;
99 m_selection_material.Lighting = false;
101 if (g_settings->getBool("enable_shaders")) {
102 IShaderSource *shdrsrc = client->getShaderSource();
103 u16 shader_id = shdrsrc->getShader(
104 m_mode == HIGHLIGHT_HALO ? "selection_shader" : "default_shader", TILE_MATERIAL_ALPHA);
105 m_selection_material.MaterialType = shdrsrc->getShaderInfo(shader_id).material;
107 m_selection_material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
110 if (m_mode == HIGHLIGHT_BOX) {
111 m_selection_material.Thickness =
112 rangelim(g_settings->getS16("selectionbox_width"), 1, 5);
113 } else if (m_mode == HIGHLIGHT_HALO) {
114 m_selection_material.setTexture(0, tsrc->getTextureForMesh("halo.png"));
115 m_selection_material.setFlag(video::EMF_BACK_FACE_CULLING, true);
117 m_selection_material.MaterialType = video::EMT_SOLID;
120 // Prepare mesh for compass drawing
121 m_rotation_mesh_buffer.Vertices.set_used(4);
122 m_rotation_mesh_buffer.Indices.set_used(6);
124 video::SColor white(255, 255, 255, 255);
125 v3f normal(0.f, 0.f, 1.f);
127 m_rotation_mesh_buffer.Vertices[0] = video::S3DVertex(v3f(-1.f, -1.f, 0.f), normal, white, v2f(0.f, 1.f));
128 m_rotation_mesh_buffer.Vertices[1] = video::S3DVertex(v3f(-1.f, 1.f, 0.f), normal, white, v2f(0.f, 0.f));
129 m_rotation_mesh_buffer.Vertices[2] = video::S3DVertex(v3f( 1.f, 1.f, 0.f), normal, white, v2f(1.f, 0.f));
130 m_rotation_mesh_buffer.Vertices[3] = video::S3DVertex(v3f( 1.f, -1.f, 0.f), normal, white, v2f(1.f, 1.f));
132 m_rotation_mesh_buffer.Indices[0] = 0;
133 m_rotation_mesh_buffer.Indices[1] = 1;
134 m_rotation_mesh_buffer.Indices[2] = 2;
135 m_rotation_mesh_buffer.Indices[3] = 2;
136 m_rotation_mesh_buffer.Indices[4] = 3;
137 m_rotation_mesh_buffer.Indices[5] = 0;
139 m_rotation_mesh_buffer.getMaterial().Lighting = false;
140 m_rotation_mesh_buffer.getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
145 if (m_selection_mesh)
146 m_selection_mesh->drop();
149 void Hud::drawItem(const ItemStack &item, const core::rect<s32>& rect,
153 /* draw hihlighting around selected item */
154 if (use_hotbar_selected_image) {
155 core::rect<s32> imgrect2 = rect;
156 imgrect2.UpperLeftCorner.X -= (m_padding*2);
157 imgrect2.UpperLeftCorner.Y -= (m_padding*2);
158 imgrect2.LowerRightCorner.X += (m_padding*2);
159 imgrect2.LowerRightCorner.Y += (m_padding*2);
160 video::ITexture *texture = tsrc->getTexture(hotbar_selected_image);
161 core::dimension2di imgsize(texture->getOriginalSize());
162 draw2DImageFilterScaled(driver, texture, imgrect2,
163 core::rect<s32>(core::position2d<s32>(0,0), imgsize),
164 NULL, hbar_colors, true);
166 video::SColor c_outside(255,255,0,0);
167 //video::SColor c_outside(255,0,0,0);
168 //video::SColor c_inside(255,192,192,192);
169 s32 x1 = rect.UpperLeftCorner.X;
170 s32 y1 = rect.UpperLeftCorner.Y;
171 s32 x2 = rect.LowerRightCorner.X;
172 s32 y2 = rect.LowerRightCorner.Y;
173 // Black base borders
174 driver->draw2DRectangle(c_outside,
176 v2s32(x1 - m_padding, y1 - m_padding),
177 v2s32(x2 + m_padding, y1)
179 driver->draw2DRectangle(c_outside,
181 v2s32(x1 - m_padding, y2),
182 v2s32(x2 + m_padding, y2 + m_padding)
184 driver->draw2DRectangle(c_outside,
186 v2s32(x1 - m_padding, y1),
189 driver->draw2DRectangle(c_outside,
192 v2s32(x2 + m_padding, y2)
194 /*// Light inside borders
195 driver->draw2DRectangle(c_inside,
197 v2s32(x1 - padding/2, y1 - padding/2),
198 v2s32(x2 + padding/2, y1)
200 driver->draw2DRectangle(c_inside,
202 v2s32(x1 - padding/2, y2),
203 v2s32(x2 + padding/2, y2 + padding/2)
205 driver->draw2DRectangle(c_inside,
207 v2s32(x1 - padding/2, y1),
210 driver->draw2DRectangle(c_inside,
213 v2s32(x2 + padding/2, y2)
219 video::SColor bgcolor2(128, 0, 0, 0);
220 if (!use_hotbar_image)
221 driver->draw2DRectangle(bgcolor2, rect, NULL);
222 drawItemStack(driver, g_fontengine->getFont(), item, rect, NULL,
223 client, selected ? IT_ROT_SELECTED : IT_ROT_NONE);
226 //NOTE: selectitem = 0 -> no selected; selectitem 1-based
227 void Hud::drawItems(v2s32 upperleftpos, v2s32 screen_offset, s32 itemcount,
228 s32 inv_offset, InventoryList *mainlist, u16 selectitem, u16 direction)
230 #ifdef HAVE_TOUCHSCREENGUI
231 if (g_touchscreengui && inv_offset == 0)
232 g_touchscreengui->resetHud();
235 s32 height = m_hotbar_imagesize + m_padding * 2;
236 s32 width = (itemcount - inv_offset) * (m_hotbar_imagesize + m_padding * 2);
238 if (direction == HUD_DIR_TOP_BOTTOM || direction == HUD_DIR_BOTTOM_TOP) {
244 // Position of upper left corner of bar
245 v2s32 pos = screen_offset * m_scale_factor;
248 // Store hotbar_image in member variable, used by drawItem()
249 if (hotbar_image != player->hotbar_image) {
250 hotbar_image = player->hotbar_image;
251 use_hotbar_image = !hotbar_image.empty();
254 // Store hotbar_selected_image in member variable, used by drawItem()
255 if (hotbar_selected_image != player->hotbar_selected_image) {
256 hotbar_selected_image = player->hotbar_selected_image;
257 use_hotbar_selected_image = !hotbar_selected_image.empty();
260 // draw customized item background
261 if (use_hotbar_image) {
262 core::rect<s32> imgrect2(-m_padding/2, -m_padding/2,
263 width+m_padding/2, height+m_padding/2);
264 core::rect<s32> rect2 = imgrect2 + pos;
265 video::ITexture *texture = tsrc->getTexture(hotbar_image);
266 core::dimension2di imgsize(texture->getOriginalSize());
267 draw2DImageFilterScaled(driver, texture, rect2,
268 core::rect<s32>(core::position2d<s32>(0,0), imgsize),
269 NULL, hbar_colors, true);
273 core::rect<s32> imgrect(0, 0, m_hotbar_imagesize, m_hotbar_imagesize);
274 for (s32 i = inv_offset; i < itemcount && (size_t)i < mainlist->getSize(); i++) {
275 s32 fullimglen = m_hotbar_imagesize + m_padding * 2;
279 case HUD_DIR_RIGHT_LEFT:
280 steppos = v2s32(-(m_padding + (i - inv_offset) * fullimglen), m_padding);
282 case HUD_DIR_TOP_BOTTOM:
283 steppos = v2s32(m_padding, m_padding + (i - inv_offset) * fullimglen);
285 case HUD_DIR_BOTTOM_TOP:
286 steppos = v2s32(m_padding, -(m_padding + (i - inv_offset) * fullimglen));
289 steppos = v2s32(m_padding + (i - inv_offset) * fullimglen, m_padding);
293 drawItem(mainlist->getItem(i), (imgrect + pos + steppos), (i + 1) == selectitem);
295 #ifdef HAVE_TOUCHSCREENGUI
296 if (g_touchscreengui)
297 g_touchscreengui->registerHudItem(i, (imgrect + pos + steppos));
302 bool Hud::hasElementOfType(HudElementType type)
304 for (size_t i = 0; i != player->maxHudId(); i++) {
305 HudElement *e = player->getHud(i);
314 // Calculates screen position of waypoint. Returns true if waypoint is visible (in front of the player), else false.
315 bool Hud::calculateScreenPos(const v3s16 &camera_offset, HudElement *e, v2s32 *pos)
317 v3f w_pos = e->world_pos * BS;
318 scene::ICameraSceneNode* camera =
319 client->getSceneManager()->getActiveCamera();
320 w_pos -= intToFloat(camera_offset, BS);
321 core::matrix4 trans = camera->getProjectionMatrix();
322 trans *= camera->getViewMatrix();
323 f32 transformed_pos[4] = { w_pos.X, w_pos.Y, w_pos.Z, 1.0f };
324 trans.multiplyWith1x4Matrix(transformed_pos);
325 if (transformed_pos[3] < 0)
327 f32 zDiv = transformed_pos[3] == 0.0f ? 1.0f :
328 core::reciprocal(transformed_pos[3]);
329 pos->X = m_screensize.X * (0.5 * transformed_pos[0] * zDiv + 0.5);
330 pos->Y = m_screensize.Y * (0.5 - transformed_pos[1] * zDiv * 0.5);
334 void Hud::drawLuaElements(const v3s16 &camera_offset)
336 const u32 text_height = g_fontengine->getTextHeight();
337 gui::IGUIFont *const font = g_fontengine->getFont();
339 // Reorder elements by z_index
340 std::vector<HudElement*> elems;
341 elems.reserve(player->maxHudId());
343 for (size_t i = 0; i != player->maxHudId(); i++) {
344 HudElement *e = player->getHud(i);
348 auto it = elems.begin();
349 while (it != elems.end() && (*it)->z_index <= e->z_index)
355 for (HudElement *e : elems) {
357 v2s32 pos(floor(e->pos.X * (float) m_screensize.X + 0.5),
358 floor(e->pos.Y * (float) m_screensize.Y + 0.5));
360 case HUD_ELEM_TEXT: {
361 unsigned int font_size = g_fontengine->getDefaultFontSize();
364 font_size *= e->size.X;
367 // The text size on Android is not proportional with the actual scaling
368 // FIXME: why do we have such a weird unportable hack??
369 if (font_size > 3 && e->offset.X < -20)
372 auto textfont = g_fontengine->getFont(FontSpec(font_size,
373 (e->style & HUD_STYLE_MONO) ? FM_Mono : FM_Unspecified,
374 e->style & HUD_STYLE_BOLD, e->style & HUD_STYLE_ITALIC));
376 video::SColor color(255, (e->number >> 16) & 0xFF,
377 (e->number >> 8) & 0xFF,
378 (e->number >> 0) & 0xFF);
379 std::wstring text = unescape_translate(utf8_to_wide(e->text));
380 core::dimension2d<u32> textsize = textfont->getDimension(text.c_str());
382 v2s32 offset(0, (e->align.Y - 1.0) * (textsize.Height / 2));
383 core::rect<s32> size(0, 0, e->scale.X * m_scale_factor,
384 text_height * e->scale.Y * m_scale_factor);
385 v2s32 offs(e->offset.X * m_scale_factor,
386 e->offset.Y * m_scale_factor);
387 std::wstringstream wss(text);
389 while (std::getline(wss, line, L'\n'))
391 core::dimension2d<u32> linesize = textfont->getDimension(line.c_str());
392 v2s32 line_offset((e->align.X - 1.0) * (linesize.Width / 2), 0);
393 textfont->draw(line.c_str(), size + pos + offset + offs + line_offset, color);
394 offset.Y += linesize.Height;
397 case HUD_ELEM_STATBAR: {
398 v2s32 offs(e->offset.X, e->offset.Y);
399 drawStatbar(pos, HUD_CORNER_UPPER, e->dir, e->text, e->text2,
400 e->number, e->item, offs, e->size);
402 case HUD_ELEM_INVENTORY: {
403 InventoryList *inv = inventory->getList(e->text);
404 drawItems(pos, v2s32(e->offset.X, e->offset.Y), e->number, 0,
405 inv, e->item, e->dir);
407 case HUD_ELEM_WAYPOINT: {
408 if (!calculateScreenPos(camera_offset, e, &pos))
410 v3f p_pos = player->getPosition() / BS;
411 pos += v2s32(e->offset.X, e->offset.Y);
412 video::SColor color(255, (e->number >> 16) & 0xFF,
413 (e->number >> 8) & 0xFF,
414 (e->number >> 0) & 0xFF);
415 std::wstring text = unescape_translate(utf8_to_wide(e->name));
416 const std::string &unit = e->text;
417 // waypoints reuse the item field to store precision, item = precision + 1
419 float precision = (item == 0) ? 10.0f : (item - 1.f);
420 bool draw_precision = precision > 0;
422 core::rect<s32> bounds(0, 0, font->getDimension(text.c_str()).Width, (draw_precision ? 2:1) * text_height);
423 pos.Y += (e->align.Y - 1.0) * bounds.getHeight() / 2;
425 font->draw(text.c_str(), bounds + v2s32((e->align.X - 1.0) * bounds.getWidth() / 2, 0), color);
426 if (draw_precision) {
427 std::ostringstream os;
428 float distance = std::floor(precision * p_pos.getDistanceFrom(e->world_pos)) / precision;
429 os << distance << unit;
430 text = unescape_translate(utf8_to_wide(os.str()));
431 bounds.LowerRightCorner.X = bounds.UpperLeftCorner.X + font->getDimension(text.c_str()).Width;
432 font->draw(text.c_str(), bounds + v2s32((e->align.X - 1.0f) * bounds.getWidth() / 2, text_height), color);
435 case HUD_ELEM_IMAGE_WAYPOINT: {
436 if (!calculateScreenPos(camera_offset, e, &pos))
439 case HUD_ELEM_IMAGE: {
440 video::ITexture *texture = tsrc->getTexture(e->text);
444 const video::SColor color(255, 255, 255, 255);
445 const video::SColor colors[] = {color, color, color, color};
446 core::dimension2di imgsize(texture->getOriginalSize());
447 v2s32 dstsize(imgsize.Width * e->scale.X * m_scale_factor,
448 imgsize.Height * e->scale.Y * m_scale_factor);
450 dstsize.X = m_screensize.X * (e->scale.X * -0.01);
452 dstsize.Y = m_screensize.Y * (e->scale.Y * -0.01);
453 v2s32 offset((e->align.X - 1.0) * dstsize.X / 2,
454 (e->align.Y - 1.0) * dstsize.Y / 2);
455 core::rect<s32> rect(0, 0, dstsize.X, dstsize.Y);
456 rect += pos + offset + v2s32(e->offset.X * m_scale_factor,
457 e->offset.Y * m_scale_factor);
458 draw2DImageFilterScaled(driver, texture, rect,
459 core::rect<s32>(core::position2d<s32>(0,0), imgsize),
462 case HUD_ELEM_COMPASS: {
463 video::ITexture *texture = tsrc->getTexture(e->text);
468 v2s32 dstsize(e->size.X, e->size.Y);
470 dstsize.X = m_screensize.X * (e->size.X * -0.01);
472 dstsize.Y = m_screensize.Y * (e->size.Y * -0.01);
474 if (dstsize.X <= 0 || dstsize.Y <= 0)
475 return; // Avoid zero divides
477 // Angle according to camera view
478 v3f fore(0.f, 0.f, 1.f);
479 scene::ICameraSceneNode *cam = client->getSceneManager()->getActiveCamera();
480 cam->getAbsoluteTransformation().rotateVect(fore);
481 int angle = - fore.getHorizontalAngle().Y;
483 // Limit angle and ajust with given offset
484 angle = (angle + (int)e->number) % 360;
486 core::rect<s32> dstrect(0, 0, dstsize.X, dstsize.Y);
487 dstrect += pos + v2s32(
488 (e->align.X - 1.0) * dstsize.X / 2,
489 (e->align.Y - 1.0) * dstsize.Y / 2) +
490 v2s32(e->offset.X * m_hud_scaling, e->offset.Y * m_hud_scaling);
493 case HUD_COMPASS_ROTATE:
494 drawCompassRotate(e, texture, dstrect, angle);
496 case HUD_COMPASS_ROTATE_REVERSE:
497 drawCompassRotate(e, texture, dstrect, -angle);
499 case HUD_COMPASS_TRANSLATE:
500 drawCompassTranslate(e, texture, dstrect, angle);
502 case HUD_COMPASS_TRANSLATE_REVERSE:
503 drawCompassTranslate(e, texture, dstrect, -angle);
509 case HUD_ELEM_MINIMAP: {
510 if (e->size.X <= 0 || e->size.Y <= 0)
512 if (!client->getMinimap())
514 // Draw a minimap of size "size"
515 v2s32 dstsize(e->size.X * m_scale_factor,
516 e->size.Y * m_scale_factor);
517 // (no percent size as minimap would likely be anamorphosed)
518 v2s32 offset((e->align.X - 1.0) * dstsize.X / 2,
519 (e->align.Y - 1.0) * dstsize.Y / 2);
520 core::rect<s32> rect(0, 0, dstsize.X, dstsize.Y);
521 rect += pos + offset + v2s32(e->offset.X * m_scale_factor,
522 e->offset.Y * m_scale_factor);
523 client->getMinimap()->drawMinimap(rect);
526 infostream << "Hud::drawLuaElements: ignoring drawform " << e->type
527 << " due to unrecognized type" << std::endl;
532 void Hud::drawCompassTranslate(HudElement *e, video::ITexture *texture,
533 const core::rect<s32> &rect, int angle)
535 const video::SColor color(255, 255, 255, 255);
536 const video::SColor colors[] = {color, color, color, color};
538 // Compute source image scaling
539 core::dimension2di imgsize(texture->getOriginalSize());
540 core::rect<s32> srcrect(0, 0, imgsize.Width, imgsize.Height);
542 v2s32 dstsize(rect.getHeight() * e->scale.X * imgsize.Width / imgsize.Height,
543 rect.getHeight() * e->scale.Y);
545 // Avoid infinite loop
546 if (dstsize.X <= 0 || dstsize.Y <= 0)
549 core::rect<s32> tgtrect(0, 0, dstsize.X, dstsize.Y);
551 (rect.getWidth() - dstsize.X) / 2,
552 (rect.getHeight() - dstsize.Y) / 2) +
553 rect.UpperLeftCorner;
555 int offset = angle * dstsize.X / 360;
557 tgtrect += v2s32(offset, 0);
559 // Repeat image as much as needed
560 while (tgtrect.UpperLeftCorner.X > rect.UpperLeftCorner.X)
561 tgtrect -= v2s32(dstsize.X, 0);
563 draw2DImageFilterScaled(driver, texture, tgtrect, srcrect, &rect, colors, true);
564 tgtrect += v2s32(dstsize.X, 0);
566 while (tgtrect.UpperLeftCorner.X < rect.LowerRightCorner.X) {
567 draw2DImageFilterScaled(driver, texture, tgtrect, srcrect, &rect, colors, true);
568 tgtrect += v2s32(dstsize.X, 0);
572 void Hud::drawCompassRotate(HudElement *e, video::ITexture *texture,
573 const core::rect<s32> &rect, int angle)
575 core::rect<s32> oldViewPort = driver->getViewPort();
576 core::matrix4 oldProjMat = driver->getTransform(video::ETS_PROJECTION);
577 core::matrix4 oldViewMat = driver->getTransform(video::ETS_VIEW);
579 core::matrix4 Matrix;
580 Matrix.makeIdentity();
581 Matrix.setRotationDegrees(v3f(0.f, 0.f, angle));
583 driver->setViewPort(rect);
584 driver->setTransform(video::ETS_PROJECTION, core::matrix4());
585 driver->setTransform(video::ETS_VIEW, core::matrix4());
586 driver->setTransform(video::ETS_WORLD, Matrix);
588 video::SMaterial &material = m_rotation_mesh_buffer.getMaterial();
589 material.TextureLayer[0].Texture = texture;
590 driver->setMaterial(material);
591 driver->drawMeshBuffer(&m_rotation_mesh_buffer);
593 driver->setTransform(video::ETS_WORLD, core::matrix4());
594 driver->setTransform(video::ETS_VIEW, oldViewMat);
595 driver->setTransform(video::ETS_PROJECTION, oldProjMat);
597 // restore the view area
598 driver->setViewPort(oldViewPort);
601 void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir,
602 const std::string &texture, const std::string &bgtexture,
603 s32 count, s32 maxcount, v2s32 offset, v2s32 size)
605 const video::SColor color(255, 255, 255, 255);
606 const video::SColor colors[] = {color, color, color, color};
608 video::ITexture *stat_texture = tsrc->getTexture(texture);
612 video::ITexture *stat_texture_bg = nullptr;
613 if (!bgtexture.empty()) {
614 stat_texture_bg = tsrc->getTexture(bgtexture);
617 core::dimension2di srcd(stat_texture->getOriginalSize());
618 core::dimension2di dstd;
619 if (size == v2s32()) {
621 dstd.Height *= m_scale_factor;
622 dstd.Width *= m_scale_factor;
623 offset.X *= m_scale_factor;
624 offset.Y *= m_scale_factor;
626 dstd.Height = size.Y * m_scale_factor;
627 dstd.Width = size.X * m_scale_factor;
628 offset.X *= m_scale_factor;
629 offset.Y *= m_scale_factor;
633 if (corner & HUD_CORNER_LOWER)
640 case HUD_DIR_RIGHT_LEFT:
641 steppos = v2s32(-1, 0);
643 case HUD_DIR_TOP_BOTTOM:
644 steppos = v2s32(0, 1);
646 case HUD_DIR_BOTTOM_TOP:
647 steppos = v2s32(0, -1);
650 // From left to right
651 steppos = v2s32(1, 0);
655 auto calculate_clipping_rect = [] (core::dimension2di src,
656 v2s32 steppos) -> core::rect<s32> {
658 // Create basic rectangle
659 core::rect<s32> rect(0, 0,
660 src.Width - std::abs(steppos.X) * src.Width / 2,
661 src.Height - std::abs(steppos.Y) * src.Height / 2
663 // Move rectangle left or down
665 rect += v2s32(src.Width / 2, 0);
667 rect += v2s32(0, src.Height / 2);
670 // Rectangles for 1/2 the actual value to display
671 core::rect<s32> srchalfrect, dsthalfrect;
672 // Rectangles for 1/2 the "off state" texture
673 core::rect<s32> srchalfrect2, dsthalfrect2;
675 if (count % 2 == 1) {
676 // Need to draw halves: Calculate rectangles
677 srchalfrect = calculate_clipping_rect(srcd, steppos);
678 dsthalfrect = calculate_clipping_rect(dstd, steppos);
679 srchalfrect2 = calculate_clipping_rect(srcd, steppos * -1);
680 dsthalfrect2 = calculate_clipping_rect(dstd, steppos * -1);
683 steppos.X *= dstd.Width;
684 steppos.Y *= dstd.Height;
686 // Draw full textures
687 for (s32 i = 0; i < count / 2; i++) {
688 core::rect<s32> srcrect(0, 0, srcd.Width, srcd.Height);
689 core::rect<s32> dstrect(0, 0, dstd.Width, dstd.Height);
692 draw2DImageFilterScaled(driver, stat_texture,
693 dstrect, srcrect, NULL, colors, true);
697 if (count % 2 == 1) {
698 // Draw half a texture
699 draw2DImageFilterScaled(driver, stat_texture,
700 dsthalfrect + p, srchalfrect, NULL, colors, true);
702 if (stat_texture_bg && maxcount > count) {
703 draw2DImageFilterScaled(driver, stat_texture_bg,
704 dsthalfrect2 + p, srchalfrect2,
710 if (stat_texture_bg && maxcount > count / 2) {
711 // Draw "off state" textures
714 start_offset = count / 2 + 1;
716 start_offset = count / 2;
717 for (s32 i = start_offset; i < maxcount / 2; i++) {
718 core::rect<s32> srcrect(0, 0, srcd.Width, srcd.Height);
719 core::rect<s32> dstrect(0, 0, dstd.Width, dstd.Height);
722 draw2DImageFilterScaled(driver, stat_texture_bg,
728 if (maxcount % 2 == 1) {
729 draw2DImageFilterScaled(driver, stat_texture_bg,
730 dsthalfrect + p, srchalfrect,
737 void Hud::drawHotbar(u16 playeritem) {
739 v2s32 centerlowerpos(m_displaycenter.X, m_screensize.Y);
741 InventoryList *mainlist = inventory->getList("main");
742 if (mainlist == NULL) {
743 //silently ignore this we may not be initialized completely
747 s32 hotbar_itemcount = player->hud_hotbar_itemcount;
748 s32 width = hotbar_itemcount * (m_hotbar_imagesize + m_padding * 2);
749 v2s32 pos = centerlowerpos - v2s32(width / 2, m_hotbar_imagesize + m_padding * 3);
751 const v2u32 &window_size = RenderingEngine::getWindowSize();
752 if ((float) width / (float) window_size.X <=
753 g_settings->getFloat("hud_hotbar_max_width")) {
754 if (player->hud_flags & HUD_FLAG_HOTBAR_VISIBLE) {
755 drawItems(pos, v2s32(0, 0), hotbar_itemcount, 0, mainlist, playeritem + 1, 0);
760 v2s32 secondpos = pos;
761 pos = pos - v2s32(0, m_hotbar_imagesize + m_padding);
763 if (player->hud_flags & HUD_FLAG_HOTBAR_VISIBLE) {
764 drawItems(pos, v2s32(0, 0), hotbar_itemcount / 2, 0,
765 mainlist, playeritem + 1, 0);
766 drawItems(secondpos, v2s32(0, 0), hotbar_itemcount,
767 hotbar_itemcount / 2, mainlist, playeritem + 1, 0);
773 void Hud::drawCrosshair()
775 if (pointing_at_object) {
776 if (use_object_crosshair_image) {
777 video::ITexture *object_crosshair = tsrc->getTexture("object_crosshair.png");
778 v2u32 size = object_crosshair->getOriginalSize();
779 v2s32 lsize = v2s32(m_displaycenter.X - (size.X / 2),
780 m_displaycenter.Y - (size.Y / 2));
781 driver->draw2DImage(object_crosshair, lsize,
782 core::rect<s32>(0, 0, size.X, size.Y),
783 nullptr, crosshair_argb, true);
786 m_displaycenter - v2s32(OBJECT_CROSSHAIR_LINE_SIZE,
787 OBJECT_CROSSHAIR_LINE_SIZE),
788 m_displaycenter + v2s32(OBJECT_CROSSHAIR_LINE_SIZE,
789 OBJECT_CROSSHAIR_LINE_SIZE), crosshair_argb);
791 m_displaycenter + v2s32(OBJECT_CROSSHAIR_LINE_SIZE,
792 -OBJECT_CROSSHAIR_LINE_SIZE),
793 m_displaycenter + v2s32(-OBJECT_CROSSHAIR_LINE_SIZE,
794 OBJECT_CROSSHAIR_LINE_SIZE), crosshair_argb);
800 if (use_crosshair_image) {
801 video::ITexture *crosshair = tsrc->getTexture("crosshair.png");
802 v2u32 size = crosshair->getOriginalSize();
803 v2s32 lsize = v2s32(m_displaycenter.X - (size.X / 2),
804 m_displaycenter.Y - (size.Y / 2));
805 driver->draw2DImage(crosshair, lsize,
806 core::rect<s32>(0, 0, size.X, size.Y),
807 nullptr, crosshair_argb, true);
809 driver->draw2DLine(m_displaycenter - v2s32(CROSSHAIR_LINE_SIZE, 0),
810 m_displaycenter + v2s32(CROSSHAIR_LINE_SIZE, 0), crosshair_argb);
811 driver->draw2DLine(m_displaycenter - v2s32(0, CROSSHAIR_LINE_SIZE),
812 m_displaycenter + v2s32(0, CROSSHAIR_LINE_SIZE), crosshair_argb);
816 void Hud::setSelectionPos(const v3f &pos, const v3s16 &camera_offset)
818 m_camera_offset = camera_offset;
819 m_selection_pos = pos;
820 m_selection_pos_with_offset = pos - intToFloat(camera_offset, BS);
823 void Hud::drawSelectionMesh()
825 if (m_mode == HIGHLIGHT_BOX) {
826 // Draw 3D selection boxes
827 video::SMaterial oldmaterial = driver->getMaterial2D();
828 driver->setMaterial(m_selection_material);
829 for (auto & selection_box : m_selection_boxes) {
831 selection_box.MinEdge + m_selection_pos_with_offset,
832 selection_box.MaxEdge + m_selection_pos_with_offset);
834 u32 r = (selectionbox_argb.getRed() *
835 m_selection_mesh_color.getRed() / 255);
836 u32 g = (selectionbox_argb.getGreen() *
837 m_selection_mesh_color.getGreen() / 255);
838 u32 b = (selectionbox_argb.getBlue() *
839 m_selection_mesh_color.getBlue() / 255);
840 driver->draw3DBox(box, video::SColor(255, r, g, b));
842 driver->setMaterial(oldmaterial);
843 } else if (m_mode == HIGHLIGHT_HALO && m_selection_mesh) {
844 // Draw selection mesh
845 video::SMaterial oldmaterial = driver->getMaterial2D();
846 driver->setMaterial(m_selection_material);
847 setMeshColor(m_selection_mesh, m_selection_mesh_color);
848 video::SColor face_color(0,
849 MYMIN(255, m_selection_mesh_color.getRed() * 1.5),
850 MYMIN(255, m_selection_mesh_color.getGreen() * 1.5),
851 MYMIN(255, m_selection_mesh_color.getBlue() * 1.5));
852 setMeshColorByNormal(m_selection_mesh, m_selected_face_normal,
854 scene::IMesh* mesh = cloneMesh(m_selection_mesh);
855 translateMesh(mesh, m_selection_pos_with_offset);
856 u32 mc = m_selection_mesh->getMeshBufferCount();
857 for (u32 i = 0; i < mc; i++) {
858 scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
859 driver->drawMeshBuffer(buf);
862 driver->setMaterial(oldmaterial);
866 enum Hud::BlockBoundsMode Hud::toggleBlockBounds()
868 m_block_bounds_mode = static_cast<BlockBoundsMode>(m_block_bounds_mode + 1);
870 if (m_block_bounds_mode >= BLOCK_BOUNDS_MAX) {
871 m_block_bounds_mode = BLOCK_BOUNDS_OFF;
873 return m_block_bounds_mode;
876 void Hud::disableBlockBounds()
878 m_block_bounds_mode = BLOCK_BOUNDS_OFF;
881 void Hud::drawBlockBounds()
883 if (m_block_bounds_mode == BLOCK_BOUNDS_OFF) {
887 video::SMaterial old_material = driver->getMaterial2D();
888 driver->setMaterial(m_selection_material);
890 v3s16 pos = player->getStandingNodePos();
893 floorf((float) pos.X / MAP_BLOCKSIZE),
894 floorf((float) pos.Y / MAP_BLOCKSIZE),
895 floorf((float) pos.Z / MAP_BLOCKSIZE)
898 v3f offset = intToFloat(client->getCamera()->getOffset(), BS);
900 s8 radius = m_block_bounds_mode == BLOCK_BOUNDS_NEAR ? 2 : 0;
902 v3f halfNode = v3f(BS, BS, BS) / 2.0f;
904 for (s8 x = -radius; x <= radius; x++)
905 for (s8 y = -radius; y <= radius; y++)
906 for (s8 z = -radius; z <= radius; z++) {
907 v3s16 blockOffset(x, y, z);
910 intToFloat((blockPos + blockOffset) * MAP_BLOCKSIZE, BS) - offset - halfNode,
911 intToFloat(((blockPos + blockOffset) * MAP_BLOCKSIZE) + (MAP_BLOCKSIZE - 1), BS) - offset + halfNode
914 driver->draw3DBox(box, video::SColor(255, 255, 0, 0));
917 driver->setMaterial(old_material);
920 void Hud::updateSelectionMesh(const v3s16 &camera_offset)
922 m_camera_offset = camera_offset;
923 if (m_mode != HIGHLIGHT_HALO)
926 if (m_selection_mesh) {
927 m_selection_mesh->drop();
928 m_selection_mesh = NULL;
931 if (m_selection_boxes.empty()) {
936 // New pointed object, create new mesh.
938 // Texture UV coordinates for selection boxes
939 static f32 texture_uv[24] = {
948 // Use single halo box instead of multiple overlapping boxes.
949 // Temporary solution - problem can be solved with multiple
950 // rendering targets, or some method to remove inner surfaces.
951 // Thats because of halo transparency.
953 aabb3f halo_box(100.0, 100.0, 100.0, -100.0, -100.0, -100.0);
954 m_halo_boxes.clear();
956 for (const auto &selection_box : m_selection_boxes) {
957 halo_box.addInternalBox(selection_box);
960 m_halo_boxes.push_back(halo_box);
961 m_selection_mesh = convertNodeboxesToMesh(
962 m_halo_boxes, texture_uv, 0.5);
965 void Hud::resizeHotbar() {
966 const v2u32 &window_size = RenderingEngine::getWindowSize();
968 if (m_screensize != window_size) {
969 m_hotbar_imagesize = floor(HOTBAR_IMAGE_SIZE *
970 RenderingEngine::getDisplayDensity() + 0.5);
971 m_hotbar_imagesize *= m_hud_scaling;
972 m_padding = m_hotbar_imagesize / 12;
973 m_screensize = window_size;
974 m_displaycenter = v2s32(m_screensize.X/2,m_screensize.Y/2);
978 struct MeshTimeInfo {
980 scene::IMesh *mesh = nullptr;
984 video::IVideoDriver *driver,
986 const ItemStack &item,
987 const core::rect<s32> &rect,
988 const core::rect<s32> *clip,
990 ItemRotationKind rotation_kind,
992 const v3s16 &rotation_speed)
994 static MeshTimeInfo rotation_time_infos[IT_ROT_NONE];
997 if (rotation_kind < IT_ROT_NONE && rotation_kind != IT_ROT_OTHER) {
998 rotation_time_infos[rotation_kind].mesh = NULL;
1003 const static thread_local bool enable_animations =
1004 g_settings->getBool("inventory_items_animations");
1006 const ItemDefinition &def = item.getDefinition(client->idef());
1008 bool draw_overlay = false;
1010 bool has_mesh = false;
1013 // Render as mesh if animated or no inventory image
1014 if ((enable_animations && rotation_kind < IT_ROT_NONE) || def.inventory_image.empty()) {
1015 imesh = client->idef()->getWieldMesh(def.name, client);
1016 has_mesh = imesh && imesh->mesh;
1019 scene::IMesh *mesh = imesh->mesh;
1020 driver->clearBuffers(video::ECBF_DEPTH);
1022 if (rotation_kind < IT_ROT_NONE) {
1023 MeshTimeInfo &ti = rotation_time_infos[rotation_kind];
1024 if (mesh != ti.mesh && rotation_kind != IT_ROT_OTHER) {
1026 ti.time = porting::getTimeMs();
1028 delta = porting::getDeltaMs(ti.time, porting::getTimeMs()) % 100000;
1031 core::rect<s32> oldViewPort = driver->getViewPort();
1032 core::matrix4 oldProjMat = driver->getTransform(video::ETS_PROJECTION);
1033 core::matrix4 oldViewMat = driver->getTransform(video::ETS_VIEW);
1034 core::rect<s32> viewrect = rect;
1036 viewrect.clipAgainst(*clip);
1038 core::matrix4 ProjMatrix;
1039 ProjMatrix.buildProjectionMatrixOrthoLH(2.0f, 2.0f, -1.0f, 100.0f);
1041 core::matrix4 ViewMatrix;
1042 ViewMatrix.buildProjectionMatrixOrthoLH(
1043 2.0f * viewrect.getWidth() / rect.getWidth(),
1044 2.0f * viewrect.getHeight() / rect.getHeight(),
1047 ViewMatrix.setTranslation(core::vector3df(
1048 1.0f * (rect.LowerRightCorner.X + rect.UpperLeftCorner.X -
1049 viewrect.LowerRightCorner.X - viewrect.UpperLeftCorner.X) /
1050 viewrect.getWidth(),
1051 1.0f * (viewrect.LowerRightCorner.Y + viewrect.UpperLeftCorner.Y -
1052 rect.LowerRightCorner.Y - rect.UpperLeftCorner.Y) /
1053 viewrect.getHeight(),
1056 driver->setTransform(video::ETS_PROJECTION, ProjMatrix);
1057 driver->setTransform(video::ETS_VIEW, ViewMatrix);
1059 core::matrix4 matrix;
1060 matrix.makeIdentity();
1062 if (enable_animations) {
1063 float timer_f = (float) delta / 5000.f;
1064 matrix.setRotationDegrees(v3f(
1065 angle.X + rotation_speed.X * 3.60f * timer_f,
1066 angle.Y + rotation_speed.Y * 3.60f * timer_f,
1067 angle.Z + rotation_speed.Z * 3.60f * timer_f)
1071 driver->setTransform(video::ETS_WORLD, matrix);
1072 driver->setViewPort(viewrect);
1074 video::SColor basecolor =
1075 client->idef()->getItemstackColor(item, client);
1077 u32 mc = mesh->getMeshBufferCount();
1078 for (u32 j = 0; j < mc; ++j) {
1079 scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
1080 // we can modify vertices relatively fast,
1081 // because these meshes are not buffered.
1082 assert(buf->getHardwareMappingHint_Vertex() == scene::EHM_NEVER);
1083 video::SColor c = basecolor;
1085 if (imesh->buffer_colors.size() > j) {
1086 ItemPartColor *p = &imesh->buffer_colors[j];
1087 if (p->override_base)
1091 if (imesh->needs_shading)
1092 colorizeMeshBuffer(buf, &c);
1094 setMeshBufferColor(buf, c);
1096 video::SMaterial &material = buf->getMaterial();
1097 material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
1098 material.Lighting = false;
1099 driver->setMaterial(material);
1100 driver->drawMeshBuffer(buf);
1103 driver->setTransform(video::ETS_VIEW, oldViewMat);
1104 driver->setTransform(video::ETS_PROJECTION, oldProjMat);
1105 driver->setViewPort(oldViewPort);
1107 draw_overlay = def.type == ITEM_NODE && def.inventory_image.empty();
1108 } else { // Otherwise just draw as 2D
1109 video::ITexture *texture = client->idef()->getInventoryTexture(def.name, client);
1110 video::SColor color;
1112 color = client->idef()->getItemstackColor(item, client);
1114 color = video::SColor(255, 255, 255, 255);
1115 ITextureSource *tsrc = client->getTextureSource();
1116 texture = tsrc->getTexture("no_texture.png");
1121 const video::SColor colors[] = { color, color, color, color };
1123 draw2DImageFilterScaled(driver, texture, rect,
1124 core::rect<s32>({0, 0}, core::dimension2di(texture->getOriginalSize())),
1125 clip, colors, true);
1127 draw_overlay = true;
1130 // draw the inventory_overlay
1131 if (!def.inventory_overlay.empty() && draw_overlay) {
1132 ITextureSource *tsrc = client->getTextureSource();
1133 video::ITexture *overlay_texture = tsrc->getTexture(def.inventory_overlay);
1134 core::dimension2d<u32> dimens = overlay_texture->getOriginalSize();
1135 core::rect<s32> srcrect(0, 0, dimens.Width, dimens.Height);
1136 draw2DImageFilterScaled(driver, overlay_texture, rect, srcrect, clip, 0, true);
1139 if (def.type == ITEM_TOOL && item.wear != 0) {
1140 // Draw a progressbar
1141 float barheight = static_cast<float>(rect.getHeight()) / 16;
1142 float barpad_x = static_cast<float>(rect.getWidth()) / 16;
1143 float barpad_y = static_cast<float>(rect.getHeight()) / 16;
1145 core::rect<s32> progressrect(
1146 rect.UpperLeftCorner.X + barpad_x,
1147 rect.LowerRightCorner.Y - barpad_y - barheight,
1148 rect.LowerRightCorner.X - barpad_x,
1149 rect.LowerRightCorner.Y - barpad_y);
1151 // Shrink progressrect by amount of tool damage
1152 float wear = item.wear / 65535.0f;
1154 wear * progressrect.UpperLeftCorner.X +
1155 (1 - wear) * progressrect.LowerRightCorner.X;
1157 // Compute progressbar color
1158 // wear = 0.0: green
1159 // wear = 0.5: yellow
1161 video::SColor color(255, 255, 255, 255);
1162 int wear_i = MYMIN(std::floor(wear * 600), 511);
1163 wear_i = MYMIN(wear_i + 10, 511);
1166 color.set(255, wear_i, 255, 0);
1168 color.set(255, 255, 511 - wear_i, 0);
1170 core::rect<s32> progressrect2 = progressrect;
1171 progressrect2.LowerRightCorner.X = progressmid;
1172 driver->draw2DRectangle(color, progressrect2, clip);
1174 color = video::SColor(255, 0, 0, 0);
1175 progressrect2 = progressrect;
1176 progressrect2.UpperLeftCorner.X = progressmid;
1177 driver->draw2DRectangle(color, progressrect2, clip);
1180 if (font != NULL && item.count >= 2) {
1181 // Get the item count as a string
1182 std::string text = itos(item.count);
1183 v2u32 dim = font->getDimension(utf8_to_wide(text).c_str());
1184 v2s32 sdim(dim.X, dim.Y);
1186 core::rect<s32> rect2(
1187 /*rect.UpperLeftCorner,
1188 core::dimension2d<u32>(rect.getWidth(), 15)*/
1189 rect.LowerRightCorner - sdim,
1193 video::SColor bgcolor(128, 0, 0, 0);
1194 driver->draw2DRectangle(bgcolor, rect2, clip);
1196 video::SColor color(255, 255, 255, 255);
1197 font->draw(text.c_str(), rect2, color, false, false, clip);
1202 video::IVideoDriver *driver,
1203 gui::IGUIFont *font,
1204 const ItemStack &item,
1205 const core::rect<s32> &rect,
1206 const core::rect<s32> *clip,
1208 ItemRotationKind rotation_kind)
1210 drawItemStack(driver, font, item, rect, clip, client, rotation_kind,
1211 v3s16(0, 0, 0), v3s16(0, 100, 0));