aboutsummaryrefslogtreecommitdiff
path: root/src/client
diff options
context:
space:
mode:
authorElias Fleckenstein <eliasfleckenstein@web.de>2022-05-17 22:12:00 +0200
committerElias Fleckenstein <eliasfleckenstein@web.de>2022-05-17 22:12:00 +0200
commit21df26984da91143c15587f5a03c98d68c3adc4e (patch)
treeaaa707a628ad331f67890023dffe1b4f60dd01d3 /src/client
parentb09fc5de5cdb021f43ad32b7e3f50dc75c0bc622 (diff)
parenteabf05758e3ba5f6f4bb1b8d1d1f02179b84e410 (diff)
downloaddragonfireclient-21df26984da91143c15587f5a03c98d68c3adc4e.tar.xz
Merge branch 'master' of https://github.com/minetest/minetest
Diffstat (limited to 'src/client')
-rw-r--r--src/client/CMakeLists.txt4
-rw-r--r--src/client/camera.cpp82
-rw-r--r--src/client/camera.h9
-rw-r--r--src/client/client.cpp139
-rw-r--r--src/client/client.h19
-rw-r--r--src/client/clientenvironment.cpp38
-rw-r--r--src/client/clientlauncher.cpp13
-rw-r--r--src/client/clientmap.cpp337
-rw-r--r--src/client/clientmap.h79
-rw-r--r--src/client/clientmedia.h6
-rw-r--r--src/client/clouds.h2
-rw-r--r--src/client/content_cao.cpp179
-rw-r--r--src/client/content_cao.h8
-rw-r--r--src/client/content_mapblock.cpp150
-rw-r--r--src/client/content_mapblock.h5
-rw-r--r--src/client/fontengine.cpp214
-rw-r--r--src/client/fontengine.h14
-rw-r--r--src/client/game.cpp410
-rw-r--r--src/client/game.h34
-rw-r--r--src/client/gameui.cpp37
-rw-r--r--src/client/gameui.h6
-rw-r--r--src/client/guiscalingfilter.cpp12
-rw-r--r--src/client/hud.cpp125
-rw-r--r--src/client/imagefilters.cpp3
-rw-r--r--src/client/inputhandler.h52
-rw-r--r--src/client/joystick_controller.cpp50
-rw-r--r--src/client/localplayer.cpp36
-rw-r--r--src/client/localplayer.h10
-rw-r--r--src/client/mapblock_mesh.cpp328
-rw-r--r--src/client/mapblock_mesh.h114
-rw-r--r--src/client/mesh.cpp605
-rw-r--r--src/client/mesh.h7
-rw-r--r--src/client/mesh_generator_thread.cpp53
-rw-r--r--src/client/mesh_generator_thread.h5
-rw-r--r--src/client/minimap.cpp2
-rw-r--r--src/client/render/anaglyph.cpp1
-rw-r--r--src/client/render/core.cpp5
-rw-r--r--src/client/renderingengine.cpp8
-rw-r--r--src/client/shader.cpp93
-rw-r--r--src/client/shadows/dynamicshadows.cpp79
-rw-r--r--src/client/shadows/dynamicshadows.h21
-rw-r--r--src/client/shadows/dynamicshadowsrender.cpp148
-rw-r--r--src/client/shadows/dynamicshadowsrender.h17
-rw-r--r--src/client/shadows/shadowsshadercallbacks.cpp12
-rw-r--r--src/client/shadows/shadowsshadercallbacks.h12
-rw-r--r--src/client/sky.cpp178
-rw-r--r--src/client/sky.h11
-rw-r--r--src/client/tile.cpp174
-rw-r--r--src/client/tile.h19
-rw-r--r--src/client/wieldmesh.cpp36
50 files changed, 2151 insertions, 1850 deletions
diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt
index 8d058852a..656ad45ce 100644
--- a/src/client/CMakeLists.txt
+++ b/src/client/CMakeLists.txt
@@ -60,7 +60,7 @@ set(client_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/wieldmesh.cpp
${CMAKE_CURRENT_SOURCE_DIR}/shadows/dynamicshadows.cpp
${CMAKE_CURRENT_SOURCE_DIR}/shadows/dynamicshadowsrender.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/shadows/shadowsshadercallbacks.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/shadows/shadowsScreenQuad.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/shadows/shadowsshadercallbacks.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/shadows/shadowsScreenQuad.cpp
PARENT_SCOPE
)
diff --git a/src/client/camera.cpp b/src/client/camera.cpp
index 52bedcba9..0c387262e 100644
--- a/src/client/camera.cpp
+++ b/src/client/camera.cpp
@@ -26,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "player.h"
#include <cmath>
#include "client/renderingengine.h"
+#include "client/content_cao.h"
#include "settings.h"
#include "wieldmesh.h"
#include "noise.h" // easeCurve
@@ -37,6 +38,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "fontengine.h"
#include "guiscalingfilter.h"
#include "script/scripting_client.h"
+#include "gettext.h"
#define CAMERA_OFFSET_STEP 200
#define WIELDMESH_OFFSET_X 55.0f
@@ -133,28 +135,6 @@ void Camera::notifyFovChange()
}
}
-bool Camera::successfullyCreated(std::string &error_message)
-{
- if (!m_playernode) {
- error_message = "Failed to create the player scene node";
- } else if (!m_headnode) {
- error_message = "Failed to create the head scene node";
- } else if (!m_cameranode) {
- error_message = "Failed to create the camera scene node";
- } else if (!m_wieldmgr) {
- error_message = "Failed to create the wielded item scene manager";
- } else if (!m_wieldnode) {
- error_message = "Failed to create the wielded item scene node";
- } else {
- error_message.clear();
- }
-
- if (m_client->modsLoaded())
- m_client->getScript()->on_camera_ready(this);
-
- return error_message.empty();
-}
-
// Returns the fractional part of x
inline f32 my_modf(f32 x)
{
@@ -187,9 +167,7 @@ void Camera::step(f32 dtime)
m_view_bobbing_anim -= offset;
} else if (m_view_bobbing_anim > 0.75) {
m_view_bobbing_anim += offset;
- }
-
- if (m_view_bobbing_anim < 0.5) {
+ } else if (m_view_bobbing_anim < 0.5) {
m_view_bobbing_anim += offset;
if (m_view_bobbing_anim > 0.5)
m_view_bobbing_anim = 0.5;
@@ -331,7 +309,7 @@ void Camera::addArmInertia(f32 player_yaw)
}
}
-void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_reload_ratio)
+void Camera::update(LocalPlayer* player, f32 frametime, f32 tool_reload_ratio)
{
// Get player position
// Smooth the movement when walking up stairs
@@ -344,13 +322,16 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_r
if (player->getParent())
player_position = player->getParent()->getPosition() + v3f(0, g_settings->getBool("float_above_parent") ? BS : 0, 0);
- // Smooth the camera movement when the player instantly moves upward due to stepheight.
- // To smooth the 'not touching_ground' stepheight, smoothing is necessary when jumping
- // or swimming (for when moving from liquid to land).
- // Disable smoothing if climbing or flying, to avoid upwards offset of player model
- // when seen in 3rd person view.
- bool flying = (g_settings->getBool("free_move") && m_client->checkLocalPrivilege("fly")) || g_settings->getBool("freecam");
- if (player_position.Y > old_player_position.Y && !player->is_climbing && !flying) {
+ // Smooth the camera movement after the player instantly moves upward due to stepheight.
+ // The smoothing usually continues until the camera position reaches the player position.
+ float player_stepheight = player->getCAO() ? player->getCAO()->getStepHeight() : HUGE_VALF;
+ float upward_movement = player_position.Y - old_player_position.Y;
+ if (upward_movement < 0.01f || upward_movement > player_stepheight) {
+ m_stepheight_smooth_active = false;
+ } else if (player->touching_ground) {
+ m_stepheight_smooth_active = true;
+ }
+ if (m_stepheight_smooth_active) {
f32 oldy = old_player_position.Y;
f32 newy = player_position.Y;
f32 t = std::exp(-23 * frametime);
@@ -379,7 +360,8 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_r
// Smoothen and invert the above
fall_bobbing = sin(fall_bobbing * 0.5 * M_PI) * -1;
// Amplify according to the intensity of the impact
- fall_bobbing *= (1 - rangelim(50 / player->camera_impact, 0, 1)) * 5;
+ if (player->camera_impact > 0.0f)
+ fall_bobbing *= (1 - rangelim(50 / player->camera_impact, 0, 1)) * 5;
fall_bobbing *= m_cache_fall_bobbing_amount;
}
@@ -410,41 +392,17 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_r
f32 bobfrac = my_modf(m_view_bobbing_anim * 2);
f32 bobdir = (m_view_bobbing_anim < 0.5) ? 1.0 : -1.0;
- #if 1
f32 bobknob = 1.2;
f32 bobtmp = sin(pow(bobfrac, bobknob) * M_PI);
- //f32 bobtmp2 = cos(pow(bobfrac, bobknob) * M_PI);
v3f bobvec = v3f(
0.3 * bobdir * sin(bobfrac * M_PI),
-0.28 * bobtmp * bobtmp,
0.);
- //rel_cam_pos += 0.2 * bobvec;
- //rel_cam_target += 0.03 * bobvec;
- //rel_cam_up.rotateXYBy(0.02 * bobdir * bobtmp * M_PI);
- float f = 1.0;
- f *= m_cache_view_bobbing_amount;
- rel_cam_pos += bobvec * f;
- //rel_cam_target += 0.995 * bobvec * f;
- rel_cam_target += bobvec * f;
- rel_cam_target.Z -= 0.005 * bobvec.Z * f;
- //rel_cam_target.X -= 0.005 * bobvec.X * f;
- //rel_cam_target.Y -= 0.005 * bobvec.Y * f;
- rel_cam_up.rotateXYBy(-0.03 * bobdir * bobtmp * M_PI * f);
- #else
- f32 angle_deg = 1 * bobdir * sin(bobfrac * M_PI);
- f32 angle_rad = angle_deg * M_PI / 180;
- f32 r = 0.05;
- v3f off = v3f(
- r * sin(angle_rad),
- r * (cos(angle_rad) - 1),
- 0);
- rel_cam_pos += off;
- //rel_cam_target += off;
- rel_cam_up.rotateXYBy(angle_deg);
- #endif
-
+ rel_cam_pos += bobvec * m_cache_view_bobbing_amount;
+ rel_cam_target += bobvec * m_cache_view_bobbing_amount;
+ rel_cam_up.rotateXYBy(-0.03 * bobdir * bobtmp * M_PI * m_cache_view_bobbing_amount);
}
// Compute absolute camera position and target
@@ -613,6 +571,8 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_r
const bool walking = movement_XZ && player->touching_ground;
const bool swimming = (movement_XZ || player->swimming_vertical) && player->in_liquid;
const bool climbing = movement_Y && player->is_climbing;
+ const bool flying = g_settings->getBool("free_move")
+ && m_client->checkLocalPrivilege("fly");
if ((walking || swimming || climbing) && !flying) {
// Start animation
m_view_bobbing_state = 1;
diff --git a/src/client/camera.h b/src/client/camera.h
index 30fac5289..ecd71f1e7 100644
--- a/src/client/camera.h
+++ b/src/client/camera.h
@@ -162,16 +162,11 @@ public:
// Notify about new server-sent FOV and initialize smooth FOV transition
void notifyFovChange();
- // Checks if the constructor was able to create the scene nodes
- bool successfullyCreated(std::string &error_message);
-
// Step the camera: updates the viewing range and view bobbing.
void step(f32 dtime);
// Update the camera from the local player's position.
- // busytime is used to adjust the viewing range.
- void update(LocalPlayer* player, f32 frametime, f32 busytime,
- f32 tool_reload_ratio);
+ void update(LocalPlayer* player, f32 frametime, f32 tool_reload_ratio);
// Update render distance
void updateViewingRange();
@@ -245,6 +240,8 @@ private:
// Camera offset
v3s16 m_camera_offset;
+ bool m_stepheight_smooth_active = false;
+
// Server-sent FOV variables
bool m_server_sent_fov = false;
f32 m_curr_fov_degrees, m_old_fov_degrees, m_target_fov_degrees;
diff --git a/src/client/client.cpp b/src/client/client.cpp
index 3c4ea5f95..4e4bb8a97 100644
--- a/src/client/client.cpp
+++ b/src/client/client.cpp
@@ -51,6 +51,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "clientmap.h"
#include "clientmedia.h"
#include "version.h"
+#include "database/database-files.h"
#include "database/database-sqlite3.h"
#include "serialization.h"
#include "guiscalingfilter.h"
@@ -129,6 +130,11 @@ Client::Client(
// Add local player
m_env.setLocalPlayer(new LocalPlayer(this, playername));
+ // Make the mod storage database and begin the save for later
+ m_mod_storage_database =
+ new ModMetadataDatabaseSQLite3(porting::path_user + DIR_DELIM + "client");
+ m_mod_storage_database->beginSave();
+
if (g_settings->getBool("enable_minimap")) {
m_minimap = new Minimap(this);
}
@@ -136,6 +142,33 @@ Client::Client(
m_cache_save_interval = g_settings->getU16("server_map_save_interval");
}
+void Client::migrateModStorage()
+{
+ std::string mod_storage_dir = porting::path_user + DIR_DELIM + "client";
+ std::string old_mod_storage = mod_storage_dir + DIR_DELIM + "mod_storage";
+ if (fs::IsDir(old_mod_storage)) {
+ infostream << "Migrating client mod storage to SQLite3 database" << std::endl;
+ {
+ ModMetadataDatabaseFiles files_db(mod_storage_dir);
+ std::vector<std::string> mod_list;
+ files_db.listMods(&mod_list);
+ for (const std::string &modname : mod_list) {
+ infostream << "Migrating client mod storage for mod " << modname << std::endl;
+ StringMap meta;
+ files_db.getModEntries(modname, &meta);
+ for (const auto &pair : meta) {
+ m_mod_storage_database->setModEntry(modname, pair.first, pair.second);
+ }
+ }
+ }
+ if (!fs::Rename(old_mod_storage, old_mod_storage + ".bak")) {
+ // Execution cannot move forward if the migration does not complete.
+ throw BaseException("Could not finish migrating client mod storage");
+ }
+ infostream << "Finished migration of client mod storage" << std::endl;
+ }
+}
+
void Client::loadMods()
{
// Don't load mods twice.
@@ -304,10 +337,17 @@ Client::~Client()
// cleanup 3d model meshes on client shutdown
m_rendering_engine->cleanupMeshCache();
+ guiScalingCacheClear();
+
delete m_minimap;
m_minimap = nullptr;
delete m_media_downloader;
+
+ // Write the changes and delete
+ if (m_mod_storage_database)
+ m_mod_storage_database->endSave();
+ delete m_mod_storage_database;
}
void Client::connect(Address address, bool is_local_server)
@@ -644,19 +684,12 @@ void Client::step(float dtime)
}
}
+ // Write changes to the mod storage
m_mod_storage_save_timer -= dtime;
if (m_mod_storage_save_timer <= 0.0f) {
m_mod_storage_save_timer = g_settings->getFloat("server_map_save_interval");
- int n = 0;
- for (std::unordered_map<std::string, ModMetadata *>::const_iterator
- it = m_mod_storages.begin(); it != m_mod_storages.end(); ++it) {
- if (it->second->isModified()) {
- it->second->save(getModStoragePath());
- n++;
- }
- }
- if (n > 0)
- infostream << "Saved " << n << " modified mod storages." << std::endl;
+ m_mod_storage_database->endSave();
+ m_mod_storage_database->beginSave();
}
// Write server map
@@ -880,7 +913,7 @@ void Client::ProcessData(NetworkPacket *pkt)
*/
if(sender_peer_id != PEER_ID_SERVER) {
infostream << "Client::ProcessData(): Discarding data not "
- "coming from server: peer_id=" << sender_peer_id
+ "coming from server: peer_id=" << sender_peer_id << " command=" << pkt->getCommand()
<< std::endl;
return;
}
@@ -931,7 +964,7 @@ void writePlayerPos(LocalPlayer *myplayer, ClientMap *clientMap, NetworkPacket *
v3f sf = myplayer->getSendSpeed() * 100;
s32 pitch = myplayer->getPitch() * 100;
s32 yaw = myplayer->getYaw() * 100;
- u32 keyPressed = myplayer->keyPressed;
+ u32 keyPressed = myplayer->control.getKeysPressed();
// scaled by 80, so that pi can fit into a u8
u8 fov = clientMap->getCameraFov() * 80;
u8 wanted_range = MYMIN(255,
@@ -1287,22 +1320,24 @@ void Client::sendPlayerPos(v3f pos)
if (!player)
return;
- ClientMap &map = m_env.getClientMap();
- u8 camera_fov = map.getCameraFov();
- u8 wanted_range = map.getControl().wanted_range;
-
// Save bandwidth by only updating position when
// player is not dead and something changed
if (m_activeobjects_received && player->isDead())
return;
+ ClientMap &map = m_env.getClientMap();
+ u8 camera_fov = map.getCameraFov();
+ u8 wanted_range = map.getControl().wanted_range;
+
+ u32 keyPressed = player->control.getKeysPressed();
+
if (
player->last_position == pos &&
player->last_speed == player->getSendSpeed() &&
player->last_pitch == player->getPitch() &&
player->last_yaw == player->getYaw() &&
- player->last_keyPressed == player->keyPressed &&
+ player->last_keyPressed == keyPressed &&
player->last_camera_fov == camera_fov &&
player->last_wanted_range == wanted_range)
return;
@@ -1311,7 +1346,7 @@ void Client::sendPlayerPos(v3f pos)
player->last_speed = player->getSendSpeed();
player->last_pitch = player->getPitch();
player->last_yaw = player->getYaw();
- player->last_keyPressed = player->keyPressed;
+ player->last_keyPressed = keyPressed;
player->last_camera_fov = camera_fov;
player->last_wanted_range = wanted_range;
@@ -1614,20 +1649,7 @@ void Client::addUpdateMeshTask(v3s16 p, bool ack_to_server, bool urgent)
void Client::addUpdateMeshTaskWithEdge(v3s16 blockpos, bool ack_to_server, bool urgent)
{
- try{
- addUpdateMeshTask(blockpos, ack_to_server, urgent);
- }
- catch(InvalidPositionException &e){}
-
- // Leading edge
- for (int i=0;i<6;i++)
- {
- try{
- v3s16 p = blockpos + g_6dirs[i];
- addUpdateMeshTask(p, false, urgent);
- }
- catch(InvalidPositionException &e){}
- }
+ m_mesh_update_thread.updateBlock(&m_env.getMap(), blockpos, ack_to_server, urgent, true);
}
void Client::addUpdateMeshTaskForNode(v3s16 nodepos, bool ack_to_server, bool urgent)
@@ -1639,38 +1661,16 @@ void Client::addUpdateMeshTaskForNode(v3s16 nodepos, bool ack_to_server, bool ur
<<std::endl;
}
- v3s16 blockpos = getNodeBlockPos(nodepos);
+ v3s16 blockpos = getNodeBlockPos(nodepos);
v3s16 blockpos_relative = blockpos * MAP_BLOCKSIZE;
-
- try{
- addUpdateMeshTask(blockpos, ack_to_server, urgent);
- }
- catch(InvalidPositionException &e) {}
-
+ m_mesh_update_thread.updateBlock(&m_env.getMap(), blockpos, ack_to_server, urgent, false);
// Leading edge
- if(nodepos.X == blockpos_relative.X){
- try{
- v3s16 p = blockpos + v3s16(-1,0,0);
- addUpdateMeshTask(p, false, urgent);
- }
- catch(InvalidPositionException &e){}
- }
-
- if(nodepos.Y == blockpos_relative.Y){
- try{
- v3s16 p = blockpos + v3s16(0,-1,0);
- addUpdateMeshTask(p, false, urgent);
- }
- catch(InvalidPositionException &e){}
- }
-
- if(nodepos.Z == blockpos_relative.Z){
- try{
- v3s16 p = blockpos + v3s16(0,0,-1);
- addUpdateMeshTask(p, false, urgent);
- }
- catch(InvalidPositionException &e){}
- }
+ if (nodepos.X == blockpos_relative.X)
+ addUpdateMeshTask(blockpos + v3s16(-1, 0, 0), false, urgent);
+ if (nodepos.Y == blockpos_relative.Y)
+ addUpdateMeshTask(blockpos + v3s16(0, -1, 0), false, urgent);
+ if (nodepos.Z == blockpos_relative.Z)
+ addUpdateMeshTask(blockpos + v3s16(0, 0, -1), false, urgent);
}
void Client::updateAllMapBlocks()
@@ -1834,11 +1834,10 @@ void Client::makeScreenshot()
if (!raw_image)
return;
- time_t t = time(NULL);
- struct tm *tm = localtime(&t);
+ const struct tm tm = mt_localtime();
char timetstamp_c[64];
- strftime(timetstamp_c, sizeof(timetstamp_c), "%Y%m%d_%H%M%S", tm);
+ strftime(timetstamp_c, sizeof(timetstamp_c), "%Y%m%d_%H%M%S", &tm);
std::string screenshot_dir;
@@ -2025,16 +2024,8 @@ void Client::unregisterModStorage(const std::string &name)
{
std::unordered_map<std::string, ModMetadata *>::const_iterator it =
m_mod_storages.find(name);
- if (it != m_mod_storages.end()) {
- // Save unconditionaly on unregistration
- it->second->save(getModStoragePath());
+ if (it != m_mod_storages.end())
m_mod_storages.erase(name);
- }
-}
-
-std::string Client::getModStoragePath() const
-{
- return porting::path_user + DIR_DELIM + "client" + DIR_DELIM + "mod_storage";
}
/*
diff --git a/src/client/client.h b/src/client/client.h
index 1493d3ce3..d49f2f9ad 100644
--- a/src/client/client.h
+++ b/src/client/client.h
@@ -226,6 +226,7 @@ public:
void handleCommand_PlayerSpeed(NetworkPacket *pkt);
void handleCommand_MediaPush(NetworkPacket *pkt);
void handleCommand_MinimapModes(NetworkPacket *pkt);
+ void handleCommand_SetLighting(NetworkPacket *pkt);
void ProcessData(NetworkPacket *pkt);
@@ -335,13 +336,13 @@ public:
// disconnect client when CSM failed.
const std::string &accessDeniedReason() const { return m_access_denied_reason; }
- const bool itemdefReceived() const
+ bool itemdefReceived() const
{ return m_itemdef_received; }
- const bool nodedefReceived() const
+ bool nodedefReceived() const
{ return m_nodedef_received; }
- const bool mediaReceived() const
+ bool mediaReceived() const
{ return !m_media_downloader; }
- const bool activeObjectsReceived() const
+ bool activeObjectsReceived() const
{ return m_activeobjects_received; }
u16 getProtoVersion()
@@ -382,11 +383,14 @@ public:
bool checkLocalPrivilege(const std::string &priv){ return checkPrivilege(priv); }
virtual scene::IAnimatedMesh* getMesh(const std::string &filename, bool cache = false);
const std::string* getModFile(std::string filename);
+ ModMetadataDatabase *getModStorageDatabase() override { return m_mod_storage_database; }
- std::string getModStoragePath() const override;
bool registerModStorage(ModMetadata *meta) override;
void unregisterModStorage(const std::string &name) override;
+ // Migrates away old files-based mod storage if necessary
+ void migrateModStorage();
+
// The following set of functions is used by ClientMediaDownloader
// Insert a media file appropriately into the appropriate manager
bool loadMedia(const std::string &data, const std::string &filename,
@@ -405,7 +409,7 @@ public:
}
ClientScripting *getScript() { return m_script; }
- const bool modsLoaded() const { return m_mods_loaded; }
+ bool modsLoaded() const { return m_mods_loaded; }
void pushToEventQueue(ClientEvent *event);
@@ -554,7 +558,7 @@ private:
// Set of media filenames pushed by server at runtime
std::unordered_set<std::string> m_media_pushed_files;
// Pending downloads of dynamic media (key: token)
- std::vector<std::pair<u32, std::unique_ptr<SingleMediaDownloader>>> m_pending_media_downloads;
+ std::vector<std::pair<u32, std::shared_ptr<SingleMediaDownloader>>> m_pending_media_downloads;
// time_of_day speed approximation for old protocol
bool m_time_of_day_set = false;
@@ -596,6 +600,7 @@ private:
// Client modding
ClientScripting *m_script = nullptr;
std::unordered_map<std::string, ModMetadata *> m_mod_storages;
+ ModMetadataDatabase *m_mod_storage_database = nullptr;
float m_mod_storage_save_timer = 10.0f;
std::vector<ModSpec> m_mods;
StringMap m_mod_vfs;
diff --git a/src/client/clientenvironment.cpp b/src/client/clientenvironment.cpp
index f9c20b2df..01aaa0408 100644
--- a/src/client/clientenvironment.cpp
+++ b/src/client/clientenvironment.cpp
@@ -193,33 +193,41 @@ void ClientEnvironment::step(float dtime)
// Control local player
lplayer->applyControl(dtime_part, this);
- // Apply physics
- if (!free_move && !is_climbing && !g_settings->getBool("freecam")) {
+ if (!free_move && !g_settings->getBool("freecam")) {
// Gravity
v3f speed = lplayer->getSpeed();
- if (!lplayer->in_liquid)
+ if (!is_climbing && !lplayer->in_liquid)
speed.Y -= lplayer->movement_gravity *
lplayer->physics_override_gravity * dtime_part * 2.0f;
// Liquid floating / sinking
- if (lplayer->in_liquid && !lplayer->swimming_vertical &&
+ if (!is_climbing && lplayer->in_liquid &&
+ !lplayer->swimming_vertical &&
!lplayer->swimming_pitch)
speed.Y -= lplayer->movement_liquid_sink * dtime_part * 2.0f;
- // Liquid resistance
- if (lplayer->in_liquid_stable || lplayer->in_liquid) {
- // How much the node's viscosity blocks movement, ranges
- // between 0 and 1. Should match the scale at which viscosity
+ // Movement resistance
+ if (lplayer->move_resistance > 0) {
+ // How much the node's move_resistance blocks movement, ranges
+ // between 0 and 1. Should match the scale at which liquid_viscosity
// increase affects other liquid attributes.
- static const f32 viscosity_factor = 0.3f;
-
- v3f d_wanted = -speed / lplayer->movement_liquid_fluidity;
+ static const f32 resistance_factor = 0.3f;
+
+ v3f d_wanted;
+ bool in_liquid_stable = lplayer->in_liquid_stable || lplayer->in_liquid;
+ if (in_liquid_stable) {
+ d_wanted = -speed / lplayer->movement_liquid_fluidity;
+ } else {
+ d_wanted = -speed / BS;
+ }
f32 dl = d_wanted.getLength();
- if (dl > lplayer->movement_liquid_fluidity_smooth)
- dl = lplayer->movement_liquid_fluidity_smooth;
+ if (in_liquid_stable) {
+ if (dl > lplayer->movement_liquid_fluidity_smooth)
+ dl = lplayer->movement_liquid_fluidity_smooth;
+ }
- dl *= (lplayer->liquid_viscosity * viscosity_factor) +
- (1 - viscosity_factor);
+ dl *= (lplayer->move_resistance * resistance_factor) +
+ (1 - resistance_factor);
v3f d = d_wanted.normalize() * (dl * dtime_part * 100.0f);
speed += d;
}
diff --git a/src/client/clientlauncher.cpp b/src/client/clientlauncher.cpp
index 6ab610670..54c561d11 100644
--- a/src/client/clientlauncher.cpp
+++ b/src/client/clientlauncher.cpp
@@ -38,9 +38,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#if USE_SOUND
#include "sound_openal.h"
#endif
-#ifdef __ANDROID__
- #include "porting.h"
-#endif
/* mainmenumanager.h
*/
@@ -73,10 +70,10 @@ static void dump_start_data(const GameStartData &data)
ClientLauncher::~ClientLauncher()
{
- delete receiver;
-
delete input;
+ delete receiver;
+
delete g_fontengine;
delete g_gamecallback;
@@ -147,8 +144,8 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args)
skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(255, 0, 0, 0));
skin->setColor(gui::EGDC_HIGH_LIGHT, video::SColor(255, 70, 120, 50));
skin->setColor(gui::EGDC_HIGH_LIGHT_TEXT, video::SColor(255, 255, 255, 255));
-#ifdef __ANDROID__
- float density = porting::getDisplayDensity();
+#ifdef HAVE_TOUCHSCREENGUI
+ float density = RenderingEngine::getDisplayDensity();
skin->setSize(gui::EGDS_CHECK_BOX_WIDTH, (s32)(17.0f * density));
skin->setSize(gui::EGDS_SCROLLBAR_SIZE, (s32)(14.0f * density));
skin->setSize(gui::EGDS_WINDOW_BUTTON_WIDTH, (s32)(15.0f * density));
@@ -567,6 +564,8 @@ void ClientLauncher::speed_tests()
// volatile to avoid some potential compiler optimisations
volatile static s16 temp16;
volatile static f32 tempf;
+ // Silence compiler warning
+ (void)temp16;
static v3f tempv3f1;
static v3f tempv3f2;
static std::string tempstring;
diff --git a/src/client/clientmap.cpp b/src/client/clientmap.cpp
index 4a4784f91..51f0f6896 100644
--- a/src/client/clientmap.cpp
+++ b/src/client/clientmap.cpp
@@ -73,7 +73,8 @@ ClientMap::ClientMap(
rendering_engine->get_scene_manager(), id),
m_client(client),
m_rendering_engine(rendering_engine),
- m_control(control)
+ m_control(control),
+ m_drawlist(MapBlockComparer(v3s16(0,0,0)))
{
/*
@@ -96,9 +97,32 @@ ClientMap::ClientMap(
m_cache_trilinear_filter = g_settings->getBool("trilinear_filter");
m_cache_bilinear_filter = g_settings->getBool("bilinear_filter");
m_cache_anistropic_filter = g_settings->getBool("anisotropic_filter");
+ m_cache_transparency_sorting_distance = g_settings->getU16("transparency_sorting_distance");
}
+void ClientMap::updateCamera(v3f pos, v3f dir, f32 fov, v3s16 offset)
+{
+ v3s16 previous_node = floatToInt(m_camera_position, BS) + m_camera_offset;
+ v3s16 previous_block = getContainerPos(previous_node, MAP_BLOCKSIZE);
+
+ m_camera_position = pos;
+ m_camera_direction = dir;
+ m_camera_fov = fov;
+ m_camera_offset = offset;
+
+ v3s16 current_node = floatToInt(m_camera_position, BS) + m_camera_offset;
+ v3s16 current_block = getContainerPos(current_node, MAP_BLOCKSIZE);
+
+ // reorder the blocks when camera crosses block boundary
+ if (previous_block != current_block)
+ m_needs_update_drawlist = true;
+
+ // reorder transparent meshes when camera crosses node boundary
+ if (previous_node != current_node)
+ m_needs_update_transparent_meshes = true;
+}
+
MapSector * ClientMap::emergeSector(v2s16 p2d)
{
// Check that it doesn't exist already
@@ -164,6 +188,8 @@ void ClientMap::updateDrawList()
{
ScopeProfiler sp(g_profiler, "CM::updateDrawList()", SPT_AVG);
+ m_needs_update_drawlist = false;
+
for (auto &i : m_drawlist) {
MapBlock *block = i.second;
block->refDrop();
@@ -178,6 +204,7 @@ void ClientMap::updateDrawList()
const f32 camera_fov = m_camera_fov * 1.1f;
v3s16 cam_pos_nodes = floatToInt(camera_position, BS);
+
v3s16 p_blocks_min;
v3s16 p_blocks_max;
getBlocksInViewRange(cam_pos_nodes, &p_blocks_min, &p_blocks_max);
@@ -202,6 +229,8 @@ void ClientMap::updateDrawList()
occlusion_culling_enabled = false;
}
+ v3s16 camera_block = getContainerPos(cam_pos_nodes, MAP_BLOCKSIZE);
+ m_drawlist = std::map<v3s16, MapBlock*, MapBlockComparer>(MapBlockComparer(camera_block));
// Uncomment to debug occluded blocks in the wireframe mode
// TODO: Include this as a flag for an extended debugging setting
@@ -318,10 +347,18 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
//u32 mesh_animate_count_far = 0;
/*
+ Update transparent meshes
+ */
+ if (is_transparent_pass)
+ updateTransparentMeshBuffers();
+
+ /*
Draw the selected MapBlocks
*/
- MeshBufListList drawbufs;
+ MeshBufListList grouped_buffers;
+ std::vector<DrawDescriptor> draw_order;
+ video::SMaterial previous_material;
for (auto &i : m_drawlist) {
v3s16 block_pos = i.first;
@@ -356,7 +393,15 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
/*
Get the meshbuffers of the block
*/
- {
+ if (is_transparent_pass) {
+ // In transparent pass, the mesh will give us
+ // the partial buffers in the correct order
+ for (auto &buffer : block->mesh->getTransparentBuffers())
+ draw_order.emplace_back(block_pos, &buffer);
+ }
+ else {
+ // otherwise, group buffers across meshes
+ // using MeshBufListList
MapBlockMesh *mapBlockMesh = block->mesh;
assert(mapBlockMesh);
@@ -370,69 +415,94 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
video::SMaterial& material = buf->getMaterial();
video::IMaterialRenderer* rnd =
- driver->getMaterialRenderer(material.MaterialType);
+ driver->getMaterialRenderer(material.MaterialType);
bool transparent = (rnd && rnd->isTransparent());
- if (transparent == is_transparent_pass) {
+ if (!transparent) {
if (buf->getVertexCount() == 0)
errorstream << "Block [" << analyze_block(block)
- << "] contains an empty meshbuf" << std::endl;
-
- material.setFlag(video::EMF_TRILINEAR_FILTER,
- m_cache_trilinear_filter);
- material.setFlag(video::EMF_BILINEAR_FILTER,
- m_cache_bilinear_filter);
- material.setFlag(video::EMF_ANISOTROPIC_FILTER,
- m_cache_anistropic_filter);
- material.setFlag(video::EMF_WIREFRAME,
- m_control.show_wireframe);
-
- drawbufs.add(buf, block_pos, layer);
+ << "] contains an empty meshbuf" << std::endl;
+
+ grouped_buffers.add(buf, block_pos, layer);
}
}
}
}
}
+ // Capture draw order for all solid meshes
+ for (auto &lists : grouped_buffers.lists) {
+ for (MeshBufList &list : lists) {
+ // iterate in reverse to draw closest blocks first
+ for (auto it = list.bufs.rbegin(); it != list.bufs.rend(); ++it) {
+ draw_order.emplace_back(it->first, it->second, it != list.bufs.rbegin());
+ }
+ }
+ }
+
TimeTaker draw("Drawing mesh buffers");
core::matrix4 m; // Model matrix
v3f offset = intToFloat(m_camera_offset, BS);
+ u32 material_swaps = 0;
- // Render all layers in order
- for (auto &lists : drawbufs.lists) {
- for (MeshBufList &list : lists) {
- // Check and abort if the machine is swapping a lot
- if (draw.getTimerTime() > 2000) {
- infostream << "ClientMap::renderMap(): Rendering took >2s, " <<
- "returning." << std::endl;
- return;
- }
+ // Render all mesh buffers in order
+ drawcall_count += draw_order.size();
+
+ for (auto &descriptor : draw_order) {
+ scene::IMeshBuffer *buf;
+
+ if (descriptor.m_use_partial_buffer) {
+ descriptor.m_partial_buffer->beforeDraw();
+ buf = descriptor.m_partial_buffer->getBuffer();
+ }
+ else {
+ buf = descriptor.m_buffer;
+ }
+
+ // Check and abort if the machine is swapping a lot
+ if (draw.getTimerTime() > 2000) {
+ infostream << "ClientMap::renderMap(): Rendering took >2s, " <<
+ "returning." << std::endl;
+ return;
+ }
+
+ if (!descriptor.m_reuse_material) {
+ auto &material = buf->getMaterial();
+
+ // Apply filter settings
+ material.setFlag(video::EMF_TRILINEAR_FILTER,
+ m_cache_trilinear_filter);
+ material.setFlag(video::EMF_BILINEAR_FILTER,
+ m_cache_bilinear_filter);
+ material.setFlag(video::EMF_ANISOTROPIC_FILTER,
+ m_cache_anistropic_filter);
+ material.setFlag(video::EMF_WIREFRAME,
+ m_control.show_wireframe);
// pass the shadow map texture to the buffer texture
ShadowRenderer *shadow = m_rendering_engine->get_shadow_renderer();
if (shadow && shadow->is_active()) {
- auto &layer = list.m.TextureLayer[3];
+ auto &layer = material.TextureLayer[3];
layer.Texture = shadow->get_texture();
layer.TextureWrapU = video::E_TEXTURE_CLAMP::ETC_CLAMP_TO_EDGE;
layer.TextureWrapV = video::E_TEXTURE_CLAMP::ETC_CLAMP_TO_EDGE;
- layer.TrilinearFilter = true;
+ // Do not enable filter on shadow texture to avoid visual artifacts
+ // with colored shadows.
+ // Filtering is done in shader code anyway
+ layer.TrilinearFilter = false;
}
+ driver->setMaterial(material);
+ ++material_swaps;
+ }
- driver->setMaterial(list.m);
-
- drawcall_count += list.bufs.size();
- for (auto &pair : list.bufs) {
- scene::IMeshBuffer *buf = pair.second;
-
- v3f block_wpos = intToFloat(pair.first * MAP_BLOCKSIZE, BS);
- m.setTranslation(block_wpos - offset);
+ v3f block_wpos = intToFloat(descriptor.m_pos * MAP_BLOCKSIZE, BS);
+ m.setTranslation(block_wpos - offset);
- driver->setTransform(video::ETS_WORLD, m);
- driver->drawMeshBuffer(buf);
- vertex_count += buf->getVertexCount();
- }
- }
+ driver->setTransform(video::ETS_WORLD, m);
+ driver->drawMeshBuffer(buf);
+ vertex_count += buf->getIndexCount();
}
+
g_profiler->avg(prefix + "draw meshes [ms]", draw.stop(true));
// Log only on solid pass because values are the same
@@ -440,8 +510,13 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
g_profiler->avg("renderMap(): animated meshes [#]", mesh_animate_count);
}
+ if (pass == scene::ESNRP_TRANSPARENT) {
+ g_profiler->avg("renderMap(): transparent buffers [#]", draw_order.size());
+ }
+
g_profiler->avg(prefix + "vertices drawn [#]", vertex_count);
g_profiler->avg(prefix + "drawcalls [#]", drawcall_count);
+ g_profiler->avg(prefix + "material swaps [#]", material_swaps);
}
static bool getVisibleBrightness(Map *map, const v3f &p0, v3f dir, float step,
@@ -648,7 +723,9 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
u32 drawcall_count = 0;
u32 vertex_count = 0;
- MeshBufListList drawbufs;
+ MeshBufListList grouped_buffers;
+ std::vector<DrawDescriptor> draw_order;
+
int count = 0;
int low_bound = is_transparent_pass ? 0 : m_drawlist_shadow.size() / total_frames * frame;
@@ -677,7 +754,15 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
/*
Get the meshbuffers of the block
*/
- {
+ if (is_transparent_pass) {
+ // In transparent pass, the mesh will give us
+ // the partial buffers in the correct order
+ for (auto &buffer : block->mesh->getTransparentBuffers())
+ draw_order.emplace_back(block_pos, &buffer);
+ }
+ else {
+ // otherwise, group buffers across meshes
+ // using MeshBufListList
MapBlockMesh *mapBlockMesh = block->mesh;
assert(mapBlockMesh);
@@ -692,79 +777,99 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
video::SMaterial &mat = buf->getMaterial();
auto rnd = driver->getMaterialRenderer(mat.MaterialType);
bool transparent = rnd && rnd->isTransparent();
- if (transparent == is_transparent_pass)
- drawbufs.add(buf, block_pos, layer);
+ if (!transparent)
+ grouped_buffers.add(buf, block_pos, layer);
}
}
}
}
+ u32 buffer_count = 0;
+ for (auto &lists : grouped_buffers.lists)
+ for (MeshBufList &list : lists)
+ buffer_count += list.bufs.size();
+
+ draw_order.reserve(draw_order.size() + buffer_count);
+
+ // Capture draw order for all solid meshes
+ for (auto &lists : grouped_buffers.lists) {
+ for (MeshBufList &list : lists) {
+ // iterate in reverse to draw closest blocks first
+ for (auto it = list.bufs.rbegin(); it != list.bufs.rend(); ++it)
+ draw_order.emplace_back(it->first, it->second, it != list.bufs.rbegin());
+ }
+ }
+
TimeTaker draw("Drawing shadow mesh buffers");
core::matrix4 m; // Model matrix
v3f offset = intToFloat(m_camera_offset, BS);
+ u32 material_swaps = 0;
- // Render all layers in order
- for (auto &lists : drawbufs.lists) {
- for (MeshBufList &list : lists) {
- // Check and abort if the machine is swapping a lot
- if (draw.getTimerTime() > 1000) {
- infostream << "ClientMap::renderMapShadows(): Rendering "
- "took >1s, returning." << std::endl;
- break;
- }
- for (auto &pair : list.bufs) {
- scene::IMeshBuffer *buf = pair.second;
-
- // override some material properties
- video::SMaterial local_material = buf->getMaterial();
- local_material.MaterialType = material.MaterialType;
- local_material.BackfaceCulling = material.BackfaceCulling;
- local_material.FrontfaceCulling = material.FrontfaceCulling;
- local_material.BlendOperation = material.BlendOperation;
- local_material.Lighting = false;
- driver->setMaterial(local_material);
-
- v3f block_wpos = intToFloat(pair.first * MAP_BLOCKSIZE, BS);
- m.setTranslation(block_wpos - offset);
-
- driver->setTransform(video::ETS_WORLD, m);
- driver->drawMeshBuffer(buf);
- vertex_count += buf->getVertexCount();
- }
+ // Render all mesh buffers in order
+ drawcall_count += draw_order.size();
+
+ for (auto &descriptor : draw_order) {
+ scene::IMeshBuffer *buf;
+
+ if (descriptor.m_use_partial_buffer) {
+ descriptor.m_partial_buffer->beforeDraw();
+ buf = descriptor.m_partial_buffer->getBuffer();
+ }
+ else {
+ buf = descriptor.m_buffer;
+ }
+
+ // Check and abort if the machine is swapping a lot
+ if (draw.getTimerTime() > 1000) {
+ infostream << "ClientMap::renderMapShadows(): Rendering "
+ "took >1s, returning." << std::endl;
+ break;
+ }
- drawcall_count += list.bufs.size();
+ if (!descriptor.m_reuse_material) {
+ // override some material properties
+ video::SMaterial local_material = buf->getMaterial();
+ local_material.MaterialType = material.MaterialType;
+ local_material.BackfaceCulling = material.BackfaceCulling;
+ local_material.FrontfaceCulling = material.FrontfaceCulling;
+ local_material.BlendOperation = material.BlendOperation;
+ local_material.Lighting = false;
+ driver->setMaterial(local_material);
+ ++material_swaps;
}
+
+ v3f block_wpos = intToFloat(descriptor.m_pos * MAP_BLOCKSIZE, BS);
+ m.setTranslation(block_wpos - offset);
+
+ driver->setTransform(video::ETS_WORLD, m);
+ driver->drawMeshBuffer(buf);
+ vertex_count += buf->getIndexCount();
}
- // restore the driver material state
+ // restore the driver material state
video::SMaterial clean;
clean.BlendOperation = video::EBO_ADD;
driver->setMaterial(clean); // reset material to defaults
driver->draw3DLine(v3f(), v3f(), video::SColor(0));
-
+
g_profiler->avg(prefix + "draw meshes [ms]", draw.stop(true));
g_profiler->avg(prefix + "vertices drawn [#]", vertex_count);
g_profiler->avg(prefix + "drawcalls [#]", drawcall_count);
+ g_profiler->avg(prefix + "material swaps [#]", material_swaps);
}
/*
Custom update draw list for the pov of shadow light.
*/
-void ClientMap::updateDrawListShadow(const v3f &shadow_light_pos, const v3f &shadow_light_dir, float shadow_range)
+void ClientMap::updateDrawListShadow(v3f shadow_light_pos, v3f shadow_light_dir, float radius, float length)
{
ScopeProfiler sp(g_profiler, "CM::updateDrawListShadow()", SPT_AVG);
- const v3f camera_position = shadow_light_pos;
- const v3f camera_direction = shadow_light_dir;
- // I "fake" fov just to avoid creating a new function to handle orthographic
- // projection.
- const f32 camera_fov = m_camera_fov * 1.9f;
-
- v3s16 cam_pos_nodes = floatToInt(camera_position, BS);
+ v3s16 cam_pos_nodes = floatToInt(shadow_light_pos, BS);
v3s16 p_blocks_min;
v3s16 p_blocks_max;
- getBlocksInViewRange(cam_pos_nodes, &p_blocks_min, &p_blocks_max, shadow_range);
+ getBlocksInViewRange(cam_pos_nodes, &p_blocks_min, &p_blocks_max, radius + length);
std::vector<v2s16> blocks_in_range;
@@ -774,15 +879,6 @@ void ClientMap::updateDrawListShadow(const v3f &shadow_light_pos, const v3f &sha
}
m_drawlist_shadow.clear();
- // We need to append the blocks from the camera POV because sometimes
- // they are not inside the light frustum and it creates glitches.
- // FIXME: This could be removed if we figure out why they are missing
- // from the light frustum.
- for (auto &i : m_drawlist) {
- i.second->refGrab();
- m_drawlist_shadow[i.first] = i.second;
- }
-
// Number of blocks currently loaded by the client
u32 blocks_loaded = 0;
// Number of blocks with mesh in rendering range
@@ -808,23 +904,13 @@ void ClientMap::updateDrawListShadow(const v3f &shadow_light_pos, const v3f &sha
continue;
}
- float range = shadow_range;
-
- float d = 0.0;
- if (!isBlockInSight(block->getPos(), camera_position,
- camera_direction, camera_fov, range, &d))
+ v3f block_pos = intToFloat(block->getPos() * MAP_BLOCKSIZE, BS);
+ v3f projection = shadow_light_pos + shadow_light_dir * shadow_light_dir.dotProduct(block_pos - shadow_light_pos);
+ if (projection.getDistanceFrom(block_pos) > radius)
continue;
blocks_in_range_with_mesh++;
- /*
- Occlusion culling
- */
- if (isBlockOccluded(block, cam_pos_nodes)) {
- blocks_occlusion_culled++;
- continue;
- }
-
// This block is in range. Reset usage timer.
block->resetUsageTimer();
@@ -841,3 +927,40 @@ void ClientMap::updateDrawListShadow(const v3f &shadow_light_pos, const v3f &sha
g_profiler->avg("SHADOW MapBlocks drawn [#]", m_drawlist_shadow.size());
g_profiler->avg("SHADOW MapBlocks loaded [#]", blocks_loaded);
}
+
+void ClientMap::updateTransparentMeshBuffers()
+{
+ ScopeProfiler sp(g_profiler, "CM::updateTransparentMeshBuffers", SPT_AVG);
+ u32 sorted_blocks = 0;
+ u32 unsorted_blocks = 0;
+ f32 sorting_distance_sq = pow(m_cache_transparency_sorting_distance * BS, 2.0f);
+
+
+ // Update the order of transparent mesh buffers in each mesh
+ for (auto it = m_drawlist.begin(); it != m_drawlist.end(); it++) {
+ MapBlock* block = it->second;
+ if (!block->mesh)
+ continue;
+
+ if (m_needs_update_transparent_meshes ||
+ block->mesh->getTransparentBuffers().size() == 0) {
+
+ v3s16 block_pos = block->getPos();
+ v3f block_pos_f = intToFloat(block_pos * MAP_BLOCKSIZE + MAP_BLOCKSIZE / 2, BS);
+ f32 distance = m_camera_position.getDistanceFromSQ(block_pos_f);
+ if (distance <= sorting_distance_sq) {
+ block->mesh->updateTransparentBuffers(m_camera_position, block_pos);
+ ++sorted_blocks;
+ }
+ else {
+ block->mesh->consolidateTransparentBuffers();
+ ++unsorted_blocks;
+ }
+ }
+ }
+
+ g_profiler->avg("CM::Transparent Buffers - Sorted", sorted_blocks);
+ g_profiler->avg("CM::Transparent Buffers - Unsorted", unsorted_blocks);
+ m_needs_update_transparent_meshes = false;
+}
+
diff --git a/src/client/clientmap.h b/src/client/clientmap.h
index 97ce8d355..6d57f1911 100644
--- a/src/client/clientmap.h
+++ b/src/client/clientmap.h
@@ -56,6 +56,7 @@ struct MeshBufListList
class Client;
class ITextureSource;
+class PartialMeshBuffer;
/*
ClientMap
@@ -75,45 +76,37 @@ public:
virtual ~ClientMap() = default;
- s32 mapType() const
+ bool maySaveBlocks() override
{
- return MAPTYPE_CLIENT;
+ return false;
}
- void drop()
+ void drop() override
{
- ISceneNode::drop();
+ ISceneNode::drop(); // calls destructor
}
- void updateCamera(const v3f &pos, const v3f &dir, f32 fov, const v3s16 &offset)
- {
- m_camera_position = pos;
- m_camera_direction = dir;
- m_camera_fov = fov;
- m_camera_offset = offset;
- }
+ void updateCamera(v3f pos, v3f dir, f32 fov, v3s16 offset);
/*
Forcefully get a sector from somewhere
*/
- MapSector * emergeSector(v2s16 p);
-
- //void deSerializeSector(v2s16 p2d, std::istream &is);
+ MapSector * emergeSector(v2s16 p) override;
/*
ISceneNode methods
*/
- virtual void OnRegisterSceneNode();
+ virtual void OnRegisterSceneNode() override;
- virtual void render()
+ virtual void render() override
{
video::IVideoDriver* driver = SceneManager->getVideoDriver();
driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
renderMap(driver, SceneManager->getSceneNodeRenderPass());
}
- virtual const aabb3f &getBoundingBox() const
+ virtual const aabb3f &getBoundingBox() const override
{
return m_box;
}
@@ -121,7 +114,9 @@ public:
void getBlocksInViewRange(v3s16 cam_pos_nodes,
v3s16 *p_blocks_min, v3s16 *p_blocks_max, float range=-1.0f);
void updateDrawList();
- void updateDrawListShadow(const v3f &shadow_light_pos, const v3f &shadow_light_dir, float shadow_range);
+ void updateDrawListShadow(v3f shadow_light_pos, v3f shadow_light_dir, float radius, float length);
+ // Returns true if draw list needs updating before drawing the next frame.
+ bool needsUpdateDrawList() { return m_needs_update_drawlist; }
void renderMap(video::IVideoDriver* driver, s32 pass);
void renderMapShadows(video::IVideoDriver *driver,
@@ -133,13 +128,54 @@ public:
void renderPostFx(CameraMode cam_mode);
// For debug printing
- virtual void PrintInfo(std::ostream &out);
+ void PrintInfo(std::ostream &out) override;
const MapDrawControl & getControl() const { return m_control; }
f32 getWantedRange() const { return m_control.wanted_range; }
f32 getCameraFov() const { return m_camera_fov; }
private:
+
+ // update the vertex order in transparent mesh buffers
+ void updateTransparentMeshBuffers();
+
+ // Orders blocks by distance to the camera
+ class MapBlockComparer
+ {
+ public:
+ MapBlockComparer(const v3s16 &camera_block) : m_camera_block(camera_block) {}
+
+ bool operator() (const v3s16 &left, const v3s16 &right) const
+ {
+ auto distance_left = left.getDistanceFromSQ(m_camera_block);
+ auto distance_right = right.getDistanceFromSQ(m_camera_block);
+ return distance_left > distance_right || (distance_left == distance_right && left > right);
+ }
+
+ private:
+ v3s16 m_camera_block;
+ };
+
+
+ // reference to a mesh buffer used when rendering the map.
+ struct DrawDescriptor {
+ v3s16 m_pos;
+ union {
+ scene::IMeshBuffer *m_buffer;
+ const PartialMeshBuffer *m_partial_buffer;
+ };
+ bool m_reuse_material:1;
+ bool m_use_partial_buffer:1;
+
+ DrawDescriptor(v3s16 pos, scene::IMeshBuffer *buffer, bool reuse_material) :
+ m_pos(pos), m_buffer(buffer), m_reuse_material(reuse_material), m_use_partial_buffer(false)
+ {}
+
+ DrawDescriptor(v3s16 pos, const PartialMeshBuffer *buffer) :
+ m_pos(pos), m_partial_buffer(buffer), m_reuse_material(false), m_use_partial_buffer(true)
+ {}
+ };
+
Client *m_client;
RenderingEngine *m_rendering_engine;
@@ -152,9 +188,11 @@ private:
v3f m_camera_direction = v3f(0,0,1);
f32 m_camera_fov = M_PI;
v3s16 m_camera_offset;
+ bool m_needs_update_transparent_meshes = true;
- std::map<v3s16, MapBlock*> m_drawlist;
+ std::map<v3s16, MapBlock*, MapBlockComparer> m_drawlist;
std::map<v3s16, MapBlock*> m_drawlist_shadow;
+ bool m_needs_update_drawlist;
std::set<v2s16> m_last_drawn_sectors;
@@ -162,4 +200,5 @@ private:
bool m_cache_bilinear_filter;
bool m_cache_anistropic_filter;
bool m_added_to_shadow_renderer{false};
+ u16 m_cache_transparency_sorting_distance;
};
diff --git a/src/client/clientmedia.h b/src/client/clientmedia.h
index aa7b0f398..c297d737f 100644
--- a/src/client/clientmedia.h
+++ b/src/client/clientmedia.h
@@ -174,12 +174,12 @@ private:
s32 m_uncached_received_count = 0;
// Status of remote transfers
- unsigned long m_httpfetch_caller;
- unsigned long m_httpfetch_next_id = 0;
+ u64 m_httpfetch_caller;
+ u64 m_httpfetch_next_id = 0;
s32 m_httpfetch_active = 0;
s32 m_httpfetch_active_limit = 0;
s32 m_outstanding_hash_sets = 0;
- std::unordered_map<unsigned long, std::string> m_remote_file_transfers;
+ std::unordered_map<u64, std::string> m_remote_file_transfers;
// All files up to this name have either been received from a
// remote server or failed on all remote servers, so those files
diff --git a/src/client/clouds.h b/src/client/clouds.h
index c009a05b7..6db88d93c 100644
--- a/src/client/clouds.h
+++ b/src/client/clouds.h
@@ -22,7 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "irrlichttypes_extrabloated.h"
#include <iostream>
#include "constants.h"
-#include "cloudparams.h"
+#include "skyparams.h"
// Menu clouds
class Clouds;
diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp
index 5d8a597a2..ec1fd1c2a 100644
--- a/src/client/content_cao.cpp
+++ b/src/client/content_cao.cpp
@@ -27,7 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "client/sound.h"
#include "client/tile.h"
#include "util/basic_macros.h"
-#include "util/numeric.h" // For IntervalLimiter & setPitchYawRoll
+#include "util/numeric.h"
#include "util/serialize.h"
#include "camera.h" // CameraModes
#include "collision.h"
@@ -172,6 +172,20 @@ static void updatePositionRecursive(scene::ISceneNode *node)
node->updateAbsolutePosition();
}
+static bool logOnce(const std::ostringstream &from, std::ostream &log_to)
+{
+ thread_local std::vector<u64> logged;
+
+ std::string message = from.str();
+ u64 hash = murmur_hash_64_ua(message.data(), message.length(), 0xBADBABE);
+
+ if (std::find(logged.begin(), logged.end(), hash) != logged.end())
+ return false;
+ logged.push_back(hash);
+ log_to << message << std::endl;
+ return true;
+}
+
/*
TestCAO
*/
@@ -422,7 +436,7 @@ const v3f GenericCAO::getPosition() const
return m_position;
}
-const bool GenericCAO::isImmortal()
+bool GenericCAO::isImmortal() const
{
return itemgroup_get(getGroups(), "immortal");
}
@@ -651,7 +665,7 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr)
m_matrixnode, v2f(1, 1), v3f(0,0,0), -1);
m_spritenode->grab();
m_spritenode->setMaterialTexture(0,
- tsrc->getTextureForMesh("unknown_node.png"));
+ tsrc->getTextureForMesh("no_texture.png"));
setSceneNodeMaterial(m_spritenode);
@@ -735,9 +749,6 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr)
m_meshnode = m_smgr->addMeshSceneNode(mesh, m_matrixnode);
m_meshnode->grab();
mesh->drop();
- // Set it to use the materials of the meshbuffers directly.
- // This is needed for changing the texture in the future
- m_meshnode->setReadOnlyMaterials(true);
} else if (m_prop.visual == "cube") {
grabMatrixNode();
scene::IMesh *mesh = createCubeMesh(v3f(BS,BS,BS));
@@ -754,10 +765,6 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr)
grabMatrixNode();
scene::IAnimatedMesh *mesh = m_client->getMesh(m_prop.mesh, true);
if (mesh) {
- m_animated_meshnode = m_smgr->addAnimatedMeshSceneNode(mesh, m_matrixnode);
- m_animated_meshnode->grab();
- mesh->drop(); // The scene node took hold of it
-
if (!checkMeshNormals(mesh)) {
infostream << "GenericCAO: recalculating normals for mesh "
<< m_prop.mesh << std::endl;
@@ -765,6 +772,9 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr)
recalculateNormals(mesh, true, false);
}
+ m_animated_meshnode = m_smgr->addAnimatedMeshSceneNode(mesh, m_matrixnode);
+ m_animated_meshnode->grab();
+ mesh->drop(); // The scene node took hold of it
m_animated_meshnode->animateJoints(); // Needed for some animations
m_animated_meshnode->setScale(m_prop.visual_size);
@@ -827,12 +837,33 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr)
setNodeLight(m_last_light);
updateMeshCulling();
+ if (m_animated_meshnode) {
+ u32 mat_count = m_animated_meshnode->getMaterialCount();
+ if (mat_count == 0 || m_prop.textures.empty()) {
+ // nothing
+ } else if (mat_count > m_prop.textures.size()) {
+ std::ostringstream oss;
+ oss << "GenericCAO::addToScene(): Model "
+ << m_prop.mesh << " loaded with " << mat_count
+ << " mesh buffers but only " << m_prop.textures.size()
+ << " texture(s) specifed, this is deprecated.";
+ logOnce(oss, warningstream);
+
+ video::ITexture *last = m_animated_meshnode->getMaterial(0).TextureLayer[0].Texture;
+ for (u32 i = 1; i < mat_count; i++) {
+ auto &layer = m_animated_meshnode->getMaterial(i).TextureLayer[0];
+ if (!layer.Texture)
+ layer.Texture = last;
+ last = layer.Texture;
+ }
+ }
+ }
+
if (m_client->modsLoaded() && m_client->getScript()->on_object_add(m_id)) {
removeFromScene(false);
return;
}
-
if (m_client->modsLoaded())
m_client->getScript()->on_object_properties_change(m_id);
}
@@ -842,7 +873,8 @@ void GenericCAO::updateLight(u32 day_night_ratio)
if (m_glow < 0)
return;
- u8 light_at_pos = 0;
+ u16 light_at_pos = 0;
+ u8 light_at_pos_intensity = 0;
bool pos_ok = false;
v3s16 pos[3];
@@ -851,30 +883,36 @@ void GenericCAO::updateLight(u32 day_night_ratio)
bool this_ok;
MapNode n = m_env->getMap().getNode(pos[i], &this_ok);
if (this_ok) {
- u8 this_light = n.getLightBlend(day_night_ratio, m_client->ndef());
- light_at_pos = MYMAX(light_at_pos, this_light);
+ u16 this_light = getInteriorLight(n, 0, m_client->ndef());
+ u8 this_light_intensity = MYMAX(this_light & 0xFF, (this_light >> 8) && 0xFF);
+ if (this_light_intensity > light_at_pos_intensity) {
+ light_at_pos = this_light;
+ light_at_pos_intensity = this_light_intensity;
+ }
pos_ok = true;
}
}
if (!pos_ok)
- light_at_pos = blend_light(day_night_ratio, LIGHT_SUN, 0);
+ light_at_pos = LIGHT_SUN;
+
+ video::SColor light = encode_light(light_at_pos, m_glow);
+ if (!m_enable_shaders)
+ final_color_blend(&light, light_at_pos, day_night_ratio);
- u8 light = decode_light(light_at_pos + m_glow);
if (g_settings->getBool("fullbright"))
- light = 255;
+ light = video::SColor(0xFFFFFFFF);
+
if (light != m_last_light) {
m_last_light = light;
setNodeLight(light);
}
}
-void GenericCAO::setNodeLight(u8 light)
+void GenericCAO::setNodeLight(const video::SColor &light_color)
{
- video::SColor color(255, light, light, light);
-
if (m_prop.visual == "wielditem" || m_prop.visual == "item") {
if (m_wield_meshnode)
- m_wield_meshnode->setNodeLightColor(color);
+ m_wield_meshnode->setNodeLightColor(light_color);
return;
}
@@ -886,7 +924,7 @@ void GenericCAO::setNodeLight(u8 light)
scene::IMesh *mesh = m_meshnode->getMesh();
for (u32 i = 0; i < mesh->getMeshBufferCount(); ++i) {
scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
- buf->getMaterial().EmissiveColor = color;
+ buf->getMaterial().EmissiveColor = light_color;
}
} else {
scene::ISceneNode *node = getSceneNode();
@@ -895,16 +933,16 @@ void GenericCAO::setNodeLight(u8 light)
for (u32 i = 0; i < node->getMaterialCount(); ++i) {
video::SMaterial &material = node->getMaterial(i);
- material.EmissiveColor = color;
+ material.EmissiveColor = light_color;
}
}
} else {
if (m_meshnode) {
- setMeshColor(m_meshnode->getMesh(), color);
+ setMeshColor(m_meshnode->getMesh(), light_color);
} else if (m_animated_meshnode) {
- setAnimatedMeshColor(m_animated_meshnode, color);
+ setAnimatedMeshColor(m_animated_meshnode, light_color);
} else if (m_spritenode) {
- m_spritenode->setColor(color);
+ m_spritenode->setColor(light_color);
}
}
}
@@ -1014,12 +1052,14 @@ void GenericCAO::step(float dtime, ClientEnvironment *env)
m_velocity = v3f(0,0,0);
m_acceleration = v3f(0,0,0);
const PlayerControl &controls = player->getPlayerControl();
+ f32 new_speed = player->local_animation_speed;
bool walking = false;
- if (controls.movement_speed > 0.001f && ! g_settings->getBool("freecam"))
+ if (controls.movement_speed > 0.001f && ! g_settings->getBool("freecam")) {
+ new_speed *= controls.movement_speed;
walking = true;
+ }
- f32 new_speed = player->local_animation_speed;
v2s32 new_anim = v2s32(0,0);
bool allow_update = false;
@@ -1034,7 +1074,6 @@ void GenericCAO::step(float dtime, ClientEnvironment *env)
// slowdown speed if sneaking
if (controls.sneak && walking && ! g_settings->getBool("no_slow"))
new_speed /= 2;
- new_speed *= controls.movement_speed;
if (walking && (controls.dig || controls.place)) {
new_anim = player->local_animations[3];
@@ -1304,9 +1343,15 @@ void GenericCAO::updateTextures(std::string mod)
m_current_texture_modifier = mod;
m_glow = m_prop.glow;
+ video::ITexture *shadow_texture = nullptr;
+ if (auto shadow = RenderingEngine::get_shadow_renderer())
+ shadow_texture = shadow->get_texture();
+
+ const u32 TEXTURE_LAYER_SHADOW = 3;
+
if (m_spritenode) {
if (m_prop.visual == "sprite") {
- std::string texturestring = "unknown_node.png";
+ std::string texturestring = "no_texture.png";
if (!m_prop.textures.empty())
texturestring = m_prop.textures[0];
texturestring += mod;
@@ -1314,6 +1359,7 @@ void GenericCAO::updateTextures(std::string mod)
m_spritenode->getMaterial(0).MaterialTypeParam = 0.5f;
m_spritenode->setMaterialTexture(0,
tsrc->getTextureForMesh(texturestring));
+ m_spritenode->setMaterialTexture(TEXTURE_LAYER_SHADOW, shadow_texture);
// This allows setting per-material colors. However, until a real lighting
// system is added, the code below will have no effect. Once MineTest
@@ -1349,6 +1395,7 @@ void GenericCAO::updateTextures(std::string mod)
material.MaterialType = m_material_type;
material.MaterialTypeParam = 0.5f;
material.TextureLayer[0].Texture = texture;
+ material.TextureLayer[TEXTURE_LAYER_SHADOW].Texture = shadow_texture;
material.setFlag(video::EMF_LIGHTING, true);
material.setFlag(video::EMF_BILINEAR_FILTER, false);
material.setFlag(video::EMF_BACK_FACE_CULLING, m_prop.backface_culling);
@@ -1385,7 +1432,7 @@ void GenericCAO::updateTextures(std::string mod)
{
for (u32 i = 0; i < 6; ++i)
{
- std::string texturestring = "unknown_node.png";
+ std::string texturestring = "no_texture.png";
if(m_prop.textures.size() > i)
texturestring = m_prop.textures[i];
texturestring += mod;
@@ -1399,6 +1446,7 @@ void GenericCAO::updateTextures(std::string mod)
material.setFlag(video::EMF_BILINEAR_FILTER, false);
material.setTexture(0,
tsrc->getTextureForMesh(texturestring));
+ material.setTexture(TEXTURE_LAYER_SHADOW, shadow_texture);
material.getTextureMatrix(0).makeIdentity();
// This allows setting per-material colors. However, until a real lighting
@@ -1418,54 +1466,56 @@ void GenericCAO::updateTextures(std::string mod)
} else if (m_prop.visual == "upright_sprite") {
scene::IMesh *mesh = m_meshnode->getMesh();
{
- std::string tname = "unknown_object.png";
+ std::string tname = "no_texture.png";
if (!m_prop.textures.empty())
tname = m_prop.textures[0];
tname += mod;
- scene::IMeshBuffer *buf = mesh->getMeshBuffer(0);
- buf->getMaterial().setTexture(0,
+ auto& material = m_meshnode->getMaterial(0);
+ material.setTexture(0,
tsrc->getTextureForMesh(tname));
+ material.setTexture(TEXTURE_LAYER_SHADOW, shadow_texture);
// This allows setting per-material colors. However, until a real lighting
// system is added, the code below will have no effect. Once MineTest
// has directional lighting, it should work automatically.
if(!m_prop.colors.empty()) {
- buf->getMaterial().AmbientColor = m_prop.colors[0];
- buf->getMaterial().DiffuseColor = m_prop.colors[0];
- buf->getMaterial().SpecularColor = m_prop.colors[0];
+ material.AmbientColor = m_prop.colors[0];
+ material.DiffuseColor = m_prop.colors[0];
+ material.SpecularColor = m_prop.colors[0];
}
- buf->getMaterial().setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter);
- buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter);
- buf->getMaterial().setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter);
+ material.setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter);
+ material.setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter);
+ material.setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter);
}
{
- std::string tname = "unknown_object.png";
+ std::string tname = "no_texture.png";
if (m_prop.textures.size() >= 2)
tname = m_prop.textures[1];
else if (!m_prop.textures.empty())
tname = m_prop.textures[0];
tname += mod;
- scene::IMeshBuffer *buf = mesh->getMeshBuffer(1);
- buf->getMaterial().setTexture(0,
+ auto& material = m_meshnode->getMaterial(1);
+ material.setTexture(0,
tsrc->getTextureForMesh(tname));
+ material.setTexture(TEXTURE_LAYER_SHADOW, shadow_texture);
// This allows setting per-material colors. However, until a real lighting
// system is added, the code below will have no effect. Once MineTest
// has directional lighting, it should work automatically.
if (m_prop.colors.size() >= 2) {
- buf->getMaterial().AmbientColor = m_prop.colors[1];
- buf->getMaterial().DiffuseColor = m_prop.colors[1];
- buf->getMaterial().SpecularColor = m_prop.colors[1];
+ material.AmbientColor = m_prop.colors[1];
+ material.DiffuseColor = m_prop.colors[1];
+ material.SpecularColor = m_prop.colors[1];
} else if (!m_prop.colors.empty()) {
- buf->getMaterial().AmbientColor = m_prop.colors[0];
- buf->getMaterial().DiffuseColor = m_prop.colors[0];
- buf->getMaterial().SpecularColor = m_prop.colors[0];
+ material.AmbientColor = m_prop.colors[0];
+ material.DiffuseColor = m_prop.colors[0];
+ material.SpecularColor = m_prop.colors[0];
}
- buf->getMaterial().setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter);
- buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter);
- buf->getMaterial().setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter);
+ material.setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter);
+ material.setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter);
+ material.setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter);
}
// Set mesh color (only if lighting is disabled)
if (!m_prop.colors.empty() && m_glow < 0)
@@ -1811,6 +1861,7 @@ void GenericCAO::processMessage(const std::string &data)
{
updateAnimation();
}
+ // FIXME: ^ This code is trash. It's also broken.
}
} else if (cmd == AO_CMD_SET_ANIMATION_SPEED) {
m_animation_speed = readF32(is);
@@ -1855,6 +1906,8 @@ void GenericCAO::processMessage(const std::string &data)
m_reset_textures_timer = 0.05;
if(damage >= 2)
m_reset_textures_timer += 0.05 * damage;
+ // Cap damage overlay to 1 second
+ m_reset_textures_timer = std::min(m_reset_textures_timer, 1.0f);
updateTextures(m_current_texture_modifier + m_prop.damage_texture_modifier);
}
}
@@ -1905,7 +1958,8 @@ bool GenericCAO::directReportPunch(v3f dir, const ItemStack *punchitem,
m_armor_groups,
toolcap,
punchitem,
- time_from_last_punch);
+ time_from_last_punch,
+ punchitem->wear);
if(result.did_punch && result.damage != 0)
{
@@ -1925,6 +1979,8 @@ bool GenericCAO::directReportPunch(v3f dir, const ItemStack *punchitem,
m_reset_textures_timer = 0.05;
if (result.damage >= 2)
m_reset_textures_timer += 0.05 * result.damage;
+ // Cap damage overlay to 1 second
+ m_reset_textures_timer = std::min(m_reset_textures_timer, 1.0f);
updateTextures(m_current_texture_modifier + m_prop.damage_texture_modifier);
}
}
@@ -1953,20 +2009,17 @@ void GenericCAO::updateMeshCulling()
const bool hidden = m_client->getCamera()->getCameraMode() == CAMERA_MODE_FIRST && ! g_settings->getBool("freecam");
- if (m_meshnode && m_prop.visual == "upright_sprite") {
- u32 buffers = m_meshnode->getMesh()->getMeshBufferCount();
- for (u32 i = 0; i < buffers; i++) {
- video::SMaterial &mat = m_meshnode->getMesh()->getMeshBuffer(i)->getMaterial();
- // upright sprite has no backface culling
- mat.setFlag(video::EMF_FRONT_FACE_CULLING, hidden);
- }
- return;
- }
-
scene::ISceneNode *node = getSceneNode();
+
if (!node)
return;
+ if (m_prop.visual == "upright_sprite") {
+ // upright sprite has no backface culling
+ node->setMaterialFlag(video::EMF_FRONT_FACE_CULLING, hidden);
+ return;
+ }
+
if (hidden) {
// Hide the mesh by culling both front and
// back faces. Serious hackyness but it works for our
diff --git a/src/client/content_cao.h b/src/client/content_cao.h
index 7e9bb6671..8e5d04bfa 100644
--- a/src/client/content_cao.h
+++ b/src/client/content_cao.h
@@ -125,7 +125,7 @@ private:
std::string m_current_texture_modifier = "";
bool m_visuals_expired = false;
float m_step_distance_counter = 0.0f;
- u8 m_last_light = 255;
+ video::SColor m_last_light = video::SColor(0xFFFFFFFF);
bool m_is_visible = false;
s8 m_glow = 0;
// Material
@@ -182,12 +182,12 @@ public:
return m_velocity;
}
- inline const u16 getHp() const
+ inline u16 getHp() const
{
return m_hp;
}
- const bool isImmortal();
+ bool isImmortal() const;
inline const ObjectProperties &getProperties() const { return m_prop; }
@@ -271,7 +271,7 @@ public:
void updateLight(u32 day_night_ratio);
- void setNodeLight(u8 light);
+ void setNodeLight(const video::SColor &light);
/* Get light position(s).
* returns number of positions written into pos[], which must have space
diff --git a/src/client/content_mapblock.cpp b/src/client/content_mapblock.cpp
index bb2d6398f..8675275aa 100644
--- a/src/client/content_mapblock.cpp
+++ b/src/client/content_mapblock.cpp
@@ -150,8 +150,10 @@ void MapblockMeshGenerator::drawQuad(v3f *coords, const v3s16 &normal,
// should be (2+2)*6=24 values in the list. The order of
// the faces in the list is up-down-right-left-back-front
// (compatible with ContentFeatures).
+// mask - a bit mask that suppresses drawing of tiles.
+// tile i will not be drawn if mask & (1 << i) is 1
void MapblockMeshGenerator::drawCuboid(const aabb3f &box,
- TileSpec *tiles, int tilecount, const LightInfo *lights, const f32 *txc)
+ TileSpec *tiles, int tilecount, const LightInfo *lights, const f32 *txc, u8 mask)
{
assert(tilecount >= 1 && tilecount <= 6); // pre-condition
@@ -274,6 +276,8 @@ void MapblockMeshGenerator::drawCuboid(const aabb3f &box,
// Add to mesh collector
for (int k = 0; k < 6; ++k) {
+ if (mask & (1 << k))
+ continue;
int tileindex = MYMIN(k, tilecount - 1);
collector->append(tiles[tileindex], vertices + 4 * k, 4, quad_indices, 6);
}
@@ -363,7 +367,7 @@ void MapblockMeshGenerator::generateCuboidTextureCoords(const aabb3f &box, f32 *
}
void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, const f32 *txc,
- TileSpec *tiles, int tile_count)
+ TileSpec *tiles, int tile_count, u8 mask)
{
bool scale = std::fabs(f->visual_scale - 1.0f) > 1e-3f;
f32 texture_coord_buf[24];
@@ -373,6 +377,10 @@ void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, const f32 *txc,
f32 dx2 = box.MaxEdge.X;
f32 dy2 = box.MaxEdge.Y;
f32 dz2 = box.MaxEdge.Z;
+
+ box.MinEdge += origin;
+ box.MaxEdge += origin;
+
if (scale) {
if (!txc) { // generate texture coords before scaling
generateCuboidTextureCoords(box, texture_coord_buf);
@@ -381,12 +389,11 @@ void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, const f32 *txc,
box.MinEdge *= f->visual_scale;
box.MaxEdge *= f->visual_scale;
}
- box.MinEdge += origin;
- box.MaxEdge += origin;
if (!txc) {
generateCuboidTextureCoords(box, texture_coord_buf);
txc = texture_coord_buf;
}
+
if (!tiles) {
tiles = &tile;
tile_count = 1;
@@ -400,12 +407,49 @@ void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, const f32 *txc,
d.Z = (j & 1) ? dz2 : dz1;
lights[j] = blendLight(d);
}
- drawCuboid(box, tiles, tile_count, lights, txc);
+ drawCuboid(box, tiles, tile_count, lights, txc, mask);
} else {
- drawCuboid(box, tiles, tile_count, nullptr, txc);
+ drawCuboid(box, tiles, tile_count, nullptr, txc, mask);
}
}
+u8 MapblockMeshGenerator::getNodeBoxMask(aabb3f box, u8 solid_neighbors, u8 sametype_neighbors) const
+{
+ const f32 NODE_BOUNDARY = 0.5 * BS;
+
+ // For an oversized nodebox, return immediately
+ if (box.MaxEdge.X > NODE_BOUNDARY ||
+ box.MinEdge.X < -NODE_BOUNDARY ||
+ box.MaxEdge.Y > NODE_BOUNDARY ||
+ box.MinEdge.Y < -NODE_BOUNDARY ||
+ box.MaxEdge.Z > NODE_BOUNDARY ||
+ box.MinEdge.Z < -NODE_BOUNDARY)
+ return 0;
+
+ // We can skip faces at node boundary if the matching neighbor is solid
+ u8 solid_mask =
+ (box.MaxEdge.Y == NODE_BOUNDARY ? 1 : 0) |
+ (box.MinEdge.Y == -NODE_BOUNDARY ? 2 : 0) |
+ (box.MaxEdge.X == NODE_BOUNDARY ? 4 : 0) |
+ (box.MinEdge.X == -NODE_BOUNDARY ? 8 : 0) |
+ (box.MaxEdge.Z == NODE_BOUNDARY ? 16 : 0) |
+ (box.MinEdge.Z == -NODE_BOUNDARY ? 32 : 0);
+
+ u8 sametype_mask = 0;
+ if (f->alpha == AlphaMode::ALPHAMODE_OPAQUE) {
+ // In opaque nodeboxes, faces on opposite sides can cancel
+ // each other out if there is a matching neighbor of the same type
+ sametype_mask =
+ ((solid_mask & 3) == 3 ? 3 : 0) |
+ ((solid_mask & 12) == 12 ? 12 : 0) |
+ ((solid_mask & 48) == 48 ? 48 : 0);
+ }
+
+ // Combine masks with actual neighbors to get the faces to be skipped
+ return (solid_mask & solid_neighbors) | (sametype_mask & sametype_neighbors);
+}
+
+
void MapblockMeshGenerator::prepareLiquidNodeDrawing()
{
getSpecialTile(0, &tile_liquid_top);
@@ -1363,13 +1407,38 @@ void MapblockMeshGenerator::drawNodeboxNode()
getTile(nodebox_tile_dirs[face], &tiles[face]);
}
+ bool param2_is_rotation =
+ f->param_type_2 == CPT2_COLORED_FACEDIR ||
+ f->param_type_2 == CPT2_COLORED_WALLMOUNTED ||
+ f->param_type_2 == CPT2_FACEDIR ||
+ f->param_type_2 == CPT2_WALLMOUNTED;
+
+ bool param2_is_level =
+ f->param_type_2 == CPT2_LEVELED;
+
// locate possible neighboring nodes to connect to
u8 neighbors_set = 0;
- if (f->node_box.type == NODEBOX_CONNECTED) {
- for (int dir = 0; dir != 6; dir++) {
- u8 flag = 1 << dir;
- v3s16 p2 = blockpos_nodes + p + nodebox_connection_dirs[dir];
- MapNode n2 = data->m_vmanip.getNodeNoEx(p2);
+ u8 solid_neighbors = 0;
+ u8 sametype_neighbors = 0;
+ for (int dir = 0; dir != 6; dir++) {
+ u8 flag = 1 << dir;
+ v3s16 p2 = blockpos_nodes + p + nodebox_tile_dirs[dir];
+ MapNode n2 = data->m_vmanip.getNodeNoEx(p2);
+
+ // mark neighbors that are the same node type
+ // and have the same rotation or higher level stored as param2
+ if (n2.param0 == n.param0 &&
+ (!param2_is_rotation || n.param2 == n2.param2) &&
+ (!param2_is_level || n.param2 <= n2.param2))
+ sametype_neighbors |= flag;
+
+ // mark neighbors that are simple solid blocks
+ if (nodedef->get(n2).drawtype == NDT_NORMAL)
+ solid_neighbors |= flag;
+
+ if (f->node_box.type == NODEBOX_CONNECTED) {
+ p2 = blockpos_nodes + p + nodebox_connection_dirs[dir];
+ n2 = data->m_vmanip.getNodeNoEx(p2);
if (nodedef->nodeboxConnects(n, n2, flag))
neighbors_set |= flag;
}
@@ -1377,8 +1446,63 @@ void MapblockMeshGenerator::drawNodeboxNode()
std::vector<aabb3f> boxes;
n.getNodeBoxes(nodedef, &boxes, neighbors_set);
- for (auto &box : boxes)
- drawAutoLightedCuboid(box, nullptr, tiles, 6);
+
+ bool isTransparent = false;
+
+ for (const TileSpec &tile : tiles) {
+ if (tile.layers[0].isTransparent()) {
+ isTransparent = true;
+ break;
+ }
+ }
+
+ if (isTransparent) {
+ std::vector<float> sections;
+ // Preallocate 8 default splits + Min&Max for each nodebox
+ sections.reserve(8 + 2 * boxes.size());
+
+ for (int axis = 0; axis < 3; axis++) {
+ // identify sections
+
+ if (axis == 0) {
+ // Default split at node bounds, up to 3 nodes in each direction
+ for (float s = -3.5f * BS; s < 4.0f * BS; s += 1.0f * BS)
+ sections.push_back(s);
+ }
+ else {
+ // Avoid readding the same 8 default splits for Y and Z
+ sections.resize(8);
+ }
+
+ // Add edges of existing node boxes, rounded to 1E-3
+ for (size_t i = 0; i < boxes.size(); i++) {
+ sections.push_back(std::floor(boxes[i].MinEdge[axis] * 1E3) * 1E-3);
+ sections.push_back(std::floor(boxes[i].MaxEdge[axis] * 1E3) * 1E-3);
+ }
+
+ // split the boxes at recorded sections
+ // limit splits to avoid runaway crash if inner loop adds infinite splits
+ // due to e.g. precision problems.
+ // 100 is just an arbitrary, reasonably high number.
+ for (size_t i = 0; i < boxes.size() && i < 100; i++) {
+ aabb3f *box = &boxes[i];
+ for (float section : sections) {
+ if (box->MinEdge[axis] < section && box->MaxEdge[axis] > section) {
+ aabb3f copy(*box);
+ copy.MinEdge[axis] = section;
+ box->MaxEdge[axis] = section;
+ boxes.push_back(copy);
+ box = &boxes[i]; // find new address of the box in case of reallocation
+ }
+ }
+ }
+ }
+ }
+
+ for (auto &box : boxes) {
+ u8 mask = getNodeBoxMask(box, solid_neighbors, sametype_neighbors);
+ drawAutoLightedCuboid(box, nullptr, tiles, 6, mask);
+ }
}
void MapblockMeshGenerator::drawMeshNode()
diff --git a/src/client/content_mapblock.h b/src/client/content_mapblock.h
index 7344f05ee..b13748cbc 100644
--- a/src/client/content_mapblock.h
+++ b/src/client/content_mapblock.h
@@ -100,10 +100,11 @@ public:
// cuboid drawing!
void drawCuboid(const aabb3f &box, TileSpec *tiles, int tilecount,
- const LightInfo *lights , const f32 *txc);
+ const LightInfo *lights , const f32 *txc, u8 mask = 0);
void generateCuboidTextureCoords(aabb3f const &box, f32 *coords);
void drawAutoLightedCuboid(aabb3f box, const f32 *txc = NULL,
- TileSpec *tiles = NULL, int tile_count = 0);
+ TileSpec *tiles = NULL, int tile_count = 0, u8 mask = 0);
+ u8 getNodeBoxMask(aabb3f box, u8 solid_neighbors, u8 sametype_neighbors) const;
// liquid-specific
bool top_is_same_liquid;
diff --git a/src/client/fontengine.cpp b/src/client/fontengine.cpp
index f64315db4..ad8305b45 100644
--- a/src/client/fontengine.cpp
+++ b/src/client/fontengine.cpp
@@ -24,10 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "porting.h"
#include "filesys.h"
#include "gettext.h"
-
-#if USE_FREETYPE
#include "irrlicht_changes/CGUITTFont.h"
-#endif
/** maximum size distance for getting a "similar" font size */
#define MAX_FONT_SIZE_OFFSET 10
@@ -45,9 +42,8 @@ static void font_setting_changed(const std::string &name, void *userdata)
FontEngine::FontEngine(gui::IGUIEnvironment* env) :
m_env(env)
{
-
for (u32 &i : m_default_size) {
- i = (FontMode) FONT_SIZE_UNSPECIFIED;
+ i = FONT_SIZE_UNSPECIFIED;
}
assert(g_settings != NULL); // pre-condition
@@ -56,23 +52,19 @@ FontEngine::FontEngine(gui::IGUIEnvironment* env) :
readSettings();
- if (m_currentMode != FM_Simple) {
- g_settings->registerChangedCallback("font_size", font_setting_changed, NULL);
- g_settings->registerChangedCallback("font_bold", font_setting_changed, NULL);
- g_settings->registerChangedCallback("font_italic", font_setting_changed, NULL);
- g_settings->registerChangedCallback("font_path", font_setting_changed, NULL);
- g_settings->registerChangedCallback("font_path_bold", font_setting_changed, NULL);
- g_settings->registerChangedCallback("font_path_italic", font_setting_changed, NULL);
- g_settings->registerChangedCallback("font_path_bolditalic", font_setting_changed, NULL);
- g_settings->registerChangedCallback("font_shadow", font_setting_changed, NULL);
- g_settings->registerChangedCallback("font_shadow_alpha", font_setting_changed, NULL);
- g_settings->registerChangedCallback("fallback_font_path", font_setting_changed, NULL);
- }
+ const char *settings[] = {
+ "font_size", "font_bold", "font_italic", "font_size_divisible_by",
+ "mono_font_size", "mono_font_size_divisible_by",
+ "font_shadow", "font_shadow_alpha",
+ "font_path", "font_path_bold", "font_path_italic", "font_path_bold_italic",
+ "mono_font_path", "mono_font_path_bold", "mono_font_path_italic",
+ "mono_font_path_bold_italic",
+ "fallback_font_path",
+ "screen_dpi", "gui_scaling",
+ };
- g_settings->registerChangedCallback("mono_font_path", font_setting_changed, NULL);
- g_settings->registerChangedCallback("mono_font_size", font_setting_changed, NULL);
- g_settings->registerChangedCallback("screen_dpi", font_setting_changed, NULL);
- g_settings->registerChangedCallback("gui_scaling", font_setting_changed, NULL);
+ for (auto name : settings)
+ g_settings->registerChangedCallback(name, font_setting_changed, NULL);
}
/******************************************************************************/
@@ -84,11 +76,13 @@ FontEngine::~FontEngine()
/******************************************************************************/
void FontEngine::cleanCache()
{
+ RecursiveMutexAutoLock l(m_font_mutex);
+
for (auto &font_cache_it : m_font_cache) {
for (auto &font_it : font_cache_it) {
font_it.second->drop();
- font_it.second = NULL;
+ font_it.second = nullptr;
}
font_cache_it.clear();
}
@@ -104,16 +98,8 @@ irr::gui::IGUIFont *FontEngine::getFont(FontSpec spec, bool may_fail)
{
if (spec.mode == FM_Unspecified) {
spec.mode = m_currentMode;
- } else if (m_currentMode == FM_Simple) {
- // Freetype disabled -> Force simple mode
- spec.mode = (spec.mode == FM_Mono ||
- spec.mode == FM_SimpleMono) ?
- FM_SimpleMono : FM_Simple;
- // Support for those could be added, but who cares?
- spec.bold = false;
- spec.italic = false;
} else if (spec.mode == _FM_Fallback) {
- // Fallback font doesn't support these either
+ // Fallback font doesn't support these
spec.bold = false;
spec.italic = false;
}
@@ -122,17 +108,15 @@ irr::gui::IGUIFont *FontEngine::getFont(FontSpec spec, bool may_fail)
if (spec.size == FONT_SIZE_UNSPECIFIED)
spec.size = m_default_size[spec.mode];
+ RecursiveMutexAutoLock l(m_font_mutex);
+
const auto &cache = m_font_cache[spec.getHash()];
auto it = cache.find(spec.size);
if (it != cache.end())
return it->second;
// Font does not yet exist
- gui::IGUIFont *font = nullptr;
- if (spec.mode == FM_Simple || spec.mode == FM_SimpleMono)
- font = initSimpleFont(spec);
- else
- font = initFont(spec);
+ gui::IGUIFont *font = initFont(spec);
if (!font && !may_fail) {
errorstream << "Minetest cannot continue without a valid font. "
@@ -149,13 +133,7 @@ irr::gui::IGUIFont *FontEngine::getFont(FontSpec spec, bool may_fail)
/******************************************************************************/
unsigned int FontEngine::getTextHeight(const FontSpec &spec)
{
- irr::gui::IGUIFont *font = getFont(spec);
-
- // use current skin font as fallback
- if (font == NULL) {
- font = m_env->getSkin()->getFont();
- }
- FATAL_ERROR_IF(font == NULL, "Could not get skin font");
+ gui::IGUIFont *font = getFont(spec);
return font->getDimension(L"Some unimportant example String").Height;
}
@@ -163,28 +141,15 @@ unsigned int FontEngine::getTextHeight(const FontSpec &spec)
/******************************************************************************/
unsigned int FontEngine::getTextWidth(const std::wstring &text, const FontSpec &spec)
{
- irr::gui::IGUIFont *font = getFont(spec);
-
- // use current skin font as fallback
- if (font == NULL) {
- font = m_env->getSkin()->getFont();
- }
- FATAL_ERROR_IF(font == NULL, "Could not get font");
+ gui::IGUIFont *font = getFont(spec);
return font->getDimension(text.c_str()).Width;
}
-
/** get line height for a specific font (including empty room between lines) */
unsigned int FontEngine::getLineHeight(const FontSpec &spec)
{
- irr::gui::IGUIFont *font = getFont(spec);
-
- // use current skin font as fallback
- if (font == NULL) {
- font = m_env->getSkin()->getFont();
- }
- FATAL_ERROR_IF(font == NULL, "Could not get font");
+ gui::IGUIFont *font = getFont(spec);
return font->getDimension(L"Some unimportant example String").Height
+ font->getKerningHeight();
@@ -198,13 +163,6 @@ unsigned int FontEngine::getDefaultFontSize()
unsigned int FontEngine::getFontSize(FontMode mode)
{
- if (m_currentMode == FM_Simple) {
- if (mode == FM_Mono || mode == FM_SimpleMono)
- return m_default_size[FM_SimpleMono];
- else
- return m_default_size[FM_Simple];
- }
-
if (mode == FM_Unspecified)
return m_default_size[FM_Standard];
@@ -214,20 +172,12 @@ unsigned int FontEngine::getFontSize(FontMode mode)
/******************************************************************************/
void FontEngine::readSettings()
{
- if (USE_FREETYPE && g_settings->getBool("freetype")) {
- m_default_size[FM_Standard] = g_settings->getU16("font_size");
- m_default_size[_FM_Fallback] = g_settings->getU16("font_size");
- m_default_size[FM_Mono] = g_settings->getU16("mono_font_size");
-
- m_default_bold = g_settings->getBool("font_bold");
- m_default_italic = g_settings->getBool("font_italic");
+ m_default_size[FM_Standard] = g_settings->getU16("font_size");
+ m_default_size[_FM_Fallback] = g_settings->getU16("font_size");
+ m_default_size[FM_Mono] = g_settings->getU16("mono_font_size");
- } else {
- m_currentMode = FM_Simple;
- }
-
- m_default_size[FM_Simple] = g_settings->getU16("font_size");
- m_default_size[FM_SimpleMono] = g_settings->getU16("mono_font_size");
+ m_default_bold = g_settings->getBool("font_bold");
+ m_default_italic = g_settings->getBool("font_italic");
cleanCache();
updateFontCache();
@@ -238,22 +188,9 @@ void FontEngine::readSettings()
void FontEngine::updateSkin()
{
gui::IGUIFont *font = getFont();
+ assert(font);
- if (font)
- m_env->getSkin()->setFont(font);
- else
- errorstream << "FontEngine: Default font file: " <<
- "\n\t\"" << g_settings->get("font_path") << "\"" <<
- "\n\trequired for current screen configuration was not found" <<
- " or was invalid file format." <<
- "\n\tUsing irrlicht default font." << std::endl;
-
- // If we did fail to create a font our own make irrlicht find a default one
- font = m_env->getSkin()->getFont();
- FATAL_ERROR_IF(font == NULL, "Could not create/get font");
-
- u32 text_height = font->getDimension(L"Hello, world!").Height;
- infostream << "FontEngine: measured text_height=" << text_height << std::endl;
+ m_env->getSkin()->setFont(font);
}
/******************************************************************************/
@@ -280,15 +217,18 @@ gui::IGUIFont *FontEngine::initFont(const FontSpec &spec)
if (spec.italic)
setting_suffix.append("_italic");
- u32 size = std::floor(RenderingEngine::getDisplayDensity() *
- g_settings->getFloat("gui_scaling") * spec.size);
+ u32 size = std::max<u32>(spec.size * RenderingEngine::getDisplayDensity() *
+ g_settings->getFloat("gui_scaling"), 1);
- if (size == 0) {
- errorstream << "FontEngine: attempt to use font size 0" << std::endl;
- errorstream << " display density: " << RenderingEngine::getDisplayDensity() << std::endl;
- abort();
+ // Constrain the font size to a certain multiple, if necessary
+ u16 divisible_by = g_settings->getU16(setting_prefix + "font_size_divisible_by");
+ if (divisible_by > 1) {
+ size = std::max<u32>(
+ std::round((double)size / divisible_by) * divisible_by, divisible_by);
}
+ sanity_check(size != 0);
+
u16 font_shadow = 0;
u16 font_shadow_alpha = 0;
g_settings->getU16NoEx(setting_prefix + "font_shadow", font_shadow);
@@ -306,7 +246,6 @@ gui::IGUIFont *FontEngine::initFont(const FontSpec &spec)
Settings::getLayer(SL_DEFAULTS)->get(path_setting)
};
-#if USE_FREETYPE
for (const std::string &font_path : fallback_settings) {
gui::CGUITTFont *font = gui::CGUITTFont::createTTFont(m_env,
font_path.c_str(), size, true, true, font_shadow,
@@ -325,80 +264,5 @@ gui::IGUIFont *FontEngine::initFont(const FontSpec &spec)
}
return font;
}
-#else
- errorstream << "FontEngine: Tried to load TTF font but Minetest was"
- " compiled without Freetype." << std::endl;
-#endif
return nullptr;
}
-
-/** initialize a font without freetype */
-gui::IGUIFont *FontEngine::initSimpleFont(const FontSpec &spec)
-{
- assert(spec.mode == FM_Simple || spec.mode == FM_SimpleMono);
- assert(spec.size != FONT_SIZE_UNSPECIFIED);
-
- const std::string &font_path = g_settings->get(
- (spec.mode == FM_SimpleMono) ? "mono_font_path" : "font_path");
-
- size_t pos_dot = font_path.find_last_of('.');
- std::string basename = font_path, ending;
- if (pos_dot != std::string::npos)
- ending = lowercase(font_path.substr(pos_dot));
-
- if (ending == ".ttf") {
- errorstream << "FontEngine: Found font \"" << font_path
- << "\" but freetype is not available." << std::endl;
- return nullptr;
- }
-
- if (ending == ".xml" || ending == ".png")
- basename = font_path.substr(0, pos_dot);
-
- u32 size = std::floor(
- RenderingEngine::getDisplayDensity() *
- g_settings->getFloat("gui_scaling") *
- spec.size);
-
- irr::gui::IGUIFont *font = nullptr;
- std::string font_extensions[] = { ".png", ".xml" };
-
- // Find nearest matching font scale
- // Does a "zig-zag motion" (positibe/negative), from 0 to MAX_FONT_SIZE_OFFSET
- for (s32 zoffset = 0; zoffset < MAX_FONT_SIZE_OFFSET * 2; zoffset++) {
- std::stringstream path;
-
- // LSB to sign
- s32 sign = (zoffset & 1) ? -1 : 1;
- s32 offset = zoffset >> 1;
-
- for (const std::string &ext : font_extensions) {
- path.str(""); // Clear
- path << basename << "_" << (size + offset * sign) << ext;
-
- if (!fs::PathExists(path.str()))
- continue;
-
- font = m_env->getFont(path.str().c_str());
-
- if (font) {
- verbosestream << "FontEngine: found font: " << path.str() << std::endl;
- break;
- }
- }
-
- if (font)
- break;
- }
-
- // try name direct
- if (font == NULL) {
- if (fs::PathExists(font_path)) {
- font = m_env->getFont(font_path.c_str());
- if (font)
- verbosestream << "FontEngine: found font: " << font_path << std::endl;
- }
- }
-
- return font;
-}
diff --git a/src/client/fontengine.h b/src/client/fontengine.h
index 3d389ea48..78608e517 100644
--- a/src/client/fontengine.h
+++ b/src/client/fontengine.h
@@ -20,13 +20,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#pragma once
#include <map>
-#include <vector>
#include "util/basic_macros.h"
#include "irrlichttypes.h"
#include <IGUIFont.h>
#include <IGUISkin.h>
#include <IGUIEnvironment.h>
#include "settings.h"
+#include "threading/mutex_auto_lock.h"
#define FONT_SIZE_UNSPECIFIED 0xFFFFFFFF
@@ -34,8 +34,6 @@ enum FontMode : u8 {
FM_Standard = 0,
FM_Mono,
_FM_Fallback, // do not use directly
- FM_Simple,
- FM_SimpleMono,
FM_MaxMode,
FM_Unspecified
};
@@ -140,9 +138,6 @@ private:
/** initialize a new TTF font */
gui::IGUIFont *initFont(const FontSpec &spec);
- /** initialize a font without freetype */
- gui::IGUIFont *initSimpleFont(const FontSpec &spec);
-
/** update current minetest skin with font changes */
void updateSkin();
@@ -152,6 +147,9 @@ private:
/** pointer to irrlicht gui environment */
gui::IGUIEnvironment* m_env = nullptr;
+ /** mutex used to protect font init and cache */
+ std::recursive_mutex m_font_mutex;
+
/** internal storage for caching fonts of different size */
std::map<unsigned int, irr::gui::IGUIFont*> m_font_cache[FM_MaxMode << 2];
@@ -162,8 +160,8 @@ private:
bool m_default_bold = false;
bool m_default_italic = false;
- /** current font engine mode */
- FontMode m_currentMode = FM_Standard;
+ /** default font engine mode (fixed) */
+ static const FontMode m_currentMode = FM_Standard;
DISABLE_CLASS_COPY(FontEngine);
};
diff --git a/src/client/game.cpp b/src/client/game.cpp
index d2a751040..e439d0e32 100644
--- a/src/client/game.cpp
+++ b/src/client/game.cpp
@@ -122,7 +122,7 @@ Game::Game() :
readSettings();
-#ifdef __ANDROID__
+#ifdef HAVE_TOUCHSCREENGUI
m_cache_hold_aux1 = false; // This is initialised properly later
#endif
@@ -243,19 +243,19 @@ bool Game::startup(bool *kill,
void Game::run()
{
ProfilerGraph graph;
- RunStats stats = { 0 };
- FpsControl draw_times = { 0 };
+ RunStats stats = {};
+ FpsControl draw_times;
f32 dtime; // in seconds
/* Clear the profiler */
Profiler::GraphValues dummyvalues;
g_profiler->graphGet(dummyvalues);
- draw_times.last_time = m_rendering_engine->get_timer_time();
+ draw_times.reset();
set_light_table(g_settings->getFloat("display_gamma"));
-#ifdef __ANDROID__
+#ifdef HAVE_TOUCHSCREENGUI
m_cache_hold_aux1 = g_settings->getBool("fast_move")
&& client->checkPrivilege("fast");
#endif
@@ -283,7 +283,7 @@ void Game::run()
// Calculate dtime =
// m_rendering_engine->run() from this iteration
// + Sleep time until the wanted FPS are reached
- limitFps(&draw_times, &dtime);
+ draw_times.limit(device, &dtime);
// Prepare render data for next iteration
@@ -313,10 +313,9 @@ void Game::run()
step(&dtime);
processClientEvents(&cam_view_target);
updateDebugState();
- updateCamera(draw_times.busy_time, dtime);
+ updateCamera(dtime);
updateSound(dtime);
- processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud,
- m_game_ui->m_flags.show_basic_debug);
+ processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud);
updateFrame(&graph, &stats, dtime, cam_view);
updateProfilerGraphs(&graph);
@@ -473,9 +472,8 @@ bool Game::createSingleplayerServer(const std::string &map_dir,
}
if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) {
- *error_message = "Unable to listen on " +
- bind_addr.serializeString() +
- " because IPv6 is disabled";
+ *error_message = fmtgettext("Unable to listen on %s because IPv6 is disabled",
+ bind_addr.serializeString().c_str());
errorstream << *error_message << std::endl;
return false;
}
@@ -508,7 +506,7 @@ bool Game::createClient(const GameStartData &start_data)
if (!could_connect) {
if (error_message->empty() && !connect_aborted) {
// Should not happen if error messages are set properly
- *error_message = "Connection failed for unknown reason";
+ *error_message = gettext("Connection failed for unknown reason");
errorstream << *error_message << std::endl;
}
return false;
@@ -517,7 +515,7 @@ bool Game::createClient(const GameStartData &start_data)
if (!getServerContent(&connect_aborted)) {
if (error_message->empty() && !connect_aborted) {
// Should not happen if error messages are set properly
- *error_message = "Connection failed for unknown reason";
+ *error_message = gettext("Connection failed for unknown reason");
errorstream << *error_message << std::endl;
}
return false;
@@ -533,8 +531,8 @@ bool Game::createClient(const GameStartData &start_data)
/* Camera
*/
camera = new Camera(*draw_control, client, m_rendering_engine);
- if (!camera->successfullyCreated(*error_message))
- return false;
+ if (client->modsLoaded())
+ client->getScript()->on_camera_ready(camera);
client->setCamera(camera);
/* Clouds
@@ -575,7 +573,7 @@ bool Game::createClient(const GameStartData &start_data)
str += L" [";
str += text;
str += L"]";
- delete text;
+ delete[] text;
}
str += L" [";
str += L"Minetest Hackclient";
@@ -650,7 +648,6 @@ bool Game::connectToServer(const GameStartData &start_data,
connect_address.Resolve(start_data.address.c_str());
if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY
- //connect_address.Resolve("localhost");
if (connect_address.isIPv6()) {
IPv6AddressBytes addr_bytes;
addr_bytes.bytes[15] = 1;
@@ -661,29 +658,35 @@ bool Game::connectToServer(const GameStartData &start_data,
local_server_mode = true;
}
} catch (ResolveError &e) {
- *error_message = std::string("Couldn't resolve address: ") + e.what();
+ *error_message = fmtgettext("Couldn't resolve address: %s", e.what());
+
errorstream << *error_message << std::endl;
return false;
}
if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) {
- *error_message = "Unable to connect to " +
- connect_address.serializeString() +
- " because IPv6 is disabled";
+ *error_message = fmtgettext("Unable to connect to %s because IPv6 is disabled", connect_address.serializeString().c_str());
errorstream << *error_message << std::endl;
return false;
}
- client = new Client(start_data.name.c_str(),
- start_data.password, start_data.address,
- *draw_control, texture_src, shader_src,
- itemdef_manager, nodedef_manager, sound, eventmgr,
- m_rendering_engine, connect_address.isIPv6(), m_game_ui.get());
+ try {
+ client = new Client(start_data.name.c_str(),
+ start_data.password, start_data.address,
+ *draw_control, texture_src, shader_src,
+ itemdef_manager, nodedef_manager, sound, eventmgr,
+ m_rendering_engine, connect_address.isIPv6(), m_game_ui.get());
+ client->migrateModStorage();
+ } catch (const BaseException &e) {
+ *error_message = fmtgettext("Error creating client: %s", e.what());
+ errorstream << *error_message << std::endl;
+ return false;
+ }
client->m_simple_singleplayer_mode = simple_singleplayer_mode;
infostream << "Connecting to server at ";
- connect_address.print(&infostream);
+ connect_address.print(infostream);
infostream << std::endl;
client->connect(connect_address,
@@ -696,15 +699,15 @@ bool Game::connectToServer(const GameStartData &start_data,
try {
input->clear();
- FpsControl fps_control = { 0 };
+ FpsControl fps_control;
f32 dtime;
f32 wait_time = 0; // in seconds
- fps_control.last_time = m_rendering_engine->get_timer_time();
+ fps_control.reset();
while (m_rendering_engine->run()) {
- limitFps(&fps_control, &dtime);
+ fps_control.limit(device, &dtime);
// Update client and server
client->step(dtime);
@@ -723,8 +726,7 @@ bool Game::connectToServer(const GameStartData &start_data,
break;
if (client->accessDenied()) {
- *error_message = "Access denied. Reason: "
- + client->accessDeniedReason();
+ *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str());
*reconnect_requested = client->reconnectRequested();
errorstream << *error_message << std::endl;
break;
@@ -750,7 +752,7 @@ bool Game::connectToServer(const GameStartData &start_data,
wait_time += dtime;
// Only time out if we aren't waiting for the server we started
if (!start_data.address.empty() && wait_time > 10) {
- *error_message = "Connection timed out.";
+ *error_message = gettext("Connection timed out.");
errorstream << *error_message << std::endl;
break;
}
@@ -772,14 +774,14 @@ bool Game::getServerContent(bool *aborted)
{
input->clear();
- FpsControl fps_control = { 0 };
+ FpsControl fps_control;
f32 dtime; // in seconds
- fps_control.last_time = m_rendering_engine->get_timer_time();
+ fps_control.reset();
while (m_rendering_engine->run()) {
- limitFps(&fps_control, &dtime);
+ fps_control.limit(device, &dtime);
// Update client and server
client->step(dtime);
@@ -798,7 +800,7 @@ bool Game::getServerContent(bool *aborted)
return false;
if (client->getState() < LC_Init) {
- *error_message = "Client disconnected";
+ *error_message = gettext("Client disconnected");
errorstream << *error_message << std::endl;
return false;
}
@@ -880,8 +882,7 @@ inline void Game::updateInteractTimers(f32 dtime)
inline bool Game::checkConnection()
{
if (client->accessDenied()) {
- *error_message = "Access denied. Reason: "
- + client->accessDeniedReason();
+ *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str());
*reconnect_requested = client->reconnectRequested();
errorstream << *error_message << std::endl;
return false;
@@ -936,17 +937,16 @@ void Game::processQueues()
void Game::updateDebugState()
{
- bool has_basic_debug = client->checkPrivilege("basic_debug");
+ LocalPlayer *player = client->getEnv().getLocalPlayer();
bool has_debug = client->checkPrivilege("debug");
+ bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
if (m_game_ui->m_flags.show_basic_debug) {
- if (!has_basic_debug) {
+ if (!has_basic_debug)
m_game_ui->m_flags.show_basic_debug = false;
- }
} else if (m_game_ui->m_flags.show_minimal_debug) {
- if (has_basic_debug) {
+ if (has_basic_debug)
m_game_ui->m_flags.show_basic_debug = true;
- }
}
if (!has_basic_debug)
hud->disableBlockBounds();
@@ -977,10 +977,10 @@ void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
}
// Update update graphs
- g_profiler->graphAdd("Time non-rendering [ms]",
+ g_profiler->graphAdd("Time non-rendering [us]",
draw_times.busy_time - stats.drawtime);
- g_profiler->graphAdd("Sleep [ms]", draw_times.sleep_time);
+ g_profiler->graphAdd("Sleep [us]", draw_times.sleep_time);
g_profiler->graphAdd("FPS", 1.0f / dtime);
}
@@ -1013,9 +1013,9 @@ void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
/* Busytime average and jitter calculation
*/
jp = &stats->busy_time_jitter;
- jp->avg = jp->avg + draw_times.busy_time * 0.02;
+ jp->avg = jp->avg + draw_times.getBusyMs() * 0.02;
- jitter = draw_times.busy_time - jp->avg;
+ jitter = draw_times.getBusyMs() - jp->avg;
if (jitter > jp->max)
jp->max = jitter;
@@ -1052,6 +1052,7 @@ void Game::processUserInput(f32 dtime)
else if (g_touchscreengui) {
/* on touchscreengui step may generate own input events which ain't
* what we want in case we just did clear them */
+ g_touchscreengui->show();
g_touchscreengui->step(dtime);
}
#endif
@@ -1290,15 +1291,22 @@ void Game::openInventory()
InventoryLocation inventoryloc;
inventoryloc.setCurrentPlayer();
- if (!client->modsLoaded()
- || !client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
- TextDest *txt_dst = new TextDestPlayerInventory(client);
- auto *&formspec = m_game_ui->updateFormspec("");
- GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
- &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
+ if (client->modsLoaded() && client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
+ delete fs_src;
+ return;
+ }
- formspec->setFormSpec(fs_src->getForm(), inventoryloc);
+ if (fs_src->getForm().empty()) {
+ delete fs_src;
+ return;
}
+
+ TextDest *txt_dst = new TextDestPlayerInventory(client);
+ auto *&formspec = m_game_ui->updateFormspec("");
+ GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
+ &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
+
+ formspec->setFormSpec(fs_src->getForm(), inventoryloc);
}
void Game::openEnderchest()
@@ -1398,7 +1406,7 @@ void Game::toggleFast()
m_game_ui->showTranslatedStatusText("Fast mode disabled");
}
-#ifdef __ANDROID__
+#ifdef HAVE_TOUCHSCREENGUI
m_cache_hold_aux1 = fast_move && has_fast_privs;
#endif
}
@@ -1469,27 +1477,27 @@ void Game::toggleCinematic()
void Game::toggleBlockBounds()
{
- if (client->checkPrivilege("basic_debug")) {
- enum Hud::BlockBoundsMode newmode = hud->toggleBlockBounds();
- switch (newmode) {
- case Hud::BLOCK_BOUNDS_OFF:
- m_game_ui->showTranslatedStatusText("Block bounds hidden");
- break;
- case Hud::BLOCK_BOUNDS_CURRENT:
- m_game_ui->showTranslatedStatusText("Block bounds shown for current block");
- break;
- case Hud::BLOCK_BOUNDS_NEAR:
- m_game_ui->showTranslatedStatusText("Block bounds shown for nearby blocks");
- break;
- case Hud::BLOCK_BOUNDS_MAX:
- m_game_ui->showTranslatedStatusText("Block bounds shown for all blocks");
- break;
- default:
- break;
- }
-
- } else {
- m_game_ui->showTranslatedStatusText("Can't show block bounds (need 'basic_debug' privilege)");
+ LocalPlayer *player = client->getEnv().getLocalPlayer();
+ if (!(client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG))) {
+ m_game_ui->showTranslatedStatusText("Can't show block bounds (disabled by mod or game)");
+ return;
+ }
+ enum Hud::BlockBoundsMode newmode = hud->toggleBlockBounds();
+ switch (newmode) {
+ case Hud::BLOCK_BOUNDS_OFF:
+ m_game_ui->showTranslatedStatusText("Block bounds hidden");
+ break;
+ case Hud::BLOCK_BOUNDS_CURRENT:
+ m_game_ui->showTranslatedStatusText("Block bounds shown for current block");
+ break;
+ case Hud::BLOCK_BOUNDS_NEAR:
+ m_game_ui->showTranslatedStatusText("Block bounds shown for nearby blocks");
+ break;
+ case Hud::BLOCK_BOUNDS_MAX:
+ m_game_ui->showTranslatedStatusText("Block bounds shown for all blocks");
+ break;
+ default:
+ break;
}
}
@@ -1556,6 +1564,9 @@ void Game::toggleFog()
void Game::toggleDebug()
{
+ LocalPlayer *player = client->getEnv().getLocalPlayer();
+ bool has_debug = client->checkPrivilege("debug");
+ bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
// Initial: No debug info
// 1x toggle: Debug text
// 2x toggle: Debug text with profiler graph
@@ -1565,26 +1576,23 @@ void Game::toggleDebug()
// The debug text can be in 2 modes: minimal and basic.
// * Minimal: Only technical client info that not gameplay-relevant
// * Basic: Info that might give gameplay advantage, e.g. pos, angle
- // Basic mode is used when player has "basic_debug" priv,
+ // Basic mode is used when player has the debug HUD flag set,
// otherwise the Minimal mode is used.
if (!m_game_ui->m_flags.show_minimal_debug) {
m_game_ui->m_flags.show_minimal_debug = true;
- if (client->checkPrivilege("basic_debug")) {
+ if (has_basic_debug)
m_game_ui->m_flags.show_basic_debug = true;
- }
m_game_ui->m_flags.show_profiler_graph = false;
draw_control->show_wireframe = false;
m_game_ui->showTranslatedStatusText("Debug info shown");
} else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) {
- if (client->checkPrivilege("basic_debug")) {
+ if (has_basic_debug)
m_game_ui->m_flags.show_basic_debug = true;
- }
m_game_ui->m_flags.show_profiler_graph = true;
m_game_ui->showTranslatedStatusText("Profiler graph shown");
} else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) {
- if (client->checkPrivilege("basic_debug")) {
+ if (has_basic_debug)
m_game_ui->m_flags.show_basic_debug = true;
- }
m_game_ui->m_flags.show_profiler_graph = false;
draw_control->show_wireframe = true;
m_game_ui->showTranslatedStatusText("Wireframe shown");
@@ -1593,7 +1601,7 @@ void Game::toggleDebug()
m_game_ui->m_flags.show_basic_debug = false;
m_game_ui->m_flags.show_profiler_graph = false;
draw_control->show_wireframe = false;
- if (client->checkPrivilege("debug")) {
+ if (has_debug) {
m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
} else {
m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
@@ -1755,6 +1763,10 @@ void Game::updatePlayerControl(const CameraOrientation &cam)
//TimeTaker tt("update player control", NULL, PRECISION_NANO);
PlayerControl control(
+ isKeyDown(KeyType::FORWARD),
+ isKeyDown(KeyType::BACKWARD),
+ isKeyDown(KeyType::LEFT),
+ isKeyDown(KeyType::RIGHT),
isKeyDown(KeyType::JUMP) || player->getAutojump(),
isKeyDown(KeyType::AUX1),
isKeyDown(KeyType::SNEAK),
@@ -1774,10 +1786,10 @@ void Game::updatePlayerControl(const CameraOrientation &cam)
control.movement_direction = 0.0f;
}
-#ifdef ANDROID
- /* For Android, simulate holding down AUX1 (fast move) if the user has
+#ifdef HAVE_TOUCHSCREENGUI
+ /* For touch, simulate holding down AUX1 (fast move) if the user has
* the fast_move setting toggled on. If there is an aux1 key defined for
- * Android then its meaning is inverted (i.e. holding aux1 means walk and
+ * touch then its meaning is inverted (i.e. holding aux1 means walk and
* not fast)
*/
if (m_cache_hold_aux1) {
@@ -1785,39 +1797,7 @@ void Game::updatePlayerControl(const CameraOrientation &cam)
}
#endif
- u32 keypress_bits = (
- ( (u32)(control.jump & 0x1) << 4) |
- ( (u32)(control.aux1 & 0x1) << 5) |
- ( (u32)(control.sneak & 0x1) << 6) |
- ( (u32)(control.dig & 0x1) << 7) |
- ( (u32)(control.place & 0x1) << 8) |
- ( (u32)(control.zoom & 0x1) << 9)
- );
-
- // Set direction keys to ensure mod compatibility
- if (control.movement_speed > 0.001f) {
- float absolute_direction;
-
- // Check in original orientation (absolute value indicates forward / backward)
- absolute_direction = abs(control.movement_direction);
- if (absolute_direction < (3.0f / 8.0f * M_PI))
- keypress_bits |= (u32)(0x1 << 0); // Forward
- if (absolute_direction > (5.0f / 8.0f * M_PI))
- keypress_bits |= (u32)(0x1 << 1); // Backward
-
- // Rotate entire coordinate system by 90 degrees (absolute value indicates left / right)
- absolute_direction = control.movement_direction + M_PI_2;
- if (absolute_direction >= M_PI)
- absolute_direction -= 2 * M_PI;
- absolute_direction = abs(absolute_direction);
- if (absolute_direction < (3.0f / 8.0f * M_PI))
- keypress_bits |= (u32)(0x1 << 2); // Left
- if (absolute_direction > (5.0f / 8.0f * M_PI))
- keypress_bits |= (u32)(0x1 << 3); // Right
- }
-
client->setPlayerControl(control);
- player->keyPressed = keypress_bits;
//tt.stop();
}
@@ -2172,7 +2152,7 @@ void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam)
void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam)
{
sky->setStarsVisible(event->star_params->visible);
- sky->setStarCount(event->star_params->count, false);
+ sky->setStarCount(event->star_params->count);
sky->setStarColor(event->star_params->starcolor);
sky->setStarScale(event->star_params->scale);
delete event->star_params;
@@ -2209,7 +2189,7 @@ void Game::processClientEvents(CameraOrientation *cam)
}
}
-void Game::updateChat(f32 dtime, const v2u32 &screensize)
+void Game::updateChat(f32 dtime)
{
// Get new messages from error log buffer
while (!m_chat_log_buf.empty())
@@ -2225,11 +2205,17 @@ void Game::updateChat(f32 dtime, const v2u32 &screensize)
chat_backend->step(dtime);
// Display all messages in a static text element
- m_game_ui->setChatText(chat_backend->getRecentChat(),
- chat_backend->getRecentBuffer().getLineCount());
+ auto &buf = chat_backend->getRecentBuffer();
+ if (buf.getLinesModified()) {
+ buf.resetLinesModified();
+ m_game_ui->setChatText(chat_backend->getRecentChat(), buf.getLineCount());
+ }
+
+ // Make sure that the size is still correct
+ m_game_ui->updateChatSize();
}
-void Game::updateCamera(u32 busy_time, f32 dtime)
+void Game::updateCamera(f32 dtime)
{
LocalPlayer *player = client->getEnv().getLocalPlayer();
@@ -2259,7 +2245,7 @@ void Game::updateCamera(u32 busy_time, f32 dtime)
float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval;
tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
- camera->update(player, dtime, busy_time / 1000.0f, tool_reload_ratio);
+ camera->update(player, dtime, tool_reload_ratio);
camera->step(dtime);
v3f camera_position = camera->getPosition();
@@ -2333,7 +2319,7 @@ void Game::updateSound(f32 dtime)
}
-void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug)
+void Game::processPlayerInteraction(f32 dtime, bool show_hud)
{
LocalPlayer *player = client->getEnv().getLocalPlayer();
@@ -2391,10 +2377,12 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug)
!runData.btn_down_for_dig,
camera_offset);
- if (pointed != runData.pointed_old) {
+ if (pointed != runData.pointed_old)
infostream << "Pointing at " << pointed.dump() << std::endl;
- hud->updateSelectionMesh(camera_offset);
- }
+
+ // Note that updating the selection mesh every frame is not particularly efficient,
+ // but the halo rendering code is already inefficient so there's no point in optimizing it here
+ hud->updateSelectionMesh(camera_offset);
// Allow digging again if button is not pressed
if (runData.digging_blocked && !isKeyDown(KeyType::DIG))
@@ -2455,7 +2443,9 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug)
handlePointingAtNode(pointed, selected_item, hand_item, dtime);
} else if (pointed.type == POINTEDTHING_OBJECT) {
v3f player_position = player->getPosition();
- handlePointingAtObject(pointed, tool_item, player_position, show_debug);
+ bool basic_debug_allowed = client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
+ handlePointingAtObject(pointed, tool_item, player_position,
+ m_game_ui->m_flags.show_basic_debug && basic_debug_allowed);
} else if (isKeyDown(KeyType::DIG)) {
// When button is held down in air, show continuous animation
runData.punching = true;
@@ -2614,9 +2604,8 @@ void Game::handlePointingAtNode(const PointedThing &pointed,
} else {
MapNode n = map.getNode(nodepos);
- if (nodedef_manager->get(n).tiledef[0].name == "unknown_node.png") {
- m_game_ui->setInfoText(L"Unknown node: " +
- utf8_to_wide(nodedef_manager->get(n).name));
+ if (nodedef_manager->get(n).name == "unknown") {
+ m_game_ui->setInfoText(L"Unknown node");
}
}
@@ -2904,7 +2893,8 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
// cheat detection.
// Get digging parameters
DigParams params = getDigParams(nodedef_manager->get(n).groups,
- &selected_item.getToolCapabilities(itemdef_manager));
+ &selected_item.getToolCapabilities(itemdef_manager),
+ selected_item.wear);
// If can't dig, try hand
if (!params.diggable) {
@@ -3155,12 +3145,28 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
}
/*
- Get chat messages from client
+ Damage camera tilt
*/
+ if (player->hurt_tilt_timer > 0.0f) {
+ player->hurt_tilt_timer -= dtime * 6.0f;
- v2u32 screensize = driver->getScreenSize();
+ if (player->hurt_tilt_timer < 0.0f || g_settings->getBool("no_hurt_cam"))
+ player->hurt_tilt_strength = 0.0f;
+ }
+
+ /*
+ Update minimap pos and rotation
+ */
+ if (mapper && m_game_ui->m_flags.show_hud) {
+ mapper->setPos(floatToInt(player->getPosition(), BS));
+ mapper->setAngle(player->getYaw());
+ }
+
+ /*
+ Get chat messages from client
+ */
- updateChat(dtime, screensize);
+ updateChat(dtime);
/*
Inventory
@@ -3187,8 +3193,8 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
v3f camera_direction = camera->getDirection();
if (runData.update_draw_list_timer >= update_draw_list_delta
|| runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
- || m_camera_offset_changed) {
-
+ || m_camera_offset_changed
+ || client->getEnv().getClientMap().needsUpdateDrawList()) {
runData.update_draw_list_timer = 0;
client->getEnv().getClientMap().updateDrawList();
runData.update_draw_list_last_cam_dir = camera_direction;
@@ -3229,11 +3235,11 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
} while (false);
/*
- Drawing begins
+ ==================== Drawing begins ====================
*/
- const video::SColor &skycolor = sky->getSkyColor();
+ const video::SColor skycolor = sky->getSkyColor();
- TimeTaker tt_draw("Draw scene");
+ TimeTaker tt_draw("Draw scene", nullptr, PRECISION_MICRO);
driver->beginScene(true, true, skycolor);
bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
@@ -3254,6 +3260,8 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
/*
Profiler graph
*/
+ v2u32 screensize = driver->getScreenSize();
+
if (m_game_ui->m_flags.show_profiler_graph)
graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
@@ -3279,26 +3287,9 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
}
/*
- Damage camera tilt
- */
- if (player->hurt_tilt_timer > 0.0f) {
- player->hurt_tilt_timer -= dtime * 6.0f;
-
- if (player->hurt_tilt_timer < 0.0f || g_settings->getBool("no_hurt_cam"))
- player->hurt_tilt_strength = 0.0f;
- }
-
- /*
- Update minimap pos and rotation
- */
- if (mapper && m_game_ui->m_flags.show_hud) {
- mapper->setPos(floatToInt(player->getPosition(), BS));
- mapper->setAngle(player->getYaw());
- }
-
- /*
End scene
*/
+#if IRRLICHT_VERSION_MT_REVISION < 5
if (++m_reset_HW_buffer_counter > 500) {
/*
Periodically remove all mesh HW buffers.
@@ -3320,11 +3311,13 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
driver->removeAllHardwareBuffers();
m_reset_HW_buffer_counter = 0;
}
+#endif
+
driver->endScene();
stats->drawtime = tt_draw.stop(true);
- g_profiler->avg("Game::updateFrame(): draw scene [ms]", stats->drawtime);
- g_profiler->graphAdd("Update frame [ms]", tt_update.stop(true));
+ g_profiler->graphAdd("Draw scene [us]", stats->drawtime);
+ g_profiler->avg("Game::updateFrame(): update frame [ms]", tt_update.stop(true));
}
/* Log times and stuff for visualization */
@@ -3346,7 +3339,12 @@ void Game::updateShadows()
float in_timeofday = fmod(runData.time_of_day_smooth, 1.0f);
- float timeoftheday = fmod(getWickedTimeOfDay(in_timeofday) + 0.75f, 0.5f) + 0.25f;
+ float timeoftheday = getWickedTimeOfDay(in_timeofday);
+ bool is_day = timeoftheday > 0.25 && timeoftheday < 0.75;
+ bool is_shadow_visible = is_day ? sky->getSunVisible() : sky->getMoonVisible();
+ shadow->setShadowIntensity(is_shadow_visible ? client->getEnv().getLocalPlayer()->getLighting().shadow_intensity : 0.0f);
+
+ timeoftheday = fmod(timeoftheday + 0.75f, 0.5f) + 0.25f;
const float offset_constant = 10000.0f;
v3f light(0.0f, 0.0f, -1.0f);
@@ -3368,47 +3366,46 @@ void Game::updateShadows()
Misc
****************************************************************************/
-/* On some computers framerate doesn't seem to be automatically limited
- */
-inline void Game::limitFps(FpsControl *fps_timings, f32 *dtime)
+void FpsControl::reset()
{
- // not using getRealTime is necessary for wine
- device->getTimer()->tick(); // Maker sure device time is up-to-date
- u32 time = device->getTimer()->getTime();
- u32 last_time = fps_timings->last_time;
-
- if (time > last_time) // Make sure time hasn't overflowed
- fps_timings->busy_time = time - last_time;
- else
- fps_timings->busy_time = 0;
+ last_time = porting::getTimeUs();
+}
- u32 frametime_min = 1000 / (
+/*
+ * On some computers framerate doesn't seem to be automatically limited
+ */
+void FpsControl::limit(IrrlichtDevice *device, f32 *dtime)
+{
+ const u64 frametime_min = 1000000.0f / (
device->isWindowFocused() && !g_menumgr.pausesGame()
? g_settings->getFloat("fps_max")
: g_settings->getFloat("fps_max_unfocused"));
- if (fps_timings->busy_time < frametime_min) {
- fps_timings->sleep_time = frametime_min - fps_timings->busy_time;
- device->sleep(fps_timings->sleep_time);
+ u64 time = porting::getTimeUs();
+
+ if (time > last_time) // Make sure time hasn't overflowed
+ busy_time = time - last_time;
+ else
+ busy_time = 0;
+
+ if (busy_time < frametime_min) {
+ sleep_time = frametime_min - busy_time;
+ if (sleep_time > 1000)
+ sleep_ms(sleep_time / 1000);
} else {
- fps_timings->sleep_time = 0;
+ sleep_time = 0;
}
- /* Get the new value of the device timer. Note that device->sleep() may
- * not sleep for the entire requested time as sleep may be interrupted and
- * therefore it is arguably more accurate to get the new time from the
- * device rather than calculating it by adding sleep_time to time.
- */
-
- device->getTimer()->tick(); // Update device timer
- time = device->getTimer()->getTime();
+ // Read the timer again to accurately determine how long we actually slept,
+ // rather than calculating it by adding sleep_time to time.
+ time = porting::getTimeUs();
- if (time > last_time) // Make sure last_time hasn't overflowed
- *dtime = (time - last_time) / 1000.0;
+ if (time > last_time) // Make sure last_time hasn't overflowed
+ *dtime = (time - last_time) / 1000000.0f;
else
*dtime = 0;
- fps_timings->last_time = time;
+ last_time = time;
}
void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
@@ -3522,7 +3519,7 @@ void Game::showDeathFormspec()
#define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
void Game::showPauseMenu()
{
-#ifdef __ANDROID__
+#ifdef HAVE_TOUCHSCREENGUI
static const std::string control_text = strgettext("Default Controls:\n"
"No menu visible:\n"
"- single tap: button activate\n"
@@ -3630,16 +3627,18 @@ void Game::showPauseMenu()
if (simple_singleplayer_mode || address.empty()) {
static const std::string on = strgettext("On");
static const std::string off = strgettext("Off");
- const std::string &damage = g_settings->getBool("enable_damage") ? on : off;
- const std::string &creative = g_settings->getBool("creative_mode") ? on : off;
+ // Note: Status of enable_damage and creative_mode settings is intentionally
+ // NOT shown here because the game might roll its own damage system and/or do
+ // a per-player Creative Mode, in which case writing it here would mislead.
+ bool damage = g_settings->getBool("enable_damage");
const std::string &announced = g_settings->getBool("server_announce") ? on : off;
- os << strgettext("- Damage: ") << damage << "\n"
- << strgettext("- Creative Mode: ") << creative << "\n";
if (!simple_singleplayer_mode) {
- const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
- //~ PvP = Player versus Player
- os << strgettext("- PvP: ") << pvp << "\n"
- << strgettext("- Public: ") << announced << "\n";
+ if (damage) {
+ const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
+ //~ PvP = Player versus Player
+ os << strgettext("- PvP: ") << pvp << "\n";
+ }
+ os << strgettext("- Public: ") << announced << "\n";
std::string server_name = g_settings->get("server_name");
str_formspec_escape(server_name);
if (announced == on && !server_name.empty())
@@ -3698,14 +3697,15 @@ void the_game(bool *kill,
}
} catch (SerializationError &e) {
- error_message = std::string("A serialization error occurred:\n")
- + e.what() + "\n\nThe server is probably "
- " running a different version of " PROJECT_NAME_C ".";
+ const std::string ver_err = fmtgettext("The server is probably running a different version of %s.", PROJECT_NAME_C);
+ error_message = strgettext("A serialization error occurred:") +"\n"
+ + e.what() + "\n\n" + ver_err;
errorstream << error_message << std::endl;
} catch (ServerError &e) {
error_message = e.what();
errorstream << "ServerError: " << error_message << std::endl;
} catch (ModError &e) {
+ // DO NOT TRANSLATE the `ModError`, it's used by ui.lua
error_message = std::string("ModError: ") + e.what() +
strgettext("\nCheck debug.txt for details.");
errorstream << error_message << std::endl;
diff --git a/src/client/game.h b/src/client/game.h
index cb40d4890..0e5d0550d 100644
--- a/src/client/game.h
+++ b/src/client/game.h
@@ -86,7 +86,7 @@ struct Jitter {
};
struct RunStats {
- u32 drawtime;
+ u64 drawtime; // (us)
Jitter dtime_jitter, busy_time_jitter;
};
@@ -584,7 +584,7 @@ public:
}
};
-#ifdef __ANDROID__
+#ifdef HAVE_TOUCHSCREENGUI
#define SIZE_TAG "size[11,5.5]"
#else
#define SIZE_TAG "size[11,5.5,true]" // Fixed size on desktop
@@ -596,7 +596,16 @@ public:
const float object_hit_delay = 0.2;
struct FpsControl {
- u32 last_time, busy_time, sleep_time;
+ FpsControl() : last_time(0), busy_time(0), sleep_time(0) {}
+
+ void reset();
+
+ void limit(IrrlichtDevice *device, f32 *dtime);
+
+ u32 getBusyMs() const { return busy_time / 1000; }
+
+ // all values in microseconds (us)
+ u64 last_time, busy_time, sleep_time;
};
@@ -726,9 +735,9 @@ public:
void updatePlayerControl(const CameraOrientation &cam);
void step(f32 *dtime);
void processClientEvents(CameraOrientation *cam);
- void updateCamera(u32 busy_time, f32 dtime);
+ void updateCamera(f32 dtime);
void updateSound(f32 dtime);
- void processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug);
+ void processPlayerInteraction(f32 dtime, bool show_hud);
/*!
* Returns the object or node the player is pointing at.
* Also updates the selected thing in the Hud.
@@ -757,8 +766,6 @@ public:
void updateShadows();
// Misc
- void limitFps(FpsControl *fps_timings, f32 *dtime);
-
void showOverlayMessage(const char *msg, float dtime, int percent,
bool draw_clouds = true);
@@ -807,7 +814,7 @@ public:
CameraOrientation *cam);
void handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam);
- void updateChat(f32 dtime, const v2u32 &screensize);
+ void updateChat(f32 dtime);
bool nodePlacement(const ItemDefinition &selected_def, const ItemStack &selected_item,
const v3s16 &nodepos, const v3s16 &neighbourpos, const PointedThing &pointed,
@@ -904,12 +911,17 @@ public:
bool m_does_lost_focus_pause_game = false;
- CameraOrientation cam_view_target = { 0 };
- CameraOrientation cam_view = { 0 };
+ CameraOrientation cam_view_target = {}; // added by dragonfireclient
+ CameraOrientation cam_view = {}; // added by dragonfireclient
+#if IRRLICHT_VERSION_MT_REVISION < 5
int m_reset_HW_buffer_counter = 0;
-#ifdef __ANDROID__
+#endif
+
+#ifdef HAVE_TOUCHSCREENGUI
bool m_cache_hold_aux1;
+#endif
+#ifdef __ANDROID__
bool m_android_chat_open;
#endif
};
diff --git a/src/client/gameui.cpp b/src/client/gameui.cpp
index 66a006ea1..54be24ae2 100644
--- a/src/client/gameui.cpp
+++ b/src/client/gameui.cpp
@@ -124,16 +124,16 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_
// Minimal debug text must only contain info that can't give a gameplay advantage
if (m_flags.show_minimal_debug) {
- static float drawtime_avg = 0;
- drawtime_avg = drawtime_avg * 0.95 + stats.drawtime * 0.05;
- u16 fps = 1.0 / stats.dtime_jitter.avg;
+ const u16 fps = 1.0 / stats.dtime_jitter.avg;
+ m_drawtime_avg *= 0.95f;
+ m_drawtime_avg += 0.05f * (stats.drawtime / 1000);
std::ostringstream os(std::ios_base::binary);
os << std::fixed
<< PROJECT_NAME_C " " << g_version_hash
<< " | FPS: " << fps
<< std::setprecision(0)
- << " | drawtime: " << drawtime_avg << "ms"
+ << " | drawtime: " << m_drawtime_avg << "ms"
<< std::setprecision(1)
<< " | dtime jitter: "
<< (stats.dtime_jitter.max_fraction * 100.0) << "%"
@@ -171,9 +171,13 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_
const NodeDefManager *nodedef = client->getNodeDefManager();
MapNode n = map.getNode(pointed_old.node_undersurface);
- if (n.getContent() != CONTENT_IGNORE && nodedef->get(n).name != "unknown") {
- os << ", pointed: " << nodedef->get(n).name
- << ", param2: " << (u64) n.getParam2();
+ if (n.getContent() != CONTENT_IGNORE) {
+ if (nodedef->get(n).name == "unknown") {
+ os << ", pointed: <unknown node>";
+ } else {
+ os << ", pointed: " << nodedef->get(n).name;
+ }
+ os << ", param2: " << (u64) n.getParam2();
}
}
@@ -229,7 +233,6 @@ void GameUI::initFlags()
{
m_flags = GameUI::Flags();
m_flags.show_minimal_debug = g_settings->getBool("show_debug");
- m_flags.show_basic_debug = false;
}
void GameUI::showMinimap(bool show)
@@ -246,7 +249,13 @@ void GameUI::showTranslatedStatusText(const char *str)
void GameUI::setChatText(const EnrichedString &chat_text, u32 recent_chat_count)
{
+ setStaticText(m_guitext_chat, chat_text);
+ m_recent_chat_count = recent_chat_count;
+}
+
+void GameUI::updateChatSize()
+{
// Update gui element size and position
const v2u32 &window_size = RenderingEngine::getWindowSize();
@@ -258,15 +267,15 @@ void GameUI::setChatText(const EnrichedString &chat_text, u32 recent_chat_count)
if (m_flags.show_basic_debug)
chat_y += g_fontengine->getLineHeight();
- core::rect<s32> chat_size(10, chat_y,
- window_size.X - 20, 0);
+ core::rect<s32> chat_size(10, chat_y, window_size.X - 20, 0);
chat_size.LowerRightCorner.Y = std::min((s32)window_size.Y,
- m_guitext_chat->getTextHeight() + chat_y);
+ m_guitext_chat->getTextHeight() + chat_y);
- m_guitext_chat->setRelativePosition(chat_size);
- setStaticText(m_guitext_chat, chat_text);
+ if (chat_size == m_current_chat_size)
+ return;
+ m_current_chat_size = chat_size;
- m_recent_chat_count = recent_chat_count;
+ m_guitext_chat->setRelativePosition(chat_size);
}
void GameUI::updateProfiler()
diff --git a/src/client/gameui.h b/src/client/gameui.h
index 5404643e2..e22be068b 100644
--- a/src/client/gameui.h
+++ b/src/client/gameui.h
@@ -85,11 +85,12 @@ public:
void showTranslatedStatusText(const char *str);
inline void clearStatusText() { m_statustext.clear(); }
- const bool isChatVisible()
+ bool isChatVisible()
{
return m_flags.show_chat && m_recent_chat_count != 0 && m_profiler_current_page == 0;
}
void setChatText(const EnrichedString &chat_text, u32 recent_chat_count);
+ void updateChatSize();
void updateProfiler();
@@ -111,6 +112,8 @@ public:
private:
Flags m_flags;
+ float m_drawtime_avg = 0;
+
gui::IGUIStaticText *m_guitext = nullptr; // First line of debug text
gui::IGUIStaticText *m_guitext2 = nullptr; // Second line of debug text
gui::IGUIStaticText *m_guitext_coords = nullptr;
@@ -125,6 +128,7 @@ private:
gui::IGUIStaticText *m_guitext_chat = nullptr; // Chat text
u32 m_recent_chat_count = 0;
+ core::rect<s32> m_current_chat_size{0, 0, 0, 0};
gui::IGUIStaticText *m_guitext_profiler = nullptr; // Profiler text
u8 m_profiler_current_page = 0;
diff --git a/src/client/guiscalingfilter.cpp b/src/client/guiscalingfilter.cpp
index 232219237..de122becf 100644
--- a/src/client/guiscalingfilter.cpp
+++ b/src/client/guiscalingfilter.cpp
@@ -43,6 +43,10 @@ void guiScalingCache(const io::path &key, video::IVideoDriver *driver, video::II
{
if (!g_settings->getBool("gui_scaling_filter"))
return;
+
+ if (g_imgCache.find(key) != g_imgCache.end())
+ return; // Already cached.
+
video::IImage *copied = driver->createImage(value->getColorFormat(),
value->getDimension());
value->copyTo(copied);
@@ -90,14 +94,16 @@ video::ITexture *guiScalingResizeCached(video::IVideoDriver *driver,
io::path scalename = origname + "@guiScalingFilter:" + rectstr;
// Search for existing scaled texture.
- video::ITexture *scaled = g_txrCache[scalename];
+ auto it_txr = g_txrCache.find(scalename);
+ video::ITexture *scaled = (it_txr != g_txrCache.end()) ? it_txr->second : nullptr;
if (scaled)
return scaled;
// Try to find the texture converted to an image in the cache.
// If the image was not found, try to extract it from the texture.
- video::IImage* srcimg = g_imgCache[origname];
- if (srcimg == NULL) {
+ auto it_img = g_imgCache.find(origname);
+ video::IImage *srcimg = (it_img != g_imgCache.end()) ? it_img->second : nullptr;
+ if (!srcimg) {
if (!g_settings->getBool("gui_scaling_filter_txr2img"))
return src;
srcimg = driver->createImageFromData(src->getColorFormat(),
diff --git a/src/client/hud.cpp b/src/client/hud.cpp
index 3f687b698..d5debecfb 100644
--- a/src/client/hud.cpp
+++ b/src/client/hud.cpp
@@ -20,6 +20,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
*/
#include "client/hud.h"
+#include <string>
+#include <iostream>
#include <cmath>
#include "settings.h"
#include "util/numeric.h"
@@ -222,6 +224,7 @@ void Hud::drawItem(const ItemStack &item, const core::rect<s32>& rect,
}
//NOTE: selectitem = 0 -> no selected; selectitem 1-based
+// mainlist can be NULL, but draw the frame anyway.
void Hud::drawItems(v2s32 upperleftpos, v2s32 screen_offset, s32 itemcount,
s32 inv_offset, InventoryList *mainlist, u16 selectitem, u16 direction)
{
@@ -269,7 +272,8 @@ void Hud::drawItems(v2s32 upperleftpos, v2s32 screen_offset, s32 itemcount,
// Draw items
core::rect<s32> imgrect(0, 0, m_hotbar_imagesize, m_hotbar_imagesize);
- for (s32 i = inv_offset; i < itemcount && (size_t)i < mainlist->getSize(); i++) {
+ const s32 list_size = mainlist ? mainlist->getSize() : 0;
+ for (s32 i = inv_offset; i < itemcount && i < list_size; i++) {
s32 fullimglen = m_hotbar_imagesize + m_padding * 2;
v2s32 steppos;
@@ -377,15 +381,19 @@ void Hud::drawLuaElements(const v3s16 &camera_offset)
std::wstring text = unescape_translate(utf8_to_wide(e->text));
core::dimension2d<u32> textsize = textfont->getDimension(text.c_str());
- v2s32 offset((e->align.X - 1.0) * (textsize.Width / 2),
- (e->align.Y - 1.0) * (textsize.Height / 2));
+ v2s32 offset(0, (e->align.Y - 1.0) * (textsize.Height / 2));
core::rect<s32> size(0, 0, e->scale.X * m_scale_factor,
- text_height * e->scale.Y * m_scale_factor);
+ text_height * e->scale.Y * m_scale_factor);
v2s32 offs(e->offset.X * m_scale_factor,
- e->offset.Y * m_scale_factor);
-
+ e->offset.Y * m_scale_factor);
+ std::wstringstream wss(text);
+ std::wstring line;
+ while (std::getline(wss, line, L'\n'))
{
- textfont->draw(text.c_str(), size + pos + offset + offs, color);
+ core::dimension2d<u32> linesize = textfont->getDimension(line.c_str());
+ v2s32 line_offset((e->align.X - 1.0) * (linesize.Width / 2), 0);
+ textfont->draw(line.c_str(), size + pos + offset + offs + line_offset, color);
+ offset.Y += linesize.Height;
}
break; }
case HUD_ELEM_STATBAR: {
@@ -395,6 +403,8 @@ void Hud::drawLuaElements(const v3s16 &camera_offset)
break; }
case HUD_ELEM_INVENTORY: {
InventoryList *inv = inventory->getList(e->text);
+ if (!inv)
+ warningstream << "HUD: Unknown inventory list. name=" << e->text << std::endl;
drawItems(pos, v2s32(e->offset.X, e->offset.Y), e->number, 0,
inv, e->item, e->dir);
break; }
@@ -666,7 +676,7 @@ void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir,
// Rectangles for 1/2 the "off state" texture
core::rect<s32> srchalfrect2, dsthalfrect2;
- if (count % 2 == 1) {
+ if (count % 2 == 1 || maxcount % 2 == 1) {
// Need to draw halves: Calculate rectangles
srchalfrect = calculate_clipping_rect(srcd, steppos);
dsthalfrect = calculate_clipping_rect(dstd, steppos);
@@ -701,7 +711,7 @@ void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir,
}
}
- if (stat_texture_bg && maxcount > count / 2) {
+ if (stat_texture_bg && maxcount > count) {
// Draw "off state" textures
s32 start_offset;
if (count % 2 == 1)
@@ -721,8 +731,7 @@ void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir,
if (maxcount % 2 == 1) {
draw2DImageFilterScaled(driver, stat_texture_bg,
- dsthalfrect + p, srchalfrect,
- NULL, colors, true);
+ dsthalfrect + p, srchalfrect, NULL, colors, true);
}
}
}
@@ -1001,11 +1010,19 @@ void drawItemStack(
bool draw_overlay = false;
+ bool has_mesh = false;
+ ItemMesh *imesh;
+
+ core::rect<s32> viewrect = rect;
+ if (clip != nullptr)
+ viewrect.clipAgainst(*clip);
+
// Render as mesh if animated or no inventory image
if ((enable_animations && rotation_kind < IT_ROT_NONE) || def.inventory_image.empty()) {
- ItemMesh *imesh = client->idef()->getWieldMesh(def.name, client);
- if (!imesh || !imesh->mesh)
- return;
+ imesh = client->idef()->getWieldMesh(def.name, client);
+ has_mesh = imesh && imesh->mesh;
+ }
+ if (has_mesh) {
scene::IMesh *mesh = imesh->mesh;
driver->clearBuffers(video::ECBF_DEPTH);
s32 delta = 0;
@@ -1021,9 +1038,6 @@ void drawItemStack(
core::rect<s32> oldViewPort = driver->getViewPort();
core::matrix4 oldProjMat = driver->getTransform(video::ETS_PROJECTION);
core::matrix4 oldViewMat = driver->getTransform(video::ETS_VIEW);
- core::rect<s32> viewrect = rect;
- if (clip)
- viewrect.clipAgainst(*clip);
core::matrix4 ProjMatrix;
ProjMatrix.buildProjectionMatrixOrthoLH(2.0f, 2.0f, -1.0f, 100.0f);
@@ -1097,10 +1111,17 @@ void drawItemStack(
draw_overlay = def.type == ITEM_NODE && def.inventory_image.empty();
} else { // Otherwise just draw as 2D
video::ITexture *texture = client->idef()->getInventoryTexture(def.name, client);
- if (!texture)
- return;
- video::SColor color =
- client->idef()->getItemstackColor(item, client);
+ video::SColor color;
+ if (texture) {
+ color = client->idef()->getItemstackColor(item, client);
+ } else {
+ color = video::SColor(255, 255, 255, 255);
+ ITextureSource *tsrc = client->getTextureSource();
+ texture = tsrc->getTexture("no_texture.png");
+ if (!texture)
+ return;
+ }
+
const video::SColor colors[] = { color, color, color, color };
draw2DImageFilterScaled(driver, texture, rect,
@@ -1160,24 +1181,68 @@ void drawItemStack(
driver->draw2DRectangle(color, progressrect2, clip);
}
- if (font != NULL && item.count >= 2) {
+ const std::string &count_text = item.metadata.getString("count_meta");
+ if (font != nullptr && (item.count >= 2 || !count_text.empty())) {
// Get the item count as a string
- std::string text = itos(item.count);
- v2u32 dim = font->getDimension(utf8_to_wide(text).c_str());
+ std::string text = count_text.empty() ? itos(item.count) : count_text;
+ v2u32 dim = font->getDimension(utf8_to_wide(unescape_enriched(text)).c_str());
v2s32 sdim(dim.X, dim.Y);
core::rect<s32> rect2(
- /*rect.UpperLeftCorner,
- core::dimension2d<u32>(rect.getWidth(), 15)*/
rect.LowerRightCorner - sdim,
- sdim
+ rect.LowerRightCorner
);
- video::SColor bgcolor(128, 0, 0, 0);
- driver->draw2DRectangle(bgcolor, rect2, clip);
+ // get the count alignment
+ s32 count_alignment = stoi(item.metadata.getString("count_alignment"));
+ if (count_alignment != 0) {
+ s32 a_x = count_alignment & 3;
+ s32 a_y = (count_alignment >> 2) & 3;
+
+ s32 x1, x2, y1, y2;
+ switch (a_x) {
+ case 1: // left
+ x1 = rect.UpperLeftCorner.X;
+ x2 = x1 + sdim.X;
+ break;
+ case 2: // middle
+ x1 = (rect.UpperLeftCorner.X + rect.LowerRightCorner.X - sdim.X) / 2;
+ x2 = x1 + sdim.X;
+ break;
+ case 3: // right
+ x2 = rect.LowerRightCorner.X;
+ x1 = x2 - sdim.X;
+ break;
+ default: // 0 = default
+ x1 = rect2.UpperLeftCorner.X;
+ x2 = rect2.LowerRightCorner.X;
+ break;
+ }
+
+ switch (a_y) {
+ case 1: // up
+ y1 = rect.UpperLeftCorner.Y;
+ y2 = y1 + sdim.Y;
+ break;
+ case 2: // middle
+ y1 = (rect.UpperLeftCorner.Y + rect.LowerRightCorner.Y - sdim.Y) / 2;
+ y2 = y1 + sdim.Y;
+ break;
+ case 3: // down
+ y2 = rect.LowerRightCorner.Y;
+ y1 = y2 - sdim.Y;
+ break;
+ default: // 0 = default
+ y1 = rect2.UpperLeftCorner.Y;
+ y2 = rect2.LowerRightCorner.Y;
+ break;
+ }
+
+ rect2 = core::rect<s32>(x1, y1, x2, y2);
+ }
video::SColor color(255, 255, 255, 255);
- font->draw(text.c_str(), rect2, color, false, false, clip);
+ font->draw(utf8_to_wide(text).c_str(), rect2, color, false, false, &viewrect);
}
}
diff --git a/src/client/imagefilters.cpp b/src/client/imagefilters.cpp
index 97ad094e5..c9d1504ad 100644
--- a/src/client/imagefilters.cpp
+++ b/src/client/imagefilters.cpp
@@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <cmath>
#include <cassert>
#include <vector>
+#include <algorithm>
// Simple 2D bitmap class with just the functionality needed here
class Bitmap {
@@ -123,7 +124,7 @@ void imageCleanTransparent(video::IImage *src, u32 threshold)
// Ignore pixels we haven't processed
if (!bitmap.get(sx, sy))
continue;
-
+
// Add RGB values weighted by alpha IF the pixel is opaque, otherwise
// use full weight since we want to propagate colors.
video::SColor d = src->getPixel(sx, sy);
diff --git a/src/client/inputhandler.h b/src/client/inputhandler.h
index 951ec8884..47a61d4b8 100644
--- a/src/client/inputhandler.h
+++ b/src/client/inputhandler.h
@@ -153,8 +153,14 @@ public:
// in the subsequent iteration of Game::processPlayerInteraction
bool WasKeyReleased(const KeyPress &keycode) const { return keyWasReleased[keycode]; }
- void listenForKey(const KeyPress &keyCode) { keysListenedFor.set(keyCode); }
- void dontListenForKeys() { keysListenedFor.clear(); }
+ void listenForKey(const KeyPress &keyCode)
+ {
+ keysListenedFor.set(keyCode);
+ }
+ void dontListenForKeys()
+ {
+ keysListenedFor.clear();
+ }
s32 getMouseWheel()
{
@@ -190,14 +196,14 @@ public:
#endif
}
- s32 mouse_wheel = 0;
-
JoystickController *joystick = nullptr;
#ifdef HAVE_TOUCHSCREENGUI
TouchScreenGUI *m_touchscreengui;
#endif
+ s32 mouse_wheel = 0;
+
// The current state of keys
KeyList keyIsDown;
@@ -274,6 +280,12 @@ public:
{
m_receiver->joystick = &joystick;
}
+
+ virtual ~RealInputHandler()
+ {
+ m_receiver->joystick = nullptr;
+ }
+
virtual bool isKeyDown(GameKeyType k)
{
return m_receiver->IsKeyDown(keycache.key[k]) || joystick.isKeyDown(k);
@@ -299,6 +311,7 @@ public:
{
return m_receiver->WasKeyReleased(keycache.key[k]) || joystick.wasKeyReleased(k);
}
+
virtual float getMovementSpeed()
{
bool f = m_receiver->IsKeyDown(keycache.key[KeyType::FORWARD]),
@@ -318,6 +331,7 @@ public:
}
return joystick.getMovementSpeed();
}
+
virtual float getMovementDirection()
{
float x = 0, z = 0;
@@ -337,10 +351,12 @@ public:
else
return joystick.getMovementDirection();
}
+
virtual bool cancelPressed()
{
return wasKeyDown(KeyType::ESC) || m_receiver->WasKeyDown(CancelKey);
}
+
virtual void clearWasKeyPressed()
{
m_receiver->clearWasKeyPressed();
@@ -349,17 +365,21 @@ public:
{
m_receiver->clearWasKeyReleased();
}
+
virtual void listenForKey(const KeyPress &keyCode)
{
m_receiver->listenForKey(keyCode);
}
- virtual void dontListenForKeys() { m_receiver->dontListenForKeys(); }
+ virtual void dontListenForKeys()
+ {
+ m_receiver->dontListenForKeys();
+ }
+
virtual v2s32 getMousePos()
{
- if (RenderingEngine::get_raw_device()->getCursorControl()) {
- return RenderingEngine::get_raw_device()
- ->getCursorControl()
- ->getPosition();
+ auto control = RenderingEngine::get_raw_device()->getCursorControl();
+ if (control) {
+ return control->getPosition();
}
return m_mousepos;
@@ -367,16 +387,18 @@ public:
virtual void setMousePos(s32 x, s32 y)
{
- if (RenderingEngine::get_raw_device()->getCursorControl()) {
- RenderingEngine::get_raw_device()
- ->getCursorControl()
- ->setPosition(x, y);
+ auto control = RenderingEngine::get_raw_device()->getCursorControl();
+ if (control) {
+ control->setPosition(x, y);
} else {
m_mousepos = v2s32(x, y);
}
}
- virtual s32 getMouseWheel() { return m_receiver->getMouseWheel(); }
+ virtual s32 getMouseWheel()
+ {
+ return m_receiver->getMouseWheel();
+ }
void clear()
{
@@ -384,7 +406,7 @@ public:
m_receiver->clearInput();
}
- private:
+private:
MyEventReceiver *m_receiver = nullptr;
v2s32 m_mousepos;
};
diff --git a/src/client/joystick_controller.cpp b/src/client/joystick_controller.cpp
index 630565d8d..aae73c62d 100644
--- a/src/client/joystick_controller.cpp
+++ b/src/client/joystick_controller.cpp
@@ -154,6 +154,54 @@ JoystickLayout create_xbox_layout()
return jlo;
}
+JoystickLayout create_dragonrise_gamecube_layout()
+{
+ JoystickLayout jlo;
+
+ jlo.axes_deadzone = 7000;
+
+ const JoystickAxisLayout axes[JA_COUNT] = {
+ // Control Stick
+ {0, 1}, // JA_SIDEWARD_MOVE
+ {1, 1}, // JA_FORWARD_MOVE
+
+ // C-Stick
+ {3, 1}, // JA_FRUSTUM_HORIZONTAL
+ {4, 1}, // JA_FRUSTUM_VERTICAL
+ };
+ memcpy(jlo.axes, axes, sizeof(jlo.axes));
+
+ // The center button
+ JLO_B_PB(KeyType::ESC, 1 << 9, 1 << 9); // Start/Pause Button
+
+ // Front right buttons
+ JLO_B_PB(KeyType::JUMP, 1 << 2, 1 << 2); // A Button
+ JLO_B_PB(KeyType::SNEAK, 1 << 3, 1 << 3); // B Button
+ JLO_B_PB(KeyType::DROP, 1 << 0, 1 << 0); // Y Button
+ JLO_B_PB(KeyType::AUX1, 1 << 1, 1 << 1); // X Button
+
+ // Triggers
+ JLO_B_PB(KeyType::DIG, 1 << 4, 1 << 4); // L Trigger
+ JLO_B_PB(KeyType::PLACE, 1 << 5, 1 << 5); // R Trigger
+ JLO_B_PB(KeyType::INVENTORY, 1 << 6, 1 << 6); // Z Button
+
+ // D-Pad
+ JLO_A_PB(KeyType::HOTBAR_PREV, 5, 1, jlo.axes_deadzone); // left
+ JLO_A_PB(KeyType::HOTBAR_NEXT, 5, -1, jlo.axes_deadzone); // right
+ // Axis are hard to actuate independantly, best to leave up and down unused.
+ //JLO_A_PB(0, 6, 1, jlo.axes_deadzone); // up
+ //JLO_A_PB(0, 6, -1, jlo.axes_deadzone); // down
+
+ // Movements tied to Control Stick, important for vessels
+ JLO_A_PB(KeyType::LEFT, 0, 1, jlo.axes_deadzone);
+ JLO_A_PB(KeyType::RIGHT, 0, -1, jlo.axes_deadzone);
+ JLO_A_PB(KeyType::FORWARD, 1, 1, jlo.axes_deadzone);
+ JLO_A_PB(KeyType::BACKWARD, 1, -1, jlo.axes_deadzone);
+
+ return jlo;
+}
+
+
JoystickController::JoystickController() :
doubling_dtime(g_settings->getFloat("repeat_joystick_button_time"))
{
@@ -188,6 +236,8 @@ void JoystickController::setLayoutFromControllerName(const std::string &name)
{
if (lowercase(name).find("xbox") != std::string::npos) {
m_layout = create_xbox_layout();
+ } else if (lowercase(name).find("dragonrise_gamecube") != std::string::npos) {
+ m_layout = create_dragonrise_gamecube_layout();
} else {
m_layout = create_default_layout();
}
diff --git a/src/client/localplayer.cpp b/src/client/localplayer.cpp
index ca61f3a11..c4d9b9845 100644
--- a/src/client/localplayer.cpp
+++ b/src/client/localplayer.cpp
@@ -232,8 +232,9 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
pp = floatToInt(position + v3f(0.0f, BS * 0.1f, 0.0f), BS);
node = map->getNode(pp, &is_valid_position);
if (is_valid_position) {
- in_liquid = nodemgr->get(node.getContent()).isLiquid();
- liquid_viscosity = nodemgr->get(node.getContent()).liquid_viscosity;
+ const ContentFeatures &cf = nodemgr->get(node.getContent());
+ in_liquid = cf.liquid_move_physics;
+ move_resistance = cf.move_resistance;
} else {
in_liquid = false;
}
@@ -243,8 +244,9 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
pp = floatToInt(position + v3f(0.0f, BS * 0.5f, 0.0f), BS);
node = map->getNode(pp, &is_valid_position);
if (is_valid_position) {
- in_liquid = nodemgr->get(node.getContent()).isLiquid();
- liquid_viscosity = nodemgr->get(node.getContent()).liquid_viscosity;
+ const ContentFeatures &cf = nodemgr->get(node.getContent());
+ in_liquid = cf.liquid_move_physics;
+ move_resistance = cf.move_resistance;
} else {
in_liquid = false;
}
@@ -257,7 +259,7 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
pp = floatToInt(position + v3f(0.0f), BS);
node = map->getNode(pp, &is_valid_position);
if (is_valid_position) {
- in_liquid_stable = nodemgr->get(node.getContent()).isLiquid();
+ in_liquid_stable = nodemgr->get(node.getContent()).liquid_move_physics;
} else {
in_liquid_stable = false;
}
@@ -694,19 +696,21 @@ v3s16 LocalPlayer::getStandingNodePos()
v3s16 LocalPlayer::getFootstepNodePos()
{
+ v3f feet_pos = getPosition() + v3f(0.0f, m_collisionbox.MinEdge.Y, 0.0f);
+
// Emit swimming sound if the player is in liquid
if (in_liquid_stable)
- return floatToInt(getPosition(), BS);
+ return floatToInt(feet_pos, BS);
// BS * 0.05 below the player's feet ensures a 1/16th height
// nodebox is detected instead of the node below it.
if (touching_ground)
- return floatToInt(getPosition() - v3f(0.0f, BS * 0.05f, 0.0f), BS);
+ return floatToInt(feet_pos - v3f(0.0f, BS * 0.05f, 0.0f), BS);
// A larger distance below is necessary for a footstep sound
// when landing after a jump or fall. BS * 0.5 ensures water
// sounds when swimming in 1 node deep water.
- return floatToInt(getPosition() - v3f(0.0f, BS * 0.5f, 0.0f), BS);
+ return floatToInt(feet_pos - v3f(0.0f, BS * 0.5f, 0.0f), BS);
}
v3s16 LocalPlayer::getLightPosition() const
@@ -846,8 +850,9 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d,
pp = floatToInt(position + v3f(0.0f, BS * 0.1f, 0.0f), BS);
node = map->getNode(pp, &is_valid_position);
if (is_valid_position) {
- in_liquid = nodemgr->get(node.getContent()).isLiquid();
- liquid_viscosity = nodemgr->get(node.getContent()).liquid_viscosity;
+ const ContentFeatures &cf = nodemgr->get(node.getContent());
+ in_liquid = cf.liquid_move_physics;
+ move_resistance = cf.move_resistance;
} else {
in_liquid = false;
}
@@ -856,8 +861,9 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d,
pp = floatToInt(position + v3f(0.0f, BS * 0.5f, 0.0f), BS);
node = map->getNode(pp, &is_valid_position);
if (is_valid_position) {
- in_liquid = nodemgr->get(node.getContent()).isLiquid();
- liquid_viscosity = nodemgr->get(node.getContent()).liquid_viscosity;
+ const ContentFeatures &cf = nodemgr->get(node.getContent());
+ in_liquid = cf.liquid_move_physics;
+ move_resistance = cf.move_resistance;
} else {
in_liquid = false;
}
@@ -869,7 +875,7 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d,
pp = floatToInt(position + v3f(0.0f), BS);
node = map->getNode(pp, &is_valid_position);
if (is_valid_position)
- in_liquid_stable = nodemgr->get(node.getContent()).isLiquid();
+ in_liquid_stable = nodemgr->get(node.getContent()).liquid_move_physics;
else
in_liquid_stable = false;
@@ -1138,10 +1144,8 @@ void LocalPlayer::handleAutojump(f32 dtime, Environment *env,
if (m_autojump)
return;
- bool control_forward = keyPressed & (1 << 0);
-
bool could_autojump =
- m_can_jump && !control.jump && !control.sneak && control_forward;
+ m_can_jump && !control.jump && !control.sneak && control.isMoving();
if (!could_autojump)
return;
diff --git a/src/client/localplayer.h b/src/client/localplayer.h
index eaac216d3..ebc67c4f8 100644
--- a/src/client/localplayer.h
+++ b/src/client/localplayer.h
@@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "environment.h"
#include "constants.h"
#include "settings.h"
+#include "lighting.h"
#include <list>
class Client;
@@ -56,8 +57,8 @@ public:
bool in_liquid = false;
// This is more stable and defines the maximum speed of the player
bool in_liquid_stable = false;
- // Gets the viscosity of water to calculate friction
- u8 liquid_viscosity = 0;
+ // Slows down the player when moving through
+ u8 move_resistance = 0;
bool is_climbing = false;
bool swimming_vertical = false;
bool swimming_pitch = false;
@@ -87,7 +88,7 @@ public:
v3f last_speed;
float last_pitch = 0.0f;
float last_yaw = 0.0f;
- unsigned int last_keyPressed = 0;
+ u32 last_keyPressed = 0;
u8 last_camera_fov = 0;
u8 last_wanted_range = 0;
@@ -187,6 +188,8 @@ public:
added_velocity += vel;
}
+ inline Lighting& getLighting() { return m_lighting; }
+
void tryReattach(int id);
bool isWaitingForReattach() const;
@@ -247,4 +250,5 @@ private:
GenericCAO *m_cao = nullptr;
Client *m_client;
+ Lighting m_lighting;
};
diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp
index 52729632a..f0d43ec7e 100644
--- a/src/client/mapblock_mesh.cpp
+++ b/src/client/mapblock_mesh.cpp
@@ -30,6 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "client/meshgen/collector.h"
#include "client/renderingengine.h"
#include <array>
+#include <algorithm>
/*
MeshMakeData
@@ -1046,6 +1047,173 @@ static void applyTileColor(PreMeshBuffer &pmb)
}
/*
+ MapBlockBspTree
+*/
+
+void MapBlockBspTree::buildTree(const std::vector<MeshTriangle> *triangles)
+{
+ this->triangles = triangles;
+
+ nodes.clear();
+
+ // assert that triangle index can fit into s32
+ assert(triangles->size() <= 0x7FFFFFFFL);
+ std::vector<s32> indexes;
+ indexes.reserve(triangles->size());
+ for (u32 i = 0; i < triangles->size(); i++)
+ indexes.push_back(i);
+
+ root = buildTree(v3f(1, 0, 0), v3f(85, 85, 85), 40, indexes, 0);
+}
+
+/**
+ * @brief Find a candidate plane to split a set of triangles in two
+ *
+ * The candidate plane is represented by one of the triangles from the set.
+ *
+ * @param list Vector of indexes of the triangles in the set
+ * @param triangles Vector of all triangles in the BSP tree
+ * @return Address of the triangle that represents the proposed split plane
+ */
+static const MeshTriangle *findSplitCandidate(const std::vector<s32> &list, const std::vector<MeshTriangle> &triangles)
+{
+ // find the center of the cluster.
+ v3f center(0, 0, 0);
+ size_t n = list.size();
+ for (s32 i : list) {
+ center += triangles[i].centroid / n;
+ }
+
+ // find the triangle with the largest area and closest to the center
+ const MeshTriangle *candidate_triangle = &triangles[list[0]];
+ const MeshTriangle *ith_triangle;
+ for (s32 i : list) {
+ ith_triangle = &triangles[i];
+ if (ith_triangle->areaSQ > candidate_triangle->areaSQ ||
+ (ith_triangle->areaSQ == candidate_triangle->areaSQ &&
+ ith_triangle->centroid.getDistanceFromSQ(center) < candidate_triangle->centroid.getDistanceFromSQ(center))) {
+ candidate_triangle = ith_triangle;
+ }
+ }
+ return candidate_triangle;
+}
+
+s32 MapBlockBspTree::buildTree(v3f normal, v3f origin, float delta, const std::vector<s32> &list, u32 depth)
+{
+ // if the list is empty, don't bother
+ if (list.empty())
+ return -1;
+
+ // if there is only one triangle, or the delta is insanely small, this is a leaf node
+ if (list.size() == 1 || delta < 0.01) {
+ nodes.emplace_back(normal, origin, list, -1, -1);
+ return nodes.size() - 1;
+ }
+
+ std::vector<s32> front_list;
+ std::vector<s32> back_list;
+ std::vector<s32> node_list;
+
+ // split the list
+ for (s32 i : list) {
+ const MeshTriangle &triangle = (*triangles)[i];
+ float factor = normal.dotProduct(triangle.centroid - origin);
+ if (factor == 0)
+ node_list.push_back(i);
+ else if (factor > 0)
+ front_list.push_back(i);
+ else
+ back_list.push_back(i);
+ }
+
+ // define the new split-plane
+ v3f candidate_normal(normal.Z, normal.X, normal.Y);
+ float candidate_delta = delta;
+ if (depth % 3 == 2)
+ candidate_delta /= 2;
+
+ s32 front_index = -1;
+ s32 back_index = -1;
+
+ if (!front_list.empty()) {
+ v3f next_normal = candidate_normal;
+ v3f next_origin = origin + delta * normal;
+ float next_delta = candidate_delta;
+ if (next_delta < 10) {
+ const MeshTriangle *candidate = findSplitCandidate(front_list, *triangles);
+ next_normal = candidate->getNormal();
+ next_origin = candidate->centroid;
+ }
+ front_index = buildTree(next_normal, next_origin, next_delta, front_list, depth + 1);
+
+ // if there are no other triangles, don't create a new node
+ if (back_list.empty() && node_list.empty())
+ return front_index;
+ }
+
+ if (!back_list.empty()) {
+ v3f next_normal = candidate_normal;
+ v3f next_origin = origin - delta * normal;
+ float next_delta = candidate_delta;
+ if (next_delta < 10) {
+ const MeshTriangle *candidate = findSplitCandidate(back_list, *triangles);
+ next_normal = candidate->getNormal();
+ next_origin = candidate->centroid;
+ }
+
+ back_index = buildTree(next_normal, next_origin, next_delta, back_list, depth + 1);
+
+ // if there are no other triangles, don't create a new node
+ if (front_list.empty() && node_list.empty())
+ return back_index;
+ }
+
+ nodes.emplace_back(normal, origin, node_list, front_index, back_index);
+
+ return nodes.size() - 1;
+}
+
+void MapBlockBspTree::traverse(s32 node, v3f viewpoint, std::vector<s32> &output) const
+{
+ if (node < 0) return; // recursion break;
+
+ const TreeNode &n = nodes[node];
+ float factor = n.normal.dotProduct(viewpoint - n.origin);
+
+ if (factor > 0)
+ traverse(n.back_ref, viewpoint, output);
+ else
+ traverse(n.front_ref, viewpoint, output);
+
+ if (factor != 0)
+ for (s32 i : n.triangle_refs)
+ output.push_back(i);
+
+ if (factor > 0)
+ traverse(n.front_ref, viewpoint, output);
+ else
+ traverse(n.back_ref, viewpoint, output);
+}
+
+
+
+/*
+ PartialMeshBuffer
+*/
+
+void PartialMeshBuffer::beforeDraw() const
+{
+ // Patch the indexes in the mesh buffer before draw
+
+ m_buffer->Indices.clear();
+ if (!m_vertex_indexes.empty()) {
+ for (auto index : m_vertex_indexes)
+ m_buffer->Indices.push_back(index);
+ }
+ m_buffer->setDirty(scene::EBT_INDEX);
+}
+
+/*
MapBlockMesh
*/
@@ -1152,6 +1320,9 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
Convert MeshCollector to SMesh
*/
+ const bool desync_animations = g_settings->getBool(
+ "desynchronize_mapblock_texture_animation");
+
for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) {
for(u32 i = 0; i < collector.prebuffers[layer].size(); i++)
{
@@ -1181,18 +1352,18 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
// - Texture animation
if (p.layer.material_flags & MATERIAL_FLAG_ANIMATION) {
// Add to MapBlockMesh in order to animate these tiles
- m_animation_tiles[std::pair<u8, u32>(layer, i)] = p.layer;
- m_animation_frames[std::pair<u8, u32>(layer, i)] = 0;
- if (g_settings->getBool(
- "desynchronize_mapblock_texture_animation")) {
+ auto &info = m_animation_info[{layer, i}];
+ info.tile = p.layer;
+ info.frame = 0;
+ if (desync_animations) {
// Get starting position from noise
- m_animation_frame_offsets[std::pair<u8, u32>(layer, i)] =
+ info.frame_offset =
100000 * (2.0 + noise3d(
data->m_blockpos.X, data->m_blockpos.Y,
data->m_blockpos.Z, 0));
} else {
// Play all synchronized
- m_animation_frame_offsets[std::pair<u8, u32>(layer, i)] = 0;
+ info.frame_offset = 0;
}
// Replace tile texture with the first animation frame
p.layer.texture = (*p.layer.frames)[0].texture;
@@ -1203,19 +1374,23 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
// Dummy sunlight to handle non-sunlit areas
video::SColorf sunlight;
get_sunlight_color(&sunlight, 0);
- u32 vertex_count = p.vertices.size();
+
+ std::map<u32, video::SColor> colors;
+ const u32 vertex_count = p.vertices.size();
for (u32 j = 0; j < vertex_count; j++) {
video::SColor *vc = &p.vertices[j].Color;
video::SColor copy = *vc;
if (vc->getAlpha() == 0) // No sunlight - no need to animate
final_color_blend(vc, copy, sunlight); // Finalize color
else // Record color to animate
- m_daynight_diffs[std::pair<u8, u32>(layer, i)][j] = copy;
+ colors[j] = copy;
// The sunlight ratio has been stored,
// delete alpha (for the final rendering).
vc->setAlpha(255);
}
+ if (!colors.empty())
+ m_daynight_diffs[{layer, i}] = std::move(colors);
}
// Create material
@@ -1241,8 +1416,31 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
scene::SMeshBuffer *buf = new scene::SMeshBuffer();
buf->Material = material;
- buf->append(&p.vertices[0], p.vertices.size(),
- &p.indices[0], p.indices.size());
+ switch (p.layer.material_type) {
+ // list of transparent materials taken from tile.h
+ case TILE_MATERIAL_ALPHA:
+ case TILE_MATERIAL_LIQUID_TRANSPARENT:
+ case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT:
+ {
+ buf->append(&p.vertices[0], p.vertices.size(),
+ &p.indices[0], 0);
+
+ MeshTriangle t;
+ t.buffer = buf;
+ for (u32 i = 0; i < p.indices.size(); i += 3) {
+ t.p1 = p.indices[i];
+ t.p2 = p.indices[i + 1];
+ t.p3 = p.indices[i + 2];
+ t.updateAttributes();
+ m_transparent_triangles.push_back(t);
+ }
+ }
+ break;
+ default:
+ buf->append(&p.vertices[0], p.vertices.size(),
+ &p.indices[0], p.indices.size());
+ break;
+ }
mesh->addMeshBuffer(buf);
buf->drop();
}
@@ -1255,23 +1453,26 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
}
//std::cout<<"added "<<fastfaces.getSize()<<" faces."<<std::endl;
+ m_bsp_tree.buildTree(&m_transparent_triangles);
// Check if animation is required for this mesh
m_has_animation =
!m_crack_materials.empty() ||
!m_daynight_diffs.empty() ||
- !m_animation_tiles.empty();
+ !m_animation_info.empty();
}
MapBlockMesh::~MapBlockMesh()
{
for (scene::IMesh *m : m_mesh) {
+#if IRRLICHT_VERSION_MT_REVISION < 5
if (m_enable_vbo) {
for (u32 i = 0; i < m->getMeshBufferCount(); i++) {
scene::IMeshBuffer *buf = m->getMeshBuffer(i);
RenderingEngine::get_video_driver()->removeHardwareBuffer(buf);
}
}
+#endif
m->drop();
}
delete m_minimap_mapblock;
@@ -1292,25 +1493,22 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack,
for (auto &crack_material : m_crack_materials) {
scene::IMeshBuffer *buf = m_mesh[crack_material.first.first]->
getMeshBuffer(crack_material.first.second);
- std::string basename = crack_material.second;
// Create new texture name from original
- std::ostringstream os;
- os << basename << crack;
+ std::string s = crack_material.second + itos(crack);
u32 new_texture_id = 0;
video::ITexture *new_texture =
- m_tsrc->getTextureForMesh(os.str(), &new_texture_id);
+ m_tsrc->getTextureForMesh(s, &new_texture_id);
buf->getMaterial().setTexture(0, new_texture);
- // If the current material is also animated,
- // update animation info
- auto anim_iter = m_animation_tiles.find(crack_material.first);
- if (anim_iter != m_animation_tiles.end()) {
- TileLayer &tile = anim_iter->second;
+ // If the current material is also animated, update animation info
+ auto anim_it = m_animation_info.find(crack_material.first);
+ if (anim_it != m_animation_info.end()) {
+ TileLayer &tile = anim_it->second.tile;
tile.texture = new_texture;
tile.texture_id = new_texture_id;
// force animation update
- m_animation_frames[crack_material.first] = -1;
+ anim_it->second.frame = -1;
}
}
@@ -1318,28 +1516,25 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack,
}
// Texture animation
- for (auto &animation_tile : m_animation_tiles) {
- const TileLayer &tile = animation_tile.second;
+ for (auto &it : m_animation_info) {
+ const TileLayer &tile = it.second.tile;
// Figure out current frame
- int frameoffset = m_animation_frame_offsets[animation_tile.first];
- int frame = (int)(time * 1000 / tile.animation_frame_length_ms
- + frameoffset) % tile.animation_frame_count;
+ int frameno = (int)(time * 1000 / tile.animation_frame_length_ms
+ + it.second.frame_offset) % tile.animation_frame_count;
// If frame doesn't change, skip
- if (frame == m_animation_frames[animation_tile.first])
+ if (frameno == it.second.frame)
continue;
- m_animation_frames[animation_tile.first] = frame;
+ it.second.frame = frameno;
- scene::IMeshBuffer *buf = m_mesh[animation_tile.first.first]->
- getMeshBuffer(animation_tile.first.second);
+ scene::IMeshBuffer *buf = m_mesh[it.first.first]->getMeshBuffer(it.first.second);
- const FrameSpec &animation_frame = (*tile.frames)[frame];
- buf->getMaterial().setTexture(0, animation_frame.texture);
+ const FrameSpec &frame = (*tile.frames)[frameno];
+ buf->getMaterial().setTexture(0, frame.texture);
if (m_enable_shaders) {
- if (animation_frame.normal_texture)
- buf->getMaterial().setTexture(1,
- animation_frame.normal_texture);
- buf->getMaterial().setTexture(2, animation_frame.flags_texture);
+ if (frame.normal_texture)
+ buf->getMaterial().setTexture(1, frame.normal_texture);
+ buf->getMaterial().setTexture(2, frame.flags_texture);
}
}
@@ -1366,6 +1561,67 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack,
return true;
}
+void MapBlockMesh::updateTransparentBuffers(v3f camera_pos, v3s16 block_pos)
+{
+ // nothing to do if the entire block is opaque
+ if (m_transparent_triangles.empty())
+ return;
+
+ v3f block_posf = intToFloat(block_pos * MAP_BLOCKSIZE, BS);
+ v3f rel_camera_pos = camera_pos - block_posf;
+
+ std::vector<s32> triangle_refs;
+ m_bsp_tree.traverse(rel_camera_pos, triangle_refs);
+
+ // arrange index sequences into partial buffers
+ m_transparent_buffers.clear();
+
+ scene::SMeshBuffer *current_buffer = nullptr;
+ std::vector<u16> current_strain;
+ for (auto i : triangle_refs) {
+ const auto &t = m_transparent_triangles[i];
+ if (current_buffer != t.buffer) {
+ if (current_buffer) {
+ m_transparent_buffers.emplace_back(current_buffer, current_strain);
+ current_strain.clear();
+ }
+ current_buffer = t.buffer;
+ }
+ current_strain.push_back(t.p1);
+ current_strain.push_back(t.p2);
+ current_strain.push_back(t.p3);
+ }
+
+ if (!current_strain.empty())
+ m_transparent_buffers.emplace_back(current_buffer, current_strain);
+}
+
+void MapBlockMesh::consolidateTransparentBuffers()
+{
+ m_transparent_buffers.clear();
+
+ scene::SMeshBuffer *current_buffer = nullptr;
+ std::vector<u16> current_strain;
+
+ // use the fact that m_transparent_triangles is already arranged by buffer
+ for (const auto &t : m_transparent_triangles) {
+ if (current_buffer != t.buffer) {
+ if (current_buffer != nullptr) {
+ this->m_transparent_buffers.emplace_back(current_buffer, current_strain);
+ current_strain.clear();
+ }
+ current_buffer = t.buffer;
+ }
+ current_strain.push_back(t.p1);
+ current_strain.push_back(t.p2);
+ current_strain.push_back(t.p3);
+ }
+
+ if (!current_strain.empty()) {
+ this->m_transparent_buffers.emplace_back(current_buffer, current_strain);
+ }
+}
+
video::SColor encode_light(u16 light, u8 emissive_light)
{
// Get components
diff --git a/src/client/mapblock_mesh.h b/src/client/mapblock_mesh.h
index 80075fce2..5e2d70b75 100644
--- a/src/client/mapblock_mesh.h
+++ b/src/client/mapblock_mesh.h
@@ -71,6 +71,91 @@ struct MeshMakeData
void setSmoothLighting(bool smooth_lighting);
};
+// represents a triangle as indexes into the vertex buffer in SMeshBuffer
+class MeshTriangle
+{
+public:
+ scene::SMeshBuffer *buffer;
+ u16 p1, p2, p3;
+ v3f centroid;
+ float areaSQ;
+
+ void updateAttributes()
+ {
+ v3f v1 = buffer->getPosition(p1);
+ v3f v2 = buffer->getPosition(p2);
+ v3f v3 = buffer->getPosition(p3);
+
+ centroid = (v1 + v2 + v3) / 3;
+ areaSQ = (v2-v1).crossProduct(v3-v1).getLengthSQ() / 4;
+ }
+
+ v3f getNormal() const {
+ v3f v1 = buffer->getPosition(p1);
+ v3f v2 = buffer->getPosition(p2);
+ v3f v3 = buffer->getPosition(p3);
+
+ return (v2-v1).crossProduct(v3-v1);
+ }
+};
+
+/**
+ * Implements a binary space partitioning tree
+ * See also: https://en.wikipedia.org/wiki/Binary_space_partitioning
+ */
+class MapBlockBspTree
+{
+public:
+ MapBlockBspTree() {}
+
+ void buildTree(const std::vector<MeshTriangle> *triangles);
+
+ void traverse(v3f viewpoint, std::vector<s32> &output) const
+ {
+ traverse(root, viewpoint, output);
+ }
+
+private:
+ // Tree node definition;
+ struct TreeNode
+ {
+ v3f normal;
+ v3f origin;
+ std::vector<s32> triangle_refs;
+ s32 front_ref;
+ s32 back_ref;
+
+ TreeNode() = default;
+ TreeNode(v3f normal, v3f origin, const std::vector<s32> &triangle_refs, s32 front_ref, s32 back_ref) :
+ normal(normal), origin(origin), triangle_refs(triangle_refs), front_ref(front_ref), back_ref(back_ref)
+ {}
+ };
+
+
+ s32 buildTree(v3f normal, v3f origin, float delta, const std::vector<s32> &list, u32 depth);
+ void traverse(s32 node, v3f viewpoint, std::vector<s32> &output) const;
+
+ const std::vector<MeshTriangle> *triangles = nullptr; // this reference is managed externally
+ std::vector<TreeNode> nodes; // list of nodes
+ s32 root = -1; // index of the root node
+};
+
+class PartialMeshBuffer
+{
+public:
+ PartialMeshBuffer(scene::SMeshBuffer *buffer, const std::vector<u16> &vertex_indexes) :
+ m_buffer(buffer), m_vertex_indexes(vertex_indexes)
+ {}
+
+ scene::IMeshBuffer *getBuffer() const { return m_buffer; }
+ const std::vector<u16> &getVertexIndexes() const { return m_vertex_indexes; }
+
+ void beforeDraw() const;
+private:
+ scene::SMeshBuffer *m_buffer;
+ std::vector<u16> m_vertex_indexes;
+};
+
/*
Holds a mesh for a mapblock.
@@ -125,9 +210,25 @@ public:
m_animation_force_timer--;
}
+ /// update transparent buffers to render towards the camera
+ void updateTransparentBuffers(v3f camera_pos, v3s16 block_pos);
+ void consolidateTransparentBuffers();
+
+ /// get the list of transparent buffers
+ const std::vector<PartialMeshBuffer> &getTransparentBuffers() const
+ {
+ return this->m_transparent_buffers;
+ }
+
std::set<v3s16> esp_nodes;
private:
+ struct AnimationInfo {
+ int frame; // last animation frame
+ int frame_offset;
+ TileLayer tile;
+ };
+
scene::IMesh *m_mesh[MAX_TILE_LAYERS];
MinimapMapblock *m_minimap_mapblock;
ITextureSource *m_tsrc;
@@ -146,12 +247,10 @@ private:
// Maps mesh and mesh buffer (i.e. material) indices to base texture names
std::map<std::pair<u8, u32>, std::string> m_crack_materials;
- // Animation info: texture animationi
+ // Animation info: texture animation
// Maps mesh and mesh buffer indices to TileSpecs
// Keys are pairs of (mesh index, buffer index in the mesh)
- std::map<std::pair<u8, u32>, TileLayer> m_animation_tiles;
- std::map<std::pair<u8, u32>, int> m_animation_frames; // last animation frame
- std::map<std::pair<u8, u32>, int> m_animation_frame_offsets;
+ std::map<std::pair<u8, u32>, AnimationInfo> m_animation_info;
// Animation info: day/night transitions
// Last daynight_ratio value passed to animate()
@@ -160,6 +259,13 @@ private:
// of sunlit vertices
// Keys are pairs of (mesh index, buffer index in the mesh)
std::map<std::pair<u8, u32>, std::map<u32, video::SColor > > m_daynight_diffs;
+
+ // list of all semitransparent triangles in the mapblock
+ std::vector<MeshTriangle> m_transparent_triangles;
+ // Binary Space Partitioning tree for the block
+ MapBlockBspTree m_bsp_tree;
+ // Ordered list of references to parts of transparent buffers to draw
+ std::vector<PartialMeshBuffer> m_transparent_buffers;
};
/*!
diff --git a/src/client/mesh.cpp b/src/client/mesh.cpp
index e43139218..bec72fb5e 100644
--- a/src/client/mesh.cpp
+++ b/src/client/mesh.cpp
@@ -331,6 +331,9 @@ void recalculateBoundingBox(scene::IMesh *src_mesh)
bool checkMeshNormals(scene::IMesh *mesh)
{
+ // Assume correct normals if this many first faces get it right.
+ static const u16 MAX_FACES_TO_CHECK = 9;
+
u32 buffer_count = mesh->getMeshBufferCount();
for (u32 i = 0; i < buffer_count; i++) {
@@ -344,6 +347,19 @@ bool checkMeshNormals(scene::IMesh *mesh)
if (!std::isfinite(length) || length < 1e-10f)
return false;
+
+ const u16 count = MYMIN(MAX_FACES_TO_CHECK * 3, buffer->getIndexCount() - 3);
+ for (u16 i = 0; i < count; i += 3) {
+
+ core::plane3df plane(buffer->getPosition(buffer->getIndices()[i]),
+ buffer->getPosition(buffer->getIndices()[i+1]),
+ buffer->getPosition(buffer->getIndices()[i+2]));
+
+ for (u16 j = 0; j < 3; j++)
+ if (plane.Normal.dotProduct(buffer->getNormal(buffer->getIndices()[i+j])) <= 0)
+ return false;
+ }
+
}
return true;
@@ -498,592 +514,3 @@ scene::IMesh* convertNodeboxesToMesh(const std::vector<aabb3f> &boxes,
}
return dst_mesh;
}
-
-struct vcache
-{
- core::array<u32> tris;
- float score;
- s16 cachepos;
- u16 NumActiveTris;
-};
-
-struct tcache
-{
- u16 ind[3];
- float score;
- bool drawn;
-};
-
-const u16 cachesize = 32;
-
-float FindVertexScore(vcache *v)
-{
- const float CacheDecayPower = 1.5f;
- const float LastTriScore = 0.75f;
- const float ValenceBoostScale = 2.0f;
- const float ValenceBoostPower = 0.5f;
- const float MaxSizeVertexCache = 32.0f;
-
- if (v->NumActiveTris == 0)
- {
- // No tri needs this vertex!
- return -1.0f;
- }
-
- float Score = 0.0f;
- int CachePosition = v->cachepos;
- if (CachePosition < 0)
- {
- // Vertex is not in FIFO cache - no score.
- }
- else
- {
- if (CachePosition < 3)
- {
- // This vertex was used in the last triangle,
- // so it has a fixed score.
- Score = LastTriScore;
- }
- else
- {
- // Points for being high in the cache.
- const float Scaler = 1.0f / (MaxSizeVertexCache - 3);
- Score = 1.0f - (CachePosition - 3) * Scaler;
- Score = powf(Score, CacheDecayPower);
- }
- }
-
- // Bonus points for having a low number of tris still to
- // use the vert, so we get rid of lone verts quickly.
- float ValenceBoost = powf(v->NumActiveTris,
- -ValenceBoostPower);
- Score += ValenceBoostScale * ValenceBoost;
-
- return Score;
-}
-
-/*
- A specialized LRU cache for the Forsyth algorithm.
-*/
-
-class f_lru
-{
-
-public:
- f_lru(vcache *v, tcache *t): vc(v), tc(t)
- {
- for (int &i : cache) {
- i = -1;
- }
- }
-
- // Adds this vertex index and returns the highest-scoring triangle index
- u32 add(u16 vert, bool updatetris = false)
- {
- bool found = false;
-
- // Mark existing pos as empty
- for (u16 i = 0; i < cachesize; i++)
- {
- if (cache[i] == vert)
- {
- // Move everything down
- for (u16 j = i; j; j--)
- {
- cache[j] = cache[j - 1];
- }
-
- found = true;
- break;
- }
- }
-
- if (!found)
- {
- if (cache[cachesize-1] != -1)
- vc[cache[cachesize-1]].cachepos = -1;
-
- // Move everything down
- for (u16 i = cachesize - 1; i; i--)
- {
- cache[i] = cache[i - 1];
- }
- }
-
- cache[0] = vert;
-
- u32 highest = 0;
- float hiscore = 0;
-
- if (updatetris)
- {
- // Update cache positions
- for (u16 i = 0; i < cachesize; i++)
- {
- if (cache[i] == -1)
- break;
-
- vc[cache[i]].cachepos = i;
- vc[cache[i]].score = FindVertexScore(&vc[cache[i]]);
- }
-
- // Update triangle scores
- for (int i : cache) {
- if (i == -1)
- break;
-
- const u16 trisize = vc[i].tris.size();
- for (u16 t = 0; t < trisize; t++)
- {
- tcache *tri = &tc[vc[i].tris[t]];
-
- tri->score =
- vc[tri->ind[0]].score +
- vc[tri->ind[1]].score +
- vc[tri->ind[2]].score;
-
- if (tri->score > hiscore)
- {
- hiscore = tri->score;
- highest = vc[i].tris[t];
- }
- }
- }
- }
-
- return highest;
- }
-
-private:
- s32 cache[cachesize];
- vcache *vc;
- tcache *tc;
-};
-
-/**
-Vertex cache optimization according to the Forsyth paper:
-http://home.comcast.net/~tom_forsyth/papers/fast_vert_cache_opt.html
-
-The function is thread-safe (read: you can optimize several meshes in different threads)
-
-\param mesh Source mesh for the operation. */
-scene::IMesh* createForsythOptimizedMesh(const scene::IMesh *mesh)
-{
- if (!mesh)
- return 0;
-
- scene::SMesh *newmesh = new scene::SMesh();
- newmesh->BoundingBox = mesh->getBoundingBox();
-
- const u32 mbcount = mesh->getMeshBufferCount();
-
- for (u32 b = 0; b < mbcount; ++b)
- {
- const scene::IMeshBuffer *mb = mesh->getMeshBuffer(b);
-
- if (mb->getIndexType() != video::EIT_16BIT)
- {
- //os::Printer::log("Cannot optimize a mesh with 32bit indices", ELL_ERROR);
- newmesh->drop();
- return 0;
- }
-
- const u32 icount = mb->getIndexCount();
- const u32 tcount = icount / 3;
- const u32 vcount = mb->getVertexCount();
- const u16 *ind = mb->getIndices();
-
- vcache *vc = new vcache[vcount];
- tcache *tc = new tcache[tcount];
-
- f_lru lru(vc, tc);
-
- // init
- for (u16 i = 0; i < vcount; i++)
- {
- vc[i].score = 0;
- vc[i].cachepos = -1;
- vc[i].NumActiveTris = 0;
- }
-
- // First pass: count how many times a vert is used
- for (u32 i = 0; i < icount; i += 3)
- {
- vc[ind[i]].NumActiveTris++;
- vc[ind[i + 1]].NumActiveTris++;
- vc[ind[i + 2]].NumActiveTris++;
-
- const u32 tri_ind = i/3;
- tc[tri_ind].ind[0] = ind[i];
- tc[tri_ind].ind[1] = ind[i + 1];
- tc[tri_ind].ind[2] = ind[i + 2];
- }
-
- // Second pass: list of each triangle
- for (u32 i = 0; i < tcount; i++)
- {
- vc[tc[i].ind[0]].tris.push_back(i);
- vc[tc[i].ind[1]].tris.push_back(i);
- vc[tc[i].ind[2]].tris.push_back(i);
-
- tc[i].drawn = false;
- }
-
- // Give initial scores
- for (u16 i = 0; i < vcount; i++)
- {
- vc[i].score = FindVertexScore(&vc[i]);
- }
- for (u32 i = 0; i < tcount; i++)
- {
- tc[i].score =
- vc[tc[i].ind[0]].score +
- vc[tc[i].ind[1]].score +
- vc[tc[i].ind[2]].score;
- }
-
- switch(mb->getVertexType())
- {
- case video::EVT_STANDARD:
- {
- video::S3DVertex *v = (video::S3DVertex *) mb->getVertices();
-
- scene::SMeshBuffer *buf = new scene::SMeshBuffer();
- buf->Material = mb->getMaterial();
-
- buf->Vertices.reallocate(vcount);
- buf->Indices.reallocate(icount);
-
- core::map<const video::S3DVertex, const u16> sind; // search index for fast operation
- typedef core::map<const video::S3DVertex, const u16>::Node snode;
-
- // Main algorithm
- u32 highest = 0;
- u32 drawcalls = 0;
- for (;;)
- {
- if (tc[highest].drawn)
- {
- bool found = false;
- float hiscore = 0;
- for (u32 t = 0; t < tcount; t++)
- {
- if (!tc[t].drawn)
- {
- if (tc[t].score > hiscore)
- {
- highest = t;
- hiscore = tc[t].score;
- found = true;
- }
- }
- }
- if (!found)
- break;
- }
-
- // Output the best triangle
- u16 newind = buf->Vertices.size();
-
- snode *s = sind.find(v[tc[highest].ind[0]]);
-
- if (!s)
- {
- buf->Vertices.push_back(v[tc[highest].ind[0]]);
- buf->Indices.push_back(newind);
- sind.insert(v[tc[highest].ind[0]], newind);
- newind++;
- }
- else
- {
- buf->Indices.push_back(s->getValue());
- }
-
- s = sind.find(v[tc[highest].ind[1]]);
-
- if (!s)
- {
- buf->Vertices.push_back(v[tc[highest].ind[1]]);
- buf->Indices.push_back(newind);
- sind.insert(v[tc[highest].ind[1]], newind);
- newind++;
- }
- else
- {
- buf->Indices.push_back(s->getValue());
- }
-
- s = sind.find(v[tc[highest].ind[2]]);
-
- if (!s)
- {
- buf->Vertices.push_back(v[tc[highest].ind[2]]);
- buf->Indices.push_back(newind);
- sind.insert(v[tc[highest].ind[2]], newind);
- }
- else
- {
- buf->Indices.push_back(s->getValue());
- }
-
- vc[tc[highest].ind[0]].NumActiveTris--;
- vc[tc[highest].ind[1]].NumActiveTris--;
- vc[tc[highest].ind[2]].NumActiveTris--;
-
- tc[highest].drawn = true;
-
- for (u16 j : tc[highest].ind) {
- vcache *vert = &vc[j];
- for (u16 t = 0; t < vert->tris.size(); t++)
- {
- if (highest == vert->tris[t])
- {
- vert->tris.erase(t);
- break;
- }
- }
- }
-
- lru.add(tc[highest].ind[0]);
- lru.add(tc[highest].ind[1]);
- highest = lru.add(tc[highest].ind[2], true);
- drawcalls++;
- }
-
- buf->setBoundingBox(mb->getBoundingBox());
- newmesh->addMeshBuffer(buf);
- buf->drop();
- }
- break;
- case video::EVT_2TCOORDS:
- {
- video::S3DVertex2TCoords *v = (video::S3DVertex2TCoords *) mb->getVertices();
-
- scene::SMeshBufferLightMap *buf = new scene::SMeshBufferLightMap();
- buf->Material = mb->getMaterial();
-
- buf->Vertices.reallocate(vcount);
- buf->Indices.reallocate(icount);
-
- core::map<const video::S3DVertex2TCoords, const u16> sind; // search index for fast operation
- typedef core::map<const video::S3DVertex2TCoords, const u16>::Node snode;
-
- // Main algorithm
- u32 highest = 0;
- u32 drawcalls = 0;
- for (;;)
- {
- if (tc[highest].drawn)
- {
- bool found = false;
- float hiscore = 0;
- for (u32 t = 0; t < tcount; t++)
- {
- if (!tc[t].drawn)
- {
- if (tc[t].score > hiscore)
- {
- highest = t;
- hiscore = tc[t].score;
- found = true;
- }
- }
- }
- if (!found)
- break;
- }
-
- // Output the best triangle
- u16 newind = buf->Vertices.size();
-
- snode *s = sind.find(v[tc[highest].ind[0]]);
-
- if (!s)
- {
- buf->Vertices.push_back(v[tc[highest].ind[0]]);
- buf->Indices.push_back(newind);
- sind.insert(v[tc[highest].ind[0]], newind);
- newind++;
- }
- else
- {
- buf->Indices.push_back(s->getValue());
- }
-
- s = sind.find(v[tc[highest].ind[1]]);
-
- if (!s)
- {
- buf->Vertices.push_back(v[tc[highest].ind[1]]);
- buf->Indices.push_back(newind);
- sind.insert(v[tc[highest].ind[1]], newind);
- newind++;
- }
- else
- {
- buf->Indices.push_back(s->getValue());
- }
-
- s = sind.find(v[tc[highest].ind[2]]);
-
- if (!s)
- {
- buf->Vertices.push_back(v[tc[highest].ind[2]]);
- buf->Indices.push_back(newind);
- sind.insert(v[tc[highest].ind[2]], newind);
- }
- else
- {
- buf->Indices.push_back(s->getValue());
- }
-
- vc[tc[highest].ind[0]].NumActiveTris--;
- vc[tc[highest].ind[1]].NumActiveTris--;
- vc[tc[highest].ind[2]].NumActiveTris--;
-
- tc[highest].drawn = true;
-
- for (u16 j : tc[highest].ind) {
- vcache *vert = &vc[j];
- for (u16 t = 0; t < vert->tris.size(); t++)
- {
- if (highest == vert->tris[t])
- {
- vert->tris.erase(t);
- break;
- }
- }
- }
-
- lru.add(tc[highest].ind[0]);
- lru.add(tc[highest].ind[1]);
- highest = lru.add(tc[highest].ind[2]);
- drawcalls++;
- }
-
- buf->setBoundingBox(mb->getBoundingBox());
- newmesh->addMeshBuffer(buf);
- buf->drop();
-
- }
- break;
- case video::EVT_TANGENTS:
- {
- video::S3DVertexTangents *v = (video::S3DVertexTangents *) mb->getVertices();
-
- scene::SMeshBufferTangents *buf = new scene::SMeshBufferTangents();
- buf->Material = mb->getMaterial();
-
- buf->Vertices.reallocate(vcount);
- buf->Indices.reallocate(icount);
-
- core::map<const video::S3DVertexTangents, const u16> sind; // search index for fast operation
- typedef core::map<const video::S3DVertexTangents, const u16>::Node snode;
-
- // Main algorithm
- u32 highest = 0;
- u32 drawcalls = 0;
- for (;;)
- {
- if (tc[highest].drawn)
- {
- bool found = false;
- float hiscore = 0;
- for (u32 t = 0; t < tcount; t++)
- {
- if (!tc[t].drawn)
- {
- if (tc[t].score > hiscore)
- {
- highest = t;
- hiscore = tc[t].score;
- found = true;
- }
- }
- }
- if (!found)
- break;
- }
-
- // Output the best triangle
- u16 newind = buf->Vertices.size();
-
- snode *s = sind.find(v[tc[highest].ind[0]]);
-
- if (!s)
- {
- buf->Vertices.push_back(v[tc[highest].ind[0]]);
- buf->Indices.push_back(newind);
- sind.insert(v[tc[highest].ind[0]], newind);
- newind++;
- }
- else
- {
- buf->Indices.push_back(s->getValue());
- }
-
- s = sind.find(v[tc[highest].ind[1]]);
-
- if (!s)
- {
- buf->Vertices.push_back(v[tc[highest].ind[1]]);
- buf->Indices.push_back(newind);
- sind.insert(v[tc[highest].ind[1]], newind);
- newind++;
- }
- else
- {
- buf->Indices.push_back(s->getValue());
- }
-
- s = sind.find(v[tc[highest].ind[2]]);
-
- if (!s)
- {
- buf->Vertices.push_back(v[tc[highest].ind[2]]);
- buf->Indices.push_back(newind);
- sind.insert(v[tc[highest].ind[2]], newind);
- }
- else
- {
- buf->Indices.push_back(s->getValue());
- }
-
- vc[tc[highest].ind[0]].NumActiveTris--;
- vc[tc[highest].ind[1]].NumActiveTris--;
- vc[tc[highest].ind[2]].NumActiveTris--;
-
- tc[highest].drawn = true;
-
- for (u16 j : tc[highest].ind) {
- vcache *vert = &vc[j];
- for (u16 t = 0; t < vert->tris.size(); t++)
- {
- if (highest == vert->tris[t])
- {
- vert->tris.erase(t);
- break;
- }
- }
- }
-
- lru.add(tc[highest].ind[0]);
- lru.add(tc[highest].ind[1]);
- highest = lru.add(tc[highest].ind[2]);
- drawcalls++;
- }
-
- buf->setBoundingBox(mb->getBoundingBox());
- newmesh->addMeshBuffer(buf);
- buf->drop();
- }
- break;
- }
-
- delete [] vc;
- delete [] tc;
-
- } // for each meshbuffer
-
- return newmesh;
-}
diff --git a/src/client/mesh.h b/src/client/mesh.h
index dbc091a06..1ed753c01 100644
--- a/src/client/mesh.h
+++ b/src/client/mesh.h
@@ -133,10 +133,3 @@ void recalculateBoundingBox(scene::IMesh *src_mesh);
We assume normal to be valid when it's 0 < length < Inf. and not NaN
*/
bool checkMeshNormals(scene::IMesh *mesh);
-
-/*
- Vertex cache optimization according to the Forsyth paper:
- http://home.comcast.net/~tom_forsyth/papers/fast_vert_cache_opt.html
- Ported from irrlicht 1.8
-*/
-scene::IMesh* createForsythOptimizedMesh(const scene::IMesh *mesh);
diff --git a/src/client/mesh_generator_thread.cpp b/src/client/mesh_generator_thread.cpp
index c8d1cba26..5c3f4180b 100644
--- a/src/client/mesh_generator_thread.cpp
+++ b/src/client/mesh_generator_thread.cpp
@@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "client.h"
#include "mapblock.h"
#include "map.h"
+#include "util/directiontables.h"
/*
CachedMapBlockData
@@ -69,7 +70,7 @@ MeshUpdateQueue::~MeshUpdateQueue()
}
}
-void MeshUpdateQueue::addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool urgent)
+bool MeshUpdateQueue::addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool urgent)
{
MutexAutoLock lock(m_mutex);
@@ -81,20 +82,15 @@ void MeshUpdateQueue::addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool
*/
std::vector<CachedMapBlockData*> cached_blocks;
size_t cache_hit_counter = 0;
+ CachedMapBlockData *cached_block = cacheBlock(map, p, FORCE_UPDATE);
+ if (!cached_block->data)
+ return false; // nothing to update
cached_blocks.reserve(3*3*3);
- v3s16 dp;
- for (dp.X = -1; dp.X <= 1; dp.X++)
- for (dp.Y = -1; dp.Y <= 1; dp.Y++)
- for (dp.Z = -1; dp.Z <= 1; dp.Z++) {
- v3s16 p1 = p + dp;
- CachedMapBlockData *cached_block;
- if (dp == v3s16(0, 0, 0))
- cached_block = cacheBlock(map, p1, FORCE_UPDATE);
- else
- cached_block = cacheBlock(map, p1, SKIP_UPDATE_IF_ALREADY_CACHED,
- &cache_hit_counter);
- cached_blocks.push_back(cached_block);
- }
+ cached_blocks.push_back(cached_block);
+ for (v3s16 dp : g_26dirs)
+ cached_blocks.push_back(cacheBlock(map, p + dp,
+ SKIP_UPDATE_IF_ALREADY_CACHED,
+ &cache_hit_counter));
g_profiler->avg("MeshUpdateQueue: MapBlocks from cache [%]",
100.0f * cache_hit_counter / cached_blocks.size());
@@ -116,7 +112,7 @@ void MeshUpdateQueue::addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool
q->ack_block_to_server = true;
q->crack_level = m_client->getCrackLevel();
q->crack_pos = m_client->getCrackPos();
- return;
+ return true;
}
}
@@ -134,6 +130,7 @@ void MeshUpdateQueue::addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool
for (CachedMapBlockData *cached_block : cached_blocks) {
cached_block->refcount_from_queue++;
}
+ return true;
}
// Returned pointer must be deleted
@@ -212,10 +209,7 @@ void MeshUpdateQueue::fillDataFromMapBlockCache(QueuedMeshUpdate *q)
std::time_t t_now = std::time(0);
// Collect data for 3*3*3 blocks from cache
- v3s16 dp;
- for (dp.X = -1; dp.X <= 1; dp.X++)
- for (dp.Y = -1; dp.Y <= 1; dp.Y++)
- for (dp.Z = -1; dp.Z <= 1; dp.Z++) {
+ for (v3s16 dp : g_27dirs) {
v3s16 p = q->p + dp;
CachedMapBlockData *cached_block = getCachedBlock(p);
if (cached_block) {
@@ -272,10 +266,25 @@ MeshUpdateThread::MeshUpdateThread(Client *client):
}
void MeshUpdateThread::updateBlock(Map *map, v3s16 p, bool ack_block_to_server,
- bool urgent)
+ bool urgent, bool update_neighbors)
{
- // Allow the MeshUpdateQueue to do whatever it wants
- m_queue_in.addBlock(map, p, ack_block_to_server, urgent);
+ static thread_local const bool many_neighbors =
+ g_settings->getBool("smooth_lighting")
+ && !g_settings->getFlag("performance_tradeoffs");
+ if (!m_queue_in.addBlock(map, p, ack_block_to_server, urgent)) {
+ warningstream << "Update requested for non-existent block at ("
+ << p.X << ", " << p.Y << ", " << p.Z << ")" << std::endl;
+ return;
+ }
+ if (update_neighbors) {
+ if (many_neighbors) {
+ for (v3s16 dp : g_26dirs)
+ m_queue_in.addBlock(map, p + dp, false, urgent);
+ } else {
+ for (v3s16 dp : g_6dirs)
+ m_queue_in.addBlock(map, p + dp, false, urgent);
+ }
+ }
deferUpdate();
}
diff --git a/src/client/mesh_generator_thread.h b/src/client/mesh_generator_thread.h
index f393e2368..2ef695954 100644
--- a/src/client/mesh_generator_thread.h
+++ b/src/client/mesh_generator_thread.h
@@ -66,7 +66,7 @@ public:
// Caches the block at p and its neighbors (if needed) and queues a mesh
// update for the block at p
- void addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool urgent);
+ bool addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool urgent);
// Returned pointer must be deleted
// Returns NULL if queue is empty
@@ -113,7 +113,8 @@ public:
// Caches the block at p and its neighbors (if needed) and queues a mesh
// update for the block at p
- void updateBlock(Map *map, v3s16 p, bool ack_block_to_server, bool urgent);
+ void updateBlock(Map *map, v3s16 p, bool ack_block_to_server, bool urgent,
+ bool update_neighbors = false);
v3s16 m_camera_offset;
MutexedQueue<MeshUpdateResult> m_queue_out;
diff --git a/src/client/minimap.cpp b/src/client/minimap.cpp
index 3013e1406..9bb9d14e0 100644
--- a/src/client/minimap.cpp
+++ b/src/client/minimap.cpp
@@ -304,7 +304,7 @@ void Minimap::setModeIndex(size_t index)
data->mode = m_modes[index];
m_current_mode_index = index;
} else {
- data->mode = MinimapModeDef{MINIMAP_TYPE_OFF, gettext("Minimap hidden"), 0, 0, ""};
+ data->mode = {MINIMAP_TYPE_OFF, gettext("Minimap hidden"), 0, 0, "", 0};
m_current_mode_index = 0;
}
diff --git a/src/client/render/anaglyph.cpp b/src/client/render/anaglyph.cpp
index 153e77400..2571f7333 100644
--- a/src/client/render/anaglyph.cpp
+++ b/src/client/render/anaglyph.cpp
@@ -30,6 +30,7 @@ void RenderingCoreAnaglyph::drawAll()
void RenderingCoreAnaglyph::setupMaterial(int color_mask)
{
video::SOverrideMaterial &mat = driver->getOverrideMaterial();
+ mat.reset();
mat.Material.ColorMask = color_mask;
mat.EnableFlags = video::EMF_COLOR_MASK;
mat.EnablePasses = scene::ESNRP_SKY_BOX | scene::ESNRP_SOLID |
diff --git a/src/client/render/core.cpp b/src/client/render/core.cpp
index 1028a96e1..9927e2589 100644
--- a/src/client/render/core.cpp
+++ b/src/client/render/core.cpp
@@ -90,8 +90,11 @@ void RenderingCore::draw(video::SColor _skycolor, bool _show_hud, bool _show_min
entity_esp_color = video::SColor(255, entity_color.X, entity_color.Y, entity_color.Z);
player_esp_color = video::SColor(255, player_color.X, player_color.Y, player_color.Z);
- if (shadow_renderer)
+ if (shadow_renderer) {
+ // This is necessary to render shadows for animations correctly
+ smgr->getRootSceneNode()->OnAnimate(device->getTimer()->getTime());
shadow_renderer->update();
+ }
beforeDraw();
drawAll();
diff --git a/src/client/renderingengine.cpp b/src/client/renderingengine.cpp
index 2e4994a40..6ebcc784d 100644
--- a/src/client/renderingengine.cpp
+++ b/src/client/renderingengine.cpp
@@ -116,7 +116,7 @@ RenderingEngine::RenderingEngine(IEventReceiver *receiver)
}
SIrrlichtCreationParameters params = SIrrlichtCreationParameters();
- if (g_logger.getTraceEnabled())
+ if (tracestream)
params.LoggingLevel = irr::ELL_DEBUG;
params.DriverType = driverType;
params.WindowSize = core::dimension2d<u32>(screen_w, screen_h);
@@ -597,7 +597,7 @@ static float calcDisplayDensity()
float RenderingEngine::getDisplayDensity()
{
static float cached_display_density = calcDisplayDensity();
- return cached_display_density;
+ return cached_display_density * g_settings->getFloat("display_density_factor");
}
#elif defined(_WIN32)
@@ -625,14 +625,14 @@ float RenderingEngine::getDisplayDensity()
display_density = calcDisplayDensity(get_video_driver());
cached = true;
}
- return display_density;
+ return display_density * g_settings->getFloat("display_density_factor");
}
#else
float RenderingEngine::getDisplayDensity()
{
- return g_settings->getFloat("screen_dpi") / 96.0;
+ return (g_settings->getFloat("screen_dpi") / 96.0) * g_settings->getFloat("display_density_factor");
}
#endif
diff --git a/src/client/shader.cpp b/src/client/shader.cpp
index dc9e9ae6d..bbb872761 100644
--- a/src/client/shader.cpp
+++ b/src/client/shader.cpp
@@ -40,20 +40,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "client/tile.h"
#include "config.h"
-#if ENABLE_GLES
-#ifdef _IRR_COMPILE_WITH_OGLES1_
-#include <GLES/gl.h>
-#else
-#include <GLES2/gl2.h>
-#endif
-#else
-#ifndef __APPLE__
-#include <GL/gl.h>
-#else
-#define GL_SILENCE_DEPRECATION
-#include <OpenGL/gl.h>
-#endif
-#endif
+#include <mt_opengl.h>
/*
A cache from shader name to shader path
@@ -223,17 +210,24 @@ public:
class MainShaderConstantSetter : public IShaderConstantSetter
{
- CachedVertexShaderSetting<float, 16> m_world_view_proj;
- CachedVertexShaderSetting<float, 16> m_world;
+ CachedVertexShaderSetting<f32, 16> m_world_view_proj;
+ CachedVertexShaderSetting<f32, 16> m_world;
// Shadow-related
- CachedPixelShaderSetting<float, 16> m_shadow_view_proj;
- CachedPixelShaderSetting<float, 3> m_light_direction;
- CachedPixelShaderSetting<float> m_texture_res;
- CachedPixelShaderSetting<float> m_shadow_strength;
- CachedPixelShaderSetting<float> m_time_of_day;
- CachedPixelShaderSetting<float> m_shadowfar;
+ CachedPixelShaderSetting<f32, 16> m_shadow_view_proj;
+ CachedPixelShaderSetting<f32, 3> m_light_direction;
+ CachedPixelShaderSetting<f32> m_texture_res;
+ CachedPixelShaderSetting<f32> m_shadow_strength;
+ CachedPixelShaderSetting<f32> m_time_of_day;
+ CachedPixelShaderSetting<f32> m_shadowfar;
+ CachedPixelShaderSetting<f32, 4> m_camera_pos;
CachedPixelShaderSetting<s32> m_shadow_texture;
+ CachedVertexShaderSetting<f32> m_perspective_bias0_vertex;
+ CachedPixelShaderSetting<f32> m_perspective_bias0_pixel;
+ CachedVertexShaderSetting<f32> m_perspective_bias1_vertex;
+ CachedPixelShaderSetting<f32> m_perspective_bias1_pixel;
+ CachedVertexShaderSetting<f32> m_perspective_zbias_vertex;
+ CachedPixelShaderSetting<f32> m_perspective_zbias_pixel;
#if ENABLE_GLES
// Modelview matrix
@@ -248,18 +242,25 @@ public:
MainShaderConstantSetter() :
m_world_view_proj("mWorldViewProj")
, m_world("mWorld")
-#if ENABLE_GLES
- , m_world_view("mWorldView")
- , m_texture("mTexture")
- , m_normal("mNormal")
-#endif
, m_shadow_view_proj("m_ShadowViewProj")
, m_light_direction("v_LightDirection")
, m_texture_res("f_textureresolution")
, m_shadow_strength("f_shadow_strength")
, m_time_of_day("f_timeofday")
, m_shadowfar("f_shadowfar")
+ , m_camera_pos("CameraPos")
, m_shadow_texture("ShadowMapSampler")
+ , m_perspective_bias0_vertex("xyPerspectiveBias0")
+ , m_perspective_bias0_pixel("xyPerspectiveBias0")
+ , m_perspective_bias1_vertex("xyPerspectiveBias1")
+ , m_perspective_bias1_pixel("xyPerspectiveBias1")
+ , m_perspective_zbias_vertex("zPerspectiveBias")
+ , m_perspective_zbias_pixel("zPerspectiveBias")
+#if ENABLE_GLES
+ , m_world_view("mWorldView")
+ , m_texture("mTexture")
+ , m_normal("mNormal")
+#endif
{}
~MainShaderConstantSetter() = default;
@@ -306,26 +307,40 @@ public:
shadowViewProj *= light.getViewMatrix();
m_shadow_view_proj.set(shadowViewProj.pointer(), services);
- float v_LightDirection[3];
+ f32 v_LightDirection[3];
light.getDirection().getAs3Values(v_LightDirection);
m_light_direction.set(v_LightDirection, services);
- float TextureResolution = light.getMapResolution();
+ f32 TextureResolution = light.getMapResolution();
m_texture_res.set(&TextureResolution, services);
- float ShadowStrength = shadow->getShadowStrength();
+ f32 ShadowStrength = shadow->getShadowStrength();
m_shadow_strength.set(&ShadowStrength, services);
- float timeOfDay = shadow->getTimeOfDay();
+ f32 timeOfDay = shadow->getTimeOfDay();
m_time_of_day.set(&timeOfDay, services);
- float shadowFar = shadow->getMaxShadowFar();
+ f32 shadowFar = shadow->getMaxShadowFar();
m_shadowfar.set(&shadowFar, services);
+ f32 cam_pos[4];
+ shadowViewProj.transformVect(cam_pos, light.getPlayerPos());
+ m_camera_pos.set(cam_pos, services);
+
// I dont like using this hardcoded value. maybe something like
// MAX_TEXTURE - 1 or somthing like that??
s32 TextureLayerID = 3;
m_shadow_texture.set(&TextureLayerID, services);
+
+ f32 bias0 = shadow->getPerspectiveBiasXY();
+ m_perspective_bias0_vertex.set(&bias0, services);
+ m_perspective_bias0_pixel.set(&bias0, services);
+ f32 bias1 = 1.0f - bias0 + 1e-5f;
+ m_perspective_bias1_vertex.set(&bias1, services);
+ m_perspective_bias1_pixel.set(&bias1, services);
+ f32 zbias = shadow->getPerspectiveBiasZ();
+ m_perspective_zbias_vertex.set(&zbias, services);
+ m_perspective_zbias_pixel.set(&zbias, services);
}
}
};
@@ -667,13 +682,19 @@ ShaderInfo ShaderSource::generateShader(const std::string &name,
)";
}
+ // Since this is the first time we're using the GL bindings be extra careful.
+ // This should be removed before 5.6.0 or similar.
+ if (!GL.GetString) {
+ errorstream << "OpenGL procedures were not loaded correctly, "
+ "please open a bug report with details about your platform/OS." << std::endl;
+ abort();
+ }
+
bool use_discard = use_gles;
-#ifdef __unix__
// For renderers that should use discard instead of GL_ALPHA_TEST
- const char* gl_renderer = (const char*)glGetString(GL_RENDERER);
- if (strstr(gl_renderer, "GC7000"))
+ const char *renderer = reinterpret_cast<const char*>(GL.GetString(GL.RENDERER));
+ if (strstr(renderer, "GC7000"))
use_discard = true;
-#endif
if (use_discard) {
if (shaderinfo.base_material == video::EMT_TRANSPARENT_ALPHA_CHANNEL)
shaders_header << "#define USE_DISCARD 1\n";
diff --git a/src/client/shadows/dynamicshadows.cpp b/src/client/shadows/dynamicshadows.cpp
index 0c7eea0e7..ca2d3ce37 100644
--- a/src/client/shadows/dynamicshadows.cpp
+++ b/src/client/shadows/dynamicshadows.cpp
@@ -29,7 +29,6 @@ using m4f = core::matrix4;
void DirectionalLight::createSplitMatrices(const Camera *cam)
{
- float radius;
v3f newCenter;
v3f look = cam->getDirection();
@@ -42,59 +41,38 @@ void DirectionalLight::createSplitMatrices(const Camera *cam)
float sfFar = adjustDist(future_frustum.zFar, cam->getFovY());
// adjusted camera positions
- v3f camPos2 = cam->getPosition();
- v3f camPos = v3f(camPos2.X - cam->getOffset().X * BS,
- camPos2.Y - cam->getOffset().Y * BS,
- camPos2.Z - cam->getOffset().Z * BS);
- camPos += look * sfNear;
- camPos2 += look * sfNear;
+ v3f cam_pos_world = cam->getPosition();
+ v3f cam_pos_scene = v3f(cam_pos_world.X - cam->getOffset().X * BS,
+ cam_pos_world.Y - cam->getOffset().Y * BS,
+ cam_pos_world.Z - cam->getOffset().Z * BS);
+ cam_pos_scene += look * sfNear;
+ cam_pos_world += look * sfNear;
// center point of light frustum
- float end = sfNear + sfFar;
- newCenter = camPos + look * (sfNear + 0.05f * end);
- v3f world_center = camPos2 + look * (sfNear + 0.05f * end);
+ v3f center_scene = cam_pos_scene + look * 0.35 * (sfFar - sfNear);
+ v3f center_world = cam_pos_world + look * 0.35 * (sfFar - sfNear);
// Create a vector to the frustum far corner
const v3f &viewUp = cam->getCameraNode()->getUpVector();
v3f viewRight = look.crossProduct(viewUp);
- v3f farCorner = look + viewRight * tanFovX + viewUp * tanFovY;
+ v3f farCorner = (look + viewRight * tanFovX + viewUp * tanFovY).normalize();
// Compute the frustumBoundingSphere radius
- v3f boundVec = (camPos + farCorner * sfFar) - newCenter;
- radius = boundVec.getLength() * 2.0f;
- // boundVec.getLength();
- float vvolume = radius * 2.0f;
-
- float texelsPerUnit = getMapResolution() / vvolume;
- m4f mTexelScaling;
- mTexelScaling.setScale(texelsPerUnit);
-
- m4f mLookAt, mLookAtInv;
-
- mLookAt.buildCameraLookAtMatrixLH(v3f(0.0f, 0.0f, 0.0f), -direction, v3f(0.0f, 1.0f, 0.0f));
-
- mLookAt *= mTexelScaling;
- mLookAtInv = mLookAt;
- mLookAtInv.makeInverse();
-
- v3f frustumCenter = newCenter;
- mLookAt.transformVect(frustumCenter);
- frustumCenter.X = floorf(frustumCenter.X); // clamp to texel increment
- frustumCenter.Y = floorf(frustumCenter.Y); // clamp to texel increment
- frustumCenter.Z = floorf(frustumCenter.Z);
- mLookAtInv.transformVect(frustumCenter);
- // probar radius multipliacdor en funcion del I, a menor I mas multiplicador
- v3f eye_displacement = direction * vvolume;
+ v3f boundVec = (cam_pos_scene + farCorner * sfFar) - center_scene;
+ float radius = boundVec.getLength();
+ float length = radius * 3.0f;
+ v3f eye_displacement = direction * length;
// we must compute the viewmat with the position - the camera offset
// but the future_frustum position must be the actual world position
- v3f eye = frustumCenter - eye_displacement;
- future_frustum.position = world_center - eye_displacement;
- future_frustum.length = vvolume;
- future_frustum.ViewMat.buildCameraLookAtMatrixLH(eye, frustumCenter, v3f(0.0f, 1.0f, 0.0f));
- future_frustum.ProjOrthMat.buildProjectionMatrixOrthoLH(future_frustum.length,
- future_frustum.length, -future_frustum.length,
- future_frustum.length,false);
+ v3f eye = center_scene - eye_displacement;
+ future_frustum.player = cam_pos_scene;
+ future_frustum.position = center_world - eye_displacement;
+ future_frustum.length = length;
+ future_frustum.radius = radius;
+ future_frustum.ViewMat.buildCameraLookAtMatrixLH(eye, center_scene, v3f(0.0f, 1.0f, 0.0f));
+ future_frustum.ProjOrthMat.buildProjectionMatrixOrthoLH(radius, radius,
+ 0.0f, length, false);
future_frustum.camera_offset = cam->getOffset();
}
@@ -112,6 +90,8 @@ void DirectionalLight::update_frustum(const Camera *cam, Client *client, bool fo
float zNear = cam->getCameraNode()->getNearValue();
float zFar = getMaxFarValue();
+ if (!client->getEnv().getClientMap().getControl().range_all)
+ zFar = MYMIN(zFar, client->getEnv().getClientMap().getControl().wanted_range * BS);
///////////////////////////////////
// update splits near and fars
@@ -122,7 +102,7 @@ void DirectionalLight::update_frustum(const Camera *cam, Client *client, bool fo
createSplitMatrices(cam);
// get the draw list for shadows
client->getEnv().getClientMap().updateDrawListShadow(
- getPosition(), getDirection(), future_frustum.length);
+ getPosition(), getDirection(), future_frustum.radius, future_frustum.length);
should_update_map_shadow = true;
dirty = true;
@@ -132,6 +112,7 @@ void DirectionalLight::update_frustum(const Camera *cam, Client *client, bool fo
v3f rotated_offset;
shadow_frustum.ViewMat.rotateVect(rotated_offset, intToFloat(cam_offset - shadow_frustum.camera_offset, BS));
shadow_frustum.ViewMat.setTranslation(shadow_frustum.ViewMat.getTranslation() + rotated_offset);
+ shadow_frustum.player += intToFloat(shadow_frustum.camera_offset - cam->getOffset(), BS);
shadow_frustum.camera_offset = cam_offset;
}
}
@@ -156,6 +137,16 @@ v3f DirectionalLight::getPosition() const
return shadow_frustum.position;
}
+v3f DirectionalLight::getPlayerPos() const
+{
+ return shadow_frustum.player;
+}
+
+v3f DirectionalLight::getFuturePlayerPos() const
+{
+ return future_frustum.player;
+}
+
const m4f &DirectionalLight::getViewMatrix() const
{
return shadow_frustum.ViewMat;
diff --git a/src/client/shadows/dynamicshadows.h b/src/client/shadows/dynamicshadows.h
index d8be66be8..6e9d96b15 100644
--- a/src/client/shadows/dynamicshadows.h
+++ b/src/client/shadows/dynamicshadows.h
@@ -22,18 +22,21 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "irrlichttypes_bloated.h"
#include <matrix4.h>
#include "util/basic_macros.h"
+#include "constants.h"
class Camera;
class Client;
struct shadowFrustum
{
- float zNear{0.0f};
- float zFar{0.0f};
- float length{0.0f};
+ f32 zNear{0.0f};
+ f32 zFar{0.0f};
+ f32 length{0.0f};
+ f32 radius{0.0f};
core::matrix4 ProjOrthMat;
core::matrix4 ViewMat;
v3f position;
+ v3f player;
v3s16 camera_offset;
};
@@ -56,6 +59,8 @@ public:
return direction;
};
v3f getPosition() const;
+ v3f getPlayerPos() const;
+ v3f getFuturePlayerPos() const;
/// Gets the light's matrices.
const core::matrix4 &getViewMatrix() const;
@@ -64,10 +69,16 @@ public:
const core::matrix4 &getFutureProjectionMatrix() const;
core::matrix4 getViewProjMatrix();
- /// Gets the light's far value.
+ /// Gets the light's maximum far value, i.e. the shadow boundary
f32 getMaxFarValue() const
{
- return farPlane;
+ return farPlane * BS;
+ }
+
+ /// Gets the current far value of the light
+ f32 getFarValue() const
+ {
+ return shadow_frustum.zFar;
}
diff --git a/src/client/shadows/dynamicshadowsrender.cpp b/src/client/shadows/dynamicshadowsrender.cpp
index a913a9290..07dc6daf2 100644
--- a/src/client/shadows/dynamicshadowsrender.cpp
+++ b/src/client/shadows/dynamicshadowsrender.cpp
@@ -18,6 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
*/
#include <cstring>
+#include <cmath>
#include "client/shadows/dynamicshadowsrender.h"
#include "client/shadows/shadowsScreenQuad.h"
#include "client/shadows/shadowsshadercallbacks.h"
@@ -30,12 +31,19 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "profiler.h"
ShadowRenderer::ShadowRenderer(IrrlichtDevice *device, Client *client) :
- m_device(device), m_smgr(device->getSceneManager()),
- m_driver(device->getVideoDriver()), m_client(client), m_current_frame(0)
+ m_smgr(device->getSceneManager()), m_driver(device->getVideoDriver()),
+ m_client(client), m_current_frame(0),
+ m_perspective_bias_xy(0.8), m_perspective_bias_z(0.5)
{
+ (void) m_client;
+
+ m_shadows_supported = true; // assume shadows supported. We will check actual support in initialize
m_shadows_enabled = true;
- m_shadow_strength = g_settings->getFloat("shadow_strength");
+ m_shadow_strength_gamma = g_settings->getFloat("shadow_strength_gamma");
+ if (std::isnan(m_shadow_strength_gamma))
+ m_shadow_strength_gamma = 1.0f;
+ m_shadow_strength_gamma = core::clamp(m_shadow_strength_gamma, 0.1f, 10.0f);
m_shadow_map_max_distance = g_settings->getFloat("shadow_map_max_distance");
@@ -49,8 +57,15 @@ ShadowRenderer::ShadowRenderer(IrrlichtDevice *device, Client *client) :
ShadowRenderer::~ShadowRenderer()
{
+ // call to disable releases dynamically allocated resources
+ disable();
+
if (m_shadow_depth_cb)
delete m_shadow_depth_cb;
+ if (m_shadow_depth_entity_cb)
+ delete m_shadow_depth_entity_cb;
+ if (m_shadow_depth_trans_cb)
+ delete m_shadow_depth_trans_cb;
if (m_shadow_mix_cb)
delete m_shadow_mix_cb;
m_shadow_node_array.clear();
@@ -72,15 +87,25 @@ ShadowRenderer::~ShadowRenderer()
m_driver->removeTexture(shadowMapClientMapFuture);
}
+void ShadowRenderer::disable()
+{
+ m_shadows_enabled = false;
+ if (shadowMapTextureFinal) {
+ m_driver->setRenderTarget(shadowMapTextureFinal, true, true,
+ video::SColor(255, 255, 255, 255));
+ m_driver->setRenderTarget(0, true, true);
+ }
+}
+
void ShadowRenderer::initialize()
{
auto *gpu = m_driver->getGPUProgrammingServices();
// we need glsl
- if (m_shadows_enabled && gpu && m_driver->queryFeature(video::EVDF_ARB_GLSL)) {
+ if (m_shadows_supported && gpu && m_driver->queryFeature(video::EVDF_ARB_GLSL)) {
createShaders();
} else {
- m_shadows_enabled = false;
+ m_shadows_supported = false;
warningstream << "Shadows: GLSL Shader not supported on this system."
<< std::endl;
@@ -94,6 +119,8 @@ void ShadowRenderer::initialize()
m_texture_format_color = m_shadow_map_texture_32bit
? video::ECOLOR_FORMAT::ECF_G32R32F
: video::ECOLOR_FORMAT::ECF_G16R16F;
+
+ m_shadows_enabled &= m_shadows_supported;
}
@@ -118,16 +145,21 @@ size_t ShadowRenderer::getDirectionalLightCount() const
f32 ShadowRenderer::getMaxShadowFar() const
{
if (!m_light_list.empty()) {
- float wanted_range = m_client->getEnv().getClientMap().getWantedRange();
-
- float zMax = m_light_list[0].getMaxFarValue() > wanted_range
- ? wanted_range
- : m_light_list[0].getMaxFarValue();
- return zMax * MAP_BLOCKSIZE;
+ float zMax = m_light_list[0].getFarValue();
+ return zMax;
}
return 0.0f;
}
+void ShadowRenderer::setShadowIntensity(float shadow_intensity)
+{
+ m_shadow_strength = pow(shadow_intensity, 1.0f / m_shadow_strength_gamma);
+ if (m_shadow_strength > 1E-2)
+ enable();
+ else
+ disable();
+}
+
void ShadowRenderer::addNodeToShadowList(
scene::ISceneNode *node, E_SHADOW_MODE shadowMode)
{
@@ -157,6 +189,7 @@ void ShadowRenderer::updateSMTextures()
shadowMapTextureDynamicObjects = getSMTexture(
std::string("shadow_dynamic_") + itos(m_shadow_map_texture_size),
m_texture_format, true);
+ assert(shadowMapTextureDynamicObjects != nullptr);
}
if (!shadowMapClientMap) {
@@ -165,6 +198,7 @@ void ShadowRenderer::updateSMTextures()
std::string("shadow_clientmap_") + itos(m_shadow_map_texture_size),
m_shadow_map_colored ? m_texture_format_color : m_texture_format,
true);
+ assert(shadowMapClientMap != nullptr);
}
if (!shadowMapClientMapFuture && m_map_shadow_update_frames > 1) {
@@ -172,6 +206,7 @@ void ShadowRenderer::updateSMTextures()
std::string("shadow_clientmap_bb_") + itos(m_shadow_map_texture_size),
m_shadow_map_colored ? m_texture_format_color : m_texture_format,
true);
+ assert(shadowMapClientMapFuture != nullptr);
}
if (m_shadow_map_colored && !shadowMapTextureColors) {
@@ -179,6 +214,7 @@ void ShadowRenderer::updateSMTextures()
std::string("shadow_colored_") + itos(m_shadow_map_texture_size),
m_shadow_map_colored ? m_texture_format_color : m_texture_format,
true);
+ assert(shadowMapTextureColors != nullptr);
}
// The merge all shadowmaps texture
@@ -198,6 +234,7 @@ void ShadowRenderer::updateSMTextures()
shadowMapTextureFinal = getSMTexture(
std::string("shadowmap_final_") + itos(m_shadow_map_texture_size),
frt, true);
+ assert(shadowMapTextureFinal != nullptr);
}
if (!m_shadow_node_array.empty() && !m_light_list.empty()) {
@@ -219,9 +256,15 @@ void ShadowRenderer::updateSMTextures()
// Update SM incrementally:
for (DirectionalLight &light : m_light_list) {
// Static shader values.
- m_shadow_depth_cb->MapRes = (f32)m_shadow_map_texture_size;
- m_shadow_depth_cb->MaxFar = (f32)m_shadow_map_max_distance * BS;
-
+ for (auto cb : {m_shadow_depth_cb, m_shadow_depth_entity_cb, m_shadow_depth_trans_cb})
+ if (cb) {
+ cb->MapRes = (f32)m_shadow_map_texture_size;
+ cb->MaxFar = (f32)m_shadow_map_max_distance * BS;
+ cb->PerspectiveBiasXY = getPerspectiveBiasXY();
+ cb->PerspectiveBiasZ = getPerspectiveBiasZ();
+ cb->CameraPos = light.getFuturePlayerPos();
+ }
+
// set the Render Target
// right now we can only render in usual RTT, not
// Depth texture is available in irrlicth maybe we
@@ -274,12 +317,17 @@ void ShadowRenderer::update(video::ITexture *outputTarget)
updateSMTextures();
+ if (shadowMapTextureFinal == nullptr) {
+ return;
+ }
+
if (!m_shadow_node_array.empty() && !m_light_list.empty()) {
for (DirectionalLight &light : m_light_list) {
- // Static shader values.
- m_shadow_depth_cb->MapRes = (f32)m_shadow_map_texture_size;
- m_shadow_depth_cb->MaxFar = (f32)m_shadow_map_max_distance * BS;
+ // Static shader values for entities are set in updateSMTextures
+ // SM texture for entities is not updated incrementally and
+ // must by updated using current player position.
+ m_shadow_depth_entity_cb->CameraPos = light.getPlayerPos();
// render shadows for the n0n-map objects.
m_driver->setRenderTarget(shadowMapTextureDynamicObjects, true,
@@ -311,19 +359,23 @@ void ShadowRenderer::drawDebug()
/* this code just shows shadows textures in screen and in ONLY for debugging*/
#if 0
// this is debug, ignore for now.
- m_driver->draw2DImage(shadowMapTextureFinal,
- core::rect<s32>(0, 50, 128, 128 + 50),
- core::rect<s32>({0, 0}, shadowMapTextureFinal->getSize()));
+ if (shadowMapTextureFinal)
+ m_driver->draw2DImage(shadowMapTextureFinal,
+ core::rect<s32>(0, 50, 128, 128 + 50),
+ core::rect<s32>({0, 0}, shadowMapTextureFinal->getSize()));
- m_driver->draw2DImage(shadowMapClientMap,
- core::rect<s32>(0, 50 + 128, 128, 128 + 50 + 128),
- core::rect<s32>({0, 0}, shadowMapTextureFinal->getSize()));
- m_driver->draw2DImage(shadowMapTextureDynamicObjects,
- core::rect<s32>(0, 128 + 50 + 128, 128,
- 128 + 50 + 128 + 128),
- core::rect<s32>({0, 0}, shadowMapTextureDynamicObjects->getSize()));
+ if (shadowMapClientMap)
+ m_driver->draw2DImage(shadowMapClientMap,
+ core::rect<s32>(0, 50 + 128, 128, 128 + 50 + 128),
+ core::rect<s32>({0, 0}, shadowMapTextureFinal->getSize()));
+
+ if (shadowMapTextureDynamicObjects)
+ m_driver->draw2DImage(shadowMapTextureDynamicObjects,
+ core::rect<s32>(0, 128 + 50 + 128, 128,
+ 128 + 50 + 128 + 128),
+ core::rect<s32>({0, 0}, shadowMapTextureDynamicObjects->getSize()));
- if (m_shadow_map_colored) {
+ if (m_shadow_map_colored && shadowMapTextureColors) {
m_driver->draw2DImage(shadowMapTextureColors,
core::rect<s32>(128,128 + 50 + 128 + 128,
@@ -368,10 +420,6 @@ void ShadowRenderer::renderShadowMap(video::ITexture *target,
material.BackfaceCulling = false;
material.FrontfaceCulling = true;
- material.PolygonOffsetFactor = 4.0f;
- material.PolygonOffsetDirection = video::EPO_BACK;
- //material.PolygonOffsetDepthBias = 1.0f/4.0f;
- //material.PolygonOffsetSlopeScale = -1.f;
if (m_shadow_map_colored && pass != scene::ESNRP_SOLID) {
material.MaterialType = (video::E_MATERIAL_TYPE) depth_shader_trans;
@@ -381,9 +429,6 @@ void ShadowRenderer::renderShadowMap(video::ITexture *target,
material.BlendOperation = video::EBO_MIN;
}
- // FIXME: I don't think this is needed here
- map_node->OnAnimate(m_device->getTimer()->getTime());
-
m_driver->setTransform(video::ETS_WORLD,
map_node->getAbsoluteTransformation());
@@ -429,10 +474,6 @@ void ShadowRenderer::renderShadowObjects(
current_mat.BackfaceCulling = true;
current_mat.FrontfaceCulling = false;
- current_mat.PolygonOffsetFactor = 1.0f/2048.0f;
- current_mat.PolygonOffsetDirection = video::EPO_BACK;
- //current_mat.PolygonOffsetDepthBias = 1.0 * 2.8e-6;
- //current_mat.PolygonOffsetSlopeScale = -1.f;
}
m_driver->setTransform(video::ETS_WORLD,
@@ -473,13 +514,13 @@ void ShadowRenderer::createShaders()
if (depth_shader == -1) {
std::string depth_shader_vs = getShaderPath("shadow_shaders", "pass1_vertex.glsl");
if (depth_shader_vs.empty()) {
- m_shadows_enabled = false;
+ m_shadows_supported = false;
errorstream << "Error shadow mapping vs shader not found." << std::endl;
return;
}
std::string depth_shader_fs = getShaderPath("shadow_shaders", "pass1_fragment.glsl");
if (depth_shader_fs.empty()) {
- m_shadows_enabled = false;
+ m_shadows_supported = false;
errorstream << "Error shadow mapping fs shader not found." << std::endl;
return;
}
@@ -494,7 +535,9 @@ void ShadowRenderer::createShaders()
if (depth_shader == -1) {
// upsi, something went wrong loading shader.
delete m_shadow_depth_cb;
+ m_shadow_depth_cb = nullptr;
m_shadows_enabled = false;
+ m_shadows_supported = false;
errorstream << "Error compiling shadow mapping shader." << std::endl;
return;
}
@@ -510,26 +553,30 @@ void ShadowRenderer::createShaders()
if (depth_shader_entities == -1) {
std::string depth_shader_vs = getShaderPath("shadow_shaders", "pass1_vertex.glsl");
if (depth_shader_vs.empty()) {
- m_shadows_enabled = false;
+ m_shadows_supported = false;
errorstream << "Error shadow mapping vs shader not found." << std::endl;
return;
}
std::string depth_shader_fs = getShaderPath("shadow_shaders", "pass1_fragment.glsl");
if (depth_shader_fs.empty()) {
- m_shadows_enabled = false;
+ m_shadows_supported = false;
errorstream << "Error shadow mapping fs shader not found." << std::endl;
return;
}
+ m_shadow_depth_entity_cb = new ShadowDepthShaderCB();
depth_shader_entities = gpu->addHighLevelShaderMaterial(
readShaderFile(depth_shader_vs).c_str(), "vertexMain",
video::EVST_VS_1_1,
readShaderFile(depth_shader_fs).c_str(), "pixelMain",
- video::EPST_PS_1_2, m_shadow_depth_cb);
+ video::EPST_PS_1_2, m_shadow_depth_entity_cb);
if (depth_shader_entities == -1) {
// upsi, something went wrong loading shader.
+ delete m_shadow_depth_entity_cb;
+ m_shadow_depth_entity_cb = nullptr;
m_shadows_enabled = false;
+ m_shadows_supported = false;
errorstream << "Error compiling shadow mapping shader (dynamic)." << std::endl;
return;
}
@@ -543,14 +590,14 @@ void ShadowRenderer::createShaders()
if (mixcsm_shader == -1) {
std::string depth_shader_vs = getShaderPath("shadow_shaders", "pass2_vertex.glsl");
if (depth_shader_vs.empty()) {
- m_shadows_enabled = false;
+ m_shadows_supported = false;
errorstream << "Error cascade shadow mapping fs shader not found." << std::endl;
return;
}
std::string depth_shader_fs = getShaderPath("shadow_shaders", "pass2_fragment.glsl");
if (depth_shader_fs.empty()) {
- m_shadows_enabled = false;
+ m_shadows_supported = false;
errorstream << "Error cascade shadow mapping fs shader not found." << std::endl;
return;
}
@@ -569,7 +616,7 @@ void ShadowRenderer::createShaders()
// upsi, something went wrong loading shader.
delete m_shadow_mix_cb;
delete m_screen_quad;
- m_shadows_enabled = false;
+ m_shadows_supported = false;
errorstream << "Error compiling cascade shadow mapping shader." << std::endl;
return;
}
@@ -583,13 +630,13 @@ void ShadowRenderer::createShaders()
if (m_shadow_map_colored && depth_shader_trans == -1) {
std::string depth_shader_vs = getShaderPath("shadow_shaders", "pass1_trans_vertex.glsl");
if (depth_shader_vs.empty()) {
- m_shadows_enabled = false;
+ m_shadows_supported = false;
errorstream << "Error shadow mapping vs shader not found." << std::endl;
return;
}
std::string depth_shader_fs = getShaderPath("shadow_shaders", "pass1_trans_fragment.glsl");
if (depth_shader_fs.empty()) {
- m_shadows_enabled = false;
+ m_shadows_supported = false;
errorstream << "Error shadow mapping fs shader not found." << std::endl;
return;
}
@@ -604,8 +651,9 @@ void ShadowRenderer::createShaders()
if (depth_shader_trans == -1) {
// upsi, something went wrong loading shader.
delete m_shadow_depth_trans_cb;
+ m_shadow_depth_trans_cb = nullptr;
m_shadow_map_colored = false;
- m_shadows_enabled = false;
+ m_shadows_supported = false;
errorstream << "Error compiling colored shadow mapping shader." << std::endl;
return;
}
diff --git a/src/client/shadows/dynamicshadowsrender.h b/src/client/shadows/dynamicshadowsrender.h
index e4b3c3e22..0e4ef6b70 100644
--- a/src/client/shadows/dynamicshadowsrender.h
+++ b/src/client/shadows/dynamicshadowsrender.h
@@ -82,13 +82,17 @@ public:
}
- bool is_active() const { return m_shadows_enabled; }
+ bool is_active() const { return m_shadows_enabled && shadowMapTextureFinal != nullptr; }
void setTimeOfDay(float isDay) { m_time_day = isDay; };
+ void setShadowIntensity(float shadow_intensity);
s32 getShadowSamples() const { return m_shadow_samples; }
- float getShadowStrength() const { return m_shadow_strength; }
+ float getShadowStrength() const { return m_shadows_enabled ? m_shadow_strength : 0.0f; }
float getTimeOfDay() const { return m_time_day; }
+ f32 getPerspectiveBiasXY() { return m_perspective_bias_xy; }
+ f32 getPerspectiveBiasZ() { return m_perspective_bias_z; }
+
private:
video::ITexture *getSMTexture(const std::string &shadow_map_name,
video::ECOLOR_FORMAT texture_format,
@@ -101,8 +105,10 @@ private:
void mixShadowsQuad();
void updateSMTextures();
+ void disable();
+ void enable() { m_shadows_enabled = m_shadows_supported; }
+
// a bunch of variables
- IrrlichtDevice *m_device{nullptr};
scene::ISceneManager *m_smgr{nullptr};
video::IVideoDriver *m_driver{nullptr};
Client *m_client{nullptr};
@@ -116,15 +122,19 @@ private:
std::vector<NodeToApply> m_shadow_node_array;
float m_shadow_strength;
+ float m_shadow_strength_gamma;
float m_shadow_map_max_distance;
float m_shadow_map_texture_size;
float m_time_day{0.0f};
int m_shadow_samples;
bool m_shadow_map_texture_32bit;
bool m_shadows_enabled;
+ bool m_shadows_supported;
bool m_shadow_map_colored;
u8 m_map_shadow_update_frames; /* Use this number of frames to update map shaodw */
u8 m_current_frame{0}; /* Current frame */
+ f32 m_perspective_bias_xy;
+ f32 m_perspective_bias_z;
video::ECOLOR_FORMAT m_texture_format{video::ECOLOR_FORMAT::ECF_R16F};
video::ECOLOR_FORMAT m_texture_format_color{video::ECOLOR_FORMAT::ECF_R16G16};
@@ -140,6 +150,7 @@ private:
s32 mixcsm_shader{-1};
ShadowDepthShaderCB *m_shadow_depth_cb{nullptr};
+ ShadowDepthShaderCB *m_shadow_depth_entity_cb{nullptr};
ShadowDepthShaderCB *m_shadow_depth_trans_cb{nullptr};
shadowScreenQuad *m_screen_quad{nullptr};
diff --git a/src/client/shadows/shadowsshadercallbacks.cpp b/src/client/shadows/shadowsshadercallbacks.cpp
index 65a63f49c..b571ea939 100644
--- a/src/client/shadows/shadowsshadercallbacks.cpp
+++ b/src/client/shadows/shadowsshadercallbacks.cpp
@@ -26,6 +26,10 @@ void ShadowDepthShaderCB::OnSetConstants(
core::matrix4 lightMVP = driver->getTransform(video::ETS_PROJECTION);
lightMVP *= driver->getTransform(video::ETS_VIEW);
+
+ f32 cam_pos[4];
+ lightMVP.transformVect(cam_pos, CameraPos);
+
lightMVP *= driver->getTransform(video::ETS_WORLD);
m_light_mvp_setting.set(lightMVP.pointer(), services);
@@ -33,4 +37,12 @@ void ShadowDepthShaderCB::OnSetConstants(
m_max_far_setting.set(&MaxFar, services);
s32 TextureId = 0;
m_color_map_sampler_setting.set(&TextureId, services);
+ f32 bias0 = PerspectiveBiasXY;
+ m_perspective_bias0.set(&bias0, services);
+ f32 bias1 = 1.0f - bias0 + 1e-5f;
+ m_perspective_bias1.set(&bias1, services);
+ f32 zbias = PerspectiveBiasZ;
+ m_perspective_zbias.set(&zbias, services);
+
+ m_cam_pos_setting.set(cam_pos, services);
}
diff --git a/src/client/shadows/shadowsshadercallbacks.h b/src/client/shadows/shadowsshadercallbacks.h
index 3549567c3..87833c0ec 100644
--- a/src/client/shadows/shadowsshadercallbacks.h
+++ b/src/client/shadows/shadowsshadercallbacks.h
@@ -30,7 +30,11 @@ public:
m_light_mvp_setting("LightMVP"),
m_map_resolution_setting("MapResolution"),
m_max_far_setting("MaxFar"),
- m_color_map_sampler_setting("ColorMapSampler")
+ m_color_map_sampler_setting("ColorMapSampler"),
+ m_perspective_bias0("xyPerspectiveBias0"),
+ m_perspective_bias1("xyPerspectiveBias1"),
+ m_perspective_zbias("zPerspectiveBias"),
+ m_cam_pos_setting("CameraPos")
{}
void OnSetMaterial(const video::SMaterial &material) override {}
@@ -39,10 +43,16 @@ public:
s32 userData) override;
f32 MaxFar{2048.0f}, MapRes{1024.0f};
+ f32 PerspectiveBiasXY {0.9f}, PerspectiveBiasZ {0.5f};
+ v3f CameraPos;
private:
CachedVertexShaderSetting<f32, 16> m_light_mvp_setting;
CachedVertexShaderSetting<f32> m_map_resolution_setting;
CachedVertexShaderSetting<f32> m_max_far_setting;
CachedPixelShaderSetting<s32> m_color_map_sampler_setting;
+ CachedVertexShaderSetting<f32> m_perspective_bias0;
+ CachedVertexShaderSetting<f32> m_perspective_bias1;
+ CachedVertexShaderSetting<f32> m_perspective_zbias;
+ CachedVertexShaderSetting<f32, 4> m_cam_pos_setting;
};
diff --git a/src/client/sky.cpp b/src/client/sky.cpp
index 1cf9a4afc..0ab710eee 100644
--- a/src/client/sky.cpp
+++ b/src/client/sky.cpp
@@ -18,21 +18,21 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include <cmath>
#include "sky.h"
-#include "ITexture.h"
-#include "IVideoDriver.h"
-#include "ISceneManager.h"
-#include "ICameraSceneNode.h"
-#include "S3DVertex.h"
+#include <ITexture.h>
+#include <IVideoDriver.h>
+#include <ISceneManager.h>
+#include <ICameraSceneNode.h>
+#include <S3DVertex.h>
#include "client/tile.h"
#include "noise.h" // easeCurve
#include "profiler.h"
#include "util/numeric.h"
-#include <cmath>
#include "client/renderingengine.h"
#include "settings.h"
#include "camera.h" // CameraModes
-#include "config.h"
+
using namespace irr::core;
static video::SMaterial baseMaterial()
@@ -51,7 +51,14 @@ static video::SMaterial baseMaterial()
mat.TextureLayer[0].TextureWrapV = video::ETC_CLAMP_TO_EDGE;
mat.BackfaceCulling = false;
return mat;
-};
+}
+
+static inline void disableTextureFiltering(video::SMaterial &mat)
+{
+ mat.setFlag(video::E_MATERIAL_FLAG::EMF_BILINEAR_FILTER, false);
+ mat.setFlag(video::E_MATERIAL_FLAG::EMF_TRILINEAR_FILTER, false);
+ mat.setFlag(video::E_MATERIAL_FLAG::EMF_ANISOTROPIC_FILTER, false);
+}
Sky::Sky(s32 id, RenderingEngine *rendering_engine, ITextureSource *tsrc, IShaderSource *ssrc) :
scene::ISceneNode(rendering_engine->get_scene_manager()->getRootSceneNode(),
@@ -65,6 +72,11 @@ Sky::Sky(s32 id, RenderingEngine *rendering_engine, ITextureSource *tsrc, IShade
m_enable_shaders = g_settings->getBool("enable_shaders");
+ m_sky_params = SkyboxDefaults::getSkyDefaults();
+ m_sun_params = SkyboxDefaults::getSunDefaults();
+ m_moon_params = SkyboxDefaults::getMoonDefaults();
+ m_star_params = SkyboxDefaults::getStarDefaults();
+
// Create materials
m_materials[0] = baseMaterial();
@@ -73,49 +85,15 @@ Sky::Sky(s32 id, RenderingEngine *rendering_engine, ITextureSource *tsrc, IShade
m_materials[0].ColorMaterial = video::ECM_NONE;
m_materials[1] = baseMaterial();
- //m_materials[1].MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA;
m_materials[1].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
m_materials[2] = baseMaterial();
m_materials[2].setTexture(0, tsrc->getTextureForMesh("sunrisebg.png"));
m_materials[2].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
- //m_materials[2].MaterialType = video::EMT_TRANSPARENT_ADD_COLOR;
-
- // Ensures that sun and moon textures and tonemaps are correct.
- setSkyDefaults();
- m_sun_texture = tsrc->isKnownSourceImage(m_sun_params.texture) ?
- tsrc->getTextureForMesh(m_sun_params.texture) : nullptr;
- m_moon_texture = tsrc->isKnownSourceImage(m_moon_params.texture) ?
- tsrc->getTextureForMesh(m_moon_params.texture) : nullptr;
- m_sun_tonemap = tsrc->isKnownSourceImage(m_sun_params.tonemap) ?
- tsrc->getTexture(m_sun_params.tonemap) : nullptr;
- m_moon_tonemap = tsrc->isKnownSourceImage(m_moon_params.tonemap) ?
- tsrc->getTexture(m_moon_params.tonemap) : nullptr;
- if (m_sun_texture) {
- m_materials[3] = baseMaterial();
- m_materials[3].setTexture(0, m_sun_texture);
- m_materials[3].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
- // Disables texture filtering
- m_materials[3].setFlag(video::E_MATERIAL_FLAG::EMF_BILINEAR_FILTER, false);
- m_materials[3].setFlag(video::E_MATERIAL_FLAG::EMF_TRILINEAR_FILTER, false);
- m_materials[3].setFlag(video::E_MATERIAL_FLAG::EMF_ANISOTROPIC_FILTER, false);
- // Use tonemaps if available
- if (m_sun_tonemap)
- m_materials[3].Lighting = true;
- }
- if (m_moon_texture) {
- m_materials[4] = baseMaterial();
- m_materials[4].setTexture(0, m_moon_texture);
- m_materials[4].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
- // Disables texture filtering
- m_materials[4].setFlag(video::E_MATERIAL_FLAG::EMF_BILINEAR_FILTER, false);
- m_materials[4].setFlag(video::E_MATERIAL_FLAG::EMF_TRILINEAR_FILTER, false);
- m_materials[4].setFlag(video::E_MATERIAL_FLAG::EMF_ANISOTROPIC_FILTER, false);
- // Use tonemaps if available
- if (m_moon_tonemap)
- m_materials[4].Lighting = true;
- }
+ setSunTexture(m_sun_params.texture, m_sun_params.tonemap, tsrc);
+
+ setMoonTexture(m_moon_params.texture, m_moon_params.tonemap, tsrc);
for (int i = 5; i < 11; i++) {
m_materials[i] = baseMaterial();
@@ -130,7 +108,7 @@ Sky::Sky(s32 id, RenderingEngine *rendering_engine, ITextureSource *tsrc, IShade
m_sky_body_orbit_tilt = rangelim(val, 0.0f, 60.0f);
}
- setStarCount(1000, true);
+ setStarCount(1000);
}
void Sky::OnRegisterSceneNode()
@@ -386,20 +364,6 @@ void Sky::update(float time_of_day, float time_brightness,
bool is_dawn = (time_brightness >= 0.20 && time_brightness < 0.35);
- /*
- Development colours
-
- video::SColorf bgcolor_bright_normal_f(170. / 255, 200. / 255, 230. / 255, 1.0);
- video::SColorf bgcolor_bright_dawn_f(0.666, 200. / 255 * 0.7, 230. / 255 * 0.5, 1.0);
- video::SColorf bgcolor_bright_dawn_f(0.666, 0.549, 0.220, 1.0);
- video::SColorf bgcolor_bright_dawn_f(0.666 * 1.2, 0.549 * 1.0, 0.220 * 1.0, 1.0);
- video::SColorf bgcolor_bright_dawn_f(0.666 * 1.2, 0.549 * 1.0, 0.220 * 1.2, 1.0);
-
- video::SColorf cloudcolor_bright_dawn_f(1.0, 0.591, 0.4);
- video::SColorf cloudcolor_bright_dawn_f(1.0, 0.65, 0.44);
- video::SColorf cloudcolor_bright_dawn_f(1.0, 0.7, 0.5);
- */
-
video::SColorf bgcolor_bright_normal_f = m_sky_params.sky_color.day_horizon;
video::SColorf bgcolor_bright_indoor_f = m_sky_params.sky_color.indoors;
video::SColorf bgcolor_bright_dawn_f = m_sky_params.sky_color.dawn_horizon;
@@ -754,33 +718,29 @@ void Sky::setSunTexture(const std::string &sun_texture,
// Ignore matching textures (with modifiers) entirely,
// but lets at least update the tonemap before hand.
m_sun_params.tonemap = sun_tonemap;
- m_sun_tonemap = tsrc->isKnownSourceImage(m_sun_params.tonemap) ?
- tsrc->getTexture(m_sun_params.tonemap) : nullptr;
+ m_sun_tonemap = tsrc->isKnownSourceImage(sun_tonemap) ?
+ tsrc->getTexture(sun_tonemap) : nullptr;
m_materials[3].Lighting = !!m_sun_tonemap;
- if (m_sun_params.texture == sun_texture)
+ if (m_sun_params.texture == sun_texture && !m_first_update)
return;
m_sun_params.texture = sun_texture;
- if (sun_texture != "") {
- // We want to ensure the texture exists first.
- m_sun_texture = tsrc->getTextureForMesh(m_sun_params.texture);
-
- if (m_sun_texture) {
- m_materials[3] = baseMaterial();
- m_materials[3].setTexture(0, m_sun_texture);
- m_materials[3].MaterialType = video::
- EMT_TRANSPARENT_ALPHA_CHANNEL;
- // Disables texture filtering
- m_materials[3].setFlag(
- video::E_MATERIAL_FLAG::EMF_BILINEAR_FILTER, false);
- m_materials[3].setFlag(
- video::E_MATERIAL_FLAG::EMF_TRILINEAR_FILTER, false);
- m_materials[3].setFlag(
- video::E_MATERIAL_FLAG::EMF_ANISOTROPIC_FILTER, false);
- }
- } else {
- m_sun_texture = nullptr;
+ m_sun_texture = nullptr;
+ if (sun_texture == "sun.png") {
+ // Dumb compatibility fix: sun.png transparently falls back to no texture
+ m_sun_texture = tsrc->isKnownSourceImage(sun_texture) ?
+ tsrc->getTexture(sun_texture) : nullptr;
+ } else if (!sun_texture.empty()) {
+ m_sun_texture = tsrc->getTextureForMesh(sun_texture);
+ }
+
+ if (m_sun_texture) {
+ m_materials[3] = baseMaterial();
+ m_materials[3].setTexture(0, m_sun_texture);
+ m_materials[3].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
+ disableTextureFiltering(m_materials[3]);
+ m_materials[3].Lighting = !!m_sun_tonemap;
}
}
@@ -802,40 +762,36 @@ void Sky::setMoonTexture(const std::string &moon_texture,
// Ignore matching textures (with modifiers) entirely,
// but lets at least update the tonemap before hand.
m_moon_params.tonemap = moon_tonemap;
- m_moon_tonemap = tsrc->isKnownSourceImage(m_moon_params.tonemap) ?
- tsrc->getTexture(m_moon_params.tonemap) : nullptr;
+ m_moon_tonemap = tsrc->isKnownSourceImage(moon_tonemap) ?
+ tsrc->getTexture(moon_tonemap) : nullptr;
m_materials[4].Lighting = !!m_moon_tonemap;
- if (m_moon_params.texture == moon_texture)
+ if (m_moon_params.texture == moon_texture && !m_first_update)
return;
m_moon_params.texture = moon_texture;
- if (moon_texture != "") {
- // We want to ensure the texture exists first.
- m_moon_texture = tsrc->getTextureForMesh(m_moon_params.texture);
-
- if (m_moon_texture) {
- m_materials[4] = baseMaterial();
- m_materials[4].setTexture(0, m_moon_texture);
- m_materials[4].MaterialType = video::
- EMT_TRANSPARENT_ALPHA_CHANNEL;
- // Disables texture filtering
- m_materials[4].setFlag(
- video::E_MATERIAL_FLAG::EMF_BILINEAR_FILTER, false);
- m_materials[4].setFlag(
- video::E_MATERIAL_FLAG::EMF_TRILINEAR_FILTER, false);
- m_materials[4].setFlag(
- video::E_MATERIAL_FLAG::EMF_ANISOTROPIC_FILTER, false);
- }
- } else {
- m_moon_texture = nullptr;
+ m_moon_texture = nullptr;
+ if (moon_texture == "moon.png") {
+ // Dumb compatibility fix: moon.png transparently falls back to no texture
+ m_moon_texture = tsrc->isKnownSourceImage(moon_texture) ?
+ tsrc->getTexture(moon_texture) : nullptr;
+ } else if (!moon_texture.empty()) {
+ m_moon_texture = tsrc->getTextureForMesh(moon_texture);
+ }
+
+ if (m_moon_texture) {
+ m_materials[4] = baseMaterial();
+ m_materials[4].setTexture(0, m_moon_texture);
+ m_materials[4].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
+ disableTextureFiltering(m_materials[4]);
+ m_materials[4].Lighting = !!m_moon_tonemap;
}
}
-void Sky::setStarCount(u16 star_count, bool force_update)
+void Sky::setStarCount(u16 star_count)
{
// Allow force updating star count at game init.
- if (m_star_params.count != star_count || force_update) {
+ if (m_star_params.count != star_count || m_first_update) {
m_star_params.count = star_count;
updateStars();
}
@@ -924,16 +880,6 @@ void Sky::addTextureToSkybox(const std::string &texture, int material_id,
m_materials[material_id+5].MaterialType = video::EMT_SOLID;
}
-// To be called once at game init to setup default values.
-void Sky::setSkyDefaults()
-{
- SkyboxDefaults sky_defaults;
- m_sky_params.sky_color = sky_defaults.getSkyColorDefaults();
- m_sun_params = sky_defaults.getSunDefaults();
- m_moon_params = sky_defaults.getMoonDefaults();
- m_star_params = sky_defaults.getStarDefaults();
-}
-
float getWickedTimeOfDay(float time_of_day)
{
float nightlength = 0.415f;
diff --git a/src/client/sky.h b/src/client/sky.h
index 83106453b..e03683f12 100644
--- a/src/client/sky.h
+++ b/src/client/sky.h
@@ -17,6 +17,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#pragma once
+
#include "irrlichttypes_extrabloated.h"
#include <ISceneNode.h>
#include <array>
@@ -25,8 +27,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "shader.h"
#include "skyparams.h"
-#pragma once
-
#define SKY_MATERIAL_COUNT 12
class ITextureSource;
@@ -65,6 +65,7 @@ public:
}
void setSunVisible(bool sun_visible) { m_sun_params.visible = sun_visible; }
+ bool getSunVisible() const { return m_sun_params.visible; }
void setSunTexture(const std::string &sun_texture,
const std::string &sun_tonemap, ITextureSource *tsrc);
void setSunScale(f32 sun_scale) { m_sun_params.scale = sun_scale; }
@@ -72,12 +73,13 @@ public:
void setSunriseTexture(const std::string &sunglow_texture, ITextureSource* tsrc);
void setMoonVisible(bool moon_visible) { m_moon_params.visible = moon_visible; }
+ bool getMoonVisible() const { return m_moon_params.visible; }
void setMoonTexture(const std::string &moon_texture,
const std::string &moon_tonemap, ITextureSource *tsrc);
void setMoonScale(f32 moon_scale) { m_moon_params.scale = moon_scale; }
void setStarsVisible(bool stars_visible) { m_star_params.visible = stars_visible; }
- void setStarCount(u16 star_count, bool force_update);
+ void setStarCount(u16 star_count);
void setStarColor(video::SColor star_color) { m_star_params.starcolor = star_color; }
void setStarScale(f32 star_scale) { m_star_params.scale = star_scale; updateStars(); }
@@ -150,7 +152,7 @@ private:
bool m_visible = true;
// Used when m_visible=false
video::SColor m_fallback_bg_color = video::SColor(255, 255, 255, 255);
- bool m_first_update = true;
+ bool m_first_update = true; // Set before the sky is updated for the first time
float m_time_of_day;
float m_time_brightness;
bool m_sunlight_seen;
@@ -206,7 +208,6 @@ private:
void draw_stars(video::IVideoDriver *driver, float wicked_time_of_day);
void place_sky_body(std::array<video::S3DVertex, 4> &vertices,
float horizon_position, float day_position);
- void setSkyDefaults();
};
// calculates value for sky body positions for the given observed time of day
diff --git a/src/client/tile.cpp b/src/client/tile.cpp
index a31e3aca1..aa78c50f0 100644
--- a/src/client/tile.cpp
+++ b/src/client/tile.cpp
@@ -33,6 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "imagefilters.h"
#include "guiscalingfilter.h"
#include "renderingengine.h"
+#include "util/base64.h"
/*
A cache from texture name to texture path
@@ -762,6 +763,9 @@ void TextureSource::rebuildImagesAndTextures()
// Recreate textures
for (TextureInfo &ti : m_textureinfo_cache) {
+ if (ti.name.empty())
+ continue; // Skip dummy entry
+
video::IImage *img = generateImage(ti.name);
#if ENABLE_GLES
img = Align2Npot2(img, driver);
@@ -1056,6 +1060,45 @@ static std::string unescape_string(const std::string &str, const char esc = '\\'
return out;
}
+void blitBaseImage(video::IImage* &src, video::IImage* &dst)
+{
+ //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
+ // Size of the copied area
+ core::dimension2d<u32> dim = src->getDimension();
+ //core::dimension2d<u32> dim(16,16);
+ // Position to copy the blitted to in the base image
+ core::position2d<s32> pos_to(0,0);
+ // Position to copy the blitted from in the blitted image
+ core::position2d<s32> pos_from(0,0);
+ // Blit
+ /*image->copyToWithAlpha(baseimg, pos_to,
+ core::rect<s32>(pos_from, dim),
+ video::SColor(255,255,255,255),
+ NULL);*/
+
+ core::dimension2d<u32> dim_dst = dst->getDimension();
+ if (dim == dim_dst) {
+ blit_with_alpha(src, dst, pos_from, pos_to, dim);
+ } else if (dim.Width * dim.Height < dim_dst.Width * dim_dst.Height) {
+ // Upscale overlying image
+ video::IImage *scaled_image = RenderingEngine::get_video_driver()->
+ createImage(video::ECF_A8R8G8B8, dim_dst);
+ src->copyToScaling(scaled_image);
+
+ blit_with_alpha(scaled_image, dst, pos_from, pos_to, dim_dst);
+ scaled_image->drop();
+ } else {
+ // Upscale base image
+ video::IImage *scaled_base = RenderingEngine::get_video_driver()->
+ createImage(video::ECF_A8R8G8B8, dim);
+ dst->copyToScaling(scaled_base);
+ dst->drop();
+ dst = scaled_base;
+
+ blit_with_alpha(src, dst, pos_from, pos_to, dim);
+ }
+}
+
bool TextureSource::generateImagePart(std::string part_of_name,
video::IImage *& baseimg)
{
@@ -1066,9 +1109,6 @@ bool TextureSource::generateImagePart(std::string part_of_name,
// Stuff starting with [ are special commands
if (part_of_name.empty() || part_of_name[0] != '[') {
video::IImage *image = m_sourcecache.getOrLoad(part_of_name);
-#if ENABLE_GLES
- image = Align2Npot2(image, driver);
-#endif
if (image == NULL) {
if (!part_of_name.empty()) {
@@ -1119,41 +1159,7 @@ bool TextureSource::generateImagePart(std::string part_of_name,
// Else blit on base.
else
{
- //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
- // Size of the copied area
- core::dimension2d<u32> dim = image->getDimension();
- //core::dimension2d<u32> dim(16,16);
- // Position to copy the blitted to in the base image
- core::position2d<s32> pos_to(0,0);
- // Position to copy the blitted from in the blitted image
- core::position2d<s32> pos_from(0,0);
- // Blit
- /*image->copyToWithAlpha(baseimg, pos_to,
- core::rect<s32>(pos_from, dim),
- video::SColor(255,255,255,255),
- NULL);*/
-
- core::dimension2d<u32> dim_dst = baseimg->getDimension();
- if (dim == dim_dst) {
- blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
- } else if (dim.Width * dim.Height < dim_dst.Width * dim_dst.Height) {
- // Upscale overlying image
- video::IImage *scaled_image = RenderingEngine::get_video_driver()->
- createImage(video::ECF_A8R8G8B8, dim_dst);
- image->copyToScaling(scaled_image);
-
- blit_with_alpha(scaled_image, baseimg, pos_from, pos_to, dim_dst);
- scaled_image->drop();
- } else {
- // Upscale base image
- video::IImage *scaled_base = RenderingEngine::get_video_driver()->
- createImage(video::ECF_A8R8G8B8, dim);
- baseimg->copyToScaling(scaled_base);
- baseimg->drop();
- baseimg = scaled_base;
-
- blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
- }
+ blitBaseImage(image, baseimg);
}
//cleanup
image->drop();
@@ -1781,6 +1787,43 @@ bool TextureSource::generateImagePart(std::string part_of_name,
baseimg->drop();
baseimg = img;
}
+ /*
+ [png:base64
+ Decodes a PNG image in base64 form.
+ Use minetest.encode_png and minetest.encode_base64
+ to produce a valid string.
+ */
+ else if (str_starts_with(part_of_name, "[png:")) {
+ Strfnd sf(part_of_name);
+ sf.next(":");
+ std::string png;
+ {
+ std::string blob = sf.next("");
+ if (!base64_is_valid(blob)) {
+ errorstream << "generateImagePart(): "
+ << "malformed base64 in '[png'"
+ << std::endl;
+ return false;
+ }
+ png = base64_decode(blob);
+ }
+
+ auto *device = RenderingEngine::get_raw_device();
+ auto *fs = device->getFileSystem();
+ auto *vd = device->getVideoDriver();
+ auto *memfile = fs->createMemoryReadFile(png.data(), png.size(), "__temp_png");
+ video::IImage* pngimg = vd->createImageFromFile(memfile);
+ memfile->drop();
+
+ if (baseimg) {
+ blitBaseImage(pngimg, baseimg);
+ } else {
+ core::dimension2d<u32> dim = pngimg->getDimension();
+ baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
+ pngimg->copyTo(baseimg);
+ }
+ pngimg->drop();
+ }
else
{
errorstream << "generateImagePart(): Invalid "
@@ -2183,6 +2226,48 @@ video::ITexture* TextureSource::getNormalTexture(const std::string &name)
return NULL;
}
+namespace {
+ // For more colourspace transformations, see for example
+ // https://github.com/tobspr/GLSL-Color-Spaces/blob/master/ColorSpaces.inc.glsl
+
+ inline float linear_to_srgb_component(float v)
+ {
+ if (v > 0.0031308f)
+ return 1.055f * powf(v, 1.0f / 2.4f) - 0.055f;
+ return 12.92f * v;
+ }
+ inline float srgb_to_linear_component(float v)
+ {
+ if (v > 0.04045f)
+ return powf((v + 0.055f) / 1.055f, 2.4f);
+ return v / 12.92f;
+ }
+
+ v3f srgb_to_linear(const video::SColor &col_srgb)
+ {
+ v3f col(col_srgb.getRed(), col_srgb.getGreen(), col_srgb.getBlue());
+ col /= 255.0f;
+ col.X = srgb_to_linear_component(col.X);
+ col.Y = srgb_to_linear_component(col.Y);
+ col.Z = srgb_to_linear_component(col.Z);
+ return col;
+ }
+
+ video::SColor linear_to_srgb(const v3f &col_linear)
+ {
+ v3f col;
+ col.X = linear_to_srgb_component(col_linear.X);
+ col.Y = linear_to_srgb_component(col_linear.Y);
+ col.Z = linear_to_srgb_component(col_linear.Z);
+ col *= 255.0f;
+ col.X = core::clamp<float>(col.X, 0.0f, 255.0f);
+ col.Y = core::clamp<float>(col.Y, 0.0f, 255.0f);
+ col.Z = core::clamp<float>(col.Z, 0.0f, 255.0f);
+ return video::SColor(0xff, myround(col.X), myround(col.Y),
+ myround(col.Z));
+ }
+}
+
video::SColor TextureSource::getTextureAverageColor(const std::string &name)
{
video::IVideoDriver *driver = RenderingEngine::get_video_driver();
@@ -2197,9 +2282,7 @@ video::SColor TextureSource::getTextureAverageColor(const std::string &name)
return c;
u32 total = 0;
- u32 tR = 0;
- u32 tG = 0;
- u32 tB = 0;
+ v3f col_acc(0, 0, 0);
core::dimension2d<u32> dim = image->getDimension();
u16 step = 1;
if (dim.Width > 16)
@@ -2209,17 +2292,14 @@ video::SColor TextureSource::getTextureAverageColor(const std::string &name)
c = image->getPixel(x,y);
if (c.getAlpha() > 0) {
total++;
- tR += c.getRed();
- tG += c.getGreen();
- tB += c.getBlue();
+ col_acc += srgb_to_linear(c);
}
}
}
image->drop();
if (total > 0) {
- c.setRed(tR / total);
- c.setGreen(tG / total);
- c.setBlue(tB / total);
+ col_acc /= total;
+ c = linear_to_srgb(col_acc);
}
c.setAlpha(255);
return c;
diff --git a/src/client/tile.h b/src/client/tile.h
index fcdc46460..e55a26e56 100644
--- a/src/client/tile.h
+++ b/src/client/tile.h
@@ -195,6 +195,7 @@ struct TileLayer
texture_id == other.texture_id &&
material_type == other.material_type &&
material_flags == other.material_flags &&
+ has_color == other.has_color &&
color == other.color &&
scale == other.scale;
}
@@ -259,6 +260,17 @@ struct TileLayer
&& (material_flags & MATERIAL_FLAG_TILEABLE_VERTICAL);
}
+ bool isTransparent() const
+ {
+ switch (material_type) {
+ case TILE_MATERIAL_ALPHA:
+ case TILE_MATERIAL_LIQUID_TRANSPARENT:
+ case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT:
+ return true;
+ }
+ return false;
+ }
+
// Ordered for size, please do not reorder
video::ITexture *texture = nullptr;
@@ -288,9 +300,9 @@ struct TileLayer
* The color of the tile, or if the tile does not own
* a color then the color of the node owning this tile.
*/
- video::SColor color;
+ video::SColor color = video::SColor(0, 0, 0, 0);
- u8 scale;
+ u8 scale = 1;
};
/*!
@@ -307,7 +319,8 @@ struct TileSpec
for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) {
if (layers[layer] != other.layers[layer])
return false;
- if (!layers[layer].isTileable())
+ // Only non-transparent tiles can be merged into fast faces
+ if (layers[layer].isTransparent() || !layers[layer].isTileable())
return false;
}
return rotation == 0
diff --git a/src/client/wieldmesh.cpp b/src/client/wieldmesh.cpp
index 6beed3f3a..25b343573 100644
--- a/src/client/wieldmesh.cpp
+++ b/src/client/wieldmesh.cpp
@@ -386,6 +386,9 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che
m_colors.emplace_back();
// overlay is white, if present
m_colors.emplace_back(true, video::SColor(0xFFFFFFFF));
+ // initialize the color
+ if (!m_lighting)
+ setColor(video::SColor(0xFFFFFFFF));
return;
}
@@ -457,13 +460,26 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che
material.setFlag(video::EMF_BILINEAR_FILTER, m_bilinear_filter);
material.setFlag(video::EMF_TRILINEAR_FILTER, m_trilinear_filter);
}
+
+ // initialize the color
+ if (!m_lighting)
+ setColor(video::SColor(0xFFFFFFFF));
return;
- } else if (!def.inventory_image.empty()) {
- setExtruded(def.inventory_image, def.inventory_overlay, def.wield_scale,
- tsrc, 1);
+ } else {
+ if (!def.inventory_image.empty()) {
+ setExtruded(def.inventory_image, def.inventory_overlay, def.wield_scale,
+ tsrc, 1);
+ } else {
+ setExtruded("no_texture.png", "", def.wield_scale, tsrc, 1);
+ }
+
m_colors.emplace_back();
// overlay is white, if present
m_colors.emplace_back(true, video::SColor(0xFFFFFFFF));
+
+ // initialize the color
+ if (!m_lighting)
+ setColor(video::SColor(0xFFFFFFFF));
return;
}
@@ -510,8 +526,9 @@ void WieldMeshSceneNode::setNodeLightColor(video::SColor color)
material.EmissiveColor = color;
}
}
-
- setColor(color);
+ else {
+ setColor(color);
+ }
}
void WieldMeshSceneNode::render()
@@ -536,9 +553,14 @@ void WieldMeshSceneNode::changeToMesh(scene::IMesh *mesh)
m_meshnode->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, m_lighting);
m_meshnode->setVisible(true);
- // Add mesh to shadow caster
- if (m_shadow)
+ if (m_shadow) {
+ // Add mesh to shadow caster
m_shadow->addNodeToShadowList(m_meshnode);
+
+ // Set shadow texture
+ for (u32 i = 0; i < m_meshnode->getMaterialCount(); i++)
+ m_meshnode->setMaterialTexture(3, m_shadow->get_texture());
+ }
}
void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result)