diff --git a/build/android/jni/Android.mk b/build/android/jni/Android.mk index d62698ce4..e606ccfc9 100644 --- a/build/android/jni/Android.mk +++ b/build/android/jni/Android.mk @@ -183,6 +183,7 @@ LOCAL_SRC_FILES := \ jni/src/mg_decoration.cpp \ jni/src/mg_ore.cpp \ jni/src/mg_schematic.cpp \ + jni/src/minimap.cpp \ jni/src/mods.cpp \ jni/src/nameidmapping.cpp \ jni/src/nodedef.cpp \ diff --git a/client/shaders/minimap_shader/opengl_fragment.glsl b/client/shaders/minimap_shader/opengl_fragment.glsl new file mode 100644 index 000000000..fa4f9cb1a --- /dev/null +++ b/client/shaders/minimap_shader/opengl_fragment.glsl @@ -0,0 +1,32 @@ +uniform sampler2D baseTexture; +uniform sampler2D normalTexture; +uniform vec3 yawVec; + +void main (void) +{ + vec2 uv = gl_TexCoord[0].st; + + //texture sampling rate + const float step = 1.0 / 256.0; + float tl = texture2D(normalTexture, vec2(uv.x - step, uv.y + step)).r; + float t = texture2D(normalTexture, vec2(uv.x - step, uv.y - step)).r; + float tr = texture2D(normalTexture, vec2(uv.x + step, uv.y + step)).r; + float r = texture2D(normalTexture, vec2(uv.x + step, uv.y)).r; + float br = texture2D(normalTexture, vec2(uv.x + step, uv.y - step)).r; + float b = texture2D(normalTexture, vec2(uv.x, uv.y - step)).r; + float bl = texture2D(normalTexture, vec2(uv.x - step, uv.y - step)).r; + float l = texture2D(normalTexture, vec2(uv.x - step, uv.y)).r; + float dX = (tr + 2.0 * r + br) - (tl + 2.0 * l + bl); + float dY = (bl + 2.0 * b + br) - (tl + 2.0 * t + tr); + vec4 bump = vec4 (normalize(vec3 (dX, dY, 0.1)),1.0); + float height = 2.0 * texture2D(normalTexture, vec2(uv.x, uv.y)).r - 1.0; + vec4 base = texture2D(baseTexture, uv).rgba; + vec3 L = normalize(vec3(0.0, 0.75, 1.0)); + float specular = pow(clamp(dot(reflect(L, bump.xyz), yawVec), 0.0, 1.0), 1.0); + float diffuse = dot(yawVec, bump.xyz); + + vec3 color = (1.1 * diffuse + 0.05 * height + 0.5 * specular) * base.rgb; + vec4 col = vec4(color.rgb, base.a); + col *= gl_Color; + gl_FragColor = vec4(col.rgb, base.a); +} diff --git a/client/shaders/minimap_shader/opengl_vertex.glsl b/client/shaders/minimap_shader/opengl_vertex.glsl new file mode 100644 index 000000000..06df5a3cf --- /dev/null +++ b/client/shaders/minimap_shader/opengl_vertex.glsl @@ -0,0 +1,11 @@ +uniform mat4 mWorldViewProj; +uniform mat4 mInvWorld; +uniform mat4 mTransWorld; +uniform mat4 mWorld; + +void main(void) +{ + gl_TexCoord[0] = gl_MultiTexCoord0; + gl_Position = mWorldViewProj * gl_Vertex; + gl_FrontColor = gl_BackColor = gl_Color; +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ef508d9b8..e2f555860 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -423,6 +423,7 @@ set(client_SRCS main.cpp mapblock_mesh.cpp mesh.cpp + minimap.cpp particles.cpp shader.cpp sky.cpp diff --git a/src/client.cpp b/src/client.cpp index d2ec7017e..ce48df953 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -34,6 +34,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "porting.h" #include "mapblock_mesh.h" #include "mapblock.h" +#include "minimap.h" #include "settings.h" #include "profiler.h" #include "gettext.h" @@ -185,11 +186,6 @@ void * MeshUpdateThread::Thread() ScopeProfiler sp(g_profiler, "Client: Mesh making"); MapBlockMesh *mesh_new = new MapBlockMesh(q->data, m_camera_offset); - if(mesh_new->getMesh()->getMeshBufferCount() == 0) - { - delete mesh_new; - mesh_new = NULL; - } MeshUpdateResult r; r.p = q->p; @@ -274,6 +270,7 @@ Client::Client( // Add local player m_env.addPlayer(new LocalPlayer(this, playername)); + m_mapper = new Mapper(device, this); m_cache_save_interval = g_settings->getU16("server_map_save_interval"); m_cache_smooth_lighting = g_settings->getBool("smooth_lighting"); @@ -537,27 +534,37 @@ void Client::step(float dtime) */ { int num_processed_meshes = 0; - while(!m_mesh_update_thread.m_queue_out.empty()) + while (!m_mesh_update_thread.m_queue_out.empty()) { num_processed_meshes++; MeshUpdateResult r = m_mesh_update_thread.m_queue_out.pop_frontNoEx(); MapBlock *block = m_env.getMap().getBlockNoCreateNoEx(r.p); - if(block) { + MinimapMapblock *minimap_mapblock = NULL; + if (block) { // Delete the old mesh - if(block->mesh != NULL) - { - // TODO: Remove hardware buffers of meshbuffers of block->mesh + if (block->mesh != NULL) { delete block->mesh; block->mesh = NULL; } - // Replace with the new mesh - block->mesh = r.mesh; + if (r.mesh) + minimap_mapblock = r.mesh->getMinimapMapblock(); + + if (r.mesh && r.mesh->getMesh()->getMeshBufferCount() == 0) { + delete r.mesh; + block->mesh = NULL; + } else { + // Replace with the new mesh + block->mesh = r.mesh; + } } else { delete r.mesh; + minimap_mapblock = NULL; } - if(r.ack_block_to_server) { + m_mapper->addBlock(r.p, minimap_mapblock); + + if (r.ack_block_to_server) { /* Acknowledge block [0] u8 count @@ -567,7 +574,7 @@ void Client::step(float dtime) } } - if(num_processed_meshes > 0) + if (num_processed_meshes > 0) g_profiler->graphAdd("num_processed_meshes", num_processed_meshes); } diff --git a/src/client.h b/src/client.h index daeef3985..474daf3bc 100644 --- a/src/client.h +++ b/src/client.h @@ -48,6 +48,8 @@ struct MapDrawControl; class MtEventManager; struct PointedThing; class Database; +class Mapper; +struct MinimapMapblock; struct QueuedMeshUpdate { @@ -504,6 +506,9 @@ public: float getCurRate(void); float getAvgRate(void); + Mapper* getMapper () + { return m_mapper; } + // IGameDef interface virtual IItemDefManager* getItemDefManager(); virtual INodeDefManager* getNodeDefManager(); @@ -583,6 +588,7 @@ private: ParticleManager m_particle_manager; con::Connection m_con; IrrlichtDevice *m_device; + Mapper *m_mapper; // Server serialization version u8 m_server_ser_ver; // Used version of the protocol with server diff --git a/src/client/tile.cpp b/src/client/tile.cpp index eba52033a..cf8061982 100644 --- a/src/client/tile.cpp +++ b/src/client/tile.cpp @@ -382,6 +382,8 @@ public: video::IImage* generateImage(const std::string &name); video::ITexture* getNormalTexture(const std::string &name); + video::SColor getTextureAverageColor(const std::string &name); + private: // The id of the thread that is allowed to use irrlicht directly @@ -2008,3 +2010,41 @@ video::ITexture* TextureSource::getNormalTexture(const std::string &name) } return NULL; } + +video::SColor TextureSource::getTextureAverageColor(const std::string &name) +{ + video::IVideoDriver *driver = m_device->getVideoDriver(); + video::SColor c(0, 0, 0, 0); + u32 id; + video::ITexture *texture = getTexture(name, &id); + video::IImage *image = driver->createImage(texture, + core::position2d(0, 0), + texture->getOriginalSize()); + u32 total = 0; + u32 tR = 0; + u32 tG = 0; + u32 tB = 0; + core::dimension2d dim = image->getDimension(); + u16 step = 1; + if (dim.Width > 16) + step = dim.Width / 16; + for (u16 x = 0; x < dim.Width; x += step) { + for (u16 y = 0; y < dim.Width; y += step) { + c = image->getPixel(x,y); + if (c.getAlpha() > 0) { + total++; + tR += c.getRed(); + tG += c.getGreen(); + tB += c.getBlue(); + } + } + } + image->drop(); + if (total > 0) { + c.setRed(tR / total); + c.setGreen(tG / total); + c.setBlue(tB / total); + } + c.setAlpha(255); + return c; +} diff --git a/src/client/tile.h b/src/client/tile.h index 38f8bb623..674da66f2 100644 --- a/src/client/tile.h +++ b/src/client/tile.h @@ -110,6 +110,7 @@ public: virtual video::ITexture* generateTextureFromMesh( const TextureFromMeshParams ¶ms)=0; virtual video::ITexture* getNormalTexture(const std::string &name)=0; + virtual video::SColor getTextureAverageColor(const std::string &name)=0; }; class IWritableTextureSource : public ITextureSource @@ -131,6 +132,7 @@ public: virtual void insertSourceImage(const std::string &name, video::IImage *img)=0; virtual void rebuildImagesAndTextures()=0; virtual video::ITexture* getNormalTexture(const std::string &name)=0; + virtual video::SColor getTextureAverageColor(const std::string &name)=0; }; IWritableTextureSource* createTextureSource(IrrlichtDevice *device); diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index eab81009f..73e36da98 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -43,6 +43,7 @@ void set_default_settings(Settings *settings) settings->setDefault("keymap_special1", "KEY_KEY_E"); settings->setDefault("keymap_chat", "KEY_KEY_T"); settings->setDefault("keymap_cmd", "/"); + settings->setDefault("keymap_minimap", "KEY_F9"); settings->setDefault("keymap_console", "KEY_F10"); settings->setDefault("keymap_rangeselect", "KEY_KEY_R"); settings->setDefault("keymap_freemove", "KEY_KEY_K"); @@ -176,6 +177,9 @@ void set_default_settings(Settings *settings) settings->setDefault("enable_particles", "true"); settings->setDefault("enable_mesh_cache", "true"); + settings->setDefault("enable_minimap", "true"); + settings->setDefault("minimap_shape_round", "true"); + settings->setDefault("curl_timeout", "5000"); settings->setDefault("curl_parallel_limit", "8"); settings->setDefault("curl_file_download_timeout", "300000"); diff --git a/src/drawscene.cpp b/src/drawscene.cpp index f7cfdd262..509f341d5 100644 --- a/src/drawscene.cpp +++ b/src/drawscene.cpp @@ -416,10 +416,11 @@ void draw_plain(Camera& camera, bool show_hud, Hud& hud, camera.drawWieldedTool(); } -void draw_scene(video::IVideoDriver* driver, scene::ISceneManager* smgr, - Camera& camera, Client& client, LocalPlayer* player, Hud& hud, - gui::IGUIEnvironment* guienv, std::vector hilightboxes, - const v2u32& screensize, video::SColor skycolor, bool show_hud) +void draw_scene(video::IVideoDriver *driver, scene::ISceneManager *smgr, + Camera &camera, Client& client, LocalPlayer *player, Hud &hud, + Mapper &mapper, gui::IGUIEnvironment *guienv, + std::vector hilightboxes, const v2u32 &screensize, + video::SColor skycolor, bool show_hud, bool show_minimap) { TimeTaker timer("smgr"); @@ -484,6 +485,8 @@ void draw_scene(video::IVideoDriver* driver, scene::ISceneManager* smgr, hud.drawCrosshair(); hud.drawHotbar(client.getPlayerItem()); hud.drawLuaElements(camera.getOffset()); + if (show_minimap) + mapper.drawMinimap(); } guienv->drawAll(); diff --git a/src/drawscene.h b/src/drawscene.h index 3268bcbf2..0630f2970 100644 --- a/src/drawscene.h +++ b/src/drawscene.h @@ -22,16 +22,18 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "camera.h" #include "hud.h" +#include "minimap.h" #include "irrlichttypes_extrabloated.h" -void draw_load_screen(const std::wstring &text, IrrlichtDevice* device, - gui::IGUIEnvironment* guienv, float dtime=0, int percent=0, - bool clouds=true); +void draw_load_screen(const std::wstring &text, IrrlichtDevice *device, + gui::IGUIEnvironment *guienv, float dtime = 0, int percent = 0, + bool clouds = true); -void draw_scene(video::IVideoDriver* driver, scene::ISceneManager* smgr, - Camera& camera, Client& client, LocalPlayer* player, Hud& hud, - gui::IGUIEnvironment* guienv, std::vector hilightboxes, - const v2u32& screensize, video::SColor skycolor, bool show_hud); +void draw_scene(video::IVideoDriver *driver, scene::ISceneManager *smgr, + Camera &camera, Client &client, LocalPlayer *player, Hud &hud, + Mapper &mapper, gui::IGUIEnvironment *guienv, + std::vector hilightboxes, const v2u32 &screensize, + video::SColor skycolor, bool show_hud, bool show_minimap); #endif /* DRAWSCENE_H_ */ diff --git a/src/game.cpp b/src/game.cpp index be4897d75..94fb41852 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -57,6 +57,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/directiontables.h" #include "util/pointedthing.h" #include "version.h" +#include "minimap.h" #include "sound.h" @@ -866,6 +867,9 @@ public: services->setPixelShaderConstant("eyePosition", (irr::f32 *)&eye_position, 3); services->setVertexShaderConstant("eyePosition", (irr::f32 *)&eye_position, 3); + v3f minimap_yaw_vec = m_client->getMapper()->getYawVec(); + services->setPixelShaderConstant("yawVec", (irr::f32 *)&minimap_yaw_vec, 3); + // Uniform sampler layers int layer0 = 0; int layer1 = 1; @@ -1238,6 +1242,7 @@ struct KeyCache { KEYMAP_ID_CHAT, KEYMAP_ID_CMD, KEYMAP_ID_CONSOLE, + KEYMAP_ID_MINIMAP, KEYMAP_ID_FREEMOVE, KEYMAP_ID_FASTMOVE, KEYMAP_ID_NOCLIP, @@ -1287,6 +1292,7 @@ void KeyCache::populate() key[KEYMAP_ID_CHAT] = getKeySetting("keymap_chat"); key[KEYMAP_ID_CMD] = getKeySetting("keymap_cmd"); key[KEYMAP_ID_CONSOLE] = getKeySetting("keymap_console"); + key[KEYMAP_ID_MINIMAP] = getKeySetting("keymap_minimap"); key[KEYMAP_ID_FREEMOVE] = getKeySetting("keymap_freemove"); key[KEYMAP_ID_FASTMOVE] = getKeySetting("keymap_fastmove"); key[KEYMAP_ID_NOCLIP] = getKeySetting("keymap_noclip"); @@ -1392,6 +1398,7 @@ struct VolatileRunFlags { bool invert_mouse; bool show_chat; bool show_hud; + bool show_minimap; bool force_fog_off; bool show_debug; bool show_profiler_graph; @@ -1490,6 +1497,8 @@ protected: void toggleChat(float *statustext_time, bool *flag); void toggleHud(float *statustext_time, bool *flag); + void toggleMinimap(float *statustext_time, bool *flag1, bool *flag2, + bool shift_pressed); void toggleFog(float *statustext_time, bool *flag); void toggleDebug(float *statustext_time, bool *show_debug, bool *show_profiler_graph); @@ -1568,6 +1577,7 @@ private: Sky *sky; // Free using ->Drop() Inventory *local_inventory; Hud *hud; + Mapper *mapper; /* 'cache' This class does take ownership/responsibily for cleaning up etc of any of @@ -1648,7 +1658,8 @@ Game::Game() : clouds(NULL), sky(NULL), local_inventory(NULL), - hud(NULL) + hud(NULL), + mapper(NULL) { m_cache_doubletap_jump = g_settings->getBool("doubletap_jump"); m_cache_enable_node_highlighting = g_settings->getBool("enable_node_highlighting"); @@ -1750,6 +1761,7 @@ void Game::run() flags.show_chat = true; flags.show_hud = true; + flags.show_minimap = g_settings->getBool("enable_minimap"); flags.show_debug = g_settings->getBool("show_debug"); flags.invert_mouse = g_settings->getBool("invert_mouse"); flags.first_loop_after_window_activation = true; @@ -2065,6 +2077,9 @@ bool Game::createClient(const std::string &playername, return false; } + mapper = client->getMapper(); + mapper->setMinimapMode(MINIMAP_MODE_OFF); + return true; } @@ -2599,6 +2614,9 @@ void Game::processKeyboardInput(VolatileRunFlags *flags, client->makeScreenshot(device); } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_TOGGLE_HUD])) { toggleHud(statustext_time, &flags->show_hud); + } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_MINIMAP])) { + toggleMinimap(statustext_time, &flags->show_minimap, &flags->show_hud, + input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_SNEAK])); } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_TOGGLE_CHAT])) { toggleChat(statustext_time, &flags->show_chat); } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_TOGGLE_FORCE_FOG_OFF])) { @@ -2819,6 +2837,44 @@ void Game::toggleHud(float *statustext_time, bool *flag) client->setHighlighted(client->getHighlighted(), *flag); } +void Game::toggleMinimap(float *statustext_time, bool *flag, bool *show_hud, bool shift_pressed) +{ + if (*show_hud && g_settings->getBool("enable_minimap")) { + if (shift_pressed) { + mapper->toggleMinimapShape(); + return; + } + MinimapMode mode = mapper->getMinimapMode(); + mode = (MinimapMode)((int)(mode) + 1); + *flag = true; + switch (mode) { + case MINIMAP_MODE_SURFACEx1: + statustext = L"Minimap in surface mode, Zoom x1"; + break; + case MINIMAP_MODE_SURFACEx2: + statustext = L"Minimap in surface mode, Zoom x2"; + break; + case MINIMAP_MODE_SURFACEx4: + statustext = L"Minimap in surface mode, Zoom x4"; + break; + case MINIMAP_MODE_RADARx1: + statustext = L"Minimap in radar mode, Zoom x1"; + break; + case MINIMAP_MODE_RADARx2: + statustext = L"Minimap in radar mode, Zoom x2"; + break; + case MINIMAP_MODE_RADARx4: + statustext = L"Minimap in radar mode, Zoom x4"; + break; + default: + mode = MINIMAP_MODE_OFF; + *flag = false; + statustext = L"Minimap hidden"; + } + *statustext_time = 0; + mapper->setMinimapMode(mode); + } +} void Game::toggleFog(float *statustext_time, bool *flag) { @@ -3953,8 +4009,9 @@ void Game::updateFrame(std::vector &highlight_boxes, stats->beginscenetime = timer.stop(true); } - draw_scene(driver, smgr, *camera, *client, player, *hud, guienv, - highlight_boxes, screensize, skycolor, flags.show_hud); + draw_scene(driver, smgr, *camera, *client, player, *hud, *mapper, + guienv, highlight_boxes, screensize, skycolor, flags.show_hud, + flags.show_minimap); /* Profiler graph @@ -3987,6 +4044,13 @@ void Game::updateFrame(std::vector &highlight_boxes, player->hurt_tilt_strength = 0; } + /* + Update minimap pos + */ + if (flags.show_minimap && flags.show_hud) { + mapper->setPos(floatToInt(player->getPosition(), BS)); + } + /* End scene */ diff --git a/src/mapblock_mesh.cpp b/src/mapblock_mesh.cpp index 79e3e81ba..0e4831166 100644 --- a/src/mapblock_mesh.cpp +++ b/src/mapblock_mesh.cpp @@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "nodedef.h" #include "gamedef.h" #include "mesh.h" +#include "minimap.h" #include "content_mapblock.h" #include "noise.h" #include "shader.h" @@ -1028,6 +1029,7 @@ static void updateAllFastFaceRows(MeshMakeData *data, MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): m_mesh(new scene::SMesh()), + m_minimap_mapblock(new MinimapMapblock), m_gamedef(data->m_gamedef), m_tsrc(m_gamedef->getTextureSource()), m_shdrsrc(m_gamedef->getShaderSource()), @@ -1041,6 +1043,32 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): m_enable_shaders = data->m_use_shaders; m_enable_highlighting = g_settings->getBool("enable_node_highlighting"); + if (g_settings->getBool("enable_minimap")) { + v3s16 blockpos_nodes = data->m_blockpos * MAP_BLOCKSIZE; + for(s16 x = 0; x < MAP_BLOCKSIZE; x++) { + for(s16 z = 0; z < MAP_BLOCKSIZE; z++) { + s16 air_count = 0; + bool surface_found = false; + MinimapPixel* minimap_pixel = &m_minimap_mapblock->data[x + z * MAP_BLOCKSIZE]; + for(s16 y = MAP_BLOCKSIZE -1; y > -1; y--) { + v3s16 p(x, y, z); + MapNode n = data->m_vmanip.getNodeNoEx(blockpos_nodes + p); + if (!surface_found && n.getContent() != CONTENT_AIR) { + minimap_pixel->height = y; + minimap_pixel->id = n.getContent(); + surface_found = true; + } else if (n.getContent() == CONTENT_AIR) { + air_count++; + } + } + if (!surface_found) { + minimap_pixel->id = CONTENT_AIR; + } + minimap_pixel->air_count = air_count; + } + } + } + // 4-21ms for MAP_BLOCKSIZE=16 (NOTE: probably outdated) // 24-155ms for MAP_BLOCKSIZE=32 (NOTE: probably outdated) //TimeTaker timer1("MapBlockMesh()"); diff --git a/src/mapblock_mesh.h b/src/mapblock_mesh.h index 72c90c3e5..283006331 100644 --- a/src/mapblock_mesh.h +++ b/src/mapblock_mesh.h @@ -34,6 +34,7 @@ class IShaderSource; class MapBlock; +struct MinimapMapblock; struct MeshMakeData { @@ -108,6 +109,11 @@ public: return m_mesh; } + MinimapMapblock* getMinimapMapblock() + { + return m_minimap_mapblock; + } + bool isAnimationForced() const { return m_animation_force_timer == 0; @@ -123,6 +129,7 @@ public: private: scene::SMesh *m_mesh; + MinimapMapblock *m_minimap_mapblock; IGameDef *m_gamedef; ITextureSource *m_tsrc; IShaderSource *m_shdrsrc; diff --git a/src/minimap.cpp b/src/minimap.cpp new file mode 100644 index 000000000..61960ca1e --- /dev/null +++ b/src/minimap.cpp @@ -0,0 +1,472 @@ +/* +Minetest +Copyright (C) 2010-2015 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "minimap.h" +#include "logoutputbuffer.h" +#include "jthread/jmutexautolock.h" +#include "clientmap.h" +#include "settings.h" +#include "nodedef.h" +#include "porting.h" +#include "util/numeric.h" +#include "util/string.h" +#include + +QueuedMinimapUpdate::QueuedMinimapUpdate(): + pos(-1337,-1337,-1337), + data(NULL) +{ +} + +QueuedMinimapUpdate::~QueuedMinimapUpdate() +{ + delete data; +} + +MinimapUpdateQueue::MinimapUpdateQueue() +{ +} + +MinimapUpdateQueue::~MinimapUpdateQueue() +{ + JMutexAutoLock lock(m_mutex); + + for (std::vector::iterator + i = m_queue.begin(); + i != m_queue.end(); i++) + { + QueuedMinimapUpdate *q = *i; + delete q; + } +} + +void MinimapUpdateQueue::addBlock(v3s16 pos, MinimapMapblock *data) +{ + DSTACK(__FUNCTION_NAME); + + JMutexAutoLock lock(m_mutex); + + /* + Find if block is already in queue. + If it is, update the data and quit. + */ + for (std::vector::iterator + i = m_queue.begin(); + i != m_queue.end(); i++) + { + QueuedMinimapUpdate *q = *i; + if (q->pos == pos) { + delete q->data; + q->data = data; + return; + } + } + + /* + Add the block + */ + QueuedMinimapUpdate *q = new QueuedMinimapUpdate; + q->pos = pos; + q->data = data; + m_queue.push_back(q); +} + +QueuedMinimapUpdate * MinimapUpdateQueue::pop() +{ + JMutexAutoLock lock(m_mutex); + + for (std::vector::iterator + i = m_queue.begin(); + i != m_queue.end(); i++) + { + QueuedMinimapUpdate *q = *i; + m_queue.erase(i); + return q; + } + return NULL; +} + +/* + Minimap update thread +*/ + +void *MinimapUpdateThread::Thread() +{ + ThreadStarted(); + + log_register_thread("MinimapUpdateThread"); + + DSTACK(__FUNCTION_NAME); + + BEGIN_DEBUG_EXCEPTION_HANDLER + + porting::setThreadName("MinimapUpdateThread"); + + while (!StopRequested()) { + + while (m_queue.size()) { + QueuedMinimapUpdate *q = m_queue.pop(); + std::map::iterator it; + it = m_blocks_cache.find(q->pos); + if (q->data) { + m_blocks_cache[q->pos] = q->data; + } else if (it != m_blocks_cache.end()) { + delete it->second; + m_blocks_cache.erase(it); + } + } + + if (data->map_invalidated) { + if (data->mode != MINIMAP_MODE_OFF) { + getMap(data->pos, data->map_size, data->scan_height, data->radar); + data->map_invalidated = false; + } + } + // sleep_ms(10); + } + END_DEBUG_EXCEPTION_HANDLER(errorstream) + + return NULL; +} + +MinimapPixel *MinimapUpdateThread::getMinimapPixel (v3s16 pos, s16 height, s16 &pixel_height) +{ + pixel_height = height - MAP_BLOCKSIZE; + v3s16 blockpos_max, blockpos_min, relpos; + getNodeBlockPosWithOffset(v3s16(pos.X, pos.Y - height / 2, pos.Z), blockpos_min, relpos); + getNodeBlockPosWithOffset(v3s16(pos.X, pos.Y + height / 2, pos.Z), blockpos_max, relpos); + std::map::iterator it; + for (s16 i = blockpos_max.Y; i > blockpos_min.Y - 1; i--) { + it = m_blocks_cache.find(v3s16(blockpos_max.X, i, blockpos_max.Z)); + if (it != m_blocks_cache.end()) { + MinimapPixel *pixel = &it->second->data[relpos.X + relpos.Z * MAP_BLOCKSIZE]; + if (pixel->id != CONTENT_AIR) { + pixel_height += pixel->height; + return pixel; + } + } + pixel_height -= MAP_BLOCKSIZE; + } + return NULL; +} + +s16 MinimapUpdateThread::getAirCount (v3s16 pos, s16 height) +{ + s16 air_count = 0; + v3s16 blockpos_max, blockpos_min, relpos; + getNodeBlockPosWithOffset(v3s16(pos.X, pos.Y - height / 2, pos.Z), blockpos_min, relpos); + getNodeBlockPosWithOffset(v3s16(pos.X, pos.Y + height / 2, pos.Z), blockpos_max, relpos); + std::map::iterator it; + for (s16 i = blockpos_max.Y; i > blockpos_min.Y - 1; i--) { + it = m_blocks_cache.find(v3s16(blockpos_max.X, i, blockpos_max.Z)); + if (it != m_blocks_cache.end()) { + MinimapPixel *pixel = &it->second->data[relpos.X + relpos.Z * MAP_BLOCKSIZE]; + air_count += pixel->air_count; + } + } + return air_count; +} + +void MinimapUpdateThread::getMap (v3s16 pos, s16 size, s16 height, bool radar) +{ + v3s16 p = v3s16 (pos.X - size / 2, pos.Y, pos.Z - size / 2); + + for (s16 x = 0; x < size; x++) { + for (s16 z = 0; z < size; z++){ + u16 id = CONTENT_AIR; + MinimapPixel* minimap_pixel = &data->minimap_scan[x + z * size]; + if (!radar) { + s16 pixel_height = 0; + MinimapPixel* cached_pixel = + getMinimapPixel(v3s16(p.X + x, p.Y, p.Z + z), height, pixel_height); + if (cached_pixel) { + id = cached_pixel->id; + minimap_pixel->height = pixel_height; + } + } else { + minimap_pixel->air_count = getAirCount (v3s16(p.X + x, p.Y, p.Z + z), height); + } + minimap_pixel->id = id; + } + } +} + +Mapper::Mapper(IrrlichtDevice *device, Client *client) +{ + this->device = device; + this->client = client; + this->driver = device->getVideoDriver(); + this->tsrc = client->getTextureSource(); + this->player = client->getEnv().getLocalPlayer(); + this->shdrsrc = client->getShaderSource(); + + m_enable_shaders = g_settings->getBool("enable_shaders"); + data = new MinimapData; + data->mode = MINIMAP_MODE_OFF; + data->radar = false; + data->map_invalidated = true; + data->heightmap_image = NULL; + data->minimap_image = NULL; + data->texture = NULL; + data->minimap_shape_round = g_settings->getBool("minimap_shape_round"); + std::string fname1 = "minimap_mask_round.png"; + std::string fname2 = "minimap_overlay_round.png"; + data->minimap_mask_round = driver->createImage (tsrc->getTexture(fname1), + core::position2d(0,0), core::dimension2d(512,512)); + data->minimap_overlay_round = tsrc->getTexture(fname2); + fname1 = "minimap_mask_square.png"; + fname2 = "minimap_overlay_square.png"; + data->minimap_mask_square = driver->createImage (tsrc->getTexture(fname1), + core::position2d(0,0), core::dimension2d(512,512)); + data->minimap_overlay_square = tsrc->getTexture(fname2); + data->player_marker = tsrc->getTexture("player_marker.png"); + m_meshbuffer = getMinimapMeshBuffer(); + m_minimap_update_thread = new MinimapUpdateThread(device, client); + m_minimap_update_thread->data = data; + m_minimap_update_thread->Start(); +} + +Mapper::~Mapper() +{ + m_minimap_update_thread->Stop(); + m_minimap_update_thread->Wait(); + + for (std::map::iterator + it = m_minimap_update_thread->m_blocks_cache.begin(); + it != m_minimap_update_thread->m_blocks_cache.end(); it++){ + delete it->second; + } + + m_meshbuffer->drop(); + data->minimap_mask_round->drop(); + data->minimap_mask_square->drop(); + driver->removeTexture(data->texture); + driver->removeTexture(data->heightmap_texture); + driver->removeTexture(data->minimap_overlay_round); + driver->removeTexture(data->minimap_overlay_square); + delete data; + delete m_minimap_update_thread; +} + +void Mapper::addBlock (v3s16 pos, MinimapMapblock *data) +{ + m_minimap_update_thread->m_queue.addBlock(pos, data); +} + +MinimapMode Mapper::getMinimapMode() +{ + return data->mode; +} + +void Mapper::toggleMinimapShape() +{ + data->minimap_shape_round = !data->minimap_shape_round; + g_settings->setBool(("minimap_shape_round"), data->minimap_shape_round); +} + +void Mapper::setMinimapMode(MinimapMode mode) +{ + static const u16 modeDefs[7 * 3] = { + 0, 0, 0, + 0, 256, 256, + 0, 256, 128, + 0, 256, 64, + 1, 32, 128, + 1, 32, 64, + 1, 32, 32}; + + JMutexAutoLock lock(m_mutex); + data->radar = (bool)modeDefs[(int)mode * 3]; + data->scan_height = modeDefs[(int)mode * 3 + 1]; + data->map_size = modeDefs[(int)mode * 3 + 2]; + data->mode = mode; +} + +void Mapper::setPos(v3s16 pos) +{ + JMutexAutoLock lock(m_mutex); + data->pos = pos; +} + +video::ITexture *Mapper::getMinimapTexture() +{ + // update minimap textures when new scan is ready + if (!data->map_invalidated) { + // create minimap and heightmap image + core::dimension2d dim(data->map_size,data->map_size); + video::IImage *map_image = driver->createImage(video::ECF_A8R8G8B8, dim); + video::IImage *heightmap_image = driver->createImage(video::ECF_A8R8G8B8, dim); + video::IImage *minimap_image = driver->createImage(video::ECF_A8R8G8B8, + core::dimension2d(512, 512)); + + video::SColor c; + if (!data->radar) { + // surface mode + for (s16 x = 0; x < data->map_size; x++) { + for (s16 z = 0; z < data->map_size; z++) { + MinimapPixel* minimap_pixel = &data->minimap_scan[x + z * data->map_size]; + const ContentFeatures &f = client->getNodeDefManager()->get(minimap_pixel->id); + c = f.minimap_color; + c.setAlpha(240); + map_image->setPixel(x, data->map_size - z -1, c); + u32 h = minimap_pixel->height; + heightmap_image->setPixel(x,data->map_size -z -1, + video::SColor(255, h, h, h)); + } + } + } else { + // radar mode + c = video::SColor (240, 0 , 0, 0); + for (s16 x = 0; x < data->map_size; x++) { + for (s16 z = 0; z < data->map_size; z++) { + MinimapPixel* minimap_pixel = &data->minimap_scan[x + z * data->map_size]; + if (minimap_pixel->air_count > 0) { + c.setGreen(core::clamp(core::round32(32 + minimap_pixel->air_count * 8), 0, 255)); + } else { + c.setGreen(0); + } + map_image->setPixel(x, data->map_size - z -1, c); + } + } + } + + map_image->copyToScaling(minimap_image); + map_image->drop(); + + video::IImage *minimap_mask; + if (data->minimap_shape_round) { + minimap_mask = data->minimap_mask_round; + } else { + minimap_mask = data->minimap_mask_square; + } + for (s16 x = 0; x < 512; x++) { + for (s16 y = 0; y < 512; y++) { + video::SColor mask_col = minimap_mask->getPixel(x, y); + if (!mask_col.getAlpha()) { + minimap_image->setPixel(x, y, video::SColor(0,0,0,0)); + } + } + } + + if (data->texture) { + driver->removeTexture(data->texture); + } + if (data->heightmap_texture) { + driver->removeTexture(data->heightmap_texture); + } + data->texture = driver->addTexture("minimap__", minimap_image); + data->heightmap_texture = driver->addTexture("minimap_heightmap__", heightmap_image); + minimap_image->drop(); + heightmap_image->drop(); + + data->map_invalidated = true; + } + return data->texture; +} + +v3f Mapper::getYawVec() +{ + if (data->minimap_shape_round) { + return v3f(cos(player->getYaw()* core::DEGTORAD), + sin(player->getYaw()* core::DEGTORAD), 1.0); + } else { + return v3f(1.0, 0.0, 1.0); + } +} + +scene::SMeshBuffer *Mapper::getMinimapMeshBuffer() +{ + scene::SMeshBuffer *buf = new scene::SMeshBuffer(); + buf->Vertices.set_used(4); + buf->Indices .set_used(6); + video::SColor c(255, 255, 255, 255); + + buf->Vertices[0] = video::S3DVertex(-1, -1, 0, 0, 0, 1, c, 0, 1); + buf->Vertices[1] = video::S3DVertex(-1, 1, 0, 0, 0, 1, c, 0, 0); + buf->Vertices[2] = video::S3DVertex( 1, 1, 0, 0, 0, 1, c, 1, 0); + buf->Vertices[3] = video::S3DVertex( 1, -1, 0, 0, 0, 1, c, 1, 1); + + buf->Indices[0] = 0; + buf->Indices[1] = 1; + buf->Indices[2] = 2; + buf->Indices[3] = 2; + buf->Indices[4] = 3; + buf->Indices[5] = 0; + + return buf; +} + +void Mapper::drawMinimap() +{ + v2u32 screensize = porting::getWindowSize(); + u32 size = 0.25 * screensize.Y; + video::ITexture* minimap_texture = getMinimapTexture(); + core::matrix4 matrix; + + core::rect oldViewPort = driver->getViewPort(); + driver->setViewPort(core::rect(screensize.X - size - 10, 10, + screensize.X - 10, size + 10)); + core::matrix4 oldProjMat = driver->getTransform(video::ETS_PROJECTION); + driver->setTransform(video::ETS_PROJECTION, core::matrix4()); + core::matrix4 oldViewMat = driver->getTransform(video::ETS_VIEW); + driver->setTransform(video::ETS_VIEW, core::matrix4()); + matrix.makeIdentity(); + + if (minimap_texture) { + video::SMaterial& material = m_meshbuffer->getMaterial(); + material.setFlag(video::EMF_TRILINEAR_FILTER, true); + material.Lighting = false; + material.TextureLayer[0].Texture = minimap_texture; + material.TextureLayer[1].Texture = data->heightmap_texture; + if (m_enable_shaders && !data->radar) { + u16 sid = shdrsrc->getShader("minimap_shader", 1, 1); + material.MaterialType = shdrsrc->getShaderInfo(sid).material; + } else { + material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; + } + + if (data->minimap_shape_round) + matrix.setRotationDegrees(core::vector3df(0, 0, 360 - player->getYaw())); + driver->setTransform(video::ETS_WORLD, matrix); + driver->setMaterial(material); + driver->drawMeshBuffer(m_meshbuffer); + video::ITexture *minimap_overlay; + if (data->minimap_shape_round) { + minimap_overlay = data->minimap_overlay_round; + } else { + minimap_overlay = data->minimap_overlay_square; + } + material.TextureLayer[0].Texture = minimap_overlay; + material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; + driver->setMaterial(material); + driver->drawMeshBuffer(m_meshbuffer); + + if (!data->minimap_shape_round) { + matrix.setRotationDegrees(core::vector3df(0, 0, player->getYaw())); + driver->setTransform(video::ETS_WORLD, matrix); + material.TextureLayer[0].Texture = data->player_marker; + driver->setMaterial(material); + driver->drawMeshBuffer(m_meshbuffer); + } + } + + driver->setTransform(video::ETS_VIEW, oldViewMat); + driver->setTransform(video::ETS_PROJECTION, oldProjMat); + driver->setViewPort(oldViewPort); +} diff --git a/src/minimap.h b/src/minimap.h new file mode 100644 index 000000000..ebb74c4fb --- /dev/null +++ b/src/minimap.h @@ -0,0 +1,168 @@ +/* +Minetest +Copyright (C) 2010-2015 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef MINIMAP_HEADER +#define MINIMAP_HEADER + +#include "irrlichttypes_extrabloated.h" +#include "client.h" +#include "voxel.h" +#include "jthread/jmutex.h" +#include +#include +#include + +enum MinimapMode { + MINIMAP_MODE_OFF, + MINIMAP_MODE_SURFACEx1, + MINIMAP_MODE_SURFACEx2, + MINIMAP_MODE_SURFACEx4, + MINIMAP_MODE_RADARx1, + MINIMAP_MODE_RADARx2, + MINIMAP_MODE_RADARx4 +}; + +struct MinimapPixel +{ + u16 id; + u16 height; + u16 air_count; + u16 light; +}; + +struct MinimapMapblock +{ + MinimapPixel data[MAP_BLOCKSIZE * MAP_BLOCKSIZE]; +}; + +struct MinimapData +{ + bool radar; + MinimapMode mode; + v3s16 pos; + v3s16 old_pos; + u16 scan_height; + u16 map_size; + MinimapPixel minimap_scan[512 * 512]; + bool map_invalidated; + bool minimap_shape_round; + video::IImage *minimap_image; + video::IImage *heightmap_image; + video::IImage *minimap_mask_round; + video::IImage *minimap_mask_square; + video::ITexture *texture; + video::ITexture *heightmap_texture; + video::ITexture *minimap_overlay_round; + video::ITexture *minimap_overlay_square; + video::ITexture *player_marker; +}; + +struct QueuedMinimapUpdate +{ + v3s16 pos; + MinimapMapblock *data; + + QueuedMinimapUpdate(); + ~QueuedMinimapUpdate(); +}; + +/* + A thread-safe queue of minimap mapblocks update tasks +*/ + +class MinimapUpdateQueue +{ +public: + MinimapUpdateQueue(); + + ~MinimapUpdateQueue(); + + void addBlock(v3s16 pos, MinimapMapblock *data); + + QueuedMinimapUpdate *pop(); + + u32 size() + { + JMutexAutoLock lock(m_mutex); + return m_queue.size(); + } + +private: + std::vector m_queue; + JMutex m_mutex; +}; + +class MinimapUpdateThread : public JThread +{ +private: + +public: + MinimapUpdateThread(IrrlichtDevice *device, Client *client) + { + this->device = device; + this->client = client; + this->driver = device->getVideoDriver(); + this->tsrc = client->getTextureSource(); + } + void getMap (v3s16 pos, s16 size, s16 height, bool radar); + MinimapPixel *getMinimapPixel (v3s16 pos, s16 height, s16 &pixel_height); + s16 getAirCount (v3s16 pos, s16 height); + video::SColor getColorFromId(u16 id); + IrrlichtDevice *device; + Client *client; + video::IVideoDriver *driver; + ITextureSource *tsrc; + void *Thread(); + MinimapData *data; + MinimapUpdateQueue m_queue; + std::map m_blocks_cache; +}; + +class Mapper +{ +private: + MinimapUpdateThread *m_minimap_update_thread; + video::ITexture *minimap_texture; + scene::SMeshBuffer *m_meshbuffer; + bool m_enable_shaders; + JMutex m_mutex; + +public: + Mapper(IrrlichtDevice *device, Client *client); + ~Mapper(); + + void addBlock(v3s16 pos, MinimapMapblock *data); + void setPos(v3s16 pos); + video::ITexture* getMinimapTexture(); + v3f getYawVec(); + MinimapMode getMinimapMode(); + void setMinimapMode(MinimapMode mode); + void toggleMinimapShape(); + scene::SMeshBuffer *getMinimapMeshBuffer(); + void drawMinimap(); + IrrlichtDevice *device; + Client *client; + video::IVideoDriver *driver; + LocalPlayer *player; + ITextureSource *tsrc; + IShaderSource *shdrsrc; + MinimapData *data; +}; + +#endif diff --git a/src/nodedef.cpp b/src/nodedef.cpp index 6c2e96c4f..ba1ca2c65 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -202,6 +202,7 @@ void ContentFeatures::reset() #ifndef SERVER for(u32 i = 0; i < 24; i++) mesh_ptr[i] = NULL; + minimap_color = video::SColor(0, 0, 0, 0); #endif visual_scale = 1.0; for(u32 i = 0; i < 6; i++) @@ -766,7 +767,6 @@ void CNodeDefManager::applyTextureOverrides(const std::string &override_filepath } } - void CNodeDefManager::updateTextures(IGameDef *gamedef, void (*progress_callback)(void *progress_args, u32 progress, u32 max_progress), void *progress_callback_args) @@ -774,7 +774,6 @@ void CNodeDefManager::updateTextures(IGameDef *gamedef, #ifndef SERVER infostream << "CNodeDefManager::updateTextures(): Updating " "textures in node definitions" << std::endl; - ITextureSource *tsrc = gamedef->tsrc(); IShaderSource *shdsrc = gamedef->getShaderSource(); scene::ISceneManager* smgr = gamedef->getSceneManager(); @@ -797,6 +796,10 @@ void CNodeDefManager::updateTextures(IGameDef *gamedef, for (u32 i = 0; i < size; i++) { ContentFeatures *f = &m_content_features[i]; + // minimap pixel color - the average color of a texture + if (f->tiledef[0].name != "") + f->minimap_color = tsrc->getTextureAverageColor(f->tiledef[0].name); + // Figure out the actual tiles to use TileDef tiledef[6]; for (u32 j = 0; j < 6; j++) { diff --git a/src/nodedef.h b/src/nodedef.h index 3a5e5228d..bbce6ba3e 100644 --- a/src/nodedef.h +++ b/src/nodedef.h @@ -194,6 +194,7 @@ struct ContentFeatures std::string mesh; #ifndef SERVER scene::IMesh *mesh_ptr[24]; + video::SColor minimap_color; #endif float visual_scale; // Misc. scale parameter TileDef tiledef[6]; @@ -202,6 +203,7 @@ struct ContentFeatures // Post effect color, drawn when the camera is inside the node. video::SColor post_effect_color; + // Type of MapNode::param1 ContentParamType param_type; // Type of MapNode::param2 diff --git a/textures/base/pack/minimap_mask_round.png b/textures/base/pack/minimap_mask_round.png new file mode 100644 index 000000000..797c780c7 Binary files /dev/null and b/textures/base/pack/minimap_mask_round.png differ diff --git a/textures/base/pack/minimap_mask_square.png b/textures/base/pack/minimap_mask_square.png new file mode 100644 index 000000000..5dd23f7d8 Binary files /dev/null and b/textures/base/pack/minimap_mask_square.png differ diff --git a/textures/base/pack/minimap_overlay_round.png b/textures/base/pack/minimap_overlay_round.png new file mode 100644 index 000000000..1a6b3cc41 Binary files /dev/null and b/textures/base/pack/minimap_overlay_round.png differ diff --git a/textures/base/pack/minimap_overlay_square.png b/textures/base/pack/minimap_overlay_square.png new file mode 100644 index 000000000..6d14dc78c Binary files /dev/null and b/textures/base/pack/minimap_overlay_square.png differ diff --git a/textures/base/pack/player_marker.png b/textures/base/pack/player_marker.png new file mode 100644 index 000000000..a5aedfece Binary files /dev/null and b/textures/base/pack/player_marker.png differ