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 highlighting 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 // mainlist can be NULL, but draw the frame anyway.
228 void Hud::drawItems(v2s32 upperleftpos, v2s32 screen_offset, s32 itemcount,
229 s32 inv_offset, InventoryList *mainlist, u16 selectitem, u16 direction)
231 #ifdef HAVE_TOUCHSCREENGUI
232 if (g_touchscreengui && inv_offset == 0)
233 g_touchscreengui->resetHud();
236 s32 height = m_hotbar_imagesize + m_padding * 2;
237 s32 width = (itemcount - inv_offset) * (m_hotbar_imagesize + m_padding * 2);
239 if (direction == HUD_DIR_TOP_BOTTOM || direction == HUD_DIR_BOTTOM_TOP) {
245 // Position of upper left corner of bar
246 v2s32 pos = screen_offset * m_scale_factor;
249 // Store hotbar_image in member variable, used by drawItem()
250 if (hotbar_image != player->hotbar_image) {
251 hotbar_image = player->hotbar_image;
252 use_hotbar_image = !hotbar_image.empty();
255 // Store hotbar_selected_image in member variable, used by drawItem()
256 if (hotbar_selected_image != player->hotbar_selected_image) {
257 hotbar_selected_image = player->hotbar_selected_image;
258 use_hotbar_selected_image = !hotbar_selected_image.empty();
261 // draw customized item background
262 if (use_hotbar_image) {
263 core::rect<s32> imgrect2(-m_padding/2, -m_padding/2,
264 width+m_padding/2, height+m_padding/2);
265 core::rect<s32> rect2 = imgrect2 + pos;
266 video::ITexture *texture = tsrc->getTexture(hotbar_image);
267 core::dimension2di imgsize(texture->getOriginalSize());
268 draw2DImageFilterScaled(driver, texture, rect2,
269 core::rect<s32>(core::position2d<s32>(0,0), imgsize),
270 NULL, hbar_colors, true);
274 core::rect<s32> imgrect(0, 0, m_hotbar_imagesize, m_hotbar_imagesize);
275 const s32 list_size = mainlist ? mainlist->getSize() : 0;
276 for (s32 i = inv_offset; i < itemcount && i < list_size; i++) {
277 s32 fullimglen = m_hotbar_imagesize + m_padding * 2;
281 case HUD_DIR_RIGHT_LEFT:
282 steppos = v2s32(-(m_padding + (i - inv_offset) * fullimglen), m_padding);
284 case HUD_DIR_TOP_BOTTOM:
285 steppos = v2s32(m_padding, m_padding + (i - inv_offset) * fullimglen);
287 case HUD_DIR_BOTTOM_TOP:
288 steppos = v2s32(m_padding, -(m_padding + (i - inv_offset) * fullimglen));
291 steppos = v2s32(m_padding + (i - inv_offset) * fullimglen, m_padding);
295 drawItem(mainlist->getItem(i), (imgrect + pos + steppos), (i + 1) == selectitem);
297 #ifdef HAVE_TOUCHSCREENGUI
298 if (g_touchscreengui)
299 g_touchscreengui->registerHudItem(i, (imgrect + pos + steppos));
304 bool Hud::hasElementOfType(HudElementType type)
306 for (size_t i = 0; i != player->maxHudId(); i++) {
307 HudElement *e = player->getHud(i);
316 // Calculates screen position of waypoint. Returns true if waypoint is visible (in front of the player), else false.
317 bool Hud::calculateScreenPos(const v3s16 &camera_offset, HudElement *e, v2s32 *pos)
319 v3f w_pos = e->world_pos * BS;
320 scene::ICameraSceneNode* camera =
321 client->getSceneManager()->getActiveCamera();
322 w_pos -= intToFloat(camera_offset, BS);
323 core::matrix4 trans = camera->getProjectionMatrix();
324 trans *= camera->getViewMatrix();
325 f32 transformed_pos[4] = { w_pos.X, w_pos.Y, w_pos.Z, 1.0f };
326 trans.multiplyWith1x4Matrix(transformed_pos);
327 if (transformed_pos[3] < 0)
329 f32 zDiv = transformed_pos[3] == 0.0f ? 1.0f :
330 core::reciprocal(transformed_pos[3]);
331 pos->X = m_screensize.X * (0.5 * transformed_pos[0] * zDiv + 0.5);
332 pos->Y = m_screensize.Y * (0.5 - transformed_pos[1] * zDiv * 0.5);
336 void Hud::drawLuaElements(const v3s16 &camera_offset)
338 const u32 text_height = g_fontengine->getTextHeight();
339 gui::IGUIFont *const font = g_fontengine->getFont();
341 // Reorder elements by z_index
342 std::vector<HudElement*> elems;
343 elems.reserve(player->maxHudId());
345 for (size_t i = 0; i != player->maxHudId(); i++) {
346 HudElement *e = player->getHud(i);
350 auto it = elems.begin();
351 while (it != elems.end() && (*it)->z_index <= e->z_index)
357 for (HudElement *e : elems) {
359 v2s32 pos(floor(e->pos.X * (float) m_screensize.X + 0.5),
360 floor(e->pos.Y * (float) m_screensize.Y + 0.5));
362 case HUD_ELEM_TEXT: {
363 unsigned int font_size = g_fontengine->getDefaultFontSize();
366 font_size *= e->size.X;
369 // The text size on Android is not proportional with the actual scaling
370 // FIXME: why do we have such a weird unportable hack??
371 if (font_size > 3 && e->offset.X < -20)
374 auto textfont = g_fontengine->getFont(FontSpec(font_size,
375 (e->style & HUD_STYLE_MONO) ? FM_Mono : FM_Unspecified,
376 e->style & HUD_STYLE_BOLD, e->style & HUD_STYLE_ITALIC));
378 video::SColor color(255, (e->number >> 16) & 0xFF,
379 (e->number >> 8) & 0xFF,
380 (e->number >> 0) & 0xFF);
381 std::wstring text = unescape_translate(utf8_to_wide(e->text));
382 core::dimension2d<u32> textsize = textfont->getDimension(text.c_str());
384 v2s32 offset(0, (e->align.Y - 1.0) * (textsize.Height / 2));
385 core::rect<s32> size(0, 0, e->scale.X * m_scale_factor,
386 text_height * e->scale.Y * m_scale_factor);
387 v2s32 offs(e->offset.X * m_scale_factor,
388 e->offset.Y * m_scale_factor);
389 std::wstringstream wss(text);
391 while (std::getline(wss, line, L'\n'))
393 core::dimension2d<u32> linesize = textfont->getDimension(line.c_str());
394 v2s32 line_offset((e->align.X - 1.0) * (linesize.Width / 2), 0);
395 textfont->draw(line.c_str(), size + pos + offset + offs + line_offset, color);
396 offset.Y += linesize.Height;
399 case HUD_ELEM_STATBAR: {
400 v2s32 offs(e->offset.X, e->offset.Y);
401 drawStatbar(pos, HUD_CORNER_UPPER, e->dir, e->text, e->text2,
402 e->number, e->item, offs, e->size);
404 case HUD_ELEM_INVENTORY: {
405 InventoryList *inv = inventory->getList(e->text);
407 warningstream << "HUD: Unknown inventory list. name=" << e->text << std::endl;
408 drawItems(pos, v2s32(e->offset.X, e->offset.Y), e->number, 0,
409 inv, e->item, e->dir);
411 case HUD_ELEM_WAYPOINT: {
412 if (!calculateScreenPos(camera_offset, e, &pos))
414 v3f p_pos = player->getPosition() / BS;
415 pos += v2s32(e->offset.X, e->offset.Y);
416 video::SColor color(255, (e->number >> 16) & 0xFF,
417 (e->number >> 8) & 0xFF,
418 (e->number >> 0) & 0xFF);
419 std::wstring text = unescape_translate(utf8_to_wide(e->name));
420 const std::string &unit = e->text;
421 // waypoints reuse the item field to store precision, item = precision + 1
423 float precision = (item == 0) ? 10.0f : (item - 1.f);
424 bool draw_precision = precision > 0;
426 core::rect<s32> bounds(0, 0, font->getDimension(text.c_str()).Width, (draw_precision ? 2:1) * text_height);
427 pos.Y += (e->align.Y - 1.0) * bounds.getHeight() / 2;
429 font->draw(text.c_str(), bounds + v2s32((e->align.X - 1.0) * bounds.getWidth() / 2, 0), color);
430 if (draw_precision) {
431 std::ostringstream os;
432 float distance = std::floor(precision * p_pos.getDistanceFrom(e->world_pos)) / precision;
433 os << distance << unit;
434 text = unescape_translate(utf8_to_wide(os.str()));
435 bounds.LowerRightCorner.X = bounds.UpperLeftCorner.X + font->getDimension(text.c_str()).Width;
436 font->draw(text.c_str(), bounds + v2s32((e->align.X - 1.0f) * bounds.getWidth() / 2, text_height), color);
439 case HUD_ELEM_IMAGE_WAYPOINT: {
440 if (!calculateScreenPos(camera_offset, e, &pos))
443 case HUD_ELEM_IMAGE: {
444 video::ITexture *texture = tsrc->getTexture(e->text);
448 const video::SColor color(255, 255, 255, 255);
449 const video::SColor colors[] = {color, color, color, color};
450 core::dimension2di imgsize(texture->getOriginalSize());
451 v2s32 dstsize(imgsize.Width * e->scale.X * m_scale_factor,
452 imgsize.Height * e->scale.Y * m_scale_factor);
454 dstsize.X = m_screensize.X * (e->scale.X * -0.01);
456 dstsize.Y = m_screensize.Y * (e->scale.Y * -0.01);
457 v2s32 offset((e->align.X - 1.0) * dstsize.X / 2,
458 (e->align.Y - 1.0) * dstsize.Y / 2);
459 core::rect<s32> rect(0, 0, dstsize.X, dstsize.Y);
460 rect += pos + offset + v2s32(e->offset.X * m_scale_factor,
461 e->offset.Y * m_scale_factor);
462 draw2DImageFilterScaled(driver, texture, rect,
463 core::rect<s32>(core::position2d<s32>(0,0), imgsize),
466 case HUD_ELEM_COMPASS: {
467 video::ITexture *texture = tsrc->getTexture(e->text);
472 v2s32 dstsize(e->size.X, e->size.Y);
474 dstsize.X = m_screensize.X * (e->size.X * -0.01);
476 dstsize.Y = m_screensize.Y * (e->size.Y * -0.01);
478 if (dstsize.X <= 0 || dstsize.Y <= 0)
479 return; // Avoid zero divides
481 // Angle according to camera view
482 v3f fore(0.f, 0.f, 1.f);
483 scene::ICameraSceneNode *cam = client->getSceneManager()->getActiveCamera();
484 cam->getAbsoluteTransformation().rotateVect(fore);
485 int angle = - fore.getHorizontalAngle().Y;
487 // Limit angle and ajust with given offset
488 angle = (angle + (int)e->number) % 360;
490 core::rect<s32> dstrect(0, 0, dstsize.X, dstsize.Y);
491 dstrect += pos + v2s32(
492 (e->align.X - 1.0) * dstsize.X / 2,
493 (e->align.Y - 1.0) * dstsize.Y / 2) +
494 v2s32(e->offset.X * m_hud_scaling, e->offset.Y * m_hud_scaling);
497 case HUD_COMPASS_ROTATE:
498 drawCompassRotate(e, texture, dstrect, angle);
500 case HUD_COMPASS_ROTATE_REVERSE:
501 drawCompassRotate(e, texture, dstrect, -angle);
503 case HUD_COMPASS_TRANSLATE:
504 drawCompassTranslate(e, texture, dstrect, angle);
506 case HUD_COMPASS_TRANSLATE_REVERSE:
507 drawCompassTranslate(e, texture, dstrect, -angle);
513 case HUD_ELEM_MINIMAP: {
514 if (e->size.X <= 0 || e->size.Y <= 0)
516 if (!client->getMinimap())
518 // Draw a minimap of size "size"
519 v2s32 dstsize(e->size.X * m_scale_factor,
520 e->size.Y * m_scale_factor);
521 // (no percent size as minimap would likely be anamorphosed)
522 v2s32 offset((e->align.X - 1.0) * dstsize.X / 2,
523 (e->align.Y - 1.0) * dstsize.Y / 2);
524 core::rect<s32> rect(0, 0, dstsize.X, dstsize.Y);
525 rect += pos + offset + v2s32(e->offset.X * m_scale_factor,
526 e->offset.Y * m_scale_factor);
527 client->getMinimap()->drawMinimap(rect);
530 infostream << "Hud::drawLuaElements: ignoring drawform " << e->type
531 << " due to unrecognized type" << std::endl;
536 void Hud::drawCompassTranslate(HudElement *e, video::ITexture *texture,
537 const core::rect<s32> &rect, int angle)
539 const video::SColor color(255, 255, 255, 255);
540 const video::SColor colors[] = {color, color, color, color};
542 // Compute source image scaling
543 core::dimension2di imgsize(texture->getOriginalSize());
544 core::rect<s32> srcrect(0, 0, imgsize.Width, imgsize.Height);
546 v2s32 dstsize(rect.getHeight() * e->scale.X * imgsize.Width / imgsize.Height,
547 rect.getHeight() * e->scale.Y);
549 // Avoid infinite loop
550 if (dstsize.X <= 0 || dstsize.Y <= 0)
553 core::rect<s32> tgtrect(0, 0, dstsize.X, dstsize.Y);
555 (rect.getWidth() - dstsize.X) / 2,
556 (rect.getHeight() - dstsize.Y) / 2) +
557 rect.UpperLeftCorner;
559 int offset = angle * dstsize.X / 360;
561 tgtrect += v2s32(offset, 0);
563 // Repeat image as much as needed
564 while (tgtrect.UpperLeftCorner.X > rect.UpperLeftCorner.X)
565 tgtrect -= v2s32(dstsize.X, 0);
567 draw2DImageFilterScaled(driver, texture, tgtrect, srcrect, &rect, colors, true);
568 tgtrect += v2s32(dstsize.X, 0);
570 while (tgtrect.UpperLeftCorner.X < rect.LowerRightCorner.X) {
571 draw2DImageFilterScaled(driver, texture, tgtrect, srcrect, &rect, colors, true);
572 tgtrect += v2s32(dstsize.X, 0);
576 void Hud::drawCompassRotate(HudElement *e, video::ITexture *texture,
577 const core::rect<s32> &rect, int angle)
579 core::rect<s32> oldViewPort = driver->getViewPort();
580 core::matrix4 oldProjMat = driver->getTransform(video::ETS_PROJECTION);
581 core::matrix4 oldViewMat = driver->getTransform(video::ETS_VIEW);
583 core::matrix4 Matrix;
584 Matrix.makeIdentity();
585 Matrix.setRotationDegrees(v3f(0.f, 0.f, angle));
587 driver->setViewPort(rect);
588 driver->setTransform(video::ETS_PROJECTION, core::matrix4());
589 driver->setTransform(video::ETS_VIEW, core::matrix4());
590 driver->setTransform(video::ETS_WORLD, Matrix);
592 video::SMaterial &material = m_rotation_mesh_buffer.getMaterial();
593 material.TextureLayer[0].Texture = texture;
594 driver->setMaterial(material);
595 driver->drawMeshBuffer(&m_rotation_mesh_buffer);
597 driver->setTransform(video::ETS_WORLD, core::matrix4());
598 driver->setTransform(video::ETS_VIEW, oldViewMat);
599 driver->setTransform(video::ETS_PROJECTION, oldProjMat);
601 // restore the view area
602 driver->setViewPort(oldViewPort);
605 void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir,
606 const std::string &texture, const std::string &bgtexture,
607 s32 count, s32 maxcount, v2s32 offset, v2s32 size)
609 const video::SColor color(255, 255, 255, 255);
610 const video::SColor colors[] = {color, color, color, color};
612 video::ITexture *stat_texture = tsrc->getTexture(texture);
616 video::ITexture *stat_texture_bg = nullptr;
617 if (!bgtexture.empty()) {
618 stat_texture_bg = tsrc->getTexture(bgtexture);
621 core::dimension2di srcd(stat_texture->getOriginalSize());
622 core::dimension2di dstd;
623 if (size == v2s32()) {
625 dstd.Height *= m_scale_factor;
626 dstd.Width *= m_scale_factor;
627 offset.X *= m_scale_factor;
628 offset.Y *= m_scale_factor;
630 dstd.Height = size.Y * m_scale_factor;
631 dstd.Width = size.X * m_scale_factor;
632 offset.X *= m_scale_factor;
633 offset.Y *= m_scale_factor;
637 if (corner & HUD_CORNER_LOWER)
644 case HUD_DIR_RIGHT_LEFT:
645 steppos = v2s32(-1, 0);
647 case HUD_DIR_TOP_BOTTOM:
648 steppos = v2s32(0, 1);
650 case HUD_DIR_BOTTOM_TOP:
651 steppos = v2s32(0, -1);
654 // From left to right
655 steppos = v2s32(1, 0);
659 auto calculate_clipping_rect = [] (core::dimension2di src,
660 v2s32 steppos) -> core::rect<s32> {
662 // Create basic rectangle
663 core::rect<s32> rect(0, 0,
664 src.Width - std::abs(steppos.X) * src.Width / 2,
665 src.Height - std::abs(steppos.Y) * src.Height / 2
667 // Move rectangle left or down
669 rect += v2s32(src.Width / 2, 0);
671 rect += v2s32(0, src.Height / 2);
674 // Rectangles for 1/2 the actual value to display
675 core::rect<s32> srchalfrect, dsthalfrect;
676 // Rectangles for 1/2 the "off state" texture
677 core::rect<s32> srchalfrect2, dsthalfrect2;
679 if (count % 2 == 1 || maxcount % 2 == 1) {
680 // Need to draw halves: Calculate rectangles
681 srchalfrect = calculate_clipping_rect(srcd, steppos);
682 dsthalfrect = calculate_clipping_rect(dstd, steppos);
683 srchalfrect2 = calculate_clipping_rect(srcd, steppos * -1);
684 dsthalfrect2 = calculate_clipping_rect(dstd, steppos * -1);
687 steppos.X *= dstd.Width;
688 steppos.Y *= dstd.Height;
690 // Draw full textures
691 for (s32 i = 0; i < count / 2; i++) {
692 core::rect<s32> srcrect(0, 0, srcd.Width, srcd.Height);
693 core::rect<s32> dstrect(0, 0, dstd.Width, dstd.Height);
696 draw2DImageFilterScaled(driver, stat_texture,
697 dstrect, srcrect, NULL, colors, true);
701 if (count % 2 == 1) {
702 // Draw half a texture
703 draw2DImageFilterScaled(driver, stat_texture,
704 dsthalfrect + p, srchalfrect, NULL, colors, true);
706 if (stat_texture_bg && maxcount > count) {
707 draw2DImageFilterScaled(driver, stat_texture_bg,
708 dsthalfrect2 + p, srchalfrect2,
714 if (stat_texture_bg && maxcount > count) {
715 // Draw "off state" textures
718 start_offset = count / 2 + 1;
720 start_offset = count / 2;
721 for (s32 i = start_offset; i < maxcount / 2; i++) {
722 core::rect<s32> srcrect(0, 0, srcd.Width, srcd.Height);
723 core::rect<s32> dstrect(0, 0, dstd.Width, dstd.Height);
726 draw2DImageFilterScaled(driver, stat_texture_bg,
732 if (maxcount % 2 == 1) {
733 draw2DImageFilterScaled(driver, stat_texture_bg,
734 dsthalfrect + p, srchalfrect, NULL, colors, true);
740 void Hud::drawHotbar(u16 playeritem) {
742 v2s32 centerlowerpos(m_displaycenter.X, m_screensize.Y);
744 InventoryList *mainlist = inventory->getList("main");
745 if (mainlist == NULL) {
746 //silently ignore this we may not be initialized completely
750 s32 hotbar_itemcount = player->hud_hotbar_itemcount;
751 s32 width = hotbar_itemcount * (m_hotbar_imagesize + m_padding * 2);
752 v2s32 pos = centerlowerpos - v2s32(width / 2, m_hotbar_imagesize + m_padding * 3);
754 const v2u32 &window_size = RenderingEngine::getWindowSize();
755 if ((float) width / (float) window_size.X <=
756 g_settings->getFloat("hud_hotbar_max_width")) {
757 if (player->hud_flags & HUD_FLAG_HOTBAR_VISIBLE) {
758 drawItems(pos, v2s32(0, 0), hotbar_itemcount, 0, mainlist, playeritem + 1, 0);
763 v2s32 secondpos = pos;
764 pos = pos - v2s32(0, m_hotbar_imagesize + m_padding);
766 if (player->hud_flags & HUD_FLAG_HOTBAR_VISIBLE) {
767 drawItems(pos, v2s32(0, 0), hotbar_itemcount / 2, 0,
768 mainlist, playeritem + 1, 0);
769 drawItems(secondpos, v2s32(0, 0), hotbar_itemcount,
770 hotbar_itemcount / 2, mainlist, playeritem + 1, 0);
776 void Hud::drawCrosshair()
778 if (pointing_at_object) {
779 if (use_object_crosshair_image) {
780 video::ITexture *object_crosshair = tsrc->getTexture("object_crosshair.png");
781 v2u32 size = object_crosshair->getOriginalSize();
782 v2s32 lsize = v2s32(m_displaycenter.X - (size.X / 2),
783 m_displaycenter.Y - (size.Y / 2));
784 driver->draw2DImage(object_crosshair, lsize,
785 core::rect<s32>(0, 0, size.X, size.Y),
786 nullptr, crosshair_argb, true);
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);
794 m_displaycenter + v2s32(OBJECT_CROSSHAIR_LINE_SIZE,
795 -OBJECT_CROSSHAIR_LINE_SIZE),
796 m_displaycenter + v2s32(-OBJECT_CROSSHAIR_LINE_SIZE,
797 OBJECT_CROSSHAIR_LINE_SIZE), crosshair_argb);
803 if (use_crosshair_image) {
804 video::ITexture *crosshair = tsrc->getTexture("crosshair.png");
805 v2u32 size = crosshair->getOriginalSize();
806 v2s32 lsize = v2s32(m_displaycenter.X - (size.X / 2),
807 m_displaycenter.Y - (size.Y / 2));
808 driver->draw2DImage(crosshair, lsize,
809 core::rect<s32>(0, 0, size.X, size.Y),
810 nullptr, crosshair_argb, true);
812 driver->draw2DLine(m_displaycenter - v2s32(CROSSHAIR_LINE_SIZE, 0),
813 m_displaycenter + v2s32(CROSSHAIR_LINE_SIZE, 0), crosshair_argb);
814 driver->draw2DLine(m_displaycenter - v2s32(0, CROSSHAIR_LINE_SIZE),
815 m_displaycenter + v2s32(0, CROSSHAIR_LINE_SIZE), crosshair_argb);
819 void Hud::setSelectionPos(const v3f &pos, const v3s16 &camera_offset)
821 m_camera_offset = camera_offset;
822 m_selection_pos = pos;
823 m_selection_pos_with_offset = pos - intToFloat(camera_offset, BS);
826 void Hud::drawSelectionMesh()
828 if (m_mode == HIGHLIGHT_BOX) {
829 // Draw 3D selection boxes
830 video::SMaterial oldmaterial = driver->getMaterial2D();
831 driver->setMaterial(m_selection_material);
832 for (auto & selection_box : m_selection_boxes) {
834 selection_box.MinEdge + m_selection_pos_with_offset,
835 selection_box.MaxEdge + m_selection_pos_with_offset);
837 u32 r = (selectionbox_argb.getRed() *
838 m_selection_mesh_color.getRed() / 255);
839 u32 g = (selectionbox_argb.getGreen() *
840 m_selection_mesh_color.getGreen() / 255);
841 u32 b = (selectionbox_argb.getBlue() *
842 m_selection_mesh_color.getBlue() / 255);
843 driver->draw3DBox(box, video::SColor(255, r, g, b));
845 driver->setMaterial(oldmaterial);
846 } else if (m_mode == HIGHLIGHT_HALO && m_selection_mesh) {
847 // Draw selection mesh
848 video::SMaterial oldmaterial = driver->getMaterial2D();
849 driver->setMaterial(m_selection_material);
850 setMeshColor(m_selection_mesh, m_selection_mesh_color);
851 video::SColor face_color(0,
852 MYMIN(255, m_selection_mesh_color.getRed() * 1.5),
853 MYMIN(255, m_selection_mesh_color.getGreen() * 1.5),
854 MYMIN(255, m_selection_mesh_color.getBlue() * 1.5));
855 setMeshColorByNormal(m_selection_mesh, m_selected_face_normal,
857 scene::IMesh* mesh = cloneMesh(m_selection_mesh);
858 translateMesh(mesh, m_selection_pos_with_offset);
859 u32 mc = m_selection_mesh->getMeshBufferCount();
860 for (u32 i = 0; i < mc; i++) {
861 scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
862 driver->drawMeshBuffer(buf);
865 driver->setMaterial(oldmaterial);
869 enum Hud::BlockBoundsMode Hud::toggleBlockBounds()
871 m_block_bounds_mode = static_cast<BlockBoundsMode>(m_block_bounds_mode + 1);
873 if (m_block_bounds_mode >= BLOCK_BOUNDS_MAX) {
874 m_block_bounds_mode = BLOCK_BOUNDS_OFF;
876 return m_block_bounds_mode;
879 void Hud::disableBlockBounds()
881 m_block_bounds_mode = BLOCK_BOUNDS_OFF;
884 void Hud::drawBlockBounds()
886 if (m_block_bounds_mode == BLOCK_BOUNDS_OFF) {
890 video::SMaterial old_material = driver->getMaterial2D();
891 driver->setMaterial(m_selection_material);
893 v3s16 pos = player->getStandingNodePos();
896 floorf((float) pos.X / MAP_BLOCKSIZE),
897 floorf((float) pos.Y / MAP_BLOCKSIZE),
898 floorf((float) pos.Z / MAP_BLOCKSIZE)
901 v3f offset = intToFloat(client->getCamera()->getOffset(), BS);
903 s8 radius = m_block_bounds_mode == BLOCK_BOUNDS_NEAR ? 2 : 0;
905 v3f halfNode = v3f(BS, BS, BS) / 2.0f;
907 for (s8 x = -radius; x <= radius; x++)
908 for (s8 y = -radius; y <= radius; y++)
909 for (s8 z = -radius; z <= radius; z++) {
910 v3s16 blockOffset(x, y, z);
913 intToFloat((blockPos + blockOffset) * MAP_BLOCKSIZE, BS) - offset - halfNode,
914 intToFloat(((blockPos + blockOffset) * MAP_BLOCKSIZE) + (MAP_BLOCKSIZE - 1), BS) - offset + halfNode
917 driver->draw3DBox(box, video::SColor(255, 255, 0, 0));
920 driver->setMaterial(old_material);
923 void Hud::updateSelectionMesh(const v3s16 &camera_offset)
925 m_camera_offset = camera_offset;
926 if (m_mode != HIGHLIGHT_HALO)
929 if (m_selection_mesh) {
930 m_selection_mesh->drop();
931 m_selection_mesh = NULL;
934 if (m_selection_boxes.empty()) {
939 // New pointed object, create new mesh.
941 // Texture UV coordinates for selection boxes
942 static f32 texture_uv[24] = {
951 // Use single halo box instead of multiple overlapping boxes.
952 // Temporary solution - problem can be solved with multiple
953 // rendering targets, or some method to remove inner surfaces.
954 // Thats because of halo transparency.
956 aabb3f halo_box(100.0, 100.0, 100.0, -100.0, -100.0, -100.0);
957 m_halo_boxes.clear();
959 for (const auto &selection_box : m_selection_boxes) {
960 halo_box.addInternalBox(selection_box);
963 m_halo_boxes.push_back(halo_box);
964 m_selection_mesh = convertNodeboxesToMesh(
965 m_halo_boxes, texture_uv, 0.5);
968 void Hud::resizeHotbar() {
969 const v2u32 &window_size = RenderingEngine::getWindowSize();
971 if (m_screensize != window_size) {
972 m_hotbar_imagesize = floor(HOTBAR_IMAGE_SIZE *
973 RenderingEngine::getDisplayDensity() + 0.5);
974 m_hotbar_imagesize *= m_hud_scaling;
975 m_padding = m_hotbar_imagesize / 12;
976 m_screensize = window_size;
977 m_displaycenter = v2s32(m_screensize.X/2,m_screensize.Y/2);
981 struct MeshTimeInfo {
983 scene::IMesh *mesh = nullptr;
987 video::IVideoDriver *driver,
989 const ItemStack &item,
990 const core::rect<s32> &rect,
991 const core::rect<s32> *clip,
993 ItemRotationKind rotation_kind,
995 const v3s16 &rotation_speed)
997 static MeshTimeInfo rotation_time_infos[IT_ROT_NONE];
1000 if (rotation_kind < IT_ROT_NONE && rotation_kind != IT_ROT_OTHER) {
1001 rotation_time_infos[rotation_kind].mesh = NULL;
1006 const static thread_local bool enable_animations =
1007 g_settings->getBool("inventory_items_animations");
1009 const ItemDefinition &def = item.getDefinition(client->idef());
1011 bool draw_overlay = false;
1013 bool has_mesh = false;
1016 core::rect<s32> viewrect = rect;
1017 if (clip != nullptr)
1018 viewrect.clipAgainst(*clip);
1020 // Render as mesh if animated or no inventory image
1021 if ((enable_animations && rotation_kind < IT_ROT_NONE) || def.inventory_image.empty()) {
1022 imesh = client->idef()->getWieldMesh(def.name, client);
1023 has_mesh = imesh && imesh->mesh;
1026 scene::IMesh *mesh = imesh->mesh;
1027 driver->clearBuffers(video::ECBF_DEPTH);
1029 if (rotation_kind < IT_ROT_NONE) {
1030 MeshTimeInfo &ti = rotation_time_infos[rotation_kind];
1031 if (mesh != ti.mesh && rotation_kind != IT_ROT_OTHER) {
1033 ti.time = porting::getTimeMs();
1035 delta = porting::getDeltaMs(ti.time, porting::getTimeMs()) % 100000;
1038 core::rect<s32> oldViewPort = driver->getViewPort();
1039 core::matrix4 oldProjMat = driver->getTransform(video::ETS_PROJECTION);
1040 core::matrix4 oldViewMat = driver->getTransform(video::ETS_VIEW);
1042 core::matrix4 ProjMatrix;
1043 ProjMatrix.buildProjectionMatrixOrthoLH(2.0f, 2.0f, -1.0f, 100.0f);
1045 core::matrix4 ViewMatrix;
1046 ViewMatrix.buildProjectionMatrixOrthoLH(
1047 2.0f * viewrect.getWidth() / rect.getWidth(),
1048 2.0f * viewrect.getHeight() / rect.getHeight(),
1051 ViewMatrix.setTranslation(core::vector3df(
1052 1.0f * (rect.LowerRightCorner.X + rect.UpperLeftCorner.X -
1053 viewrect.LowerRightCorner.X - viewrect.UpperLeftCorner.X) /
1054 viewrect.getWidth(),
1055 1.0f * (viewrect.LowerRightCorner.Y + viewrect.UpperLeftCorner.Y -
1056 rect.LowerRightCorner.Y - rect.UpperLeftCorner.Y) /
1057 viewrect.getHeight(),
1060 driver->setTransform(video::ETS_PROJECTION, ProjMatrix);
1061 driver->setTransform(video::ETS_VIEW, ViewMatrix);
1063 core::matrix4 matrix;
1064 matrix.makeIdentity();
1066 if (enable_animations) {
1067 float timer_f = (float) delta / 5000.f;
1068 matrix.setRotationDegrees(v3f(
1069 angle.X + rotation_speed.X * 3.60f * timer_f,
1070 angle.Y + rotation_speed.Y * 3.60f * timer_f,
1071 angle.Z + rotation_speed.Z * 3.60f * timer_f)
1075 driver->setTransform(video::ETS_WORLD, matrix);
1076 driver->setViewPort(viewrect);
1078 video::SColor basecolor =
1079 client->idef()->getItemstackColor(item, client);
1081 u32 mc = mesh->getMeshBufferCount();
1082 for (u32 j = 0; j < mc; ++j) {
1083 scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
1084 // we can modify vertices relatively fast,
1085 // because these meshes are not buffered.
1086 assert(buf->getHardwareMappingHint_Vertex() == scene::EHM_NEVER);
1087 video::SColor c = basecolor;
1089 if (imesh->buffer_colors.size() > j) {
1090 ItemPartColor *p = &imesh->buffer_colors[j];
1091 if (p->override_base)
1095 if (imesh->needs_shading)
1096 colorizeMeshBuffer(buf, &c);
1098 setMeshBufferColor(buf, c);
1100 video::SMaterial &material = buf->getMaterial();
1101 material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
1102 material.Lighting = false;
1103 driver->setMaterial(material);
1104 driver->drawMeshBuffer(buf);
1107 driver->setTransform(video::ETS_VIEW, oldViewMat);
1108 driver->setTransform(video::ETS_PROJECTION, oldProjMat);
1109 driver->setViewPort(oldViewPort);
1111 draw_overlay = def.type == ITEM_NODE && def.inventory_image.empty();
1112 } else { // Otherwise just draw as 2D
1113 video::ITexture *texture = client->idef()->getInventoryTexture(def.name, client);
1114 video::SColor color;
1116 color = client->idef()->getItemstackColor(item, client);
1118 color = video::SColor(255, 255, 255, 255);
1119 ITextureSource *tsrc = client->getTextureSource();
1120 texture = tsrc->getTexture("no_texture.png");
1125 const video::SColor colors[] = { color, color, color, color };
1127 draw2DImageFilterScaled(driver, texture, rect,
1128 core::rect<s32>({0, 0}, core::dimension2di(texture->getOriginalSize())),
1129 clip, colors, true);
1131 draw_overlay = true;
1134 // draw the inventory_overlay
1135 if (!def.inventory_overlay.empty() && draw_overlay) {
1136 ITextureSource *tsrc = client->getTextureSource();
1137 video::ITexture *overlay_texture = tsrc->getTexture(def.inventory_overlay);
1138 core::dimension2d<u32> dimens = overlay_texture->getOriginalSize();
1139 core::rect<s32> srcrect(0, 0, dimens.Width, dimens.Height);
1140 draw2DImageFilterScaled(driver, overlay_texture, rect, srcrect, clip, 0, true);
1143 if (def.type == ITEM_TOOL && item.wear != 0) {
1144 // Draw a progressbar
1145 float barheight = static_cast<float>(rect.getHeight()) / 16;
1146 float barpad_x = static_cast<float>(rect.getWidth()) / 16;
1147 float barpad_y = static_cast<float>(rect.getHeight()) / 16;
1149 core::rect<s32> progressrect(
1150 rect.UpperLeftCorner.X + barpad_x,
1151 rect.LowerRightCorner.Y - barpad_y - barheight,
1152 rect.LowerRightCorner.X - barpad_x,
1153 rect.LowerRightCorner.Y - barpad_y);
1155 // Shrink progressrect by amount of tool damage
1156 float wear = item.wear / 65535.0f;
1158 wear * progressrect.UpperLeftCorner.X +
1159 (1 - wear) * progressrect.LowerRightCorner.X;
1161 // Compute progressbar color
1162 // wear = 0.0: green
1163 // wear = 0.5: yellow
1165 video::SColor color(255, 255, 255, 255);
1166 int wear_i = MYMIN(std::floor(wear * 600), 511);
1167 wear_i = MYMIN(wear_i + 10, 511);
1170 color.set(255, wear_i, 255, 0);
1172 color.set(255, 255, 511 - wear_i, 0);
1174 core::rect<s32> progressrect2 = progressrect;
1175 progressrect2.LowerRightCorner.X = progressmid;
1176 driver->draw2DRectangle(color, progressrect2, clip);
1178 color = video::SColor(255, 0, 0, 0);
1179 progressrect2 = progressrect;
1180 progressrect2.UpperLeftCorner.X = progressmid;
1181 driver->draw2DRectangle(color, progressrect2, clip);
1184 const std::string &count_text = item.metadata.getString("count_meta");
1185 if (font != nullptr && (item.count >= 2 || !count_text.empty())) {
1186 // Get the item count as a string
1187 std::string text = count_text.empty() ? itos(item.count) : count_text;
1188 v2u32 dim = font->getDimension(utf8_to_wide(unescape_enriched(text)).c_str());
1189 v2s32 sdim(dim.X, dim.Y);
1191 core::rect<s32> rect2(
1192 rect.LowerRightCorner - sdim,
1193 rect.LowerRightCorner
1196 // get the count alignment
1197 s32 count_alignment = stoi(item.metadata.getString("count_alignment"));
1198 if (count_alignment != 0) {
1199 s32 a_x = count_alignment & 3;
1200 s32 a_y = (count_alignment >> 2) & 3;
1205 x1 = rect.UpperLeftCorner.X;
1209 x1 = (rect.UpperLeftCorner.X + rect.LowerRightCorner.X - sdim.X) / 2;
1213 x2 = rect.LowerRightCorner.X;
1216 default: // 0 = default
1217 x1 = rect2.UpperLeftCorner.X;
1218 x2 = rect2.LowerRightCorner.X;
1224 y1 = rect.UpperLeftCorner.Y;
1228 y1 = (rect.UpperLeftCorner.Y + rect.LowerRightCorner.Y - sdim.Y) / 2;
1232 y2 = rect.LowerRightCorner.Y;
1235 default: // 0 = default
1236 y1 = rect2.UpperLeftCorner.Y;
1237 y2 = rect2.LowerRightCorner.Y;
1241 rect2 = core::rect<s32>(x1, y1, x2, y2);
1244 video::SColor color(255, 255, 255, 255);
1245 font->draw(utf8_to_wide(text).c_str(), rect2, color, false, false, &viewrect);
1250 video::IVideoDriver *driver,
1251 gui::IGUIFont *font,
1252 const ItemStack &item,
1253 const core::rect<s32> &rect,
1254 const core::rect<s32> *clip,
1256 ItemRotationKind rotation_kind)
1258 drawItemStack(driver, font, item, rect, clip, client, rotation_kind,
1259 v3s16(0, 0, 0), v3s16(0, 100, 0));