diff options
-rw-r--r-- | src/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/camera.cpp | 1 | ||||
-rw-r--r-- | src/client.cpp | 1 | ||||
-rw-r--r-- | src/client.h | 1 | ||||
-rw-r--r-- | src/clientmap.cpp | 548 | ||||
-rw-r--r-- | src/clientmap.h | 146 | ||||
-rw-r--r-- | src/environment.cpp | 13 | ||||
-rw-r--r-- | src/environment.h | 8 | ||||
-rw-r--r-- | src/farmesh.cpp | 1 | ||||
-rw-r--r-- | src/game.cpp | 1 | ||||
-rw-r--r-- | src/map.cpp | 529 | ||||
-rw-r--r-- | src/map.h | 133 |
12 files changed, 717 insertions, 666 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e44580bc4..e43ff411e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -159,6 +159,7 @@ endif() # Client sources set(minetest_SRCS ${common_SRCS} + clientmap.cpp content_cso.cpp content_mapblock.cpp content_cao.cpp diff --git a/src/camera.cpp b/src/camera.cpp index b9e1273c6..531661679 100644 --- a/src/camera.cpp +++ b/src/camera.cpp @@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client.h" #include "main.h" // for g_settings #include "map.h" +#include "clientmap.h" // MapDrawControl #include "mesh.h" #include "player.h" #include "tile.h" diff --git a/src/client.cpp b/src/client.cpp index 17b24c158..6d992e717 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -37,6 +37,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <IFileSystem.h> #include "sha1.h" #include "base64.h" +#include "clientmap.h" static std::string getTextureCacheDir() { diff --git a/src/client.h b/src/client.h index a71d965c8..2f24f8813 100644 --- a/src/client.h +++ b/src/client.h @@ -43,6 +43,7 @@ class IWritableItemDefManager; class IWritableNodeDefManager; //class IWritableCraftDefManager; class ClientEnvironment; +struct MapDrawControl; class ClientNotReadyException : public BaseException { diff --git a/src/clientmap.cpp b/src/clientmap.cpp new file mode 100644 index 000000000..15a790f9a --- /dev/null +++ b/src/clientmap.cpp @@ -0,0 +1,548 @@ +/* +Minetest-c55 +Copyright (C) 2010-2012 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 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 General Public License for more details. + +You should have received a copy of the GNU 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 "clientmap.h" +#include "client.h" +#include "mapblock_mesh.h" +#include <IMaterialRenderer.h> +#include "log.h" +#include "mapsector.h" +#include "main.h" // dout_client, g_settings +#include "nodedef.h" +#include "mapblock.h" +#include "profiler.h" +#include "settings.h" + +ClientMap::ClientMap( + Client *client, + IGameDef *gamedef, + MapDrawControl &control, + scene::ISceneNode* parent, + scene::ISceneManager* mgr, + s32 id +): + Map(dout_client, gamedef), + scene::ISceneNode(parent, mgr, id), + m_client(client), + m_control(control), + m_camera_position(0,0,0), + m_camera_direction(0,0,1), + m_camera_fov(PI) +{ + m_camera_mutex.Init(); + assert(m_camera_mutex.IsInitialized()); + + m_box = core::aabbox3d<f32>(-BS*1000000,-BS*1000000,-BS*1000000, + BS*1000000,BS*1000000,BS*1000000); +} + +ClientMap::~ClientMap() +{ + /*JMutexAutoLock lock(mesh_mutex); + + if(mesh != NULL) + { + mesh->drop(); + mesh = NULL; + }*/ +} + +MapSector * ClientMap::emergeSector(v2s16 p2d) +{ + DSTACK(__FUNCTION_NAME); + // Check that it doesn't exist already + try{ + return getSectorNoGenerate(p2d); + } + catch(InvalidPositionException &e) + { + } + + // Create a sector + ClientMapSector *sector = new ClientMapSector(this, p2d, m_gamedef); + + { + //JMutexAutoLock lock(m_sector_mutex); // Bulk comment-out + m_sectors.insert(p2d, sector); + } + + return sector; +} + +#if 0 +void ClientMap::deSerializeSector(v2s16 p2d, std::istream &is) +{ + DSTACK(__FUNCTION_NAME); + ClientMapSector *sector = NULL; + + //JMutexAutoLock lock(m_sector_mutex); // Bulk comment-out + + core::map<v2s16, MapSector*>::Node *n = m_sectors.find(p2d); + + if(n != NULL) + { + sector = (ClientMapSector*)n->getValue(); + assert(sector->getId() == MAPSECTOR_CLIENT); + } + else + { + sector = new ClientMapSector(this, p2d); + { + //JMutexAutoLock lock(m_sector_mutex); // Bulk comment-out + m_sectors.insert(p2d, sector); + } + } + + sector->deSerialize(is); +} +#endif + +void ClientMap::OnRegisterSceneNode() +{ + if(IsVisible) + { + SceneManager->registerNodeForRendering(this, scene::ESNRP_SOLID); + SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT); + } + + ISceneNode::OnRegisterSceneNode(); +} + +static bool isOccluded(Map *map, v3s16 p0, v3s16 p1, float step, float stepfac, + float start_off, float end_off, u32 needed_count, INodeDefManager *nodemgr) +{ + float d0 = (float)BS * p0.getDistanceFrom(p1); + v3s16 u0 = p1 - p0; + v3f uf = v3f(u0.X, u0.Y, u0.Z) * BS; + uf.normalize(); + v3f p0f = v3f(p0.X, p0.Y, p0.Z) * BS; + u32 count = 0; + for(float s=start_off; s<d0+end_off; s+=step){ + v3f pf = p0f + uf * s; + v3s16 p = floatToInt(pf, BS); + MapNode n = map->getNodeNoEx(p); + bool is_transparent = false; + const ContentFeatures &f = nodemgr->get(n); + if(f.solidness == 0) + is_transparent = (f.visual_solidness != 2); + else + is_transparent = (f.solidness != 2); + if(!is_transparent){ + count++; + if(count >= needed_count) + return true; + } + step *= stepfac; + } + return false; +} + +void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) +{ + INodeDefManager *nodemgr = m_gamedef->ndef(); + + //m_dout<<DTIME<<"Rendering map..."<<std::endl; + DSTACK(__FUNCTION_NAME); + + bool is_transparent_pass = pass == scene::ESNRP_TRANSPARENT; + + std::string prefix; + if(pass == scene::ESNRP_SOLID) + prefix = "CM: solid: "; + else + prefix = "CM: transparent: "; + + /* + This is called two times per frame, reset on the non-transparent one + */ + if(pass == scene::ESNRP_SOLID) + { + m_last_drawn_sectors.clear(); + } + + /* + Get time for measuring timeout. + + Measuring time is very useful for long delays when the + machine is swapping a lot. + */ + int time1 = time(0); + + /* + Get animation parameters + */ + float animation_time = m_client->getAnimationTime(); + int crack = m_client->getCrackLevel(); + u32 daynight_ratio = m_client->getDayNightRatio(); + + m_camera_mutex.Lock(); + v3f camera_position = m_camera_position; + v3f camera_direction = m_camera_direction; + f32 camera_fov = m_camera_fov; + m_camera_mutex.Unlock(); + + /* + Get all blocks and draw all visible ones + */ + + v3s16 cam_pos_nodes = floatToInt(camera_position, BS); + + v3s16 box_nodes_d = m_control.wanted_range * v3s16(1,1,1); + + v3s16 p_nodes_min = cam_pos_nodes - box_nodes_d; + v3s16 p_nodes_max = cam_pos_nodes + box_nodes_d; + + // Take a fair amount as we will be dropping more out later + // Umm... these additions are a bit strange but they are needed. + v3s16 p_blocks_min( + p_nodes_min.X / MAP_BLOCKSIZE - 3, + p_nodes_min.Y / MAP_BLOCKSIZE - 3, + p_nodes_min.Z / MAP_BLOCKSIZE - 3); + v3s16 p_blocks_max( + p_nodes_max.X / MAP_BLOCKSIZE + 1, + p_nodes_max.Y / MAP_BLOCKSIZE + 1, + p_nodes_max.Z / MAP_BLOCKSIZE + 1); + + u32 vertex_count = 0; + u32 meshbuffer_count = 0; + + // For limiting number of mesh animations per frame + u32 mesh_animate_count = 0; + u32 mesh_animate_count_far = 0; + + // Number of blocks in rendering range + u32 blocks_in_range = 0; + // Number of blocks occlusion culled + u32 blocks_occlusion_culled = 0; + // Number of blocks in rendering range but don't have a mesh + u32 blocks_in_range_without_mesh = 0; + // Blocks that had mesh that would have been drawn according to + // rendering range (if max blocks limit didn't kick in) + u32 blocks_would_have_drawn = 0; + // Blocks that were drawn and had a mesh + u32 blocks_drawn = 0; + // Blocks which had a corresponding meshbuffer for this pass + u32 blocks_had_pass_meshbuf = 0; + // Blocks from which stuff was actually drawn + u32 blocks_without_stuff = 0; + + /* + Collect a set of blocks for drawing + */ + + core::map<v3s16, MapBlock*> drawset; + + { + ScopeProfiler sp(g_profiler, prefix+"collecting blocks for drawing", SPT_AVG); + + for(core::map<v2s16, MapSector*>::Iterator + si = m_sectors.getIterator(); + si.atEnd() == false; si++) + { + MapSector *sector = si.getNode()->getValue(); + v2s16 sp = sector->getPos(); + + if(m_control.range_all == false) + { + if(sp.X < p_blocks_min.X + || sp.X > p_blocks_max.X + || sp.Y < p_blocks_min.Z + || sp.Y > p_blocks_max.Z) + continue; + } + + core::list< MapBlock * > sectorblocks; + sector->getBlocks(sectorblocks); + + /* + Loop through blocks in sector + */ + + u32 sector_blocks_drawn = 0; + + core::list< MapBlock * >::Iterator i; + for(i=sectorblocks.begin(); i!=sectorblocks.end(); i++) + { + MapBlock *block = *i; + + /* + Compare block position to camera position, skip + if not seen on display + */ + + float range = 100000 * BS; + if(m_control.range_all == false) + range = m_control.wanted_range * BS; + + float d = 0.0; + if(isBlockInSight(block->getPos(), camera_position, + camera_direction, camera_fov, + range, &d) == false) + { + continue; + } + + // This is ugly (spherical distance limit?) + /*if(m_control.range_all == false && + d - 0.5*BS*MAP_BLOCKSIZE > range) + continue;*/ + + blocks_in_range++; + + /* + Ignore if mesh doesn't exist + */ + { + //JMutexAutoLock lock(block->mesh_mutex); + + if(block->mesh == NULL){ + blocks_in_range_without_mesh++; + continue; + } + } + + /* + Occlusion culling + */ + + v3s16 cpn = block->getPos() * MAP_BLOCKSIZE; + cpn += v3s16(MAP_BLOCKSIZE/2, MAP_BLOCKSIZE/2, MAP_BLOCKSIZE/2); + float step = BS*1; + float stepfac = 1.1; + float startoff = BS*1; + float endoff = -BS*MAP_BLOCKSIZE*1.42*1.42; + v3s16 spn = cam_pos_nodes + v3s16(0,0,0); + s16 bs2 = MAP_BLOCKSIZE/2 + 1; + u32 needed_count = 1; + if( + isOccluded(this, spn, cpn + v3s16(0,0,0), + step, stepfac, startoff, endoff, needed_count, nodemgr) && + isOccluded(this, spn, cpn + v3s16(bs2,bs2,bs2), + step, stepfac, startoff, endoff, needed_count, nodemgr) && + isOccluded(this, spn, cpn + v3s16(bs2,bs2,-bs2), + step, stepfac, startoff, endoff, needed_count, nodemgr) && + isOccluded(this, spn, cpn + v3s16(bs2,-bs2,bs2), + step, stepfac, startoff, endoff, needed_count, nodemgr) && + isOccluded(this, spn, cpn + v3s16(bs2,-bs2,-bs2), + step, stepfac, startoff, endoff, needed_count, nodemgr) && + isOccluded(this, spn, cpn + v3s16(-bs2,bs2,bs2), + step, stepfac, startoff, endoff, needed_count, nodemgr) && + isOccluded(this, spn, cpn + v3s16(-bs2,bs2,-bs2), + step, stepfac, startoff, endoff, needed_count, nodemgr) && + isOccluded(this, spn, cpn + v3s16(-bs2,-bs2,bs2), + step, stepfac, startoff, endoff, needed_count, nodemgr) && + isOccluded(this, spn, cpn + v3s16(-bs2,-bs2,-bs2), + step, stepfac, startoff, endoff, needed_count, nodemgr) + ) + { + blocks_occlusion_culled++; + continue; + } + + // This block is in range. Reset usage timer. + block->resetUsageTimer(); + + // Limit block count in case of a sudden increase + blocks_would_have_drawn++; + if(blocks_drawn >= m_control.wanted_max_blocks + && m_control.range_all == false + && d > m_control.wanted_min_range * BS) + continue; + + // Mesh animation + { + //JMutexAutoLock lock(block->mesh_mutex); + MapBlockMesh *mapBlockMesh = block->mesh; + // Pretty random but this should work somewhat nicely + bool faraway = d >= BS*50; + //bool faraway = d >= m_control.wanted_range * BS; + if(mapBlockMesh->isAnimationForced() || + !faraway || + mesh_animate_count_far < (m_control.range_all ? 200 : 50)) + { + bool animated = mapBlockMesh->animate( + faraway, + animation_time, + crack, + daynight_ratio); + if(animated) + mesh_animate_count++; + if(animated && faraway) + mesh_animate_count_far++; + } + else + { + mapBlockMesh->decreaseAnimationForceTimer(); + } + } + + // Add to set + drawset[block->getPos()] = block; + + sector_blocks_drawn++; + blocks_drawn++; + + } // foreach sectorblocks + + if(sector_blocks_drawn != 0) + m_last_drawn_sectors[sp] = true; + } + } // ScopeProfiler + + /* + Draw the selected MapBlocks + */ + + { + ScopeProfiler sp(g_profiler, prefix+"drawing blocks", SPT_AVG); + + int timecheck_counter = 0; + for(core::map<v3s16, MapBlock*>::Iterator + i = drawset.getIterator(); + i.atEnd() == false; i++) + { + { + timecheck_counter++; + if(timecheck_counter > 50) + { + timecheck_counter = 0; + int time2 = time(0); + if(time2 > time1 + 4) + { + infostream<<"ClientMap::renderMap(): " + "Rendering takes ages, returning." + <<std::endl; + return; + } + } + } + + MapBlock *block = i.getNode()->getValue(); + + /* + Draw the faces of the block + */ + { + //JMutexAutoLock lock(block->mesh_mutex); + + MapBlockMesh *mapBlockMesh = block->mesh; + assert(mapBlockMesh); + + scene::SMesh *mesh = mapBlockMesh->getMesh(); + assert(mesh); + + u32 c = mesh->getMeshBufferCount(); + bool stuff_actually_drawn = false; + for(u32 i=0; i<c; i++) + { + scene::IMeshBuffer *buf = mesh->getMeshBuffer(i); + const video::SMaterial& material = buf->getMaterial(); + video::IMaterialRenderer* rnd = + driver->getMaterialRenderer(material.MaterialType); + bool transparent = (rnd && rnd->isTransparent()); + // Render transparent on transparent pass and likewise. + if(transparent == is_transparent_pass) + { + if(buf->getVertexCount() == 0) + errorstream<<"Block ["<<analyze_block(block) + <<"] contains an empty meshbuf"<<std::endl; + /* + This *shouldn't* hurt too much because Irrlicht + doesn't change opengl textures if the old + material has the same texture. + */ + driver->setMaterial(buf->getMaterial()); + driver->drawMeshBuffer(buf); + vertex_count += buf->getVertexCount(); + meshbuffer_count++; + stuff_actually_drawn = true; + } + } + if(stuff_actually_drawn) + blocks_had_pass_meshbuf++; + else + blocks_without_stuff++; + } + } + } // ScopeProfiler + + // Log only on solid pass because values are the same + if(pass == scene::ESNRP_SOLID){ + g_profiler->avg("CM: blocks in range", blocks_in_range); + g_profiler->avg("CM: blocks occlusion culled", blocks_occlusion_culled); + if(blocks_in_range != 0) + g_profiler->avg("CM: blocks in range without mesh (frac)", + (float)blocks_in_range_without_mesh/blocks_in_range); + g_profiler->avg("CM: blocks drawn", blocks_drawn); + g_profiler->avg("CM: animated meshes", mesh_animate_count); + g_profiler->avg("CM: animated meshes (far)", mesh_animate_count_far); + } + + g_profiler->avg(prefix+"vertices drawn", vertex_count); + if(blocks_had_pass_meshbuf != 0) + g_profiler->avg(prefix+"meshbuffers per block", + (float)meshbuffer_count / (float)blocks_had_pass_meshbuf); + if(blocks_drawn != 0) + g_profiler->avg(prefix+"empty blocks (frac)", + (float)blocks_without_stuff / blocks_drawn); + + m_control.blocks_drawn = blocks_drawn; + m_control.blocks_would_have_drawn = blocks_would_have_drawn; + + /*infostream<<"renderMap(): is_transparent_pass="<<is_transparent_pass + <<", rendered "<<vertex_count<<" vertices."<<std::endl;*/ +} + +void ClientMap::renderPostFx() +{ + INodeDefManager *nodemgr = m_gamedef->ndef(); + + // Sadly ISceneManager has no "post effects" render pass, in that case we + // could just register for that and handle it in renderMap(). + + m_camera_mutex.Lock(); + v3f camera_position = m_camera_position; + m_camera_mutex.Unlock(); + + MapNode n = getNodeNoEx(floatToInt(camera_position, BS)); + + // - If the player is in a solid node, make everything black. + // - If the player is in liquid, draw a semi-transparent overlay. + const ContentFeatures& features = nodemgr->get(n); + video::SColor post_effect_color = features.post_effect_color; + if(features.solidness == 2 && g_settings->getBool("free_move") == false) + { + post_effect_color = video::SColor(255, 0, 0, 0); + } + if (post_effect_color.getAlpha() != 0) + { + // Draw a full-screen rectangle + video::IVideoDriver* driver = SceneManager->getVideoDriver(); + v2u32 ss = driver->getScreenSize(); + core::rect<s32> rect(0,0, ss.X, ss.Y); + driver->draw2DRectangle(post_effect_color, rect); + } +} + +void ClientMap::PrintInfo(std::ostream &out) +{ + out<<"ClientMap: "; +} + + diff --git a/src/clientmap.h b/src/clientmap.h new file mode 100644 index 000000000..aead670c1 --- /dev/null +++ b/src/clientmap.h @@ -0,0 +1,146 @@ +/* +Minetest-c55 +Copyright (C) 2010-2012 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 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 General Public License for more details. + +You should have received a copy of the GNU 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 CLIENTMAP_HEADER +#define CLIENTMAP_HEADER + +#include "common_irrlicht.h" +#include "map.h" + +struct MapDrawControl +{ + MapDrawControl(): + range_all(false), + wanted_range(50), + wanted_max_blocks(0), + wanted_min_range(0), + blocks_drawn(0), + blocks_would_have_drawn(0) + { + } + // Overrides limits by drawing everything + bool range_all; + // Wanted drawing range + float wanted_range; + // Maximum number of blocks to draw + u32 wanted_max_blocks; + // Blocks in this range are drawn regardless of number of blocks drawn + float wanted_min_range; + // Number of blocks rendered is written here by the renderer + u32 blocks_drawn; + // Number of blocks that would have been drawn in wanted_range + u32 blocks_would_have_drawn; +}; + +class Client; +class ITextureSource; + +/* + ClientMap + + This is the only map class that is able to render itself on screen. +*/ + +class ClientMap : public Map, public scene::ISceneNode +{ +public: + ClientMap( + Client *client, + IGameDef *gamedef, + MapDrawControl &control, + scene::ISceneNode* parent, + scene::ISceneManager* mgr, + s32 id + ); + + ~ClientMap(); + + s32 mapType() const + { + return MAPTYPE_CLIENT; + } + + void drop() + { + ISceneNode::drop(); + } + + void updateCamera(v3f pos, v3f dir, f32 fov) + { + JMutexAutoLock lock(m_camera_mutex); + m_camera_position = pos; + m_camera_direction = dir; + m_camera_fov = fov; + } + + /* + Forcefully get a sector from somewhere + */ + MapSector * emergeSector(v2s16 p); + + //void deSerializeSector(v2s16 p2d, std::istream &is); + + /* + ISceneNode methods + */ + + virtual void OnRegisterSceneNode(); + + virtual void render() + { + video::IVideoDriver* driver = SceneManager->getVideoDriver(); + driver->setTransform(video::ETS_WORLD, AbsoluteTransformation); + renderMap(driver, SceneManager->getSceneNodeRenderPass()); + } + + virtual const core::aabbox3d<f32>& getBoundingBox() const + { + return m_box; + } + + void renderMap(video::IVideoDriver* driver, s32 pass); + + void renderPostFx(); + + // For debug printing + virtual void PrintInfo(std::ostream &out); + + // Check if sector was drawn on last render() + bool sectorWasDrawn(v2s16 p) + { + return (m_last_drawn_sectors.find(p) != NULL); + } + +private: + Client *m_client; + + core::aabbox3d<f32> m_box; + + MapDrawControl &m_control; + + v3f m_camera_position; + v3f m_camera_direction; + f32 m_camera_fov; + JMutex m_camera_mutex; + + core::map<v2s16, bool> m_last_drawn_sectors; +}; + +#endif + diff --git a/src/environment.cpp b/src/environment.cpp index c3ff7b75a..93649eed5 100644 --- a/src/environment.cpp +++ b/src/environment.cpp @@ -38,6 +38,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "main.h" // For g_settings, g_profiler #include "gamedef.h" #include "serverremoteplayer.h" +#ifndef SERVER +#include "clientmap.h" +#endif #define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")" @@ -1820,6 +1823,16 @@ ClientEnvironment::~ClientEnvironment() m_map->drop(); } +Map & ClientEnvironment::getMap() +{ + return *m_map; +} + +ClientMap & ClientEnvironment::getClientMap() +{ + return *m_map; +} + void ClientEnvironment::addPlayer(Player *player) { DSTACK(__FUNCTION_NAME); diff --git a/src/environment.h b/src/environment.h index 65495fc85..a3a659c5b 100644 --- a/src/environment.h +++ b/src/environment.h @@ -45,6 +45,7 @@ class ServerActiveObject; typedef struct lua_State lua_State; class ITextureSource; class IGameDef; +class ClientMap; class Environment { @@ -393,11 +394,8 @@ public: IrrlichtDevice *device); ~ClientEnvironment(); - Map & getMap() - { return *m_map; } - - ClientMap & getClientMap() - { return *m_map; } + Map & getMap(); + ClientMap & getClientMap(); IGameDef *getGameDef() { return m_gamedef; } diff --git a/src/farmesh.cpp b/src/farmesh.cpp index fdc81057b..836d9bd87 100644 --- a/src/farmesh.cpp +++ b/src/farmesh.cpp @@ -30,6 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "map.h" #include "client.h" #include "tile.h" // ITextureSource +#include "clientmap.h" #include "mapgen.h" // Shouldn't really be done this way diff --git a/src/game.cpp b/src/game.cpp index 09b1a3961..778615cf9 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -53,6 +53,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "logoutputbuffer.h" #include "subgame.h" #include "quicktune_shortcutter.h" +#include "clientmap.h" /* Setting this to 1 enables a special camera mode that forces diff --git a/src/map.cpp b/src/map.cpp index 03a842e74..b3954019b 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -32,11 +32,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "profiler.h" #include "nodedef.h" #include "gamedef.h" -#ifndef SERVER -#include "client.h" -#include "mapblock_mesh.h" -#include <IMaterialRenderer.h> -#endif #define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")" @@ -3514,530 +3509,6 @@ void ServerMap::PrintInfo(std::ostream &out) out<<"ServerMap: "; } -#ifndef SERVER - -/* - ClientMap -*/ - -ClientMap::ClientMap( - Client *client, - IGameDef *gamedef, - MapDrawControl &control, - scene::ISceneNode* parent, - scene::ISceneManager* mgr, - s32 id -): - Map(dout_client, gamedef), - scene::ISceneNode(parent, mgr, id), - m_client(client), - m_control(control), - m_camera_position(0,0,0), - m_camera_direction(0,0,1), - m_camera_fov(PI) -{ - m_camera_mutex.Init(); - assert(m_camera_mutex.IsInitialized()); - - m_box = core::aabbox3d<f32>(-BS*1000000,-BS*1000000,-BS*1000000, - BS*1000000,BS*1000000,BS*1000000); -} - -ClientMap::~ClientMap() -{ - /*JMutexAutoLock lock(mesh_mutex); - - if(mesh != NULL) - { - mesh->drop(); - mesh = NULL; - }*/ -} - -MapSector * ClientMap::emergeSector(v2s16 p2d) -{ - DSTACK(__FUNCTION_NAME); - // Check that it doesn't exist already - try{ - return getSectorNoGenerate(p2d); - } - catch(InvalidPositionException &e) - { - } - - // Create a sector - ClientMapSector *sector = new ClientMapSector(this, p2d, m_gamedef); - - { - //JMutexAutoLock lock(m_sector_mutex); // Bulk comment-out - m_sectors.insert(p2d, sector); - } - - return sector; -} - -#if 0 -void ClientMap::deSerializeSector(v2s16 p2d, std::istream &is) -{ - DSTACK(__FUNCTION_NAME); - ClientMapSector *sector = NULL; - - //JMutexAutoLock lock(m_sector_mutex); // Bulk comment-out - - core::map<v2s16, MapSector*>::Node *n = m_sectors.find(p2d); - - if(n != NULL) - { - sector = (ClientMapSector*)n->getValue(); - assert(sector->getId() == MAPSECTOR_CLIENT); - } - else - { - sector = new ClientMapSector(this, p2d); - { - //JMutexAutoLock lock(m_sector_mutex); // Bulk comment-out - m_sectors.insert(p2d, sector); - } - } - - sector->deSerialize(is); -} -#endif - -void ClientMap::OnRegisterSceneNode() -{ - if(IsVisible) - { - SceneManager->registerNodeForRendering(this, scene::ESNRP_SOLID); - SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT); - } - - ISceneNode::OnRegisterSceneNode(); -} - -static bool isOccluded(Map *map, v3s16 p0, v3s16 p1, float step, float stepfac, - float start_off, float end_off, u32 needed_count, INodeDefManager *nodemgr) -{ - float d0 = (float)BS * p0.getDistanceFrom(p1); - v3s16 u0 = p1 - p0; - v3f uf = v3f(u0.X, u0.Y, u0.Z) * BS; - uf.normalize(); - v3f p0f = v3f(p0.X, p0.Y, p0.Z) * BS; - u32 count = 0; - for(float s=start_off; s<d0+end_off; s+=step){ - v3f pf = p0f + uf * s; - v3s16 p = floatToInt(pf, BS); - MapNode n = map->getNodeNoEx(p); - bool is_transparent = false; - const ContentFeatures &f = nodemgr->get(n); - if(f.solidness == 0) - is_transparent = (f.visual_solidness != 2); - else - is_transparent = (f.solidness != 2); - if(!is_transparent){ - count++; - if(count >= needed_count) - return true; - } - step *= stepfac; - } - return false; -} - -void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) -{ - INodeDefManager *nodemgr = m_gamedef->ndef(); - - //m_dout<<DTIME<<"Rendering map..."<<std::endl; - DSTACK(__FUNCTION_NAME); - - bool is_transparent_pass = pass == scene::ESNRP_TRANSPARENT; - - std::string prefix; - if(pass == scene::ESNRP_SOLID) - prefix = "CM: solid: "; - else - prefix = "CM: transparent: "; - - /* - This is called two times per frame, reset on the non-transparent one - */ - if(pass == scene::ESNRP_SOLID) - { - m_last_drawn_sectors.clear(); - } - - /* - Get time for measuring timeout. - - Measuring time is very useful for long delays when the - machine is swapping a lot. - */ - int time1 = time(0); - - /* - Get animation parameters - */ - float animation_time = m_client->getAnimationTime(); - int crack = m_client->getCrackLevel(); - u32 daynight_ratio = m_client->getDayNightRatio(); - - m_camera_mutex.Lock(); - v3f camera_position = m_camera_position; - v3f camera_direction = m_camera_direction; - f32 camera_fov = m_camera_fov; - m_camera_mutex.Unlock(); - - /* - Get all blocks and draw all visible ones - */ - - v3s16 cam_pos_nodes = floatToInt(camera_position, BS); - - v3s16 box_nodes_d = m_control.wanted_range * v3s16(1,1,1); - - v3s16 p_nodes_min = cam_pos_nodes - box_nodes_d; - v3s16 p_nodes_max = cam_pos_nodes + box_nodes_d; - - // Take a fair amount as we will be dropping more out later - // Umm... these additions are a bit strange but they are needed. - v3s16 p_blocks_min( - p_nodes_min.X / MAP_BLOCKSIZE - 3, - p_nodes_min.Y / MAP_BLOCKSIZE - 3, - p_nodes_min.Z / MAP_BLOCKSIZE - 3); - v3s16 p_blocks_max( - p_nodes_max.X / MAP_BLOCKSIZE + 1, - p_nodes_max.Y / MAP_BLOCKSIZE + 1, - p_nodes_max.Z / MAP_BLOCKSIZE + 1); - - u32 vertex_count = 0; - u32 meshbuffer_count = 0; - - // For limiting number of mesh animations per frame - u32 mesh_animate_count = 0; - u32 mesh_animate_count_far = 0; - - // Number of blocks in rendering range - u32 blocks_in_range = 0; - // Number of blocks occlusion culled - u32 blocks_occlusion_culled = 0; - // Number of blocks in rendering range but don't have a mesh - u32 blocks_in_range_without_mesh = 0; - // Blocks that had mesh that would have been drawn according to - // rendering range (if max blocks limit didn't kick in) - u32 blocks_would_have_drawn = 0; - // Blocks that were drawn and had a mesh - u32 blocks_drawn = 0; - // Blocks which had a corresponding meshbuffer for this pass - u32 blocks_had_pass_meshbuf = 0; - // Blocks from which stuff was actually drawn - u32 blocks_without_stuff = 0; - - /* - Collect a set of blocks for drawing - */ - - core::map<v3s16, MapBlock*> drawset; - - { - ScopeProfiler sp(g_profiler, prefix+"collecting blocks for drawing", SPT_AVG); - - for(core::map<v2s16, MapSector*>::Iterator - si = m_sectors.getIterator(); - si.atEnd() == false; si++) - { - MapSector *sector = si.getNode()->getValue(); - v2s16 sp = sector->getPos(); - - if(m_control.range_all == false) - { - if(sp.X < p_blocks_min.X - || sp.X > p_blocks_max.X - || sp.Y < p_blocks_min.Z - || sp.Y > p_blocks_max.Z) - continue; - } - - core::list< MapBlock * > sectorblocks; - sector->getBlocks(sectorblocks); - - /* - Loop through blocks in sector - */ - - u32 sector_blocks_drawn = 0; - - core::list< MapBlock * >::Iterator i; - for(i=sectorblocks.begin(); i!=sectorblocks.end(); i++) - { - MapBlock *block = *i; - - /* - Compare block position to camera position, skip - if not seen on display - */ - - float range = 100000 * BS; - if(m_control.range_all == false) - range = m_control.wanted_range * BS; - - float d = 0.0; - if(isBlockInSight(block->getPos(), camera_position, - camera_direction, camera_fov, - range, &d) == false) - { - continue; - } - - // This is ugly (spherical distance limit?) - /*if(m_control.range_all == false && - d - 0.5*BS*MAP_BLOCKSIZE > range) - continue;*/ - - blocks_in_range++; - - /* - Ignore if mesh doesn't exist - */ - { - //JMutexAutoLock lock(block->mesh_mutex); - - if(block->mesh == NULL){ - blocks_in_range_without_mesh++; - continue; - } - } - - /* - Occlusion culling - */ - - v3s16 cpn = block->getPos() * MAP_BLOCKSIZE; - cpn += v3s16(MAP_BLOCKSIZE/2, MAP_BLOCKSIZE/2, MAP_BLOCKSIZE/2); - float step = BS*1; - float stepfac = 1.1; - float startoff = BS*1; - float endoff = -BS*MAP_BLOCKSIZE*1.42*1.42; - v3s16 spn = cam_pos_nodes + v3s16(0,0,0); - s16 bs2 = MAP_BLOCKSIZE/2 + 1; - u32 needed_count = 1; - if( - isOccluded(this, spn, cpn + v3s16(0,0,0), - step, stepfac, startoff, endoff, needed_count, nodemgr) && - isOccluded(this, spn, cpn + v3s16(bs2,bs2,bs2), - step, stepfac, startoff, endoff, needed_count, nodemgr) && - isOccluded(this, spn, cpn + v3s16(bs2,bs2,-bs2), - step, stepfac, startoff, endoff, needed_count, nodemgr) && - isOccluded(this, spn, cpn + v3s16(bs2,-bs2,bs2), - step, stepfac, startoff, endoff, needed_count, nodemgr) && - isOccluded(this, spn, cpn + v3s16(bs2,-bs2,-bs2), - step, stepfac, startoff, endoff, needed_count, nodemgr) && - isOccluded(this, spn, cpn + v3s16(-bs2,bs2,bs2), - step, stepfac, startoff, endoff, needed_count, nodemgr) && - isOccluded(this, spn, cpn + v3s16(-bs2,bs2,-bs2), - step, stepfac, startoff, endoff, needed_count, nodemgr) && - isOccluded(this, spn, cpn + v3s16(-bs2,-bs2,bs2), - step, stepfac, startoff, endoff, needed_count, nodemgr) && - isOccluded(this, spn, cpn + v3s16(-bs2,-bs2,-bs2), - step, stepfac, startoff, endoff, needed_count, nodemgr) - ) - { - blocks_occlusion_culled++; - continue; - } - - // This block is in range. Reset usage timer. - block->resetUsageTimer(); - - // Limit block count in case of a sudden increase - blocks_would_have_drawn++; - if(blocks_drawn >= m_control.wanted_max_blocks - && m_control.range_all == false - && d > m_control.wanted_min_range * BS) - continue; - - // Mesh animation - { - //JMutexAutoLock lock(block->mesh_mutex); - MapBlockMesh *mapBlockMesh = block->mesh; - // Pretty random but this should work somewhat nicely - bool faraway = d >= BS*50; - //bool faraway = d >= m_control.wanted_range * BS; - if(mapBlockMesh->isAnimationForced() || - !faraway || - mesh_animate_count_far < (m_control.range_all ? 200 : 50)) - { - bool animated = mapBlockMesh->animate( - faraway, - animation_time, - crack, - daynight_ratio); - if(animated) - mesh_animate_count++; - if(animated && faraway) - mesh_animate_count_far++; - } - else - { - mapBlockMesh->decreaseAnimationForceTimer(); - } - } - - // Add to set - drawset[block->getPos()] = block; - - sector_blocks_drawn++; - blocks_drawn++; - - } // foreach sectorblocks - - if(sector_blocks_drawn != 0) - m_last_drawn_sectors[sp] = true; - } - } // ScopeProfiler - - /* - Draw the selected MapBlocks - */ - - { - ScopeProfiler sp(g_profiler, prefix+"drawing blocks", SPT_AVG); - - int timecheck_counter = 0; - for(core::map<v3s16, MapBlock*>::Iterator - i = drawset.getIterator(); - i.atEnd() == false; i++) - { - { - timecheck_counter++; - if(timecheck_counter > 50) - { - timecheck_counter = 0; - int time2 = time(0); - if(time2 > time1 + 4) - { - infostream<<"ClientMap::renderMap(): " - "Rendering takes ages, returning." - <<std::endl; - return; - } - } - } - - MapBlock *block = i.getNode()->getValue(); - - /* - Draw the faces of the block - */ - { - //JMutexAutoLock lock(block->mesh_mutex); - - MapBlockMesh *mapBlockMesh = block->mesh; - assert(mapBlockMesh); - - scene::SMesh *mesh = mapBlockMesh->getMesh(); - assert(mesh); - - u32 c = mesh->getMeshBufferCount(); - bool stuff_actually_drawn = false; - for(u32 i=0; i<c; i++) - { - scene::IMeshBuffer *buf = mesh->getMeshBuffer(i); - const video::SMaterial& material = buf->getMaterial(); - video::IMaterialRenderer* rnd = - driver->getMaterialRenderer(material.MaterialType); - bool transparent = (rnd && rnd->isTransparent()); - // Render transparent on transparent pass and likewise. - if(transparent == is_transparent_pass) - { - if(buf->getVertexCount() == 0) - errorstream<<"Block ["<<analyze_block(block) - <<"] contains an empty meshbuf"<<std::endl; - /* - This *shouldn't* hurt too much because Irrlicht - doesn't change opengl textures if the old - material has the same texture. - */ - driver->setMaterial(buf->getMaterial()); - driver->drawMeshBuffer(buf); - vertex_count += buf->getVertexCount(); - meshbuffer_count++; - stuff_actually_drawn = true; - } - } - if(stuff_actually_drawn) - blocks_had_pass_meshbuf++; - else - blocks_without_stuff++; - } - } - } // ScopeProfiler - - // Log only on solid pass because values are the same - if(pass == scene::ESNRP_SOLID){ - g_profiler->avg("CM: blocks in range", blocks_in_range); - g_profiler->avg("CM: blocks occlusion culled", blocks_occlusion_culled); - if(blocks_in_range != 0) - g_profiler->avg("CM: blocks in range without mesh (frac)", - (float)blocks_in_range_without_mesh/blocks_in_range); - g_profiler->avg("CM: blocks drawn", blocks_drawn); - g_profiler->avg("CM: animated meshes", mesh_animate_count); - g_profiler->avg("CM: animated meshes (far)", mesh_animate_count_far); - } - - g_profiler->avg(prefix+"vertices drawn", vertex_count); - if(blocks_had_pass_meshbuf != 0) - g_profiler->avg(prefix+"meshbuffers per block", - (float)meshbuffer_count / (float)blocks_had_pass_meshbuf); - if(blocks_drawn != 0) - g_profiler->avg(prefix+"empty blocks (frac)", - (float)blocks_without_stuff / blocks_drawn); - - m_control.blocks_drawn = blocks_drawn; - m_control.blocks_would_have_drawn = blocks_would_have_drawn; - - /*infostream<<"renderMap(): is_transparent_pass="<<is_transparent_pass - <<", rendered "<<vertex_count<<" vertices."<<std::endl;*/ -} - -void ClientMap::renderPostFx() -{ - INodeDefManager *nodemgr = m_gamedef->ndef(); - - // Sadly ISceneManager has no "post effects" render pass, in that case we - // could just register for that and handle it in renderMap(). - - m_camera_mutex.Lock(); - v3f camera_position = m_camera_position; - m_camera_mutex.Unlock(); - - MapNode n = getNodeNoEx(floatToInt(camera_position, BS)); - - // - If the player is in a solid node, make everything black. - // - If the player is in liquid, draw a semi-transparent overlay. - const ContentFeatures& features = nodemgr->get(n); - video::SColor post_effect_color = features.post_effect_color; - if(features.solidness == 2 && g_settings->getBool("free_move") == false) - { - post_effect_color = video::SColor(255, 0, 0, 0); - } - if (post_effect_color.getAlpha() != 0) - { - // Draw a full-screen rectangle - video::IVideoDriver* driver = SceneManager->getVideoDriver(); - v2u32 ss = driver->getScreenSize(); - core::rect<s32> rect(0,0, ss.X, ss.Y); - driver->draw2DRectangle(post_effect_color, rect); - } -} - -void ClientMap::PrintInfo(std::ostream &out) -{ - out<<"ClientMap: "; -} - -#endif // !SERVER - /* MapVoxelManipulator */ @@ -37,9 +37,9 @@ extern "C" { #include "sqlite3.h" } +class ClientMap; class MapSector; class ServerMapSector; -class ClientMapSector; class MapBlock; class NodeMetadata; class IGameDef; @@ -468,137 +468,6 @@ private: sqlite3_stmt *m_database_list; }; -/* - ClientMap stuff -*/ - -#ifndef SERVER - -struct MapDrawControl -{ - MapDrawControl(): - range_all(false), - wanted_range(50), - wanted_max_blocks(0), - wanted_min_range(0), - blocks_drawn(0), - blocks_would_have_drawn(0) - { - } - // Overrides limits by drawing everything - bool range_all; - // Wanted drawing range - float wanted_range; - // Maximum number of blocks to draw - u32 wanted_max_blocks; - // Blocks in this range are drawn regardless of number of blocks drawn - float wanted_min_range; - // Number of blocks rendered is written here by the renderer - u32 blocks_drawn; - // Number of blocks that would have been drawn in wanted_range - u32 blocks_would_have_drawn; -}; - -class Client; -class ITextureSource; - -/* - ClientMap - - This is the only map class that is able to render itself on screen. -*/ - -class ClientMap : public Map, public scene::ISceneNode -{ -public: - ClientMap( - Client *client, - IGameDef *gamedef, - MapDrawControl &control, - scene::ISceneNode* parent, - scene::ISceneManager* mgr, - s32 id - ); - - ~ClientMap(); - - s32 mapType() const - { - return MAPTYPE_CLIENT; - } - - void drop() - { - ISceneNode::drop(); - } - - void updateCamera(v3f pos, v3f dir, f32 fov) - { - JMutexAutoLock lock(m_camera_mutex); - m_camera_position = pos; - m_camera_direction = dir; - m_camera_fov = fov; - } - - /* - Forcefully get a sector from somewhere - */ - MapSector * emergeSector(v2s16 p); - - //void deSerializeSector(v2s16 p2d, std::istream &is); - - /* - ISceneNode methods - */ - - virtual void OnRegisterSceneNode(); - - virtual void render() - { - video::IVideoDriver* driver = SceneManager->getVideoDriver(); - driver->setTransform(video::ETS_WORLD, AbsoluteTransformation); - renderMap(driver, SceneManager->getSceneNodeRenderPass()); - } - - virtual const core::aabbox3d<f32>& getBoundingBox() const - { - return m_box; - } - - void renderMap(video::IVideoDriver* driver, s32 pass); - - void renderPostFx(); - - // For debug printing - virtual void PrintInfo(std::ostream &out); - - // Check if sector was drawn on last render() - bool sectorWasDrawn(v2s16 p) - { - return (m_last_drawn_sectors.find(p) != NULL); - } - -private: - Client *m_client; - - core::aabbox3d<f32> m_box; - - // This is the master heightmap mesh - //scene::SMesh *mesh; - //JMutex mesh_mutex; - - MapDrawControl &m_control; - - v3f m_camera_position; - v3f m_camera_direction; - f32 m_camera_fov; - JMutex m_camera_mutex; - - core::map<v2s16, bool> m_last_drawn_sectors; -}; - -#endif - class MapVoxelManipulator : public VoxelManipulator { public: |