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 highlighting 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 const u32 text_height = g_fontengine->getTextHeight();
335 gui::IGUIFont *const 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 unsigned int font_size = g_fontengine->getDefaultFontSize();
362 font_size *= e->size.X;
365 // The text size on Android is not proportional with the actual scaling
366 // FIXME: why do we have such a weird unportable hack??
367 if (font_size > 3 && e->offset.X < -20)
370 auto textfont = g_fontengine->getFont(FontSpec(font_size,
371 (e->style & HUD_STYLE_MONO) ? FM_Mono : FM_Unspecified,
372 e->style & HUD_STYLE_BOLD, e->style & HUD_STYLE_ITALIC));
374 video::SColor color(255, (e->number >> 16) & 0xFF,
375 (e->number >> 8) & 0xFF,
376 (e->number >> 0) & 0xFF);
377 std::wstring text = unescape_translate(utf8_to_wide(e->text));
378 core::dimension2d<u32> textsize = textfont->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);
388 textfont->draw(text.c_str(), size + pos + offset + offs, color);
391 case HUD_ELEM_STATBAR: {
392 v2s32 offs(e->offset.X, e->offset.Y);
393 drawStatbar(pos, HUD_CORNER_UPPER, e->dir, e->text, e->text2,
394 e->number, e->item, offs, e->size);
396 case HUD_ELEM_INVENTORY: {
397 InventoryList *inv = inventory->getList(e->text);
398 drawItems(pos, v2s32(e->offset.X, e->offset.Y), e->number, 0,
399 inv, e->item, e->dir);
401 case HUD_ELEM_WAYPOINT: {
402 if (!calculateScreenPos(camera_offset, e, &pos))
404 v3f p_pos = player->getPosition() / BS;
405 pos += v2s32(e->offset.X, e->offset.Y);
406 video::SColor color(255, (e->number >> 16) & 0xFF,
407 (e->number >> 8) & 0xFF,
408 (e->number >> 0) & 0xFF);
409 std::wstring text = unescape_translate(utf8_to_wide(e->name));
410 const std::string &unit = e->text;
411 // waypoints reuse the item field to store precision, item = precision + 1
413 float precision = (item == 0) ? 10.0f : (item - 1.f);
414 bool draw_precision = precision > 0;
416 core::rect<s32> bounds(0, 0, font->getDimension(text.c_str()).Width, (draw_precision ? 2:1) * text_height);
417 pos.Y += (e->align.Y - 1.0) * bounds.getHeight() / 2;
419 font->draw(text.c_str(), bounds + v2s32((e->align.X - 1.0) * bounds.getWidth() / 2, 0), color);
420 if (draw_precision) {
421 std::ostringstream os;
422 float distance = std::floor(precision * p_pos.getDistanceFrom(e->world_pos)) / precision;
423 os << distance << unit;
424 text = unescape_translate(utf8_to_wide(os.str()));
425 bounds.LowerRightCorner.X = bounds.UpperLeftCorner.X + font->getDimension(text.c_str()).Width;
426 font->draw(text.c_str(), bounds + v2s32((e->align.X - 1.0f) * bounds.getWidth() / 2, text_height), color);
429 case HUD_ELEM_IMAGE_WAYPOINT: {
430 if (!calculateScreenPos(camera_offset, e, &pos))
433 case HUD_ELEM_IMAGE: {
434 video::ITexture *texture = tsrc->getTexture(e->text);
438 const video::SColor color(255, 255, 255, 255);
439 const video::SColor colors[] = {color, color, color, color};
440 core::dimension2di imgsize(texture->getOriginalSize());
441 v2s32 dstsize(imgsize.Width * e->scale.X * m_scale_factor,
442 imgsize.Height * e->scale.Y * m_scale_factor);
444 dstsize.X = m_screensize.X * (e->scale.X * -0.01);
446 dstsize.Y = m_screensize.Y * (e->scale.Y * -0.01);
447 v2s32 offset((e->align.X - 1.0) * dstsize.X / 2,
448 (e->align.Y - 1.0) * dstsize.Y / 2);
449 core::rect<s32> rect(0, 0, dstsize.X, dstsize.Y);
450 rect += pos + offset + v2s32(e->offset.X * m_scale_factor,
451 e->offset.Y * m_scale_factor);
452 draw2DImageFilterScaled(driver, texture, rect,
453 core::rect<s32>(core::position2d<s32>(0,0), imgsize),
456 case HUD_ELEM_COMPASS: {
457 video::ITexture *texture = tsrc->getTexture(e->text);
462 v2s32 dstsize(e->size.X, e->size.Y);
464 dstsize.X = m_screensize.X * (e->size.X * -0.01);
466 dstsize.Y = m_screensize.Y * (e->size.Y * -0.01);
468 if (dstsize.X <= 0 || dstsize.Y <= 0)
469 return; // Avoid zero divides
471 // Angle according to camera view
472 v3f fore(0.f, 0.f, 1.f);
473 scene::ICameraSceneNode *cam = client->getSceneManager()->getActiveCamera();
474 cam->getAbsoluteTransformation().rotateVect(fore);
475 int angle = - fore.getHorizontalAngle().Y;
477 // Limit angle and ajust with given offset
478 angle = (angle + (int)e->number) % 360;
480 core::rect<s32> dstrect(0, 0, dstsize.X, dstsize.Y);
481 dstrect += pos + v2s32(
482 (e->align.X - 1.0) * dstsize.X / 2,
483 (e->align.Y - 1.0) * dstsize.Y / 2) +
484 v2s32(e->offset.X * m_hud_scaling, e->offset.Y * m_hud_scaling);
487 case HUD_COMPASS_ROTATE:
488 drawCompassRotate(e, texture, dstrect, angle);
490 case HUD_COMPASS_ROTATE_REVERSE:
491 drawCompassRotate(e, texture, dstrect, -angle);
493 case HUD_COMPASS_TRANSLATE:
494 drawCompassTranslate(e, texture, dstrect, angle);
496 case HUD_COMPASS_TRANSLATE_REVERSE:
497 drawCompassTranslate(e, texture, dstrect, -angle);
503 case HUD_ELEM_MINIMAP: {
504 if (e->size.X <= 0 || e->size.Y <= 0)
506 if (!client->getMinimap())
508 // Draw a minimap of size "size"
509 v2s32 dstsize(e->size.X * m_scale_factor,
510 e->size.Y * m_scale_factor);
511 // (no percent size as minimap would likely be anamorphosed)
512 v2s32 offset((e->align.X - 1.0) * dstsize.X / 2,
513 (e->align.Y - 1.0) * dstsize.Y / 2);
514 core::rect<s32> rect(0, 0, dstsize.X, dstsize.Y);
515 rect += pos + offset + v2s32(e->offset.X * m_scale_factor,
516 e->offset.Y * m_scale_factor);
517 client->getMinimap()->drawMinimap(rect);
520 infostream << "Hud::drawLuaElements: ignoring drawform " << e->type
521 << " due to unrecognized type" << std::endl;
526 void Hud::drawCompassTranslate(HudElement *e, video::ITexture *texture,
527 const core::rect<s32> &rect, int angle)
529 const video::SColor color(255, 255, 255, 255);
530 const video::SColor colors[] = {color, color, color, color};
532 // Compute source image scaling
533 core::dimension2di imgsize(texture->getOriginalSize());
534 core::rect<s32> srcrect(0, 0, imgsize.Width, imgsize.Height);
536 v2s32 dstsize(rect.getHeight() * e->scale.X * imgsize.Width / imgsize.Height,
537 rect.getHeight() * e->scale.Y);
539 // Avoid infinite loop
540 if (dstsize.X <= 0 || dstsize.Y <= 0)
543 core::rect<s32> tgtrect(0, 0, dstsize.X, dstsize.Y);
545 (rect.getWidth() - dstsize.X) / 2,
546 (rect.getHeight() - dstsize.Y) / 2) +
547 rect.UpperLeftCorner;
549 int offset = angle * dstsize.X / 360;
551 tgtrect += v2s32(offset, 0);
553 // Repeat image as much as needed
554 while (tgtrect.UpperLeftCorner.X > rect.UpperLeftCorner.X)
555 tgtrect -= v2s32(dstsize.X, 0);
557 draw2DImageFilterScaled(driver, texture, tgtrect, srcrect, &rect, colors, true);
558 tgtrect += v2s32(dstsize.X, 0);
560 while (tgtrect.UpperLeftCorner.X < rect.LowerRightCorner.X) {
561 draw2DImageFilterScaled(driver, texture, tgtrect, srcrect, &rect, colors, true);
562 tgtrect += v2s32(dstsize.X, 0);
566 void Hud::drawCompassRotate(HudElement *e, video::ITexture *texture,
567 const core::rect<s32> &rect, int angle)
569 core::rect<s32> oldViewPort = driver->getViewPort();
570 core::matrix4 oldProjMat = driver->getTransform(video::ETS_PROJECTION);
571 core::matrix4 oldViewMat = driver->getTransform(video::ETS_VIEW);
573 core::matrix4 Matrix;
574 Matrix.makeIdentity();
575 Matrix.setRotationDegrees(v3f(0.f, 0.f, angle));
577 driver->setViewPort(rect);
578 driver->setTransform(video::ETS_PROJECTION, core::matrix4());
579 driver->setTransform(video::ETS_VIEW, core::matrix4());
580 driver->setTransform(video::ETS_WORLD, Matrix);
582 video::SMaterial &material = m_rotation_mesh_buffer.getMaterial();
583 material.TextureLayer[0].Texture = texture;
584 driver->setMaterial(material);
585 driver->drawMeshBuffer(&m_rotation_mesh_buffer);
587 driver->setTransform(video::ETS_WORLD, core::matrix4());
588 driver->setTransform(video::ETS_VIEW, oldViewMat);
589 driver->setTransform(video::ETS_PROJECTION, oldProjMat);
591 // restore the view area
592 driver->setViewPort(oldViewPort);
595 void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir,
596 const std::string &texture, const std::string &bgtexture,
597 s32 count, s32 maxcount, v2s32 offset, v2s32 size)
599 const video::SColor color(255, 255, 255, 255);
600 const video::SColor colors[] = {color, color, color, color};
602 video::ITexture *stat_texture = tsrc->getTexture(texture);
606 video::ITexture *stat_texture_bg = nullptr;
607 if (!bgtexture.empty()) {
608 stat_texture_bg = tsrc->getTexture(bgtexture);
611 core::dimension2di srcd(stat_texture->getOriginalSize());
612 core::dimension2di dstd;
613 if (size == v2s32()) {
615 dstd.Height *= m_scale_factor;
616 dstd.Width *= m_scale_factor;
617 offset.X *= m_scale_factor;
618 offset.Y *= m_scale_factor;
620 dstd.Height = size.Y * m_scale_factor;
621 dstd.Width = size.X * m_scale_factor;
622 offset.X *= m_scale_factor;
623 offset.Y *= m_scale_factor;
627 if (corner & HUD_CORNER_LOWER)
634 case HUD_DIR_RIGHT_LEFT:
635 steppos = v2s32(-1, 0);
637 case HUD_DIR_TOP_BOTTOM:
638 steppos = v2s32(0, 1);
640 case HUD_DIR_BOTTOM_TOP:
641 steppos = v2s32(0, -1);
644 // From left to right
645 steppos = v2s32(1, 0);
649 auto calculate_clipping_rect = [] (core::dimension2di src,
650 v2s32 steppos) -> core::rect<s32> {
652 // Create basic rectangle
653 core::rect<s32> rect(0, 0,
654 src.Width - std::abs(steppos.X) * src.Width / 2,
655 src.Height - std::abs(steppos.Y) * src.Height / 2
657 // Move rectangle left or down
659 rect += v2s32(src.Width / 2, 0);
661 rect += v2s32(0, src.Height / 2);
664 // Rectangles for 1/2 the actual value to display
665 core::rect<s32> srchalfrect, dsthalfrect;
666 // Rectangles for 1/2 the "off state" texture
667 core::rect<s32> srchalfrect2, dsthalfrect2;
669 if (count % 2 == 1) {
670 // Need to draw halves: Calculate rectangles
671 srchalfrect = calculate_clipping_rect(srcd, steppos);
672 dsthalfrect = calculate_clipping_rect(dstd, steppos);
673 srchalfrect2 = calculate_clipping_rect(srcd, steppos * -1);
674 dsthalfrect2 = calculate_clipping_rect(dstd, steppos * -1);
677 steppos.X *= dstd.Width;
678 steppos.Y *= dstd.Height;
680 // Draw full textures
681 for (s32 i = 0; i < count / 2; i++) {
682 core::rect<s32> srcrect(0, 0, srcd.Width, srcd.Height);
683 core::rect<s32> dstrect(0, 0, dstd.Width, dstd.Height);
686 draw2DImageFilterScaled(driver, stat_texture,
687 dstrect, srcrect, NULL, colors, true);
691 if (count % 2 == 1) {
692 // Draw half a texture
693 draw2DImageFilterScaled(driver, stat_texture,
694 dsthalfrect + p, srchalfrect, NULL, colors, true);
696 if (stat_texture_bg && maxcount > count) {
697 draw2DImageFilterScaled(driver, stat_texture_bg,
698 dsthalfrect2 + p, srchalfrect2,
704 if (stat_texture_bg && maxcount > count / 2) {
705 // Draw "off state" textures
708 start_offset = count / 2 + 1;
710 start_offset = count / 2;
711 for (s32 i = start_offset; i < maxcount / 2; i++) {
712 core::rect<s32> srcrect(0, 0, srcd.Width, srcd.Height);
713 core::rect<s32> dstrect(0, 0, dstd.Width, dstd.Height);
716 draw2DImageFilterScaled(driver, stat_texture_bg,
722 if (maxcount % 2 == 1) {
723 draw2DImageFilterScaled(driver, stat_texture_bg,
724 dsthalfrect + p, srchalfrect,
731 void Hud::drawHotbar(u16 playeritem) {
733 v2s32 centerlowerpos(m_displaycenter.X, m_screensize.Y);
735 InventoryList *mainlist = inventory->getList("main");
736 if (mainlist == NULL) {
737 //silently ignore this we may not be initialized completely
741 s32 hotbar_itemcount = player->hud_hotbar_itemcount;
742 s32 width = hotbar_itemcount * (m_hotbar_imagesize + m_padding * 2);
743 v2s32 pos = centerlowerpos - v2s32(width / 2, m_hotbar_imagesize + m_padding * 3);
745 const v2u32 &window_size = RenderingEngine::getWindowSize();
746 if ((float) width / (float) window_size.X <=
747 g_settings->getFloat("hud_hotbar_max_width")) {
748 if (player->hud_flags & HUD_FLAG_HOTBAR_VISIBLE) {
749 drawItems(pos, v2s32(0, 0), hotbar_itemcount, 0, mainlist, playeritem + 1, 0);
754 v2s32 secondpos = pos;
755 pos = pos - v2s32(0, m_hotbar_imagesize + m_padding);
757 if (player->hud_flags & HUD_FLAG_HOTBAR_VISIBLE) {
758 drawItems(pos, v2s32(0, 0), hotbar_itemcount / 2, 0,
759 mainlist, playeritem + 1, 0);
760 drawItems(secondpos, v2s32(0, 0), hotbar_itemcount,
761 hotbar_itemcount / 2, mainlist, playeritem + 1, 0);
767 void Hud::drawCrosshair()
769 if (pointing_at_object) {
770 if (use_object_crosshair_image) {
771 video::ITexture *object_crosshair = tsrc->getTexture("object_crosshair.png");
772 v2u32 size = object_crosshair->getOriginalSize();
773 v2s32 lsize = v2s32(m_displaycenter.X - (size.X / 2),
774 m_displaycenter.Y - (size.Y / 2));
775 driver->draw2DImage(object_crosshair, lsize,
776 core::rect<s32>(0, 0, size.X, size.Y),
777 nullptr, crosshair_argb, true);
780 m_displaycenter - v2s32(OBJECT_CROSSHAIR_LINE_SIZE,
781 OBJECT_CROSSHAIR_LINE_SIZE),
782 m_displaycenter + v2s32(OBJECT_CROSSHAIR_LINE_SIZE,
783 OBJECT_CROSSHAIR_LINE_SIZE), crosshair_argb);
785 m_displaycenter + v2s32(OBJECT_CROSSHAIR_LINE_SIZE,
786 -OBJECT_CROSSHAIR_LINE_SIZE),
787 m_displaycenter + v2s32(-OBJECT_CROSSHAIR_LINE_SIZE,
788 OBJECT_CROSSHAIR_LINE_SIZE), crosshair_argb);
794 if (use_crosshair_image) {
795 video::ITexture *crosshair = tsrc->getTexture("crosshair.png");
796 v2u32 size = crosshair->getOriginalSize();
797 v2s32 lsize = v2s32(m_displaycenter.X - (size.X / 2),
798 m_displaycenter.Y - (size.Y / 2));
799 driver->draw2DImage(crosshair, lsize,
800 core::rect<s32>(0, 0, size.X, size.Y),
801 nullptr, crosshair_argb, true);
803 driver->draw2DLine(m_displaycenter - v2s32(CROSSHAIR_LINE_SIZE, 0),
804 m_displaycenter + v2s32(CROSSHAIR_LINE_SIZE, 0), crosshair_argb);
805 driver->draw2DLine(m_displaycenter - v2s32(0, CROSSHAIR_LINE_SIZE),
806 m_displaycenter + v2s32(0, CROSSHAIR_LINE_SIZE), crosshair_argb);
810 void Hud::setSelectionPos(const v3f &pos, const v3s16 &camera_offset)
812 m_camera_offset = camera_offset;
813 m_selection_pos = pos;
814 m_selection_pos_with_offset = pos - intToFloat(camera_offset, BS);
817 void Hud::drawSelectionMesh()
819 if (m_mode == HIGHLIGHT_BOX) {
820 // Draw 3D selection boxes
821 video::SMaterial oldmaterial = driver->getMaterial2D();
822 driver->setMaterial(m_selection_material);
823 for (auto & selection_box : m_selection_boxes) {
825 selection_box.MinEdge + m_selection_pos_with_offset,
826 selection_box.MaxEdge + m_selection_pos_with_offset);
828 u32 r = (selectionbox_argb.getRed() *
829 m_selection_mesh_color.getRed() / 255);
830 u32 g = (selectionbox_argb.getGreen() *
831 m_selection_mesh_color.getGreen() / 255);
832 u32 b = (selectionbox_argb.getBlue() *
833 m_selection_mesh_color.getBlue() / 255);
834 driver->draw3DBox(box, video::SColor(255, r, g, b));
836 driver->setMaterial(oldmaterial);
837 } else if (m_mode == HIGHLIGHT_HALO && m_selection_mesh) {
838 // Draw selection mesh
839 video::SMaterial oldmaterial = driver->getMaterial2D();
840 driver->setMaterial(m_selection_material);
841 setMeshColor(m_selection_mesh, m_selection_mesh_color);
842 video::SColor face_color(0,
843 MYMIN(255, m_selection_mesh_color.getRed() * 1.5),
844 MYMIN(255, m_selection_mesh_color.getGreen() * 1.5),
845 MYMIN(255, m_selection_mesh_color.getBlue() * 1.5));
846 setMeshColorByNormal(m_selection_mesh, m_selected_face_normal,
848 scene::IMesh* mesh = cloneMesh(m_selection_mesh);
849 translateMesh(mesh, m_selection_pos_with_offset);
850 u32 mc = m_selection_mesh->getMeshBufferCount();
851 for (u32 i = 0; i < mc; i++) {
852 scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
853 driver->drawMeshBuffer(buf);
856 driver->setMaterial(oldmaterial);
860 enum Hud::BlockBoundsMode Hud::toggleBlockBounds()
862 m_block_bounds_mode = static_cast<BlockBoundsMode>(m_block_bounds_mode + 1);
864 if (m_block_bounds_mode >= BLOCK_BOUNDS_MAX) {
865 m_block_bounds_mode = BLOCK_BOUNDS_OFF;
867 return m_block_bounds_mode;
870 void Hud::disableBlockBounds()
872 m_block_bounds_mode = BLOCK_BOUNDS_OFF;
875 void Hud::drawBlockBounds()
877 if (m_block_bounds_mode == BLOCK_BOUNDS_OFF) {
881 video::SMaterial old_material = driver->getMaterial2D();
882 driver->setMaterial(m_selection_material);
884 v3s16 pos = player->getStandingNodePos();
887 floorf((float) pos.X / MAP_BLOCKSIZE),
888 floorf((float) pos.Y / MAP_BLOCKSIZE),
889 floorf((float) pos.Z / MAP_BLOCKSIZE)
892 v3f offset = intToFloat(client->getCamera()->getOffset(), BS);
894 s8 radius = m_block_bounds_mode == BLOCK_BOUNDS_NEAR ? 2 : 0;
896 v3f halfNode = v3f(BS, BS, BS) / 2.0f;
898 for (s8 x = -radius; x <= radius; x++)
899 for (s8 y = -radius; y <= radius; y++)
900 for (s8 z = -radius; z <= radius; z++) {
901 v3s16 blockOffset(x, y, z);
904 intToFloat((blockPos + blockOffset) * MAP_BLOCKSIZE, BS) - offset - halfNode,
905 intToFloat(((blockPos + blockOffset) * MAP_BLOCKSIZE) + (MAP_BLOCKSIZE - 1), BS) - offset + halfNode
908 driver->draw3DBox(box, video::SColor(255, 255, 0, 0));
911 driver->setMaterial(old_material);
914 void Hud::updateSelectionMesh(const v3s16 &camera_offset)
916 m_camera_offset = camera_offset;
917 if (m_mode != HIGHLIGHT_HALO)
920 if (m_selection_mesh) {
921 m_selection_mesh->drop();
922 m_selection_mesh = NULL;
925 if (m_selection_boxes.empty()) {
930 // New pointed object, create new mesh.
932 // Texture UV coordinates for selection boxes
933 static f32 texture_uv[24] = {
942 // Use single halo box instead of multiple overlapping boxes.
943 // Temporary solution - problem can be solved with multiple
944 // rendering targets, or some method to remove inner surfaces.
945 // Thats because of halo transparency.
947 aabb3f halo_box(100.0, 100.0, 100.0, -100.0, -100.0, -100.0);
948 m_halo_boxes.clear();
950 for (const auto &selection_box : m_selection_boxes) {
951 halo_box.addInternalBox(selection_box);
954 m_halo_boxes.push_back(halo_box);
955 m_selection_mesh = convertNodeboxesToMesh(
956 m_halo_boxes, texture_uv, 0.5);
959 void Hud::resizeHotbar() {
960 const v2u32 &window_size = RenderingEngine::getWindowSize();
962 if (m_screensize != window_size) {
963 m_hotbar_imagesize = floor(HOTBAR_IMAGE_SIZE *
964 RenderingEngine::getDisplayDensity() + 0.5);
965 m_hotbar_imagesize *= m_hud_scaling;
966 m_padding = m_hotbar_imagesize / 12;
967 m_screensize = window_size;
968 m_displaycenter = v2s32(m_screensize.X/2,m_screensize.Y/2);
972 struct MeshTimeInfo {
974 scene::IMesh *mesh = nullptr;
978 video::IVideoDriver *driver,
980 const ItemStack &item,
981 const core::rect<s32> &rect,
982 const core::rect<s32> *clip,
984 ItemRotationKind rotation_kind,
986 const v3s16 &rotation_speed)
988 static MeshTimeInfo rotation_time_infos[IT_ROT_NONE];
991 if (rotation_kind < IT_ROT_NONE && rotation_kind != IT_ROT_OTHER) {
992 rotation_time_infos[rotation_kind].mesh = NULL;
997 const static thread_local bool enable_animations =
998 g_settings->getBool("inventory_items_animations");
1000 const ItemDefinition &def = item.getDefinition(client->idef());
1002 bool draw_overlay = false;
1004 // Render as mesh if animated or no inventory image
1005 if ((enable_animations && rotation_kind < IT_ROT_NONE) || def.inventory_image.empty()) {
1006 ItemMesh *imesh = client->idef()->getWieldMesh(def.name, client);
1007 if (!imesh || !imesh->mesh)
1009 scene::IMesh *mesh = imesh->mesh;
1010 driver->clearBuffers(video::ECBF_DEPTH);
1012 if (rotation_kind < IT_ROT_NONE) {
1013 MeshTimeInfo &ti = rotation_time_infos[rotation_kind];
1014 if (mesh != ti.mesh && rotation_kind != IT_ROT_OTHER) {
1016 ti.time = porting::getTimeMs();
1018 delta = porting::getDeltaMs(ti.time, porting::getTimeMs()) % 100000;
1021 core::rect<s32> oldViewPort = driver->getViewPort();
1022 core::matrix4 oldProjMat = driver->getTransform(video::ETS_PROJECTION);
1023 core::matrix4 oldViewMat = driver->getTransform(video::ETS_VIEW);
1024 core::rect<s32> viewrect = rect;
1026 viewrect.clipAgainst(*clip);
1028 core::matrix4 ProjMatrix;
1029 ProjMatrix.buildProjectionMatrixOrthoLH(2.0f, 2.0f, -1.0f, 100.0f);
1031 core::matrix4 ViewMatrix;
1032 ViewMatrix.buildProjectionMatrixOrthoLH(
1033 2.0f * viewrect.getWidth() / rect.getWidth(),
1034 2.0f * viewrect.getHeight() / rect.getHeight(),
1037 ViewMatrix.setTranslation(core::vector3df(
1038 1.0f * (rect.LowerRightCorner.X + rect.UpperLeftCorner.X -
1039 viewrect.LowerRightCorner.X - viewrect.UpperLeftCorner.X) /
1040 viewrect.getWidth(),
1041 1.0f * (viewrect.LowerRightCorner.Y + viewrect.UpperLeftCorner.Y -
1042 rect.LowerRightCorner.Y - rect.UpperLeftCorner.Y) /
1043 viewrect.getHeight(),
1046 driver->setTransform(video::ETS_PROJECTION, ProjMatrix);
1047 driver->setTransform(video::ETS_VIEW, ViewMatrix);
1049 core::matrix4 matrix;
1050 matrix.makeIdentity();
1052 if (enable_animations) {
1053 float timer_f = (float) delta / 5000.f;
1054 matrix.setRotationDegrees(v3f(
1055 angle.X + rotation_speed.X * 3.60f * timer_f,
1056 angle.Y + rotation_speed.Y * 3.60f * timer_f,
1057 angle.Z + rotation_speed.Z * 3.60f * timer_f)
1061 driver->setTransform(video::ETS_WORLD, matrix);
1062 driver->setViewPort(viewrect);
1064 video::SColor basecolor =
1065 client->idef()->getItemstackColor(item, client);
1067 u32 mc = mesh->getMeshBufferCount();
1068 for (u32 j = 0; j < mc; ++j) {
1069 scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
1070 // we can modify vertices relatively fast,
1071 // because these meshes are not buffered.
1072 assert(buf->getHardwareMappingHint_Vertex() == scene::EHM_NEVER);
1073 video::SColor c = basecolor;
1075 if (imesh->buffer_colors.size() > j) {
1076 ItemPartColor *p = &imesh->buffer_colors[j];
1077 if (p->override_base)
1081 if (imesh->needs_shading)
1082 colorizeMeshBuffer(buf, &c);
1084 setMeshBufferColor(buf, c);
1086 video::SMaterial &material = buf->getMaterial();
1087 material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
1088 material.Lighting = false;
1089 driver->setMaterial(material);
1090 driver->drawMeshBuffer(buf);
1093 driver->setTransform(video::ETS_VIEW, oldViewMat);
1094 driver->setTransform(video::ETS_PROJECTION, oldProjMat);
1095 driver->setViewPort(oldViewPort);
1097 draw_overlay = def.type == ITEM_NODE && def.inventory_image.empty();
1098 } else { // Otherwise just draw as 2D
1099 video::ITexture *texture = client->idef()->getInventoryTexture(def.name, client);
1102 video::SColor color =
1103 client->idef()->getItemstackColor(item, client);
1104 const video::SColor colors[] = { color, color, color, color };
1106 draw2DImageFilterScaled(driver, texture, rect,
1107 core::rect<s32>({0, 0}, core::dimension2di(texture->getOriginalSize())),
1108 clip, colors, true);
1110 draw_overlay = true;
1113 // draw the inventory_overlay
1114 if (!def.inventory_overlay.empty() && draw_overlay) {
1115 ITextureSource *tsrc = client->getTextureSource();
1116 video::ITexture *overlay_texture = tsrc->getTexture(def.inventory_overlay);
1117 core::dimension2d<u32> dimens = overlay_texture->getOriginalSize();
1118 core::rect<s32> srcrect(0, 0, dimens.Width, dimens.Height);
1119 draw2DImageFilterScaled(driver, overlay_texture, rect, srcrect, clip, 0, true);
1122 if (def.type == ITEM_TOOL && item.wear != 0) {
1123 // Draw a progressbar
1124 float barheight = static_cast<float>(rect.getHeight()) / 16;
1125 float barpad_x = static_cast<float>(rect.getWidth()) / 16;
1126 float barpad_y = static_cast<float>(rect.getHeight()) / 16;
1128 core::rect<s32> progressrect(
1129 rect.UpperLeftCorner.X + barpad_x,
1130 rect.LowerRightCorner.Y - barpad_y - barheight,
1131 rect.LowerRightCorner.X - barpad_x,
1132 rect.LowerRightCorner.Y - barpad_y);
1134 // Shrink progressrect by amount of tool damage
1135 float wear = item.wear / 65535.0f;
1137 wear * progressrect.UpperLeftCorner.X +
1138 (1 - wear) * progressrect.LowerRightCorner.X;
1140 // Compute progressbar color
1141 // wear = 0.0: green
1142 // wear = 0.5: yellow
1144 video::SColor color(255, 255, 255, 255);
1145 int wear_i = MYMIN(std::floor(wear * 600), 511);
1146 wear_i = MYMIN(wear_i + 10, 511);
1149 color.set(255, wear_i, 255, 0);
1151 color.set(255, 255, 511 - wear_i, 0);
1153 core::rect<s32> progressrect2 = progressrect;
1154 progressrect2.LowerRightCorner.X = progressmid;
1155 driver->draw2DRectangle(color, progressrect2, clip);
1157 color = video::SColor(255, 0, 0, 0);
1158 progressrect2 = progressrect;
1159 progressrect2.UpperLeftCorner.X = progressmid;
1160 driver->draw2DRectangle(color, progressrect2, clip);
1163 if (font != NULL && item.count >= 2) {
1164 // Get the item count as a string
1165 std::string text = itos(item.count);
1166 v2u32 dim = font->getDimension(utf8_to_wide(text).c_str());
1167 v2s32 sdim(dim.X, dim.Y);
1169 core::rect<s32> rect2(
1170 /*rect.UpperLeftCorner,
1171 core::dimension2d<u32>(rect.getWidth(), 15)*/
1172 rect.LowerRightCorner - sdim,
1176 video::SColor bgcolor(128, 0, 0, 0);
1177 driver->draw2DRectangle(bgcolor, rect2, clip);
1179 video::SColor color(255, 255, 255, 255);
1180 font->draw(text.c_str(), rect2, color, false, false, clip);
1185 video::IVideoDriver *driver,
1186 gui::IGUIFont *font,
1187 const ItemStack &item,
1188 const core::rect<s32> &rect,
1189 const core::rect<s32> *clip,
1191 ItemRotationKind rotation_kind)
1193 drawItemStack(driver, font, item, rect, clip, client, rotation_kind,
1194 v3s16(0, 0, 0), v3s16(0, 100, 0));