aboutsummaryrefslogtreecommitdiff
path: root/src/client
diff options
context:
space:
mode:
Diffstat (limited to 'src/client')
-rw-r--r--src/client/CMakeLists.txt4
-rw-r--r--src/client/camera.cpp90
-rw-r--r--src/client/camera.h12
-rw-r--r--src/client/client.cpp243
-rw-r--r--src/client/client.h30
-rw-r--r--src/client/clientenvironment.cpp65
-rw-r--r--src/client/clientevent.h55
-rw-r--r--src/client/clientlauncher.cpp92
-rw-r--r--src/client/clientlauncher.h2
-rw-r--r--src/client/clientmap.cpp383
-rw-r--r--src/client/clientmap.h42
-rw-r--r--src/client/clientmedia.cpp260
-rw-r--r--src/client/clientmedia.h166
-rw-r--r--src/client/clientobject.h2
-rw-r--r--src/client/clouds.cpp4
-rw-r--r--src/client/clouds.h5
-rw-r--r--src/client/content_cao.cpp147
-rw-r--r--src/client/content_cao.h4
-rw-r--r--src/client/content_mapblock.cpp80
-rw-r--r--src/client/content_mapblock.h7
-rw-r--r--src/client/fontengine.cpp284
-rw-r--r--src/client/fontengine.h22
-rw-r--r--src/client/game.cpp891
-rw-r--r--src/client/game.h6
-rw-r--r--src/client/gameui.cpp68
-rw-r--r--src/client/gameui.h9
-rw-r--r--src/client/guiscalingfilter.cpp7
-rw-r--r--src/client/hud.cpp173
-rw-r--r--src/client/hud.h26
-rw-r--r--src/client/imagefilters.cpp113
-rw-r--r--src/client/imagefilters.h6
-rw-r--r--src/client/inputhandler.cpp47
-rw-r--r--src/client/inputhandler.h95
-rw-r--r--src/client/joystick_controller.cpp80
-rw-r--r--src/client/joystick_controller.h5
-rw-r--r--src/client/keycode.cpp2
-rw-r--r--src/client/keys.h3
-rw-r--r--src/client/localplayer.cpp50
-rw-r--r--src/client/localplayer.h6
-rw-r--r--src/client/mapblock_mesh.cpp24
-rw-r--r--src/client/mesh.cpp597
-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.cpp5
-rw-r--r--src/client/particles.cpp4
-rw-r--r--src/client/render/anaglyph.cpp3
-rw-r--r--src/client/render/core.cpp19
-rw-r--r--src/client/render/core.h5
-rw-r--r--src/client/render/interlaced.cpp4
-rw-r--r--src/client/renderingengine.cpp168
-rw-r--r--src/client/renderingengine.h100
-rw-r--r--src/client/shader.cpp84
-rw-r--r--src/client/shader.h5
-rw-r--r--src/client/shadows/dynamicshadows.cpp165
-rw-r--r--src/client/shadows/dynamicshadows.h109
-rw-r--r--src/client/shadows/dynamicshadowsrender.cpp630
-rw-r--r--src/client/shadows/dynamicshadowsrender.h147
-rw-r--r--src/client/shadows/shadowsScreenQuad.cpp61
-rw-r--r--src/client/shadows/shadowsScreenQuad.h54
-rw-r--r--src/client/shadows/shadowsshadercallbacks.cpp36
-rw-r--r--src/client/shadows/shadowsshadercallbacks.h48
-rw-r--r--src/client/sky.cpp224
-rw-r--r--src/client/sky.h18
-rw-r--r--src/client/sound_openal.cpp12
-rw-r--r--src/client/tile.cpp231
-rw-r--r--src/client/tile.h3
-rw-r--r--src/client/wieldmesh.cpp79
-rw-r--r--src/client/wieldmesh.h3
69 files changed, 4091 insertions, 2398 deletions
diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt
index 140814911..8d058852a 100644
--- a/src/client/CMakeLists.txt
+++ b/src/client/CMakeLists.txt
@@ -58,5 +58,9 @@ set(client_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/sky.cpp
${CMAKE_CURRENT_SOURCE_DIR}/tile.cpp
${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
PARENT_SCOPE
)
diff --git a/src/client/camera.cpp b/src/client/camera.cpp
index 350b685e1..d1f19adb3 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
@@ -36,6 +37,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "constants.h"
#include "fontengine.h"
#include "script/scripting_client.h"
+#include "gettext.h"
#define CAMERA_OFFSET_STEP 200
#define WIELDMESH_OFFSET_X 55.0f
@@ -43,11 +45,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#define WIELDMESH_AMPLITUDE_X 7.0f
#define WIELDMESH_AMPLITUDE_Y 10.0f
-Camera::Camera(MapDrawControl &draw_control, Client *client):
+Camera::Camera(MapDrawControl &draw_control, Client *client, RenderingEngine *rendering_engine):
m_draw_control(draw_control),
m_client(client)
{
- scene::ISceneManager *smgr = RenderingEngine::get_scene_manager();
+ auto smgr = rendering_engine->get_scene_manager();
// note: making the camera node a child of the player node
// would lead to unexpected behaviour, so we don't do that.
m_playernode = smgr->addEmptySceneNode(smgr->getRootSceneNode());
@@ -132,28 +134,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)
{
@@ -186,9 +166,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;
@@ -330,7 +308,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
@@ -343,13 +321,16 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_r
if (player->getParent())
player_position = player->getParent()->getPosition();
- // 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");
- 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);
@@ -378,7 +359,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;
}
@@ -409,41 +391,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
@@ -541,7 +499,7 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_r
m_curr_fov_degrees = rangelim(m_curr_fov_degrees, 1.0f, 160.0f);
// FOV and aspect ratio
- const v2u32 &window_size = RenderingEngine::get_instance()->getWindowSize();
+ const v2u32 &window_size = RenderingEngine::getWindowSize();
m_aspect = (f32) window_size.X / (f32) window_size.Y;
m_fov_y = m_curr_fov_degrees * M_PI / 180.0;
// Increase vertical FOV on lower aspect ratios (<16:10)
@@ -612,6 +570,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;
@@ -664,7 +624,7 @@ void Camera::wield(const ItemStack &item)
void Camera::drawWieldedTool(irr::core::matrix4* translation)
{
// Clear Z buffer so that the wielded tool stays in front of world geometry
- m_wieldmgr->getVideoDriver()->clearZBuffer();
+ m_wieldmgr->getVideoDriver()->clearBuffers(video::ECBF_DEPTH);
// Draw the wielded node (in a separate scene manager)
scene::ICameraSceneNode* cam = m_wieldmgr->getActiveCamera();
diff --git a/src/client/camera.h b/src/client/camera.h
index 6fd8d9aa7..403d6024c 100644
--- a/src/client/camera.h
+++ b/src/client/camera.h
@@ -30,6 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
class LocalPlayer;
struct MapDrawControl;
class Client;
+class RenderingEngine;
class WieldMeshSceneNode;
struct Nametag
@@ -78,7 +79,7 @@ enum CameraMode {CAMERA_MODE_FIRST, CAMERA_MODE_THIRD, CAMERA_MODE_THIRD_FRONT};
class Camera
{
public:
- Camera(MapDrawControl &draw_control, Client *client);
+ Camera(MapDrawControl &draw_control, Client *client, RenderingEngine *rendering_engine);
~Camera();
// Get camera scene node.
@@ -135,16 +136,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();
@@ -217,6 +213,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 ef4a3cdfc..935a82653 100644
--- a/src/client/client.cpp
+++ b/src/client/client.cpp
@@ -50,6 +50,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"
@@ -97,6 +98,7 @@ Client::Client(
NodeDefManager *nodedef,
ISoundManager *sound,
MtEventManager *event,
+ RenderingEngine *rendering_engine,
bool ipv6,
GameUI *game_ui
):
@@ -106,9 +108,10 @@ Client::Client(
m_nodedef(nodedef),
m_sound(sound),
m_event(event),
+ m_rendering_engine(rendering_engine),
m_mesh_update_thread(this),
m_env(
- new ClientMap(this, control, 666),
+ new ClientMap(this, rendering_engine, control, 666),
tsrc, this
),
m_particle_manager(&m_env),
@@ -126,6 +129,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);
}
@@ -133,6 +141,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.
@@ -175,11 +210,7 @@ void Client::loadMods()
// Load "mod" scripts
for (const ModSpec &mod : m_mods) {
- if (!string_allowed(mod.name, MODNAME_ALLOWED_CHARS)) {
- throw ModError("Error loading mod \"" + mod.name +
- "\": Mod name does not follow naming conventions: "
- "Only characters [a-z0-9_] are allowed.");
- }
+ mod.checkAndLog();
scanModIntoMemory(mod.name, mod.path);
}
@@ -208,6 +239,9 @@ void Client::scanModSubfolder(const std::string &mod_name, const std::string &mo
std::string full_path = mod_path + DIR_DELIM + mod_subpath;
std::vector<fs::DirListNode> mod = fs::GetDirListing(full_path);
for (const fs::DirListNode &j : mod) {
+ if (j.name[0] == '.')
+ continue;
+
if (j.dir) {
scanModSubfolder(mod_name, mod_path, mod_subpath + j.name + DIR_DELIM);
continue;
@@ -298,17 +332,17 @@ Client::~Client()
}
// cleanup 3d model meshes on client shutdown
- while (RenderingEngine::get_mesh_cache()->getMeshCount() != 0) {
- scene::IAnimatedMesh *mesh = RenderingEngine::get_mesh_cache()->getMeshByIndex(0);
-
- if (mesh)
- RenderingEngine::get_mesh_cache()->removeMesh(mesh);
- }
+ m_rendering_engine->cleanupMeshCache();
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)
@@ -559,6 +593,29 @@ void Client::step(float dtime)
m_media_downloader = NULL;
}
}
+ {
+ // Acknowledge dynamic media downloads to server
+ std::vector<u32> done;
+ for (auto it = m_pending_media_downloads.begin();
+ it != m_pending_media_downloads.end();) {
+ assert(it->second->isStarted());
+ it->second->step(this);
+ if (it->second->isDone()) {
+ done.emplace_back(it->first);
+
+ it = m_pending_media_downloads.erase(it);
+ } else {
+ it++;
+ }
+
+ if (done.size() == 255) { // maximum in one packet
+ sendHaveMedia(done);
+ done.clear();
+ }
+ }
+ if (!done.empty())
+ sendHaveMedia(done);
+ }
/*
If the server didn't update the inventory in a while, revert
@@ -622,19 +679,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
@@ -660,15 +710,11 @@ bool Client::loadMedia(const std::string &data, const std::string &filename,
TRACESTREAM(<< "Client: Attempting to load image "
<< "file \"" << filename << "\"" << std::endl);
- io::IFileSystem *irrfs = RenderingEngine::get_filesystem();
- video::IVideoDriver *vdrv = RenderingEngine::get_video_driver();
-
- // Silly irrlicht's const-incorrectness
- Buffer<char> data_rw(data.c_str(), data.size());
+ io::IFileSystem *irrfs = m_rendering_engine->get_filesystem();
+ video::IVideoDriver *vdrv = m_rendering_engine->get_video_driver();
- // Create an irrlicht memory file
io::IReadFile *rfile = irrfs->createMemoryReadFile(
- *data_rw, data_rw.getSize(), "_tempreadfile");
+ data.c_str(), data.size(), "_tempreadfile");
FATAL_ERROR_IF(!rfile, "Could not create irrlicht memory file.");
@@ -778,7 +824,8 @@ void Client::request_media(const std::vector<std::string> &file_requests)
Send(&pkt);
infostream << "Client: Sending media request list to server ("
- << file_requests.size() << " files. packet size)" << std::endl;
+ << file_requests.size() << " files, packet size "
+ << pkt.getSize() << ")" << std::endl;
}
void Client::initLocalMapSaving(const Address &address,
@@ -861,7 +908,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;
}
@@ -912,7 +959,7 @@ void writePlayerPos(LocalPlayer *myplayer, ClientMap *clientMap, NetworkPacket *
v3f sf = myplayer->getSpeed() * 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,
@@ -1268,22 +1315,24 @@ void Client::sendPlayerPos()
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 == player->getPosition() &&
player->last_speed == player->getSpeed() &&
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;
@@ -1292,7 +1341,7 @@ void Client::sendPlayerPos()
player->last_speed = player->getSpeed();
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;
@@ -1303,6 +1352,19 @@ void Client::sendPlayerPos()
Send(&pkt);
}
+void Client::sendHaveMedia(const std::vector<u32> &tokens)
+{
+ NetworkPacket pkt(TOSERVER_HAVE_MEDIA, 1 + tokens.size() * 4);
+
+ sanity_check(tokens.size() < 256);
+
+ pkt << static_cast<u8>(tokens.size());
+ for (u32 token : tokens)
+ pkt << token;
+
+ Send(&pkt);
+}
+
void Client::removeNode(v3s16 p)
{
std::map<v3s16, MapBlock*> modified_blocks;
@@ -1416,6 +1478,11 @@ bool Client::updateWieldedItem()
return true;
}
+scene::ISceneManager* Client::getSceneManager()
+{
+ return m_rendering_engine->get_scene_manager();
+}
+
Inventory* Client::getInventory(const InventoryLocation &loc)
{
switch(loc.type){
@@ -1577,20 +1644,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)
@@ -1602,38 +1656,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);
}
ClientEvent *Client::getClientEvent()
@@ -1659,15 +1691,15 @@ float Client::mediaReceiveProgress()
return 1.0; // downloader only exists when not yet done
}
-typedef struct TextureUpdateArgs {
+struct TextureUpdateArgs {
gui::IGUIEnvironment *guienv;
u64 last_time_ms;
u16 last_percent;
const wchar_t* text_base;
ITextureSource *tsrc;
-} TextureUpdateArgs;
+};
-void texture_update_progress(void *args, u32 progress, u32 max_progress)
+void Client::showUpdateProgressTexture(void *args, u32 progress, u32 max_progress)
{
TextureUpdateArgs* targs = (TextureUpdateArgs*) args;
u16 cur_percent = ceil(progress / (double) max_progress * 100.);
@@ -1684,9 +1716,9 @@ void texture_update_progress(void *args, u32 progress, u32 max_progress)
if (do_draw) {
targs->last_time_ms = time_ms;
- std::basic_stringstream<wchar_t> strm;
- strm << targs->text_base << " " << targs->last_percent << "%...";
- RenderingEngine::draw_load_screen(strm.str(), targs->guienv, targs->tsrc, 0,
+ std::wostringstream strm;
+ strm << targs->text_base << L" " << targs->last_percent << L"%...";
+ m_rendering_engine->draw_load_screen(strm.str(), targs->guienv, targs->tsrc, 0,
72 + (u16) ((18. / 100.) * (double) targs->last_percent), true);
}
}
@@ -1707,21 +1739,21 @@ void Client::afterContentReceived()
// Rebuild inherited images and recreate textures
infostream<<"- Rebuilding images and textures"<<std::endl;
- RenderingEngine::draw_load_screen(text, guienv, m_tsrc, 0, 70);
+ m_rendering_engine->draw_load_screen(text, guienv, m_tsrc, 0, 70);
m_tsrc->rebuildImagesAndTextures();
delete[] text;
// Rebuild shaders
infostream<<"- Rebuilding shaders"<<std::endl;
text = wgettext("Rebuilding shaders...");
- RenderingEngine::draw_load_screen(text, guienv, m_tsrc, 0, 71);
+ m_rendering_engine->draw_load_screen(text, guienv, m_tsrc, 0, 71);
m_shsrc->rebuildShaders();
delete[] text;
// Update node aliases
infostream<<"- Updating node aliases"<<std::endl;
text = wgettext("Initializing nodes...");
- RenderingEngine::draw_load_screen(text, guienv, m_tsrc, 0, 72);
+ m_rendering_engine->draw_load_screen(text, guienv, m_tsrc, 0, 72);
m_nodedef->updateAliases(m_itemdef);
for (const auto &path : getTextureDirs()) {
TextureOverrideSource override_source(path + DIR_DELIM + "override.txt");
@@ -1738,9 +1770,9 @@ void Client::afterContentReceived()
tu_args.guienv = guienv;
tu_args.last_time_ms = porting::getTimeMs();
tu_args.last_percent = 0;
- tu_args.text_base = wgettext("Initializing nodes");
+ tu_args.text_base = wgettext("Initializing nodes");
tu_args.tsrc = m_tsrc;
- m_nodedef->updateTextures(this, texture_update_progress, &tu_args);
+ m_nodedef->updateTextures(this, &tu_args);
delete[] tu_args.text_base;
// Start mesh update thread after setting up content definitions
@@ -1754,7 +1786,7 @@ void Client::afterContentReceived()
m_script->on_client_ready(m_env.getLocalPlayer());
text = wgettext("Done!");
- RenderingEngine::draw_load_screen(text, guienv, m_tsrc, 0, 100);
+ m_rendering_engine->draw_load_screen(text, guienv, m_tsrc, 0, 100);
infostream<<"Client::afterContentReceived() done"<<std::endl;
delete[] text;
}
@@ -1772,7 +1804,7 @@ float Client::getCurRate()
void Client::makeScreenshot()
{
- irr::video::IVideoDriver *driver = RenderingEngine::get_video_driver();
+ irr::video::IVideoDriver *driver = m_rendering_engine->get_video_driver();
irr::video::IImage* const raw_image = driver->createScreenShot();
if (!raw_image)
@@ -1914,16 +1946,17 @@ scene::IAnimatedMesh* Client::getMesh(const std::string &filename, bool cache)
// Create the mesh, remove it from cache and return it
// This allows unique vertex colors and other properties for each instance
- Buffer<char> data_rw(data.c_str(), data.size()); // Const-incorrect Irrlicht
- io::IReadFile *rfile = RenderingEngine::get_filesystem()->createMemoryReadFile(
- *data_rw, data_rw.getSize(), filename.c_str());
+ io::IReadFile *rfile = m_rendering_engine->get_filesystem()->createMemoryReadFile(
+ data.c_str(), data.size(), filename.c_str());
FATAL_ERROR_IF(!rfile, "Could not create/open RAM file");
- scene::IAnimatedMesh *mesh = RenderingEngine::get_scene_manager()->getMesh(rfile);
+ scene::IAnimatedMesh *mesh = m_rendering_engine->get_scene_manager()->getMesh(rfile);
rfile->drop();
+ if (!mesh)
+ return nullptr;
mesh->grab();
if (!cache)
- RenderingEngine::get_mesh_cache()->removeMesh(mesh);
+ m_rendering_engine->removeMesh(mesh);
return mesh;
}
@@ -1960,16 +1993,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 2dba1506e..84c85471d 100644
--- a/src/client/client.h
+++ b/src/client/client.h
@@ -45,6 +45,7 @@ struct ClientEvent;
struct MeshMakeData;
struct ChatMessage;
class MapBlockMesh;
+class RenderingEngine;
class IWritableTextureSource;
class IWritableShaderSource;
class IWritableItemDefManager;
@@ -52,6 +53,7 @@ class ISoundManager;
class NodeDefManager;
//class IWritableCraftDefManager;
class ClientMediaDownloader;
+class SingleMediaDownloader;
struct MapDrawControl;
class ModChannelMgr;
class MtEventManager;
@@ -123,6 +125,7 @@ public:
NodeDefManager *nodedef,
ISoundManager *sound,
MtEventManager *event,
+ RenderingEngine *rendering_engine,
bool ipv6,
GameUI *game_ui
);
@@ -243,6 +246,7 @@ public:
void sendDamage(u16 damage);
void sendRespawn();
void sendReady();
+ void sendHaveMedia(const std::vector<u32> &tokens);
ClientEnvironment& getEnv() { return m_env; }
ITextureSource *tsrc() { return getTextureSource(); }
@@ -321,18 +325,22 @@ public:
m_access_denied = true;
m_access_denied_reason = reason;
}
+ inline void setFatalError(const LuaError &e)
+ {
+ setFatalError(std::string("Lua: ") + e.what());
+ }
// Renaming accessDeniedReason to better name could be good as it's used to
// 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()
@@ -345,6 +353,7 @@ public:
float mediaReceiveProgress();
void afterContentReceived();
+ void showUpdateProgressTexture(void *args, u32 progress, u32 max_progress);
float getRTT();
float getCurRate();
@@ -353,6 +362,7 @@ public:
void setCamera(Camera* camera) { m_camera = camera; }
Camera* getCamera () { return m_camera; }
+ scene::ISceneManager *getSceneManager();
bool shouldShowMinimap() const;
@@ -370,15 +380,19 @@ public:
{ 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,
bool from_media_push = false);
+
// Send a request for conventional media transfer
void request_media(const std::vector<std::string> &file_requests);
@@ -469,6 +483,7 @@ private:
NodeDefManager *m_nodedef;
ISoundManager *m_sound;
MtEventManager *m_event;
+ RenderingEngine *m_rendering_engine;
MeshUpdateThread m_mesh_update_thread;
@@ -530,9 +545,13 @@ private:
bool m_activeobjects_received = false;
bool m_mods_loaded = false;
+ std::vector<std::string> m_remote_media_servers;
+ // Media downloader, only exists during init
ClientMediaDownloader *m_media_downloader;
// 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::shared_ptr<SingleMediaDownloader>>> m_pending_media_downloads;
// time_of_day speed approximation for old protocol
bool m_time_of_day_set = false;
@@ -574,6 +593,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 fc7cbe254..448af36c6 100644
--- a/src/client/clientenvironment.cpp
+++ b/src/client/clientenvironment.cpp
@@ -194,32 +194,41 @@ void ClientEnvironment::step(float dtime)
lplayer->applyControl(dtime_part, this);
// Apply physics
- if (!free_move && !is_climbing) {
+ if (!free_move) {
// 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;
}
@@ -235,7 +244,16 @@ void ClientEnvironment::step(float dtime)
&player_collisions);
}
- bool player_immortal = lplayer->getCAO() && lplayer->getCAO()->isImmortal();
+ bool player_immortal = false;
+ f32 player_fall_factor = 1.0f;
+ GenericCAO *playercao = lplayer->getCAO();
+ if (playercao) {
+ player_immortal = playercao->isImmortal();
+ int addp_p = itemgroup_get(playercao->getGroups(),
+ "fall_damage_add_percent");
+ // convert armor group into an usable fall damage factor
+ player_fall_factor = 1.0f + (float)addp_p / 100.0f;
+ }
for (const CollisionInfo &info : player_collisions) {
v3f speed_diff = info.new_speed - info.old_speed;;
@@ -248,17 +266,20 @@ void ClientEnvironment::step(float dtime)
speed_diff.Z = 0;
f32 pre_factor = 1; // 1 hp per node/s
f32 tolerance = BS*14; // 5 without damage
- f32 post_factor = 1; // 1 hp per node/s
if (info.type == COLLISION_NODE) {
const ContentFeatures &f = m_client->ndef()->
get(m_map->getNode(info.node_p));
- // Determine fall damage multiplier
- int addp = itemgroup_get(f.groups, "fall_damage_add_percent");
- pre_factor = 1.0f + (float)addp / 100.0f;
+ // Determine fall damage modifier
+ int addp_n = itemgroup_get(f.groups, "fall_damage_add_percent");
+ // convert node group to an usable fall damage factor
+ f32 node_fall_factor = 1.0f + (float)addp_n / 100.0f;
+ // combine both player fall damage modifiers
+ pre_factor = node_fall_factor * player_fall_factor;
}
float speed = pre_factor * speed_diff.getLength();
- if (speed > tolerance && !player_immortal) {
- f32 damage_f = (speed - tolerance) / BS * post_factor;
+
+ if (speed > tolerance && !player_immortal && pre_factor > 0.0f) {
+ f32 damage_f = (speed - tolerance) / BS;
u16 damage = (u16)MYMIN(damage_f + 0.5, U16_MAX);
if (damage != 0) {
damageLocalPlayer(damage, true);
@@ -340,7 +361,7 @@ u16 ClientEnvironment::addActiveObject(ClientActiveObject *object)
if (!m_ao_manager.registerObject(object))
return 0;
- object->addToScene(m_texturesource);
+ object->addToScene(m_texturesource, m_client->getSceneManager());
// Update lighting immediately
object->updateLight(getDayNightRatio());
diff --git a/src/client/clientevent.h b/src/client/clientevent.h
index 9bd31efce..17d3aedd6 100644
--- a/src/client/clientevent.h
+++ b/src/client/clientevent.h
@@ -52,6 +52,31 @@ enum ClientEventType : u8
CLIENTEVENT_MAX,
};
+struct ClientEventHudAdd
+{
+ u32 server_id;
+ u8 type;
+ v2f pos, scale;
+ std::string name;
+ std::string text, text2;
+ u32 number, item, dir, style;
+ v2f align, offset;
+ v3f world_pos;
+ v2s32 size;
+ s16 z_index;
+};
+
+struct ClientEventHudChange
+{
+ u32 id;
+ HudElementStat stat;
+ v2f v2fdata;
+ std::string sdata;
+ u32 data;
+ v3f v3fdata;
+ v2s32 v2s32data;
+};
+
struct ClientEvent
{
ClientEventType type;
@@ -93,38 +118,12 @@ struct ClientEvent
{
u32 id;
} delete_particlespawner;
- struct
- {
- u32 server_id;
- u8 type;
- v2f *pos;
- std::string *name;
- v2f *scale;
- std::string *text;
- u32 number;
- u32 item;
- u32 dir;
- v2f *align;
- v2f *offset;
- v3f *world_pos;
- v2s32 *size;
- s16 z_index;
- std::string *text2;
- } hudadd;
+ ClientEventHudAdd *hudadd;
struct
{
u32 id;
} hudrm;
- struct
- {
- u32 id;
- HudElementStat stat;
- v2f *v2fdata;
- std::string *sdata;
- u32 data;
- v3f *v3fdata;
- v2s32 *v2s32data;
- } hudchange;
+ ClientEventHudChange *hudchange;
SkyboxParams *set_sky;
struct
{
diff --git a/src/client/clientlauncher.cpp b/src/client/clientlauncher.cpp
index 2bb0bc385..063154316 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,14 +70,14 @@ static void dump_start_data(const GameStartData &data)
ClientLauncher::~ClientLauncher()
{
- delete receiver;
-
delete input;
+ delete receiver;
+
delete g_fontengine;
delete g_gamecallback;
- delete RenderingEngine::get_instance();
+ delete m_rendering_engine;
#if USE_SOUND
g_sound_manager_singleton.reset();
@@ -99,10 +96,6 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args)
init_args(start_data, cmd_args);
- // List video modes if requested
- if (list_video_modes)
- return RenderingEngine::print_video_modes();
-
#if USE_SOUND
if (g_settings->getBool("enable_sound"))
g_sound_manager_singleton = createSoundManagerSingleton();
@@ -120,12 +113,12 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args)
return true;
}
- if (RenderingEngine::get_video_driver() == NULL) {
+ if (m_rendering_engine->get_video_driver() == NULL) {
errorstream << "Could not initialize video driver." << std::endl;
return false;
}
- RenderingEngine::get_instance()->setupTopLevelWindow(PROJECT_NAME_C);
+ m_rendering_engine->setupTopLevelWindow(PROJECT_NAME_C);
/*
This changes the minimum allowed number of vertices in a VBO.
@@ -136,23 +129,23 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args)
// Create game callback for menus
g_gamecallback = new MainGameCallback();
- RenderingEngine::get_instance()->setResizable(true);
+ m_rendering_engine->setResizable(true);
init_input();
- RenderingEngine::get_scene_manager()->getParameters()->
+ m_rendering_engine->get_scene_manager()->getParameters()->
setAttribute(scene::ALLOW_ZWRITE_ON_TRANSPARENT, true);
- guienv = RenderingEngine::get_gui_env();
- skin = RenderingEngine::get_gui_env()->getSkin();
+ guienv = m_rendering_engine->get_gui_env();
+ skin = guienv->getSkin();
skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255, 255, 255, 255));
skin->setColor(gui::EGDC_3D_LIGHT, video::SColor(0, 0, 0, 0));
skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(255, 30, 30, 30));
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));
@@ -166,7 +159,7 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args)
sprite_path.append("checkbox_16.png");
// Texture dimensions should be a power of 2
gui::IGUISpriteBank *sprites = skin->getSpriteBank();
- video::IVideoDriver *driver = RenderingEngine::get_video_driver();
+ video::IVideoDriver *driver = m_rendering_engine->get_video_driver();
video::ITexture *sprite_texture = driver->getTexture(sprite_path.c_str());
if (sprite_texture) {
s32 sprite_id = sprites->addTextureAsSprite(sprite_texture);
@@ -178,15 +171,13 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args)
g_fontengine = new FontEngine(guienv);
FATAL_ERROR_IF(g_fontengine == NULL, "Font engine creation failed.");
-#if (IRRLICHT_VERSION_MAJOR >= 1 && IRRLICHT_VERSION_MINOR >= 8) || IRRLICHT_VERSION_MAJOR >= 2
// Irrlicht 1.8 input colours
skin->setColor(gui::EGDC_EDITABLE, video::SColor(255, 128, 128, 128));
skin->setColor(gui::EGDC_FOCUSED_EDITABLE, video::SColor(255, 96, 134, 49));
-#endif
// Create the menu clouds
if (!g_menucloudsmgr)
- g_menucloudsmgr = RenderingEngine::get_scene_manager()->createNewSceneManager();
+ g_menucloudsmgr = m_rendering_engine->get_scene_manager()->createNewSceneManager();
if (!g_menuclouds)
g_menuclouds = new Clouds(g_menucloudsmgr, -1, rand());
g_menuclouds->setHeight(100.0f);
@@ -214,11 +205,11 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args)
bool retval = true;
bool *kill = porting::signal_handler_killstatus();
- while (RenderingEngine::run() && !*kill &&
+ while (m_rendering_engine->run() && !*kill &&
!g_gamecallback->shutdown_requested) {
// Set the window caption
const wchar_t *text = wgettext("Main Menu");
- RenderingEngine::get_raw_device()->
+ m_rendering_engine->get_raw_device()->
setWindowCaption((utf8_to_wide(PROJECT_NAME_C) +
L" " + utf8_to_wide(g_version_hash) +
L" [" + text + L"]").c_str());
@@ -226,14 +217,14 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args)
try { // This is used for catching disconnects
- RenderingEngine::get_gui_env()->clear();
+ m_rendering_engine->get_gui_env()->clear();
/*
We need some kind of a root node to be able to add
custom gui elements directly on the screen.
Otherwise they won't be automatically drawn.
*/
- guiroot = RenderingEngine::get_gui_env()->addStaticText(L"",
+ guiroot = m_rendering_engine->get_gui_env()->addStaticText(L"",
core::rect<s32>(0, 0, 10000, 10000));
bool game_has_run = launch_game(error_message, reconnect_requested,
@@ -256,36 +247,29 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args)
}
// Break out of menu-game loop to shut down cleanly
- if (!RenderingEngine::get_raw_device()->run() || *kill) {
+ if (!m_rendering_engine->run() || *kill) {
if (!g_settings_path.empty())
g_settings->updateConfigFile(g_settings_path.c_str());
break;
}
- RenderingEngine::get_video_driver()->setTextureCreationFlag(
+ m_rendering_engine->get_video_driver()->setTextureCreationFlag(
video::ETCF_CREATE_MIP_MAPS, g_settings->getBool("mip_map"));
#ifdef HAVE_TOUCHSCREENGUI
- receiver->m_touchscreengui = new TouchScreenGUI(RenderingEngine::get_raw_device(), receiver);
+ receiver->m_touchscreengui = new TouchScreenGUI(m_rendering_engine->get_raw_device(), receiver);
g_touchscreengui = receiver->m_touchscreengui;
#endif
the_game(
kill,
input,
+ m_rendering_engine,
start_data,
error_message,
chat_backend,
&reconnect_requested
);
- RenderingEngine::get_scene_manager()->clear();
-
-#ifdef HAVE_TOUCHSCREENGUI
- delete g_touchscreengui;
- g_touchscreengui = NULL;
- receiver->m_touchscreengui = NULL;
-#endif
-
} //try
catch (con::PeerNotFoundException &e) {
error_message = gettext("Connection error (timed out?)");
@@ -301,6 +285,14 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args)
}
#endif
+ m_rendering_engine->get_scene_manager()->clear();
+
+#ifdef HAVE_TOUCHSCREENGUI
+ delete g_touchscreengui;
+ g_touchscreengui = NULL;
+ receiver->m_touchscreengui = NULL;
+#endif
+
// If no main menu, show error and exit
if (skip_main_menu) {
if (!error_message.empty()) {
@@ -337,8 +329,6 @@ void ClientLauncher::init_args(GameStartData &start_data, const Settings &cmd_ar
if (cmd_args.exists("name"))
start_data.name = cmd_args.get("name");
- list_video_modes = cmd_args.getFlag("videomodes");
-
random_input = g_settings->getBool("random_input")
|| cmd_args.getFlag("random-input");
}
@@ -346,8 +336,8 @@ void ClientLauncher::init_args(GameStartData &start_data, const Settings &cmd_ar
bool ClientLauncher::init_engine()
{
receiver = new MyEventReceiver();
- new RenderingEngine(receiver);
- return RenderingEngine::get_raw_device() != nullptr;
+ m_rendering_engine = new RenderingEngine(receiver);
+ return m_rendering_engine->get_raw_device() != nullptr;
}
void ClientLauncher::init_input()
@@ -364,7 +354,7 @@ void ClientLauncher::init_input()
// Make sure this is called maximum once per
// irrlicht device, otherwise it will give you
// multiple events for the same joystick.
- if (RenderingEngine::get_raw_device()->activateJoysticks(infos)) {
+ if (m_rendering_engine->get_raw_device()->activateJoysticks(infos)) {
infostream << "Joystick support enabled" << std::endl;
joystick_infos.reserve(infos.size());
for (u32 i = 0; i < infos.size(); i++) {
@@ -471,7 +461,7 @@ bool ClientLauncher::launch_game(std::string &error_message,
start_data.address.empty() && !start_data.name.empty();
}
- if (!RenderingEngine::run())
+ if (!m_rendering_engine->run())
return false;
if (!start_data.isSinglePlayer() && start_data.name.empty()) {
@@ -519,8 +509,8 @@ bool ClientLauncher::launch_game(std::string &error_message,
// Load gamespec for required game
start_data.game_spec = findWorldSubgame(worldspec.path);
if (!start_data.game_spec.isValid()) {
- error_message = gettext("Could not find or load game \"")
- + worldspec.gameid + "\"";
+ error_message = gettext("Could not find or load game: ")
+ + worldspec.gameid;
errorstream << error_message << std::endl;
return false;
}
@@ -543,14 +533,14 @@ bool ClientLauncher::launch_game(std::string &error_message,
void ClientLauncher::main_menu(MainMenuData *menudata)
{
bool *kill = porting::signal_handler_killstatus();
- video::IVideoDriver *driver = RenderingEngine::get_video_driver();
+ video::IVideoDriver *driver = m_rendering_engine->get_video_driver();
infostream << "Waiting for other menus" << std::endl;
- while (RenderingEngine::get_raw_device()->run() && !*kill) {
+ while (m_rendering_engine->run() && !*kill) {
if (!isMenuActive())
break;
driver->beginScene(true, true, video::SColor(255, 128, 128, 128));
- RenderingEngine::get_gui_env()->drawAll();
+ m_rendering_engine->get_gui_env()->drawAll();
driver->endScene();
// On some computers framerate doesn't seem to be automatically limited
sleep_ms(25);
@@ -559,14 +549,14 @@ void ClientLauncher::main_menu(MainMenuData *menudata)
// Cursor can be non-visible when coming from the game
#ifndef ANDROID
- RenderingEngine::get_raw_device()->getCursorControl()->setVisible(true);
+ m_rendering_engine->get_raw_device()->getCursorControl()->setVisible(true);
#endif
/* show main menu */
- GUIEngine mymenu(&input->joystick, guiroot, &g_menumgr, menudata, *kill);
+ GUIEngine mymenu(&input->joystick, guiroot, m_rendering_engine, &g_menumgr, menudata, *kill);
/* leave scene manager in a clean state */
- RenderingEngine::get_scene_manager()->clear();
+ m_rendering_engine->get_scene_manager()->clear();
}
void ClientLauncher::speed_tests()
diff --git a/src/client/clientlauncher.h b/src/client/clientlauncher.h
index b280d8e6b..d1fd9a258 100644
--- a/src/client/clientlauncher.h
+++ b/src/client/clientlauncher.h
@@ -46,9 +46,9 @@ private:
void speed_tests();
- bool list_video_modes = false;
bool skip_main_menu = false;
bool random_input = false;
+ RenderingEngine *m_rendering_engine = nullptr;
InputHandler *input = nullptr;
MyEventReceiver *receiver = nullptr;
gui::IGUISkin *skin = nullptr;
diff --git a/src/client/clientmap.cpp b/src/client/clientmap.cpp
index 8e02fea63..1a024e464 100644
--- a/src/client/clientmap.cpp
+++ b/src/client/clientmap.cpp
@@ -64,15 +64,24 @@ void MeshBufListList::add(scene::IMeshBuffer *buf, v3s16 position, u8 layer)
ClientMap::ClientMap(
Client *client,
+ RenderingEngine *rendering_engine,
MapDrawControl &control,
s32 id
):
Map(client),
- scene::ISceneNode(RenderingEngine::get_scene_manager()->getRootSceneNode(),
- RenderingEngine::get_scene_manager(), id),
+ scene::ISceneNode(rendering_engine->get_scene_manager()->getRootSceneNode(),
+ rendering_engine->get_scene_manager(), id),
m_client(client),
- m_control(control)
+ m_rendering_engine(rendering_engine),
+ m_control(control),
+ m_drawlist(MapBlockComparer(v3s16(0,0,0)))
{
+
+ /*
+ * @Liso: Sadly C++ doesn't have introspection, so the only way we have to know
+ * the class is whith a name ;) Name property cames from ISceneNode base class.
+ */
+ Name = "ClientMap";
m_box = aabb3f(-BS*1000000,-BS*1000000,-BS*1000000,
BS*1000000,BS*1000000,BS*1000000);
@@ -114,12 +123,21 @@ void ClientMap::OnRegisterSceneNode()
}
ISceneNode::OnRegisterSceneNode();
+
+ if (!m_added_to_shadow_renderer) {
+ m_added_to_shadow_renderer = true;
+ if (auto shadows = m_rendering_engine->get_shadow_renderer())
+ shadows->addNodeToShadowList(this);
+ }
}
void ClientMap::getBlocksInViewRange(v3s16 cam_pos_nodes,
- v3s16 *p_blocks_min, v3s16 *p_blocks_max)
+ v3s16 *p_blocks_min, v3s16 *p_blocks_max, float range)
{
- v3s16 box_nodes_d = m_control.wanted_range * v3s16(1, 1, 1);
+ if (range <= 0.0f)
+ range = m_control.wanted_range;
+
+ v3s16 box_nodes_d = range * v3s16(1, 1, 1);
// Define p_nodes_min/max as v3s32 because 'cam_pos_nodes -/+ box_nodes_d'
// can exceed the range of v3s16 when a large view range is used near the
// world edges.
@@ -147,6 +165,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();
@@ -161,10 +181,14 @@ 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);
+ // Read the vision range, unless unlimited range is enabled.
+ float range = m_control.range_all ? 1e7 : m_control.wanted_range;
+
// Number of blocks currently loaded by the client
u32 blocks_loaded = 0;
// Number of blocks with mesh in rendering range
@@ -182,6 +206,9 @@ 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
//if (occlusion_culling_enabled && m_control.show_wireframe)
@@ -218,32 +245,34 @@ void ClientMap::updateDrawList()
continue;
}
- float range = 100000 * BS;
- if (!m_control.range_all)
- range = m_control.wanted_range * BS;
+ v3s16 block_coord = block->getPos();
+ v3s16 block_position = block->getPosRelative() + MAP_BLOCKSIZE / 2;
- float d = 0.0;
- if (!isBlockInSight(block->getPos(), camera_position,
- camera_direction, camera_fov, range, &d))
- continue;
+ // First, perform a simple distance check, with a padding of one extra block.
+ if (!m_control.range_all &&
+ block_position.getDistanceFrom(cam_pos_nodes) > range + MAP_BLOCKSIZE)
+ continue; // Out of range, skip.
+ // Keep the block alive as long as it is in range.
+ block->resetUsageTimer();
blocks_in_range_with_mesh++;
- /*
- Occlusion culling
- */
+ // Frustum culling
+ float d = 0.0;
+ if (!isBlockInSight(block_coord, camera_position,
+ camera_direction, camera_fov, range * BS, &d))
+ continue;
+
+ // Occlusion culling
if ((!m_control.range_all && d > m_control.wanted_range * BS) ||
(occlusion_culling_enabled && isBlockOccluded(block, cam_pos_nodes))) {
blocks_occlusion_culled++;
continue;
}
- // This block is in range. Reset usage timer.
- block->resetUsageTimer();
-
// Add to set
block->refGrab();
- m_drawlist[block->getPos()] = block;
+ m_drawlist[block_coord] = block;
sector_blocks_drawn++;
} // foreach sectorblocks
@@ -282,8 +311,6 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
const u32 daynight_ratio = m_client->getEnv().getDayNightRatio();
const v3f camera_position = m_camera_position;
- const v3f camera_direction = m_camera_direction;
- const f32 camera_fov = m_camera_fov;
/*
Get all blocks and draw all visible ones
@@ -300,7 +327,20 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
Draw the selected MapBlocks
*/
- MeshBufListList drawbufs;
+ MeshBufListList grouped_buffers;
+
+ struct DrawDescriptor {
+ v3s16 m_pos;
+ scene::IMeshBuffer *m_buffer;
+ bool m_reuse_material;
+
+ DrawDescriptor(const v3s16 &pos, scene::IMeshBuffer *buffer, bool reuse_material) :
+ m_pos(pos), m_buffer(buffer), m_reuse_material(reuse_material)
+ {}
+ };
+
+ std::vector<DrawDescriptor> draw_order;
+ video::SMaterial previous_material;
for (auto &i : m_drawlist) {
v3s16 block_pos = i.first;
@@ -310,14 +350,12 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
if (!block->mesh)
continue;
- float d = 0.0;
- if (!isBlockInSight(block->getPos(), camera_position,
- camera_direction, camera_fov, 100000 * BS, &d))
- continue;
+ v3f block_pos_r = intToFloat(block->getPosRelative() + MAP_BLOCKSIZE / 2, BS);
+ float d = camera_position.getDistanceFrom(block_pos_r);
+ d = MYMAX(0,d - BLOCK_MAX_RADIUS);
// Mesh animation
if (pass == scene::ESNRP_SOLID) {
- //MutexAutoLock lock(block->mesh_mutex);
MapBlockMesh *mapBlockMesh = block->mesh;
assert(mapBlockMesh);
// Pretty random but this should work somewhat nicely
@@ -338,8 +376,6 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
Get the meshbuffers of the block
*/
{
- //MutexAutoLock lock(block->mesh_mutex);
-
MapBlockMesh *mapBlockMesh = block->mesh;
assert(mapBlockMesh);
@@ -369,42 +405,79 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
material.setFlag(video::EMF_WIREFRAME,
m_control.show_wireframe);
- drawbufs.add(buf, block_pos, layer);
+ if (is_transparent_pass) {
+ // Same comparison as in MeshBufListList
+ bool new_material = material.getTexture(0) != previous_material.getTexture(0) ||
+ material != previous_material;
+
+ draw_order.emplace_back(block_pos, buf, !new_material);
+
+ if (new_material)
+ previous_material = material;
+ }
+ else {
+ 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;
- }
- driver->setMaterial(list.m);
-
- drawcall_count += list.bufs.size();
- for (auto &pair : list.bufs) {
- scene::IMeshBuffer *buf = pair.second;
+ // Render all mesh buffers in order
+ drawcall_count += draw_order.size();
+ for (auto &descriptor : draw_order) {
+ scene::IMeshBuffer *buf = descriptor.m_buffer;
- v3f block_wpos = intToFloat(pair.first * MAP_BLOCKSIZE, BS);
- m.setTranslation(block_wpos - offset);
+ // Check and abort if the machine is swapping a lot
+ if (draw.getTimerTime() > 2000) {
+ infostream << "ClientMap::renderMap(): Rendering took >2s, " <<
+ "returning." << std::endl;
+ return;
+ }
- driver->setTransform(video::ETS_WORLD, m);
- driver->drawMeshBuffer(buf);
- vertex_count += buf->getVertexCount();
+ if (!descriptor.m_reuse_material) {
+ auto &material = buf->getMaterial();
+ // pass the shadow map texture to the buffer texture
+ ShadowRenderer *shadow = m_rendering_engine->get_shadow_renderer();
+ if (shadow && shadow->is_active()) {
+ 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;
+ // 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;
}
+
+ 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();
}
+
g_profiler->avg(prefix + "draw meshes [ms]", draw.stop(true));
// Log only on solid pass because values are the same
@@ -412,8 +485,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,
@@ -606,3 +684,210 @@ void ClientMap::PrintInfo(std::ostream &out)
{
out<<"ClientMap: ";
}
+
+void ClientMap::renderMapShadows(video::IVideoDriver *driver,
+ const video::SMaterial &material, s32 pass, int frame, int total_frames)
+{
+ bool is_transparent_pass = pass != scene::ESNRP_SOLID;
+ std::string prefix;
+ if (is_transparent_pass)
+ prefix = "renderMap(SHADOW TRANS): ";
+ else
+ prefix = "renderMap(SHADOW SOLID): ";
+
+ u32 drawcall_count = 0;
+ u32 vertex_count = 0;
+
+ MeshBufListList drawbufs;
+
+ int count = 0;
+ int low_bound = is_transparent_pass ? 0 : m_drawlist_shadow.size() / total_frames * frame;
+ int high_bound = is_transparent_pass ? m_drawlist_shadow.size() : m_drawlist_shadow.size() / total_frames * (frame + 1);
+
+ // transparent pass should be rendered in one go
+ if (is_transparent_pass && frame != total_frames - 1) {
+ return;
+ }
+
+ for (auto &i : m_drawlist_shadow) {
+ // only process specific part of the list & break early
+ ++count;
+ if (count <= low_bound)
+ continue;
+ if (count > high_bound)
+ break;
+
+ v3s16 block_pos = i.first;
+ MapBlock *block = i.second;
+
+ // If the mesh of the block happened to get deleted, ignore it
+ if (!block->mesh)
+ continue;
+
+ /*
+ Get the meshbuffers of the block
+ */
+ {
+ MapBlockMesh *mapBlockMesh = block->mesh;
+ assert(mapBlockMesh);
+
+ for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) {
+ scene::IMesh *mesh = mapBlockMesh->getMesh(layer);
+ assert(mesh);
+
+ u32 c = mesh->getMeshBufferCount();
+ for (u32 i = 0; i < c; i++) {
+ scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
+
+ 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);
+ }
+ }
+ }
+ }
+
+ TimeTaker draw("Drawing shadow mesh buffers");
+
+ core::matrix4 m; // Model matrix
+ v3f offset = intToFloat(m_camera_offset, BS);
+
+ // 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();
+ }
+
+ drawcall_count += list.bufs.size();
+ }
+ }
+
+ // 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);
+}
+
+/*
+ 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)
+{
+ 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 p_blocks_min;
+ v3s16 p_blocks_max;
+ getBlocksInViewRange(cam_pos_nodes, &p_blocks_min, &p_blocks_max, shadow_range);
+
+ std::vector<v2s16> blocks_in_range;
+
+ for (auto &i : m_drawlist_shadow) {
+ MapBlock *block = i.second;
+ block->refDrop();
+ }
+ 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
+ u32 blocks_in_range_with_mesh = 0;
+ // Number of blocks occlusion culled
+ u32 blocks_occlusion_culled = 0;
+
+ for (auto &sector_it : m_sectors) {
+ MapSector *sector = sector_it.second;
+ if (!sector)
+ continue;
+ blocks_loaded += sector->size();
+
+ MapBlockVect sectorblocks;
+ sector->getBlocks(sectorblocks);
+
+ /*
+ Loop through blocks in sector
+ */
+ for (MapBlock *block : sectorblocks) {
+ if (!block->mesh) {
+ // Ignore if mesh doesn't exist
+ continue;
+ }
+
+ float range = shadow_range;
+
+ float d = 0.0;
+ if (!isBlockInSight(block->getPos(), camera_position,
+ camera_direction, camera_fov, range, &d))
+ 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();
+
+ // Add to set
+ if (m_drawlist_shadow.find(block->getPos()) == m_drawlist_shadow.end()) {
+ block->refGrab();
+ m_drawlist_shadow[block->getPos()] = block;
+ }
+ }
+ }
+
+ g_profiler->avg("SHADOW MapBlock meshes in range [#]", blocks_in_range_with_mesh);
+ g_profiler->avg("SHADOW MapBlocks occlusion culled [#]", blocks_occlusion_culled);
+ g_profiler->avg("SHADOW MapBlocks drawn [#]", m_drawlist_shadow.size());
+ g_profiler->avg("SHADOW MapBlocks loaded [#]", blocks_loaded);
+}
diff --git a/src/client/clientmap.h b/src/client/clientmap.h
index 57cc4427e..b4dc42395 100644
--- a/src/client/clientmap.h
+++ b/src/client/clientmap.h
@@ -68,6 +68,7 @@ class ClientMap : public Map, public scene::ISceneNode
public:
ClientMap(
Client *client,
+ RenderingEngine *rendering_engine,
MapDrawControl &control,
s32 id
);
@@ -86,10 +87,18 @@ public:
void updateCamera(const v3f &pos, const v3f &dir, f32 fov, const v3s16 &offset)
{
+ v3s16 previous_block = getContainerPos(floatToInt(m_camera_position, BS) + m_camera_offset, MAP_BLOCKSIZE);
+
m_camera_position = pos;
m_camera_direction = dir;
m_camera_fov = fov;
m_camera_offset = offset;
+
+ v3s16 current_block = getContainerPos(floatToInt(m_camera_position, BS) + m_camera_offset, MAP_BLOCKSIZE);
+
+ // reorder the blocks when camera crosses block boundary
+ if (previous_block != current_block)
+ m_needs_update_drawlist = true;
}
/*
@@ -118,10 +127,16 @@ public:
}
void getBlocksInViewRange(v3s16 cam_pos_nodes,
- v3s16 *p_blocks_min, v3s16 *p_blocks_max);
+ 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);
+ // 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,
+ const video::SMaterial &material, s32 pass, int frame, int total_frames);
+
int getBackgroundBrightness(float max_d, u32 daylight_factor,
int oldvalue, bool *sunlight_seen_result);
@@ -131,9 +146,29 @@ public:
virtual void PrintInfo(std::ostream &out);
const MapDrawControl & getControl() const { return m_control; }
+ f32 getWantedRange() const { return m_control.wanted_range; }
f32 getCameraFov() const { return m_camera_fov; }
+
private:
+ // 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;
+ };
+
Client *m_client;
+ RenderingEngine *m_rendering_engine;
aabb3f m_box = aabb3f(-BS * 1000000, -BS * 1000000, -BS * 1000000,
BS * 1000000, BS * 1000000, BS * 1000000);
@@ -145,11 +180,14 @@ private:
f32 m_camera_fov = M_PI;
v3s16 m_camera_offset;
- 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;
bool m_cache_trilinear_filter;
bool m_cache_bilinear_filter;
bool m_cache_anistropic_filter;
+ bool m_added_to_shadow_renderer{false};
};
diff --git a/src/client/clientmedia.cpp b/src/client/clientmedia.cpp
index c4c08c05d..6c5d4a8bf 100644
--- a/src/client/clientmedia.cpp
+++ b/src/client/clientmedia.cpp
@@ -49,7 +49,6 @@ bool clientMediaUpdateCache(const std::string &raw_hash, const std::string &file
*/
ClientMediaDownloader::ClientMediaDownloader():
- m_media_cache(getMediaCacheDir()),
m_httpfetch_caller(HTTPFETCH_DISCARD)
{
}
@@ -66,6 +65,12 @@ ClientMediaDownloader::~ClientMediaDownloader()
delete remote;
}
+bool ClientMediaDownloader::loadMedia(Client *client, const std::string &data,
+ const std::string &name)
+{
+ return client->loadMedia(data, name);
+}
+
void ClientMediaDownloader::addFile(const std::string &name, const std::string &sha1)
{
assert(!m_initial_step_done); // pre-condition
@@ -105,7 +110,7 @@ void ClientMediaDownloader::addRemoteServer(const std::string &baseurl)
{
assert(!m_initial_step_done); // pre-condition
- #ifdef USE_CURL
+#ifdef USE_CURL
if (g_settings->getBool("enable_remote_media_server")) {
infostream << "Client: Adding remote server \""
@@ -117,13 +122,13 @@ void ClientMediaDownloader::addRemoteServer(const std::string &baseurl)
m_remotes.push_back(remote);
}
- #else
+#else
infostream << "Client: Ignoring remote server \""
<< baseurl << "\" because cURL support is not compiled in"
<< std::endl;
- #endif
+#endif
}
void ClientMediaDownloader::step(Client *client)
@@ -172,36 +177,21 @@ void ClientMediaDownloader::initialStep(Client *client)
// Check media cache
m_uncached_count = m_files.size();
for (auto &file_it : m_files) {
- std::string name = file_it.first;
+ const std::string &name = file_it.first;
FileStatus *filestatus = file_it.second;
const std::string &sha1 = filestatus->sha1;
- std::ostringstream tmp_os(std::ios_base::binary);
- bool found_in_cache = m_media_cache.load(hex_encode(sha1), tmp_os);
-
- // If found in cache, try to load it from there
- if (found_in_cache) {
- bool success = checkAndLoad(name, sha1,
- tmp_os.str(), true, client);
- if (success) {
- filestatus->received = true;
- m_uncached_count--;
- }
+ if (tryLoadFromCache(name, sha1, client)) {
+ filestatus->received = true;
+ m_uncached_count--;
}
}
assert(m_uncached_received_count == 0);
// Create the media cache dir if we are likely to write to it
- if (m_uncached_count != 0) {
- bool did = fs::CreateAllDirs(getMediaCacheDir());
- if (!did) {
- errorstream << "Client: "
- << "Could not create media cache directory: "
- << getMediaCacheDir()
- << std::endl;
- }
- }
+ if (m_uncached_count != 0)
+ createCacheDirs();
// If we found all files in the cache, report this fact to the server.
// If the server reported no remote servers, immediately start
@@ -216,7 +206,6 @@ void ClientMediaDownloader::initialStep(Client *client)
// This is the first time we use httpfetch, so alloc a caller ID
m_httpfetch_caller = httpfetch_caller_alloc();
- m_httpfetch_timeout = g_settings->getS32("curl_timeout");
// Set the active fetch limit to curl_parallel_limit or 84,
// whichever is greater. This gives us some leeway so that
@@ -258,8 +247,6 @@ void ClientMediaDownloader::initialStep(Client *client)
remote->baseurl + MTHASHSET_FILE_NAME;
fetch_request.caller = m_httpfetch_caller;
fetch_request.request_id = m_httpfetch_next_id; // == i
- fetch_request.timeout = m_httpfetch_timeout;
- fetch_request.connect_timeout = m_httpfetch_timeout;
fetch_request.method = HTTP_POST;
fetch_request.raw_data = required_hash_set;
fetch_request.extra_headers.emplace_back(
@@ -304,8 +291,7 @@ void ClientMediaDownloader::remoteHashSetReceived(
// available on this server, add this server
// to the available_remotes array
- for(std::map<std::string, FileStatus*>::iterator
- it = m_files.upper_bound(m_name_bound);
+ for(auto it = m_files.upper_bound(m_name_bound);
it != m_files.end(); ++it) {
FileStatus *f = it->second;
if (!f->received && sha1_set.count(f->sha1))
@@ -331,8 +317,7 @@ void ClientMediaDownloader::remoteMediaReceived(
std::string name;
{
- std::unordered_map<unsigned long, std::string>::iterator it =
- m_remote_file_transfers.find(fetch_result.request_id);
+ auto it = m_remote_file_transfers.find(fetch_result.request_id);
assert(it != m_remote_file_transfers.end());
name = it->second;
m_remote_file_transfers.erase(it);
@@ -401,8 +386,7 @@ void ClientMediaDownloader::startRemoteMediaTransfers()
{
bool changing_name_bound = true;
- for (std::map<std::string, FileStatus*>::iterator
- files_iter = m_files.upper_bound(m_name_bound);
+ for (auto files_iter = m_files.upper_bound(m_name_bound);
files_iter != m_files.end(); ++files_iter) {
// Abort if active fetch limit is exceeded
@@ -432,9 +416,8 @@ void ClientMediaDownloader::startRemoteMediaTransfers()
fetch_request.url = url;
fetch_request.caller = m_httpfetch_caller;
fetch_request.request_id = m_httpfetch_next_id;
- fetch_request.timeout = 0; // no data timeout!
- fetch_request.connect_timeout =
- m_httpfetch_timeout;
+ fetch_request.timeout =
+ g_settings->getS32("curl_file_download_timeout");
httpfetch_async(fetch_request);
m_remote_file_transfers.insert(std::make_pair(
@@ -481,19 +464,18 @@ void ClientMediaDownloader::startConventionalTransfers(Client *client)
}
}
-void ClientMediaDownloader::conventionalTransferDone(
+bool ClientMediaDownloader::conventionalTransferDone(
const std::string &name,
const std::string &data,
Client *client)
{
// Check that file was announced
- std::map<std::string, FileStatus*>::iterator
- file_iter = m_files.find(name);
+ auto file_iter = m_files.find(name);
if (file_iter == m_files.end()) {
errorstream << "Client: server sent media file that was"
<< "not announced, ignoring it: \"" << name << "\""
<< std::endl;
- return;
+ return false;
}
FileStatus *filestatus = file_iter->second;
assert(filestatus != NULL);
@@ -503,7 +485,7 @@ void ClientMediaDownloader::conventionalTransferDone(
errorstream << "Client: server sent media file that we already"
<< "received, ignoring it: \"" << name << "\""
<< std::endl;
- return;
+ return true;
}
// Mark file as received, regardless of whether loading it works and
@@ -516,9 +498,45 @@ void ClientMediaDownloader::conventionalTransferDone(
// Check that received file matches announced checksum
// If so, load it
checkAndLoad(name, filestatus->sha1, data, false, client);
+
+ return true;
+}
+
+/*
+ IClientMediaDownloader
+*/
+
+IClientMediaDownloader::IClientMediaDownloader():
+ m_media_cache(getMediaCacheDir()), m_write_to_cache(true)
+{
}
-bool ClientMediaDownloader::checkAndLoad(
+void IClientMediaDownloader::createCacheDirs()
+{
+ if (!m_write_to_cache)
+ return;
+
+ std::string path = getMediaCacheDir();
+ if (!fs::CreateAllDirs(path)) {
+ errorstream << "Client: Could not create media cache directory: "
+ << path << std::endl;
+ }
+}
+
+bool IClientMediaDownloader::tryLoadFromCache(const std::string &name,
+ const std::string &sha1, Client *client)
+{
+ std::ostringstream tmp_os(std::ios_base::binary);
+ bool found_in_cache = m_media_cache.load(hex_encode(sha1), tmp_os);
+
+ // If found in cache, try to load it from there
+ if (found_in_cache)
+ return checkAndLoad(name, sha1, tmp_os.str(), true, client);
+
+ return false;
+}
+
+bool IClientMediaDownloader::checkAndLoad(
const std::string &name, const std::string &sha1,
const std::string &data, bool is_from_cache, Client *client)
{
@@ -548,7 +566,7 @@ bool ClientMediaDownloader::checkAndLoad(
}
// Checksum is ok, try loading the file
- bool success = client->loadMedia(data, name);
+ bool success = loadMedia(client, data, name);
if (!success) {
infostream << "Client: "
<< "Failed to load " << cached_or_received << " media: "
@@ -563,7 +581,7 @@ bool ClientMediaDownloader::checkAndLoad(
<< std::endl;
// Update cache (unless we just loaded the file from the cache)
- if (!is_from_cache)
+ if (!is_from_cache && m_write_to_cache)
m_media_cache.update(sha1_hex, data);
return true;
@@ -591,12 +609,10 @@ std::string ClientMediaDownloader::serializeRequiredHashSet()
// Write list of hashes of files that have not been
// received (found in cache) yet
- for (std::map<std::string, FileStatus*>::iterator
- it = m_files.begin();
- it != m_files.end(); ++it) {
- if (!it->second->received) {
- FATAL_ERROR_IF(it->second->sha1.size() != 20, "Invalid SHA1 size");
- os << it->second->sha1;
+ for (const auto &it : m_files) {
+ if (!it.second->received) {
+ FATAL_ERROR_IF(it.second->sha1.size() != 20, "Invalid SHA1 size");
+ os << it.second->sha1;
}
}
@@ -632,3 +648,145 @@ void ClientMediaDownloader::deSerializeHashSet(const std::string &data,
result.insert(data.substr(pos, 20));
}
}
+
+/*
+ SingleMediaDownloader
+*/
+
+SingleMediaDownloader::SingleMediaDownloader(bool write_to_cache):
+ m_httpfetch_caller(HTTPFETCH_DISCARD)
+{
+ m_write_to_cache = write_to_cache;
+}
+
+SingleMediaDownloader::~SingleMediaDownloader()
+{
+ if (m_httpfetch_caller != HTTPFETCH_DISCARD)
+ httpfetch_caller_free(m_httpfetch_caller);
+}
+
+bool SingleMediaDownloader::loadMedia(Client *client, const std::string &data,
+ const std::string &name)
+{
+ return client->loadMedia(data, name, true);
+}
+
+void SingleMediaDownloader::addFile(const std::string &name, const std::string &sha1)
+{
+ assert(m_stage == STAGE_INIT); // pre-condition
+
+ assert(!name.empty());
+ assert(sha1.size() == 20);
+
+ FATAL_ERROR_IF(!m_file_name.empty(), "Cannot add a second file");
+ m_file_name = name;
+ m_file_sha1 = sha1;
+}
+
+void SingleMediaDownloader::addRemoteServer(const std::string &baseurl)
+{
+ assert(m_stage == STAGE_INIT); // pre-condition
+
+ if (g_settings->getBool("enable_remote_media_server"))
+ m_remotes.emplace_back(baseurl);
+}
+
+void SingleMediaDownloader::step(Client *client)
+{
+ if (m_stage == STAGE_INIT) {
+ m_stage = STAGE_CACHE_CHECKED;
+ initialStep(client);
+ }
+
+ // Remote media: check for completion of fetches
+ if (m_httpfetch_caller != HTTPFETCH_DISCARD) {
+ HTTPFetchResult fetch_result;
+ while (httpfetch_async_get(m_httpfetch_caller, fetch_result)) {
+ remoteMediaReceived(fetch_result, client);
+ }
+ }
+}
+
+bool SingleMediaDownloader::conventionalTransferDone(const std::string &name,
+ const std::string &data, Client *client)
+{
+ if (name != m_file_name)
+ return false;
+
+ // Mark file as received unconditionally and try to load it
+ m_stage = STAGE_DONE;
+ checkAndLoad(name, m_file_sha1, data, false, client);
+ return true;
+}
+
+void SingleMediaDownloader::initialStep(Client *client)
+{
+ if (tryLoadFromCache(m_file_name, m_file_sha1, client))
+ m_stage = STAGE_DONE;
+ if (isDone())
+ return;
+
+ createCacheDirs();
+
+ // If the server reported no remote servers, immediately fall back to
+ // conventional transfer.
+ if (!USE_CURL || m_remotes.empty()) {
+ startConventionalTransfer(client);
+ } else {
+ // Otherwise start by requesting the file from the first remote media server
+ m_httpfetch_caller = httpfetch_caller_alloc();
+ m_current_remote = 0;
+ startRemoteMediaTransfer();
+ }
+}
+
+void SingleMediaDownloader::remoteMediaReceived(
+ const HTTPFetchResult &fetch_result, Client *client)
+{
+ sanity_check(!isDone());
+ sanity_check(m_current_remote >= 0);
+
+ // If fetch succeeded, try to load it
+ if (fetch_result.succeeded) {
+ bool success = checkAndLoad(m_file_name, m_file_sha1,
+ fetch_result.data, false, client);
+ if (success) {
+ m_stage = STAGE_DONE;
+ return;
+ }
+ }
+
+ // Otherwise try the next remote server or fall back to conventional transfer
+ m_current_remote++;
+ if (m_current_remote >= (int)m_remotes.size()) {
+ infostream << "Client: Failed to remote-fetch \"" << m_file_name
+ << "\". Requesting it the usual way." << std::endl;
+ m_current_remote = -1;
+ startConventionalTransfer(client);
+ } else {
+ startRemoteMediaTransfer();
+ }
+}
+
+void SingleMediaDownloader::startRemoteMediaTransfer()
+{
+ std::string url = m_remotes.at(m_current_remote) + hex_encode(m_file_sha1);
+ verbosestream << "Client: Requesting remote media file "
+ << "\"" << m_file_name << "\" " << "\"" << url << "\"" << std::endl;
+
+ HTTPFetchRequest fetch_request;
+ fetch_request.url = url;
+ fetch_request.caller = m_httpfetch_caller;
+ fetch_request.request_id = m_httpfetch_next_id;
+ fetch_request.timeout = g_settings->getS32("curl_file_download_timeout");
+ httpfetch_async(fetch_request);
+
+ m_httpfetch_next_id++;
+}
+
+void SingleMediaDownloader::startConventionalTransfer(Client *client)
+{
+ std::vector<std::string> requests;
+ requests.emplace_back(m_file_name);
+ client->request_media(requests);
+}
diff --git a/src/client/clientmedia.h b/src/client/clientmedia.h
index 5a918535b..c297d737f 100644
--- a/src/client/clientmedia.h
+++ b/src/client/clientmedia.h
@@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "irrlichttypes.h"
#include "filecache.h"
+#include "util/basic_macros.h"
#include <ostream>
#include <map>
#include <set>
@@ -38,7 +39,62 @@ struct HTTPFetchResult;
bool clientMediaUpdateCache(const std::string &raw_hash,
const std::string &filedata);
-class ClientMediaDownloader
+// more of a base class than an interface but this name was most convenient...
+class IClientMediaDownloader
+{
+public:
+ DISABLE_CLASS_COPY(IClientMediaDownloader)
+
+ virtual bool isStarted() const = 0;
+
+ // If this returns true, the downloader is done and can be deleted
+ virtual bool isDone() const = 0;
+
+ // Add a file to the list of required file (but don't fetch it yet)
+ virtual void addFile(const std::string &name, const std::string &sha1) = 0;
+
+ // Add a remote server to the list; ignored if not built with cURL
+ virtual void addRemoteServer(const std::string &baseurl) = 0;
+
+ // Steps the media downloader:
+ // - May load media into client by calling client->loadMedia()
+ // - May check media cache for files
+ // - May add files to media cache
+ // - May start remote transfers by calling httpfetch_async
+ // - May check for completion of current remote transfers
+ // - May start conventional transfers by calling client->request_media()
+ // - May inform server that all media has been loaded
+ // by calling client->received_media()
+ // After step has been called once, don't call addFile/addRemoteServer.
+ virtual void step(Client *client) = 0;
+
+ // Must be called for each file received through TOCLIENT_MEDIA
+ // returns true if this file belongs to this downloader
+ virtual bool conventionalTransferDone(const std::string &name,
+ const std::string &data, Client *client) = 0;
+
+protected:
+ IClientMediaDownloader();
+ virtual ~IClientMediaDownloader() = default;
+
+ // Forwards the call to the appropriate Client method
+ virtual bool loadMedia(Client *client, const std::string &data,
+ const std::string &name) = 0;
+
+ void createCacheDirs();
+
+ bool tryLoadFromCache(const std::string &name, const std::string &sha1,
+ Client *client);
+
+ bool checkAndLoad(const std::string &name, const std::string &sha1,
+ const std::string &data, bool is_from_cache, Client *client);
+
+ // Filesystem-based media cache
+ FileCache m_media_cache;
+ bool m_write_to_cache;
+};
+
+class ClientMediaDownloader : public IClientMediaDownloader
{
public:
ClientMediaDownloader();
@@ -52,39 +108,29 @@ public:
return 0.0f;
}
- bool isStarted() const {
+ bool isStarted() const override {
return m_initial_step_done;
}
- // If this returns true, the downloader is done and can be deleted
- bool isDone() const {
+ bool isDone() const override {
return m_initial_step_done &&
m_uncached_received_count == m_uncached_count;
}
- // Add a file to the list of required file (but don't fetch it yet)
- void addFile(const std::string &name, const std::string &sha1);
+ void addFile(const std::string &name, const std::string &sha1) override;
- // Add a remote server to the list; ignored if not built with cURL
- void addRemoteServer(const std::string &baseurl);
+ void addRemoteServer(const std::string &baseurl) override;
- // Steps the media downloader:
- // - May load media into client by calling client->loadMedia()
- // - May check media cache for files
- // - May add files to media cache
- // - May start remote transfers by calling httpfetch_async
- // - May check for completion of current remote transfers
- // - May start conventional transfers by calling client->request_media()
- // - May inform server that all media has been loaded
- // by calling client->received_media()
- // After step has been called once, don't call addFile/addRemoteServer.
- void step(Client *client);
+ void step(Client *client) override;
- // Must be called for each file received through TOCLIENT_MEDIA
- void conventionalTransferDone(
+ bool conventionalTransferDone(
const std::string &name,
const std::string &data,
- Client *client);
+ Client *client) override;
+
+protected:
+ bool loadMedia(Client *client, const std::string &data,
+ const std::string &name) override;
private:
struct FileStatus {
@@ -107,13 +153,9 @@ private:
void startRemoteMediaTransfers();
void startConventionalTransfers(Client *client);
- bool checkAndLoad(const std::string &name, const std::string &sha1,
- const std::string &data, bool is_from_cache,
- Client *client);
-
- std::string serializeRequiredHashSet();
static void deSerializeHashSet(const std::string &data,
std::set<std::string> &result);
+ std::string serializeRequiredHashSet();
// Maps filename to file status
std::map<std::string, FileStatus*> m_files;
@@ -121,9 +163,6 @@ private:
// Array of remote media servers
std::vector<RemoteServerStatus*> m_remotes;
- // Filesystem-based media cache
- FileCache m_media_cache;
-
// Has an attempt been made to load media files from the file cache?
// Have hash sets been requested from remote servers?
bool m_initial_step_done = false;
@@ -135,13 +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;
- long m_httpfetch_timeout = 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
@@ -150,3 +188,63 @@ private:
std::string m_name_bound = "";
};
+
+// A media downloader that only downloads a single file.
+// It does/doesn't do several things the normal downloader does:
+// - won't fetch hash sets from remote servers
+// - will mark loaded media as coming from file push
+// - writing to file cache is optional
+class SingleMediaDownloader : public IClientMediaDownloader
+{
+public:
+ SingleMediaDownloader(bool write_to_cache);
+ ~SingleMediaDownloader();
+
+ bool isStarted() const override {
+ return m_stage > STAGE_INIT;
+ }
+
+ bool isDone() const override {
+ return m_stage >= STAGE_DONE;
+ }
+
+ void addFile(const std::string &name, const std::string &sha1) override;
+
+ void addRemoteServer(const std::string &baseurl) override;
+
+ void step(Client *client) override;
+
+ bool conventionalTransferDone(const std::string &name,
+ const std::string &data, Client *client) override;
+
+protected:
+ bool loadMedia(Client *client, const std::string &data,
+ const std::string &name) override;
+
+private:
+ void initialStep(Client *client);
+ void remoteMediaReceived(const HTTPFetchResult &fetch_result, Client *client);
+ void startRemoteMediaTransfer();
+ void startConventionalTransfer(Client *client);
+
+ enum Stage {
+ STAGE_INIT,
+ STAGE_CACHE_CHECKED, // we have tried to load the file from cache
+ STAGE_DONE
+ };
+
+ // Information about the one file we want to fetch
+ std::string m_file_name;
+ std::string m_file_sha1;
+ s32 m_current_remote;
+
+ // Array of remote media servers
+ std::vector<std::string> m_remotes;
+
+ enum Stage m_stage = STAGE_INIT;
+
+ // Status of remote transfers
+ unsigned long m_httpfetch_caller;
+ unsigned long m_httpfetch_next_id = 0;
+
+};
diff --git a/src/client/clientobject.h b/src/client/clientobject.h
index ecd8059ef..b192f0dcd 100644
--- a/src/client/clientobject.h
+++ b/src/client/clientobject.h
@@ -39,7 +39,7 @@ public:
ClientActiveObject(u16 id, Client *client, ClientEnvironment *env);
virtual ~ClientActiveObject();
- virtual void addToScene(ITextureSource *tsrc) {}
+ virtual void addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) = 0;
virtual void removeFromScene(bool permanent) {}
virtual void updateLight(u32 day_night_ratio) {}
diff --git a/src/client/clouds.cpp b/src/client/clouds.cpp
index 5a075aaf0..383a1d799 100644
--- a/src/client/clouds.cpp
+++ b/src/client/clouds.cpp
@@ -30,7 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
// Menu clouds are created later
class Clouds;
Clouds *g_menuclouds = NULL;
-irr::scene::ISceneManager *g_menucloudsmgr = NULL;
+scene::ISceneManager *g_menucloudsmgr = NULL;
// Constant for now
static constexpr const float cloud_size = BS * 64.0f;
@@ -352,7 +352,7 @@ void Clouds::update(const v3f &camera_p, const video::SColorf &color_diffuse)
// is the camera inside the cloud mesh?
m_camera_inside_cloud = false; // default
if (m_enable_3d) {
- float camera_height = camera_p.Y;
+ float camera_height = camera_p.Y - BS * m_camera_offset.Y;
if (camera_height >= m_box.MinEdge.Y &&
camera_height <= m_box.MaxEdge.Y) {
v2f camera_in_noise;
diff --git a/src/client/clouds.h b/src/client/clouds.h
index a4d810faa..6db88d93c 100644
--- a/src/client/clouds.h
+++ b/src/client/clouds.h
@@ -22,15 +22,14 @@ 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;
extern Clouds *g_menuclouds;
// Scene manager used for menu clouds
-namespace irr{namespace scene{class ISceneManager;}}
-extern irr::scene::ISceneManager *g_menucloudsmgr;
+extern scene::ISceneManager *g_menucloudsmgr;
class Clouds : public scene::ISceneNode
{
diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp
index 97ae9afc4..1d4636a08 100644
--- a/src/client/content_cao.cpp
+++ b/src/client/content_cao.cpp
@@ -20,7 +20,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "content_cao.h"
#include <IBillboardSceneNode.h>
#include <ICameraSceneNode.h>
-#include <ITextSceneNode.h>
#include <IMeshManipulator.h>
#include <IAnimatedMeshSceneNode.h>
#include "client/client.h"
@@ -28,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 +171,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
*/
@@ -189,7 +202,7 @@ public:
static ClientActiveObject* create(Client *client, ClientEnvironment *env);
- void addToScene(ITextureSource *tsrc);
+ void addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr);
void removeFromScene(bool permanent);
void updateLight(u32 day_night_ratio);
void updateNodePos();
@@ -220,7 +233,7 @@ ClientActiveObject* TestCAO::create(Client *client, ClientEnvironment *env)
return new TestCAO(client, env);
}
-void TestCAO::addToScene(ITextureSource *tsrc)
+void TestCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr)
{
if(m_node != NULL)
return;
@@ -249,7 +262,7 @@ void TestCAO::addToScene(ITextureSource *tsrc)
// Add to mesh
mesh->addMeshBuffer(buf);
buf->drop();
- m_node = RenderingEngine::get_scene_manager()->addMeshSceneNode(mesh, NULL);
+ m_node = smgr->addMeshSceneNode(mesh, NULL);
mesh->drop();
updateNodePos();
}
@@ -347,18 +360,6 @@ void GenericCAO::initialize(const std::string &data)
infostream<<"GenericCAO: Got init data"<<std::endl;
processInitData(data);
- if (m_is_player) {
- // Check if it's the current player
- LocalPlayer *player = m_env->getLocalPlayer();
- if (player && strcmp(player->getName(), m_name.c_str()) == 0) {
- m_is_local_player = true;
- m_is_visible = false;
- player->setCAO(this);
-
- m_prop.show_on_minimap = false;
- }
- }
-
m_enable_shaders = g_settings->getBool("enable_shaders");
}
@@ -381,6 +382,16 @@ void GenericCAO::processInitData(const std::string &data)
m_rotation = readV3F32(is);
m_hp = readU16(is);
+ if (m_is_player) {
+ // Check if it's the current player
+ LocalPlayer *player = m_env->getLocalPlayer();
+ if (player && strcmp(player->getName(), m_name.c_str()) == 0) {
+ m_is_local_player = true;
+ m_is_visible = false;
+ player->setCAO(this);
+ }
+ }
+
const u8 num_messages = readU8(is);
for (int i = 0; i < num_messages; i++) {
@@ -558,6 +569,9 @@ void GenericCAO::removeFromScene(bool permanent)
clearParentAttachment();
}
+ if (auto shadow = RenderingEngine::get_shadow_renderer())
+ shadow->removeNodeFromShadowList(getSceneNode());
+
if (m_meshnode) {
m_meshnode->remove();
m_meshnode->drop();
@@ -591,9 +605,9 @@ void GenericCAO::removeFromScene(bool permanent)
m_client->getMinimap()->removeMarker(&m_marker);
}
-void GenericCAO::addToScene(ITextureSource *tsrc)
+void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr)
{
- m_smgr = RenderingEngine::get_scene_manager();
+ m_smgr = smgr;
if (getSceneNode() != NULL) {
return;
@@ -625,8 +639,7 @@ void GenericCAO::addToScene(ITextureSource *tsrc)
}
auto grabMatrixNode = [this] {
- m_matrixnode = RenderingEngine::get_scene_manager()->
- addDummyTransformationSceneNode();
+ m_matrixnode = m_smgr->addDummyTransformationSceneNode();
m_matrixnode->grab();
};
@@ -644,11 +657,11 @@ void GenericCAO::addToScene(ITextureSource *tsrc)
if (m_prop.visual == "sprite") {
grabMatrixNode();
- m_spritenode = RenderingEngine::get_scene_manager()->addBillboardSceneNode(
+ m_spritenode = m_smgr->addBillboardSceneNode(
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);
@@ -729,8 +742,7 @@ void GenericCAO::addToScene(ITextureSource *tsrc)
mesh->addMeshBuffer(buf);
buf->drop();
}
- m_meshnode = RenderingEngine::get_scene_manager()->
- addMeshSceneNode(mesh, m_matrixnode);
+ m_meshnode = m_smgr->addMeshSceneNode(mesh, m_matrixnode);
m_meshnode->grab();
mesh->drop();
// Set it to use the materials of the meshbuffers directly.
@@ -739,8 +751,7 @@ void GenericCAO::addToScene(ITextureSource *tsrc)
} else if (m_prop.visual == "cube") {
grabMatrixNode();
scene::IMesh *mesh = createCubeMesh(v3f(BS,BS,BS));
- m_meshnode = RenderingEngine::get_scene_manager()->
- addMeshSceneNode(mesh, m_matrixnode);
+ m_meshnode = m_smgr->addMeshSceneNode(mesh, m_matrixnode);
m_meshnode->grab();
mesh->drop();
@@ -753,11 +764,6 @@ void GenericCAO::addToScene(ITextureSource *tsrc)
grabMatrixNode();
scene::IAnimatedMesh *mesh = m_client->getMesh(m_prop.mesh, true);
if (mesh) {
- m_animated_meshnode = RenderingEngine::get_scene_manager()->
- 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 +771,9 @@ void GenericCAO::addToScene(ITextureSource *tsrc)
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);
@@ -795,8 +804,7 @@ void GenericCAO::addToScene(ITextureSource *tsrc)
infostream << "serialized form: " << m_prop.wield_item << std::endl;
item.deSerialize(m_prop.wield_item, m_client->idef());
}
- m_wield_meshnode = new WieldMeshSceneNode(
- RenderingEngine::get_scene_manager(), -1);
+ m_wield_meshnode = new WieldMeshSceneNode(m_smgr, -1);
m_wield_meshnode->setItem(item, m_client,
(m_prop.visual == "wielditem"));
@@ -811,10 +819,13 @@ void GenericCAO::addToScene(ITextureSource *tsrc)
if (m_reset_textures_timer < 0)
updateTextures(m_current_texture_modifier);
- scene::ISceneNode *node = getSceneNode();
+ if (scene::ISceneNode *node = getSceneNode()) {
+ if (m_matrixnode)
+ node->setParent(m_matrixnode);
- if (node && m_matrixnode)
- node->setParent(m_matrixnode);
+ if (auto shadow = RenderingEngine::get_shadow_renderer())
+ shadow->addNodeToShadowList(node);
+ }
updateNametag();
updateMarker();
@@ -824,6 +835,28 @@ void GenericCAO::addToScene(ITextureSource *tsrc)
updateAttachments();
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;
+ }
+ }
+ }
}
void GenericCAO::updateLight(u32 day_night_ratio)
@@ -998,14 +1031,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.up || controls.down || controls.left || controls.right ||
- controls.forw_move_joystick_axis != 0.f ||
- controls.sidew_move_joystick_axis != 0.f)
+ if (controls.movement_speed > 0.001f) {
+ new_speed *= controls.movement_speed;
walking = true;
+ }
- f32 new_speed = player->local_animation_speed;
v2s32 new_anim = v2s32(0,0);
bool allow_update = false;
@@ -1017,7 +1050,7 @@ void GenericCAO::step(float dtime, ClientEnvironment *env)
g_settings->getBool("free_move") &&
m_client->checkLocalPrivilege("fly"))))
new_speed *= 1.5;
- // slowdown speed if sneeking
+ // slowdown speed if sneaking
if (controls.sneak && walking)
new_speed /= 2;
@@ -1074,7 +1107,7 @@ void GenericCAO::step(float dtime, ClientEnvironment *env)
}
removeFromScene(false);
- addToScene(m_client->tsrc());
+ addToScene(m_client->tsrc(), m_smgr);
// Attachments, part 2: Now that the parent has been refreshed, put its attachments back
for (u16 cao_id : m_attachment_child_ids) {
@@ -1291,7 +1324,7 @@ void GenericCAO::updateTextures(std::string mod)
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;
@@ -1370,7 +1403,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;
@@ -1403,7 +1436,7 @@ 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;
@@ -1425,7 +1458,7 @@ void GenericCAO::updateTextures(std::string mod)
buf->getMaterial().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())
@@ -1473,11 +1506,8 @@ void GenericCAO::updateAnimation()
if (m_animated_meshnode->getAnimationSpeed() != m_animation_speed)
m_animated_meshnode->setAnimationSpeed(m_animation_speed);
m_animated_meshnode->setTransitionTime(m_animation_blend);
-// Requires Irrlicht 1.8 or greater
-#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR >= 8) || IRRLICHT_VERSION_MAJOR > 1
if (m_animated_meshnode->getLoopMode() != m_animation_loop)
m_animated_meshnode->setLoopMode(m_animation_loop);
-#endif
}
void GenericCAO::updateAnimationSpeed()
@@ -1493,10 +1523,10 @@ void GenericCAO::updateBonePosition()
if (m_bone_position.empty() || !m_animated_meshnode)
return;
- m_animated_meshnode->setJointMode(irr::scene::EJUOR_CONTROL); // To write positions to the mesh on render
+ m_animated_meshnode->setJointMode(scene::EJUOR_CONTROL); // To write positions to the mesh on render
for (auto &it : m_bone_position) {
std::string bone_name = it.first;
- irr::scene::IBoneSceneNode* bone = m_animated_meshnode->getJointNode(bone_name.c_str());
+ scene::IBoneSceneNode* bone = m_animated_meshnode->getJointNode(bone_name.c_str());
if (bone) {
bone->setPosition(it.second.X);
bone->setRotation(it.second.Y);
@@ -1505,7 +1535,7 @@ void GenericCAO::updateBonePosition()
// search through bones to find mistakenly rotated bones due to bug in Irrlicht
for (u32 i = 0; i < m_animated_meshnode->getJointCount(); ++i) {
- irr::scene::IBoneSceneNode *bone = m_animated_meshnode->getJointNode(i);
+ scene::IBoneSceneNode *bone = m_animated_meshnode->getJointNode(i);
if (!bone)
continue;
@@ -1727,6 +1757,7 @@ void GenericCAO::processMessage(const std::string &data)
m_tx_basepos = p;
m_anim_num_frames = num_frames;
+ m_anim_frame = 0;
m_anim_framelength = framelength;
m_tx_select_horiz_by_yawpitch = select_horiz_by_yawpitch;
@@ -1784,6 +1815,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);
@@ -1828,6 +1860,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);
}
}
@@ -1875,7 +1909,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)
{
@@ -1895,6 +1930,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);
}
}
@@ -1933,7 +1970,7 @@ void GenericCAO::updateMeshCulling()
return;
}
- irr::scene::ISceneNode *node = getSceneNode();
+ scene::ISceneNode *node = getSceneNode();
if (!node)
return;
diff --git a/src/client/content_cao.h b/src/client/content_cao.h
index 7c134fb48..4bbba9134 100644
--- a/src/client/content_cao.h
+++ b/src/client/content_cao.h
@@ -174,6 +174,8 @@ public:
const bool isImmortal();
+ inline const ObjectProperties &getProperties() const { return m_prop; }
+
scene::ISceneNode *getSceneNode() const;
scene::IAnimatedMeshSceneNode *getAnimatedMeshSceneNode() const;
@@ -234,7 +236,7 @@ public:
void removeFromScene(bool permanent);
- void addToScene(ITextureSource *tsrc);
+ void addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr);
inline void expireVisuals()
{
diff --git a/src/client/content_mapblock.cpp b/src/client/content_mapblock.cpp
index 90284ecce..bb2d6398f 100644
--- a/src/client/content_mapblock.cpp
+++ b/src/client/content_mapblock.cpp
@@ -60,18 +60,16 @@ static constexpr u16 quad_indices[] = {0, 1, 2, 2, 3, 0};
const std::string MapblockMeshGenerator::raillike_groupname = "connect_to_raillike";
-MapblockMeshGenerator::MapblockMeshGenerator(MeshMakeData *input, MeshCollector *output)
+MapblockMeshGenerator::MapblockMeshGenerator(MeshMakeData *input, MeshCollector *output,
+ scene::IMeshManipulator *mm):
+ data(input),
+ collector(output),
+ nodedef(data->m_client->ndef()),
+ meshmanip(mm),
+ blockpos_nodes(data->m_blockpos * MAP_BLOCKSIZE)
{
- data = input;
- collector = output;
-
- nodedef = data->m_client->ndef();
- meshmanip = RenderingEngine::get_scene_manager()->getMeshManipulator();
-
enable_mesh_cache = g_settings->getBool("enable_mesh_cache") &&
!data->m_smooth_lighting; // Mesh cache is not supported with smooth lighting
-
- blockpos_nodes = data->m_blockpos * MAP_BLOCKSIZE;
}
void MapblockMeshGenerator::useTile(int index, u8 set_flags, u8 reset_flags, bool special)
@@ -960,15 +958,43 @@ void MapblockMeshGenerator::drawPlantlikeQuad(float rotation, float quad_offset,
vertex.rotateXZBy(rotation + rotate_degree);
vertex += offset;
}
+
+ u8 wall = n.getWallMounted(nodedef);
+ if (wall != DWM_YN) {
+ for (v3f &vertex : vertices) {
+ switch (wall) {
+ case DWM_YP:
+ vertex.rotateYZBy(180);
+ vertex.rotateXZBy(180);
+ break;
+ case DWM_XP:
+ vertex.rotateXYBy(90);
+ break;
+ case DWM_XN:
+ vertex.rotateXYBy(-90);
+ vertex.rotateYZBy(180);
+ break;
+ case DWM_ZP:
+ vertex.rotateYZBy(-90);
+ vertex.rotateXYBy(90);
+ break;
+ case DWM_ZN:
+ vertex.rotateYZBy(90);
+ vertex.rotateXYBy(90);
+ break;
+ }
+ }
+ }
+
drawQuad(vertices, v3s16(0, 0, 0), plant_height);
}
-void MapblockMeshGenerator::drawPlantlike()
+void MapblockMeshGenerator::drawPlantlike(bool is_rooted)
{
draw_style = PLANT_STYLE_CROSS;
scale = BS / 2 * f->visual_scale;
offset = v3f(0, 0, 0);
- rotate_degree = 0;
+ rotate_degree = 0.0f;
random_offset_Y = false;
face_num = 0;
plant_height = 1.0;
@@ -988,7 +1014,8 @@ void MapblockMeshGenerator::drawPlantlike()
break;
case CPT2_DEGROTATE:
- rotate_degree = n.param2 * 2;
+ case CPT2_COLORED_DEGROTATE:
+ rotate_degree = 1.5f * n.getDegRotate(nodedef);
break;
case CPT2_LEVELED:
@@ -999,6 +1026,22 @@ void MapblockMeshGenerator::drawPlantlike()
break;
}
+ if (is_rooted) {
+ u8 wall = n.getWallMounted(nodedef);
+ switch (wall) {
+ case DWM_YP:
+ offset.Y += BS*2;
+ break;
+ case DWM_XN:
+ case DWM_XP:
+ case DWM_ZN:
+ case DWM_ZP:
+ offset.X += -BS;
+ offset.Y += BS;
+ break;
+ }
+ }
+
switch (draw_style) {
case PLANT_STYLE_CROSS:
drawPlantlikeQuad(46);
@@ -1049,7 +1092,7 @@ void MapblockMeshGenerator::drawPlantlikeRootedNode()
MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + p);
light = LightPair(getInteriorLight(ntop, 1, nodedef));
}
- drawPlantlike();
+ drawPlantlike(true);
p.Y--;
}
@@ -1343,6 +1386,7 @@ void MapblockMeshGenerator::drawMeshNode()
u8 facedir = 0;
scene::IMesh* mesh;
bool private_mesh; // as a grab/drop pair is not thread-safe
+ int degrotate = 0;
if (f->param_type_2 == CPT2_FACEDIR ||
f->param_type_2 == CPT2_COLORED_FACEDIR) {
@@ -1354,9 +1398,12 @@ void MapblockMeshGenerator::drawMeshNode()
facedir = n.getWallMounted(nodedef);
if (!enable_mesh_cache)
facedir = wallmounted_to_facedir[facedir];
+ } else if (f->param_type_2 == CPT2_DEGROTATE ||
+ f->param_type_2 == CPT2_COLORED_DEGROTATE) {
+ degrotate = n.getDegRotate(nodedef);
}
- if (!data->m_smooth_lighting && f->mesh_ptr[facedir]) {
+ if (!data->m_smooth_lighting && f->mesh_ptr[facedir] && !degrotate) {
// use cached meshes
private_mesh = false;
mesh = f->mesh_ptr[facedir];
@@ -1364,7 +1411,10 @@ void MapblockMeshGenerator::drawMeshNode()
// no cache, clone and rotate mesh
private_mesh = true;
mesh = cloneMesh(f->mesh_ptr[0]);
- rotateMeshBy6dFacedir(mesh, facedir);
+ if (facedir)
+ rotateMeshBy6dFacedir(mesh, facedir);
+ else if (degrotate)
+ rotateMeshXZby(mesh, 1.5f * degrotate);
recalculateBoundingBox(mesh);
meshmanip->recalculateNormals(mesh, true, false);
} else
diff --git a/src/client/content_mapblock.h b/src/client/content_mapblock.h
index 487d84a07..7344f05ee 100644
--- a/src/client/content_mapblock.h
+++ b/src/client/content_mapblock.h
@@ -139,14 +139,14 @@ public:
// plantlike-specific
PlantlikeStyle draw_style;
v3f offset;
- int rotate_degree;
+ float rotate_degree;
bool random_offset_Y;
int face_num;
float plant_height;
void drawPlantlikeQuad(float rotation, float quad_offset = 0,
bool offset_top_only = false);
- void drawPlantlike();
+ void drawPlantlike(bool is_rooted = false);
// firelike-specific
void drawFirelikeQuad(float rotation, float opening_angle,
@@ -172,7 +172,8 @@ public:
void drawNode();
public:
- MapblockMeshGenerator(MeshMakeData *input, MeshCollector *output);
+ MapblockMeshGenerator(MeshMakeData *input, MeshCollector *output,
+ scene::IMeshManipulator *mm);
void generate();
void renderSingle(content_t node, u8 param2 = 0x00);
};
diff --git a/src/client/fontengine.cpp b/src/client/fontengine.cpp
index 47218c0d9..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,28 +52,19 @@ FontEngine::FontEngine(gui::IGUIEnvironment* env) :
readSettings();
- if (m_currentMode == FM_Standard) {
- 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);
- }
- else if (m_currentMode == FM_Fallback) {
- g_settings->registerChangedCallback("fallback_font_size", font_setting_changed, NULL);
- g_settings->registerChangedCallback("fallback_font_path", font_setting_changed, NULL);
- g_settings->registerChangedCallback("fallback_font_shadow", font_setting_changed, NULL);
- g_settings->registerChangedCallback("fallback_font_shadow_alpha", 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);
}
/******************************************************************************/
@@ -89,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();
}
@@ -102,14 +91,15 @@ void FontEngine::cleanCache()
/******************************************************************************/
irr::gui::IGUIFont *FontEngine::getFont(FontSpec spec)
{
+ return getFont(spec, false);
+}
+
+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?
+ } else if (spec.mode == _FM_Fallback) {
+ // Fallback font doesn't support these
spec.bold = false;
spec.italic = false;
}
@@ -118,17 +108,22 @@ irr::gui::IGUIFont *FontEngine::getFont(FontSpec spec)
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. "
+ "Please correct the 'font_path' setting or install the font "
+ "file in the proper location." << std::endl;
+ abort();
+ }
m_font_cache[spec.getHash()][spec.size] = font;
@@ -138,13 +133,7 @@ irr::gui::IGUIFont *FontEngine::getFont(FontSpec spec)
/******************************************************************************/
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;
}
@@ -152,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();
@@ -187,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];
@@ -203,31 +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("fallback_font_size");
- m_default_size[FM_Mono] = g_settings->getU16("mono_font_size");
-
- /*~ DO NOT TRANSLATE THIS LITERALLY!
- This is a special string. Put either "no" or "yes"
- into the translation field (literally).
- Choose "yes" if the language requires use of the fallback
- font, "no" otherwise.
- The fallback font is (normally) required for languages with
- non-Latin script, like Chinese.
- When in doubt, test your translation. */
- m_currentMode = is_yes(gettext("needs_fallback_font")) ?
- FM_Fallback : FM_Standard;
-
- m_default_bold = g_settings->getBool("font_bold");
- m_default_italic = g_settings->getBool("font_italic");
-
- } else {
- m_currentMode = FM_Simple;
- }
+ 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_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);
}
/******************************************************************************/
@@ -271,18 +208,8 @@ gui::IGUIFont *FontEngine::initFont(const FontSpec &spec)
assert(spec.size != FONT_SIZE_UNSPECIFIED);
std::string setting_prefix = "";
-
- switch (spec.mode) {
- case FM_Fallback:
- setting_prefix = "fallback_";
- break;
- case FM_Mono:
- case FM_SimpleMono:
- setting_prefix = "mono_";
- break;
- default:
- break;
- }
+ if (spec.mode == FM_Mono)
+ setting_prefix = "mono_";
std::string setting_suffix = "";
if (spec.bold)
@@ -290,121 +217,52 @@ 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);
g_settings->getU16NoEx(setting_prefix + "font_shadow_alpha",
font_shadow_alpha);
- std::string wanted_font_path;
- wanted_font_path = g_settings->get(setting_prefix + "font_path" + setting_suffix);
+ std::string path_setting;
+ if (spec.mode == _FM_Fallback)
+ path_setting = "fallback_font_path";
+ else
+ path_setting = setting_prefix + "font_path" + setting_suffix;
std::string fallback_settings[] = {
- wanted_font_path,
- g_settings->get("fallback_font_path"),
- Settings::getLayer(SL_DEFAULTS)->get(setting_prefix + "font_path")
+ g_settings->get(path_setting),
+ Settings::getLayer(SL_DEFAULTS)->get(path_setting)
};
-#if USE_FREETYPE
for (const std::string &font_path : fallback_settings) {
- irr::gui::IGUIFont *font = gui::CGUITTFont::createTTFont(m_env,
+ gui::CGUITTFont *font = gui::CGUITTFont::createTTFont(m_env,
font_path.c_str(), size, true, true, font_shadow,
font_shadow_alpha);
- if (font)
- return font;
-
- errorstream << "FontEngine: Cannot load '" << font_path <<
+ if (!font) {
+ errorstream << "FontEngine: Cannot load '" << font_path <<
"'. Trying to fall back to another path." << std::endl;
- }
-
-
- // give up
- errorstream << "minetest can not continue without a valid font. "
- "Please correct the 'font_path' setting or install the font "
- "file in the proper location" << std::endl;
-#else
- errorstream << "FontEngine: Tried to load freetype fonts but Minetest was"
- " not compiled with that library." << std::endl;
-#endif
- abort();
-}
-
-/** 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;
- std::string 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;
- }
+ continue;
}
- 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;
+ if (spec.mode != _FM_Fallback) {
+ FontSpec spec2(spec);
+ spec2.mode = _FM_Fallback;
+ font->setFallback(getFont(spec2, true));
}
+ return font;
}
-
- return font;
+ return nullptr;
}
diff --git a/src/client/fontengine.h b/src/client/fontengine.h
index e27ef60e9..78608e517 100644
--- a/src/client/fontengine.h
+++ b/src/client/fontengine.h
@@ -20,22 +20,20 @@ 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
enum FontMode : u8 {
FM_Standard = 0,
FM_Mono,
- FM_Fallback,
- FM_Simple,
- FM_SimpleMono,
+ _FM_Fallback, // do not use directly
FM_MaxMode,
FM_Unspecified
};
@@ -47,7 +45,7 @@ struct FontSpec {
bold(bold),
italic(italic) {}
- u16 getHash()
+ u16 getHash() const
{
return (mode << 2) | (static_cast<u8>(bold) << 1) | static_cast<u8>(italic);
}
@@ -132,15 +130,14 @@ public:
void readSettings();
private:
+ irr::gui::IGUIFont *getFont(FontSpec spec, bool may_fail);
+
/** update content of font cache in case of a setting change made it invalid */
void updateFontCache();
- /** initialize a new font */
+ /** 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();
@@ -150,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];
@@ -160,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 a88972d53..4337d308e 100644
--- a/src/client/game.cpp
+++ b/src/client/game.cpp
@@ -68,6 +68,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "util/pointedthing.h"
#include "util/quicktune_shortcutter.h"
#include "irrlicht_changes/static_text.h"
+#include "irr_ptr.h"
#include "version.h"
#include "script/scripting_client.h"
#include "hud.h"
@@ -399,12 +400,7 @@ public:
};
-// before 1.8 there isn't a "integer interface", only float
-#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
-typedef f32 SamplerLayer_t;
-#else
typedef s32 SamplerLayer_t;
-#endif
class GameGlobalShaderConstantSetter : public IShaderConstantSetter
@@ -512,38 +508,20 @@ public:
float eye_position_array[3];
v3f epos = m_client->getEnv().getLocalPlayer()->getEyePosition();
-#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
- eye_position_array[0] = epos.X;
- eye_position_array[1] = epos.Y;
- eye_position_array[2] = epos.Z;
-#else
epos.getAs3Values(eye_position_array);
-#endif
m_eye_position_pixel.set(eye_position_array, services);
m_eye_position_vertex.set(eye_position_array, services);
if (m_client->getMinimap()) {
float minimap_yaw_array[3];
v3f minimap_yaw = m_client->getMinimap()->getYawVec();
-#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
- minimap_yaw_array[0] = minimap_yaw.X;
- minimap_yaw_array[1] = minimap_yaw.Y;
- minimap_yaw_array[2] = minimap_yaw.Z;
-#else
minimap_yaw.getAs3Values(minimap_yaw_array);
-#endif
m_minimap_yaw.set(minimap_yaw_array, services);
}
float camera_offset_array[3];
v3f offset = intToFloat(m_client->getCamera()->getOffset(), BS);
-#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
- camera_offset_array[0] = offset.X;
- camera_offset_array[1] = offset.Y;
- camera_offset_array[2] = offset.Z;
-#else
offset.getAs3Values(camera_offset_array);
-#endif
m_camera_offset_pixel.set(camera_offset_array, services);
m_camera_offset_vertex.set(camera_offset_array, services);
@@ -588,7 +566,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
@@ -597,10 +575,19 @@ public:
/****************************************************************************
****************************************************************************/
-const float object_hit_delay = 0.2;
+const static 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;
};
@@ -650,6 +637,8 @@ struct ClientEventHandler
THE GAME
****************************************************************************/
+using PausedNodesList = std::vector<std::pair<irr_ptr<scene::IAnimatedMeshSceneNode>, float>>;
+
/* This is not intended to be a public class. If a public class becomes
* desirable then it may be better to create another 'wrapper' class that
* hides most of the stuff in this class (nothing in this class is required
@@ -662,6 +651,7 @@ public:
bool startup(bool *kill,
InputHandler *input,
+ RenderingEngine *rendering_engine,
const GameStartData &game_params,
std::string &error_message,
bool *reconnect,
@@ -672,8 +662,6 @@ public:
protected:
- void extendedResourceCleanup();
-
// Basic initialisation
bool init(const std::string &map_dir, const std::string &address,
u16 port, const SubgameSpec &gamespec);
@@ -697,6 +685,7 @@ protected:
bool handleCallbacks();
void processQueues();
void updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime);
+ void updateDebugState();
void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime);
void updateProfilerGraphs(ProfilerGraph *graph);
@@ -714,6 +703,7 @@ protected:
void toggleFast();
void toggleNoClip();
void toggleCinematic();
+ void toggleBlockBounds();
void toggleAutoforward();
void toggleMinimap(bool shift_pressed);
@@ -731,7 +721,7 @@ protected:
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);
/*!
@@ -759,10 +749,9 @@ protected:
const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
void updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
const CameraOrientation &cam);
+ void updateShadows();
// Misc
- void limitFps(FpsControl *fps_timings, f32 *dtime);
-
void showOverlayMessage(const char *msg, float dtime, int percent,
bool draw_clouds = true);
@@ -799,6 +788,9 @@ private:
void showDeathFormspec();
void showPauseMenu();
+ void pauseAnimation();
+ void resumeAnimation();
+
// ClientEvent handlers
void handleClientEvent_None(ClientEvent *event, CameraOrientation *cam);
void handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam);
@@ -819,13 +811,15 @@ private:
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,
const NodeMetadata *meta);
static const ClientEventHandler clientEventHandler[CLIENTEVENT_MAX];
+ f32 getSensitivityScaleFactor() const;
+
InputHandler *input = nullptr;
Client *client = nullptr;
@@ -870,12 +864,14 @@ private:
these items (e.g. device)
*/
IrrlichtDevice *device;
+ RenderingEngine *m_rendering_engine;
video::IVideoDriver *driver;
scene::ISceneManager *smgr;
bool *kill;
std::string *error_message;
bool *reconnect_requested;
scene::ISceneNode *skybox;
+ PausedNodesList paused_animated_nodes;
bool simple_singleplayer_mode;
/* End 'cache' */
@@ -912,8 +908,10 @@ private:
bool m_does_lost_focus_pause_game = false;
int m_reset_HW_buffer_counter = 0;
-#ifdef __ANDROID__
+#ifdef HAVE_TOUCHSCREENGUI
bool m_cache_hold_aux1;
+#endif
+#ifdef __ANDROID__
bool m_android_chat_open;
#endif
};
@@ -951,7 +949,7 @@ Game::Game() :
readSettings();
-#ifdef __ANDROID__
+#ifdef HAVE_TOUCHSCREENGUI
m_cache_hold_aux1 = false; // This is initialised properly later
#endif
@@ -982,7 +980,7 @@ Game::~Game()
delete itemdef_manager;
delete draw_control;
- extendedResourceCleanup();
+ clearTextureNameCache();
g_settings->deregisterChangedCallback("doubletap_jump",
&settingChangedCallback, this);
@@ -1010,6 +1008,7 @@ Game::~Game()
bool Game::startup(bool *kill,
InputHandler *input,
+ RenderingEngine *rendering_engine,
const GameStartData &start_data,
std::string &error_message,
bool *reconnect,
@@ -1017,21 +1016,21 @@ bool Game::startup(bool *kill,
{
// "cache"
- this->device = RenderingEngine::get_raw_device();
+ m_rendering_engine = rendering_engine;
+ device = m_rendering_engine->get_raw_device();
this->kill = kill;
this->error_message = &error_message;
- this->reconnect_requested = reconnect;
+ reconnect_requested = reconnect;
this->input = input;
this->chat_backend = chat_backend;
- this->simple_singleplayer_mode = start_data.isSinglePlayer();
+ simple_singleplayer_mode = start_data.isSinglePlayer();
input->keycache.populate();
driver = device->getVideoDriver();
- smgr = RenderingEngine::get_scene_manager();
+ smgr = m_rendering_engine->get_scene_manager();
- RenderingEngine::get_scene_manager()->getParameters()->
- setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true);
+ smgr->getParameters()->setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true);
// Reinit runData
runData = GameRunData();
@@ -1052,7 +1051,7 @@ bool Game::startup(bool *kill,
if (!createClient(start_data))
return false;
- RenderingEngine::initialize(client, hud);
+ m_rendering_engine->initialize(client, hud);
return true;
}
@@ -1064,18 +1063,18 @@ void Game::run()
RunStats stats = { 0 };
CameraOrientation cam_view_target = { 0 };
CameraOrientation cam_view = { 0 };
- FpsControl draw_times = { 0 };
+ FpsControl draw_times;
f32 dtime; // in seconds
/* Clear the profiler */
Profiler::GraphValues dummyvalues;
g_profiler->graphGet(dummyvalues);
- draw_times.last_time = RenderingEngine::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
@@ -1083,12 +1082,12 @@ void Game::run()
irr::core::dimension2d<u32> previous_screen_size(g_settings->getU16("screen_w"),
g_settings->getU16("screen_h"));
- while (RenderingEngine::run()
+ while (m_rendering_engine->run()
&& !(*kill || g_gamecallback->shutdown_requested
|| (server && server->isShutdownRequested()))) {
const irr::core::dimension2d<u32> &current_screen_size =
- RenderingEngine::get_video_driver()->getScreenSize();
+ m_rendering_engine->get_video_driver()->getScreenSize();
// Verify if window size has changed and save it if it's the case
// Ensure evaluating settings->getBool after verifying screensize
// First condition is cheaper
@@ -1101,9 +1100,9 @@ void Game::run()
}
// Calculate dtime =
- // RenderingEngine::run() from this iteration
+ // 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
@@ -1120,6 +1119,7 @@ void Game::run()
m_game_ui->clearInfoText();
hud->resizeHotbar();
+
updateProfilers(stats, draw_times, dtime);
processUserInput(dtime);
// Update camera before player movement to avoid camera lag of one frame
@@ -1131,10 +1131,11 @@ void Game::run()
updatePlayerControl(cam_view);
step(&dtime);
processClientEvents(&cam_view_target);
- updateCamera(draw_times.busy_time, dtime);
+ updateDebugState();
+ updateCamera(dtime);
updateSound(dtime);
processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud,
- m_game_ui->m_flags.show_debug);
+ m_game_ui->m_flags.show_basic_debug);
updateFrame(&graph, &stats, dtime, cam_view);
updateProfilerGraphs(&graph);
@@ -1150,7 +1151,7 @@ void Game::run()
void Game::shutdown()
{
- RenderingEngine::finalize();
+ m_rendering_engine->finalize();
#if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 8
if (g_settings->get("3d_mode") == "pageflip") {
driver->setRenderTarget(irr::video::ERT_STEREO_BOTH_BUFFERS);
@@ -1288,9 +1289,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;
}
@@ -1306,7 +1306,7 @@ bool Game::createClient(const GameStartData &start_data)
{
showOverlayMessage(N_("Creating client..."), 0, 10);
- draw_control = new MapDrawControl;
+ draw_control = new MapDrawControl();
if (!draw_control)
return false;
@@ -1323,7 +1323,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;
@@ -1332,7 +1332,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;
@@ -1347,9 +1347,9 @@ bool Game::createClient(const GameStartData &start_data)
/* Camera
*/
- camera = new Camera(*draw_control, client);
- if (!camera->successfullyCreated(*error_message))
- return false;
+ camera = new Camera(*draw_control, client, m_rendering_engine);
+ if (client->modsLoaded())
+ client->getScript()->on_camera_ready(camera);
client->setCamera(camera);
/* Clouds
@@ -1359,7 +1359,7 @@ bool Game::createClient(const GameStartData &start_data)
/* Skybox
*/
- sky = new Sky(-1, texture_src, shader_src);
+ sky = new Sky(-1, m_rendering_engine, texture_src, shader_src);
scsf->setSky(sky);
skybox = NULL; // This is used/set later on in the main run loop
@@ -1381,16 +1381,28 @@ bool Game::createClient(const GameStartData &start_data)
std::wstring str = utf8_to_wide(PROJECT_NAME_C);
str += L" ";
str += utf8_to_wide(g_version_hash);
+ {
+ const wchar_t *text = nullptr;
+ if (simple_singleplayer_mode)
+ text = wgettext("Singleplayer");
+ else
+ text = wgettext("Multiplayer");
+ str += L" [";
+ str += text;
+ str += L"]";
+ delete[] text;
+ }
str += L" [";
str += driver->getName();
str += L"]";
+
device->setWindowCaption(str.c_str());
LocalPlayer *player = client->getEnv().getLocalPlayer();
player->hurt_tilt_timer = 0;
player->hurt_tilt_strength = 0;
- hud = new Hud(guienv, client, player, &player->inventory);
+ hud = new Hud(client, player, &player->inventory);
mapper = client->getMinimap();
@@ -1439,7 +1451,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;
@@ -1450,24 +1461,30 @@ 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,
- 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;
@@ -1485,15 +1502,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 = RenderingEngine::get_timer_time();
+ fps_control.reset();
- while (RenderingEngine::run()) {
+ while (m_rendering_engine->run()) {
- limitFps(&fps_control, &dtime);
+ fps_control.limit(device, &dtime);
// Update client and server
client->step(dtime);
@@ -1512,8 +1529,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;
@@ -1528,7 +1544,7 @@ bool Game::connectToServer(const GameStartData &start_data,
if (client->m_is_registration_confirmation_state) {
if (registration_confirmation_shown) {
// Keep drawing the GUI
- RenderingEngine::draw_menu_scene(guienv, dtime, true);
+ m_rendering_engine->draw_menu_scene(guienv, dtime, true);
} else {
registration_confirmation_shown = true;
(new GUIConfirmRegistration(guienv, guienv->getRootGUIElement(), -1,
@@ -1539,7 +1555,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;
}
@@ -1561,14 +1577,14 @@ bool Game::getServerContent(bool *aborted)
{
input->clear();
- FpsControl fps_control = { 0 };
+ FpsControl fps_control;
f32 dtime; // in seconds
- fps_control.last_time = RenderingEngine::get_timer_time();
+ fps_control.reset();
- while (RenderingEngine::run()) {
+ while (m_rendering_engine->run()) {
- limitFps(&fps_control, &dtime);
+ fps_control.limit(device, &dtime);
// Update client and server
client->step(dtime);
@@ -1587,7 +1603,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;
}
@@ -1604,17 +1620,17 @@ bool Game::getServerContent(bool *aborted)
if (!client->itemdefReceived()) {
const wchar_t *text = wgettext("Item definitions...");
progress = 25;
- RenderingEngine::draw_load_screen(text, guienv, texture_src,
+ m_rendering_engine->draw_load_screen(text, guienv, texture_src,
dtime, progress);
delete[] text;
} else if (!client->nodedefReceived()) {
const wchar_t *text = wgettext("Node definitions...");
progress = 30;
- RenderingEngine::draw_load_screen(text, guienv, texture_src,
+ m_rendering_engine->draw_load_screen(text, guienv, texture_src,
dtime, progress);
delete[] text;
} else {
- std::stringstream message;
+ std::ostringstream message;
std::fixed(message);
message.precision(0);
float receive = client->mediaReceiveProgress() * 100;
@@ -1637,7 +1653,7 @@ bool Game::getServerContent(bool *aborted)
}
progress = 30 + client->mediaReceiveProgress() * 35 + 0.5;
- RenderingEngine::draw_load_screen(utf8_to_wide(message.str()), guienv,
+ m_rendering_engine->draw_load_screen(utf8_to_wide(message.str()), guienv,
texture_src, dtime, progress);
}
}
@@ -1669,8 +1685,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;
@@ -1723,6 +1738,25 @@ void Game::processQueues()
shader_src->processQueue();
}
+void Game::updateDebugState()
+{
+ const bool has_basic_debug = true;
+ bool has_debug = client->checkPrivilege("debug");
+
+ if (m_game_ui->m_flags.show_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) {
+ m_game_ui->m_flags.show_basic_debug = true;
+ }
+ }
+ if (!has_basic_debug)
+ hud->disableBlockBounds();
+ if (!has_debug)
+ draw_control->show_wireframe = false;
+}
void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
f32 dtime)
@@ -1747,10 +1781,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);
}
@@ -1783,9 +1817,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;
@@ -1822,6 +1856,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
@@ -1904,24 +1939,18 @@ void Game::processKeyInput()
} else if (wasKeyDown(KeyType::INC_VOLUME)) {
if (g_settings->getBool("enable_sound")) {
float new_volume = rangelim(g_settings->getFloat("sound_volume") + 0.1f, 0.0f, 1.0f);
- wchar_t buf[100];
g_settings->setFloat("sound_volume", new_volume);
- const wchar_t *str = wgettext("Volume changed to %d%%");
- swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100));
- delete[] str;
- m_game_ui->showStatusText(buf);
+ std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
+ m_game_ui->showStatusText(msg);
} else {
m_game_ui->showTranslatedStatusText("Sound system is disabled");
}
} else if (wasKeyDown(KeyType::DEC_VOLUME)) {
if (g_settings->getBool("enable_sound")) {
float new_volume = rangelim(g_settings->getFloat("sound_volume") - 0.1f, 0.0f, 1.0f);
- wchar_t buf[100];
g_settings->setFloat("sound_volume", new_volume);
- const wchar_t *str = wgettext("Volume changed to %d%%");
- swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100));
- delete[] str;
- m_game_ui->showStatusText(buf);
+ std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
+ m_game_ui->showStatusText(msg);
} else {
m_game_ui->showTranslatedStatusText("Sound system is disabled");
}
@@ -1934,6 +1963,8 @@ void Game::processKeyInput()
toggleCinematic();
} else if (wasKeyDown(KeyType::SCREENSHOT)) {
client->makeScreenshot();
+ } else if (wasKeyDown(KeyType::TOGGLE_BLOCK_BOUNDS)) {
+ toggleBlockBounds();
} else if (wasKeyDown(KeyType::TOGGLE_HUD)) {
m_game_ui->toggleHud();
} else if (wasKeyDown(KeyType::MINIMAP)) {
@@ -2042,15 +2073,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, &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);
}
@@ -2138,7 +2176,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
}
@@ -2171,6 +2209,32 @@ void Game::toggleCinematic()
m_game_ui->showTranslatedStatusText("Cinematic mode disabled");
}
+void Game::toggleBlockBounds()
+{
+ if (true /* 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)");
+ }
+}
+
// Autoforward by toggling continuous forward.
void Game::toggleAutoforward()
{
@@ -2234,24 +2298,39 @@ void Game::toggleFog()
void Game::toggleDebug()
{
- // Initial / 4x toggle: Chat only
- // 1x toggle: Debug text with chat
+ // Initial: No debug info
+ // 1x toggle: Debug text
// 2x toggle: Debug text with profiler graph
- // 3x toggle: Debug text and wireframe
- if (!m_game_ui->m_flags.show_debug) {
- m_game_ui->m_flags.show_debug = true;
+ // 3x toggle: Debug text and wireframe (needs "debug" priv)
+ // Next toggle: Back to initial
+ //
+ // 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 always used.
+
+ const bool has_basic_debug = true;
+ if (!m_game_ui->m_flags.show_minimal_debug) {
+ m_game_ui->m_flags.show_minimal_debug = true;
+ 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 (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 (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");
} else {
- m_game_ui->m_flags.show_debug = false;
+ m_game_ui->m_flags.show_minimal_debug = false;
+ 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")) {
@@ -2278,20 +2357,13 @@ void Game::increaseViewRange()
s16 range = g_settings->getS16("viewing_range");
s16 range_new = range + 10;
- wchar_t buf[255];
- const wchar_t *str;
if (range_new > 4000) {
range_new = 4000;
- str = wgettext("Viewing range is at maximum: %d");
- swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
- delete[] str;
- m_game_ui->showStatusText(buf);
-
+ std::wstring msg = fwgettext("Viewing range is at maximum: %d", range_new);
+ m_game_ui->showStatusText(msg);
} else {
- str = wgettext("Viewing range changed to %d");
- swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
- delete[] str;
- m_game_ui->showStatusText(buf);
+ std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
+ m_game_ui->showStatusText(msg);
}
g_settings->set("viewing_range", itos(range_new));
}
@@ -2302,19 +2374,13 @@ void Game::decreaseViewRange()
s16 range = g_settings->getS16("viewing_range");
s16 range_new = range - 10;
- wchar_t buf[255];
- const wchar_t *str;
if (range_new < 20) {
range_new = 20;
- str = wgettext("Viewing range is at minimum: %d");
- swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
- delete[] str;
- m_game_ui->showStatusText(buf);
+ std::wstring msg = fwgettext("Viewing range is at minimum: %d", range_new);
+ m_game_ui->showStatusText(msg);
} else {
- str = wgettext("Viewing range changed to %d");
- swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
- delete[] str;
- m_game_ui->showStatusText(buf);
+ std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
+ m_game_ui->showStatusText(msg);
}
g_settings->set("viewing_range", itos(range_new));
}
@@ -2337,7 +2403,6 @@ void Game::checkZoomEnabled()
m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod");
}
-
void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
{
if ((device->isWindowActive() && device->isWindowFocused()
@@ -2373,6 +2438,18 @@ void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
}
}
+// Get the factor to multiply with sensitivity to get the same mouse/joystick
+// responsiveness independently of FOV.
+f32 Game::getSensitivityScaleFactor() const
+{
+ f32 fov_y = client->getCamera()->getFovY();
+
+ // Multiply by a constant such that it becomes 1.0 at 72 degree FOV and
+ // 16:9 aspect ratio to minimize disruption of existing sensitivity
+ // settings.
+ return tan(fov_y / 2.0f) * 1.3763818698f;
+}
+
void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
{
#ifdef HAVE_TOUCHSCREENGUI
@@ -2388,8 +2465,9 @@ void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
dist.Y = -dist.Y;
}
- cam->camera_yaw -= dist.X * m_cache_mouse_sensitivity;
- cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity;
+ f32 sens_scale = getSensitivityScaleFactor();
+ cam->camera_yaw -= dist.X * m_cache_mouse_sensitivity * sens_scale;
+ cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity * sens_scale;
if (dist.X != 0 || dist.Y != 0)
input->setMousePos(center.X, center.Y);
@@ -2398,7 +2476,8 @@ void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
#endif
if (m_cache_enable_joysticks) {
- f32 c = m_cache_joystick_frustum_sensitivity * (1.f / 32767.f) * dtime;
+ f32 sens_scale = getSensitivityScaleFactor();
+ f32 c = m_cache_joystick_frustum_sensitivity * dtime * sens_scale;
cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c;
cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c;
}
@@ -2409,71 +2488,46 @@ void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
void Game::updatePlayerControl(const CameraOrientation &cam)
{
- //TimeTaker tt("update player control", NULL, PRECISION_NANO);
+ LocalPlayer *player = client->getEnv().getLocalPlayer();
- // DO NOT use the isKeyDown method for the forward, backward, left, right
- // buttons, as the code that uses the controls needs to be able to
- // distinguish between the two in order to know when to use joysticks.
+ //TimeTaker tt("update player control", NULL, PRECISION_NANO);
PlayerControl control(
- input->isKeyDown(KeyType::FORWARD),
- input->isKeyDown(KeyType::BACKWARD),
- input->isKeyDown(KeyType::LEFT),
- input->isKeyDown(KeyType::RIGHT),
- isKeyDown(KeyType::JUMP),
- isKeyDown(KeyType::SPECIAL1),
+ isKeyDown(KeyType::FORWARD),
+ isKeyDown(KeyType::BACKWARD),
+ isKeyDown(KeyType::LEFT),
+ isKeyDown(KeyType::RIGHT),
+ isKeyDown(KeyType::JUMP) || player->getAutojump(),
+ isKeyDown(KeyType::AUX1),
isKeyDown(KeyType::SNEAK),
isKeyDown(KeyType::ZOOM),
isKeyDown(KeyType::DIG),
isKeyDown(KeyType::PLACE),
cam.camera_pitch,
cam.camera_yaw,
- input->joystick.getAxisWithoutDead(JA_SIDEWARD_MOVE),
- input->joystick.getAxisWithoutDead(JA_FORWARD_MOVE)
+ input->getMovementSpeed(),
+ input->getMovementDirection()
);
- u32 keypress_bits = (
- ( (u32)(isKeyDown(KeyType::FORWARD) & 0x1) << 0) |
- ( (u32)(isKeyDown(KeyType::BACKWARD) & 0x1) << 1) |
- ( (u32)(isKeyDown(KeyType::LEFT) & 0x1) << 2) |
- ( (u32)(isKeyDown(KeyType::RIGHT) & 0x1) << 3) |
- ( (u32)(isKeyDown(KeyType::JUMP) & 0x1) << 4) |
- ( (u32)(isKeyDown(KeyType::SPECIAL1) & 0x1) << 5) |
- ( (u32)(isKeyDown(KeyType::SNEAK) & 0x1) << 6) |
- ( (u32)(isKeyDown(KeyType::DIG) & 0x1) << 7) |
- ( (u32)(isKeyDown(KeyType::PLACE) & 0x1) << 8) |
- ( (u32)(isKeyDown(KeyType::ZOOM) & 0x1) << 9)
- );
+ // autoforward if set: move towards pointed position at maximum speed
+ if (player->getPlayerSettings().continuous_forward &&
+ client->activeObjectsReceived() && !player->isDead()) {
+ control.movement_speed = 1.0f;
+ 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) {
control.aux1 = control.aux1 ^ true;
- keypress_bits ^= ((u32)(1U << 5));
}
#endif
- LocalPlayer *player = client->getEnv().getLocalPlayer();
-
- // autojump if set: simulate "jump" key
- if (player->getAutojump()) {
- control.jump = true;
- keypress_bits |= 1U << 4;
- }
-
- // autoforward if set: simulate "up" key
- if (player->getPlayerSettings().continuous_forward &&
- client->activeObjectsReceived() && !player->isDead()) {
- control.up = true;
- keypress_bits |= 1U << 0;
- }
-
client->setPlayerControl(control);
- player->keyPressed = keypress_bits;
//tt.stop();
}
@@ -2487,6 +2541,9 @@ inline void Game::step(f32 *dtime)
if (can_be_and_is_paused) { // This is for a singleplayer server
*dtime = 0; // No time passes
} else {
+ if (simple_singleplayer_mode && !paused_animated_nodes.empty())
+ resumeAnimation();
+
if (server)
server->step(*dtime);
@@ -2494,6 +2551,33 @@ inline void Game::step(f32 *dtime)
}
}
+static void pauseNodeAnimation(PausedNodesList &paused, scene::ISceneNode *node) {
+ if (!node)
+ return;
+ for (auto &&child: node->getChildren())
+ pauseNodeAnimation(paused, child);
+ if (node->getType() != scene::ESNT_ANIMATED_MESH)
+ return;
+ auto animated_node = static_cast<scene::IAnimatedMeshSceneNode *>(node);
+ float speed = animated_node->getAnimationSpeed();
+ if (!speed)
+ return;
+ paused.push_back({grab(animated_node), speed});
+ animated_node->setAnimationSpeed(0.0f);
+}
+
+void Game::pauseAnimation()
+{
+ pauseNodeAnimation(paused_animated_nodes, smgr->getRootSceneNode());
+}
+
+void Game::resumeAnimation()
+{
+ for (auto &&pair: paused_animated_nodes)
+ pair.first->setAnimationSpeed(pair.second);
+ paused_animated_nodes.clear();
+}
+
const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
{&Game::handleClientEvent_None},
{&Game::handleClientEvent_PlayerDamage},
@@ -2527,14 +2611,18 @@ void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation
// Damage flash and hurt tilt are not used at death
if (client->getHP() > 0) {
- runData.damage_flash += 95.0f + 3.2f * event->player_damage.amount;
- runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
-
LocalPlayer *player = client->getEnv().getLocalPlayer();
+ f32 hp_max = player->getCAO() ?
+ player->getCAO()->getProperties().hp_max : PLAYER_MAX_HP_DEFAULT;
+ f32 damage_ratio = event->player_damage.amount / hp_max;
+
+ runData.damage_flash += 95.0f + 64.f * damage_ratio;
+ runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
+
player->hurt_tilt_timer = 1.5f;
player->hurt_tilt_strength =
- rangelim(event->player_damage.amount / 4.0f, 1.0f, 4.0f);
+ rangelim(damage_ratio * 5.0f, 1.0f, 4.0f);
}
// Play damage sound
@@ -2578,8 +2666,8 @@ void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation
new TextDestPlayerInventory(client, *(event->show_formspec.formname));
auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname));
- GUIFormSpecMenu::create(formspec, client, &input->joystick,
- fs_src, txt_dst, client->getFormspecPrepend(), sound);
+ GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
+ &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
}
delete event->show_formspec.formspec;
@@ -2591,8 +2679,8 @@ void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrienta
FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
LocalFormspecHandler *txt_dst =
new LocalFormspecHandler(*event->show_formspec.formname, client);
- GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, &input->joystick,
- fs_src, txt_dst, client->getFormspecPrepend(), sound);
+ GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, m_rendering_engine->get_gui_env(),
+ &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
delete event->show_formspec.formspec;
delete event->show_formspec.formname;
@@ -2609,48 +2697,33 @@ void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
{
LocalPlayer *player = client->getEnv().getLocalPlayer();
- u32 server_id = event->hudadd.server_id;
+ u32 server_id = event->hudadd->server_id;
// ignore if we already have a HUD with that ID
auto i = m_hud_server_to_client.find(server_id);
if (i != m_hud_server_to_client.end()) {
- delete event->hudadd.pos;
- delete event->hudadd.name;
- delete event->hudadd.scale;
- delete event->hudadd.text;
- delete event->hudadd.align;
- delete event->hudadd.offset;
- delete event->hudadd.world_pos;
- delete event->hudadd.size;
- delete event->hudadd.text2;
+ delete event->hudadd;
return;
}
HudElement *e = new HudElement;
- e->type = (HudElementType)event->hudadd.type;
- e->pos = *event->hudadd.pos;
- e->name = *event->hudadd.name;
- e->scale = *event->hudadd.scale;
- e->text = *event->hudadd.text;
- e->number = event->hudadd.number;
- e->item = event->hudadd.item;
- e->dir = event->hudadd.dir;
- e->align = *event->hudadd.align;
- e->offset = *event->hudadd.offset;
- e->world_pos = *event->hudadd.world_pos;
- e->size = *event->hudadd.size;
- e->z_index = event->hudadd.z_index;
- e->text2 = *event->hudadd.text2;
+ e->type = static_cast<HudElementType>(event->hudadd->type);
+ e->pos = event->hudadd->pos;
+ e->name = event->hudadd->name;
+ e->scale = event->hudadd->scale;
+ e->text = event->hudadd->text;
+ e->number = event->hudadd->number;
+ e->item = event->hudadd->item;
+ e->dir = event->hudadd->dir;
+ e->align = event->hudadd->align;
+ e->offset = event->hudadd->offset;
+ e->world_pos = event->hudadd->world_pos;
+ e->size = event->hudadd->size;
+ e->z_index = event->hudadd->z_index;
+ e->text2 = event->hudadd->text2;
+ e->style = event->hudadd->style;
m_hud_server_to_client[server_id] = player->addHud(e);
- delete event->hudadd.pos;
- delete event->hudadd.name;
- delete event->hudadd.scale;
- delete event->hudadd.text;
- delete event->hudadd.align;
- delete event->hudadd.offset;
- delete event->hudadd.world_pos;
- delete event->hudadd.size;
- delete event->hudadd.text2;
+ delete event->hudadd;
}
void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
@@ -2672,77 +2745,54 @@ void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *ca
HudElement *e = nullptr;
- auto i = m_hud_server_to_client.find(event->hudchange.id);
+ auto i = m_hud_server_to_client.find(event->hudchange->id);
if (i != m_hud_server_to_client.end()) {
e = player->getHud(i->second);
}
if (e == nullptr) {
- delete event->hudchange.v3fdata;
- delete event->hudchange.v2fdata;
- delete event->hudchange.sdata;
- delete event->hudchange.v2s32data;
+ delete event->hudchange;
return;
}
- switch (event->hudchange.stat) {
- case HUD_STAT_POS:
- e->pos = *event->hudchange.v2fdata;
- break;
+#define CASE_SET(statval, prop, dataprop) \
+ case statval: \
+ e->prop = event->hudchange->dataprop; \
+ break
- case HUD_STAT_NAME:
- e->name = *event->hudchange.sdata;
- break;
+ switch (event->hudchange->stat) {
+ CASE_SET(HUD_STAT_POS, pos, v2fdata);
- case HUD_STAT_SCALE:
- e->scale = *event->hudchange.v2fdata;
- break;
+ CASE_SET(HUD_STAT_NAME, name, sdata);
- case HUD_STAT_TEXT:
- e->text = *event->hudchange.sdata;
- break;
+ CASE_SET(HUD_STAT_SCALE, scale, v2fdata);
- case HUD_STAT_NUMBER:
- e->number = event->hudchange.data;
- break;
+ CASE_SET(HUD_STAT_TEXT, text, sdata);
- case HUD_STAT_ITEM:
- e->item = event->hudchange.data;
- break;
+ CASE_SET(HUD_STAT_NUMBER, number, data);
- case HUD_STAT_DIR:
- e->dir = event->hudchange.data;
- break;
+ CASE_SET(HUD_STAT_ITEM, item, data);
- case HUD_STAT_ALIGN:
- e->align = *event->hudchange.v2fdata;
- break;
+ CASE_SET(HUD_STAT_DIR, dir, data);
- case HUD_STAT_OFFSET:
- e->offset = *event->hudchange.v2fdata;
- break;
+ CASE_SET(HUD_STAT_ALIGN, align, v2fdata);
- case HUD_STAT_WORLD_POS:
- e->world_pos = *event->hudchange.v3fdata;
- break;
+ CASE_SET(HUD_STAT_OFFSET, offset, v2fdata);
- case HUD_STAT_SIZE:
- e->size = *event->hudchange.v2s32data;
- break;
+ CASE_SET(HUD_STAT_WORLD_POS, world_pos, v3fdata);
- case HUD_STAT_Z_INDEX:
- e->z_index = event->hudchange.data;
- break;
+ CASE_SET(HUD_STAT_SIZE, size, v2s32data);
- case HUD_STAT_TEXT2:
- e->text2 = *event->hudchange.sdata;
- break;
+ CASE_SET(HUD_STAT_Z_INDEX, z_index, data);
+
+ CASE_SET(HUD_STAT_TEXT2, text2, sdata);
+
+ CASE_SET(HUD_STAT_STYLE, style, data);
}
- delete event->hudchange.v3fdata;
- delete event->hudchange.v2fdata;
- delete event->hudchange.sdata;
- delete event->hudchange.v2s32data;
+#undef CASE_SET
+
+ delete event->hudchange;
}
void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
@@ -2797,6 +2847,7 @@ void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
"custom"
);
}
+
delete event->set_sky;
}
@@ -2823,7 +2874,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;
@@ -2860,7 +2911,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())
@@ -2876,11 +2927,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();
@@ -2919,7 +2976,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();
@@ -3260,9 +3317,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");
}
}
@@ -3294,7 +3350,8 @@ bool Game::nodePlacement(const ItemDefinition &selected_def,
const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighbourpos,
const PointedThing &pointed, const NodeMetadata *meta)
{
- std::string prediction = selected_def.node_placement_prediction;
+ const auto &prediction = selected_def.node_placement_prediction;
+
const NodeDefManager *nodedef = client->ndef();
ClientMap &map = client->getEnv().getClientMap();
MapNode node;
@@ -3323,8 +3380,8 @@ bool Game::nodePlacement(const ItemDefinition &selected_def,
TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
auto *&formspec = m_game_ui->updateFormspec("");
- GUIFormSpecMenu::create(formspec, client, &input->joystick, fs_src,
- txt_dst, client->getFormspecPrepend(), sound);
+ GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
+ &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
return false;
@@ -3364,8 +3421,7 @@ bool Game::nodePlacement(const ItemDefinition &selected_def,
if (!found) {
errorstream << "Node placement prediction failed for "
- << selected_def.name << " (places "
- << prediction
+ << selected_def.name << " (places " << prediction
<< ") - Name not known" << std::endl;
// Handle this as if prediction was empty
// Report to server
@@ -3376,9 +3432,14 @@ bool Game::nodePlacement(const ItemDefinition &selected_def,
const ContentFeatures &predicted_f = nodedef->get(id);
// Predict param2 for facedir and wallmounted nodes
+ // Compare core.item_place_node() for what the server does
u8 param2 = 0;
- if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
+ const u8 place_param2 = selected_def.place_param2;
+
+ if (place_param2) {
+ param2 = place_param2;
+ } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
v3s16 dir = nodepos - neighbourpos;
@@ -3389,9 +3450,7 @@ bool Game::nodePlacement(const ItemDefinition &selected_def,
} else {
param2 = dir.Z < 0 ? 5 : 4;
}
- }
-
- if (predicted_f.param_type_2 == CPT2_FACEDIR ||
+ } else if (predicted_f.param_type_2 == CPT2_FACEDIR ||
predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
@@ -3402,11 +3461,9 @@ bool Game::nodePlacement(const ItemDefinition &selected_def,
}
}
- assert(param2 <= 5);
-
- //Check attachment if node is in group attached_node
- if (((ItemGroupList) predicted_f.groups)["attached_node"] != 0) {
- static v3s16 wallmounted_dirs[8] = {
+ // Check attachment if node is in group attached_node
+ if (itemgroup_get(predicted_f.groups, "attached_node") != 0) {
+ const static v3s16 wallmounted_dirs[8] = {
v3s16(0, 1, 0),
v3s16(0, -1, 0),
v3s16(1, 0, 0),
@@ -3431,11 +3488,11 @@ bool Game::nodePlacement(const ItemDefinition &selected_def,
}
// Apply color
- if ((predicted_f.param_type_2 == CPT2_COLOR
+ if (!place_param2 && (predicted_f.param_type_2 == CPT2_COLOR
|| predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
|| predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
- const std::string &indexstr = selected_item.metadata.getString(
- "palette_index", 0);
+ const auto &indexstr = selected_item.metadata.
+ getString("palette_index", 0);
if (!indexstr.empty()) {
s32 index = mystoi(indexstr);
if (predicted_f.param_type_2 == CPT2_COLOR) {
@@ -3475,11 +3532,10 @@ bool Game::nodePlacement(const ItemDefinition &selected_def,
soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
return false;
}
- } catch (InvalidPositionException &e) {
+ } catch (const InvalidPositionException &e) {
errorstream << "Node placement prediction failed for "
<< selected_def.name << " (places "
- << prediction
- << ") - Position not loaded" << std::endl;
+ << prediction << ") - Position not loaded" << std::endl;
soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
return false;
}
@@ -3549,7 +3605,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) {
@@ -3796,12 +3853,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)
+ 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());
+ }
- updateChat(dtime, screensize);
+ /*
+ Get chat messages from client
+ */
+
+ updateChat(dtime);
/*
Inventory
@@ -3823,15 +3896,22 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
*/
runData.update_draw_list_timer += dtime;
+ float update_draw_list_delta = 0.2f;
+
v3f camera_direction = camera->getDirection();
- if (runData.update_draw_list_timer >= 0.2
+ 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;
}
+ if (RenderingEngine::get_shadow_renderer()) {
+ updateShadows();
+ }
+
m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime);
/*
@@ -3863,11 +3943,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 &&
@@ -3882,12 +3962,14 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
} catch (SettingNotFoundException) {
}
#endif
- RenderingEngine::draw_scene(skycolor, m_game_ui->m_flags.show_hud,
+ m_rendering_engine->draw_scene(skycolor, m_game_ui->m_flags.show_hud,
m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
/*
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());
@@ -3904,25 +3986,7 @@ 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)
- 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
+ ==================== End scene ====================
*/
if (++m_reset_HW_buffer_counter > 500) {
/*
@@ -3945,11 +4009,12 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
driver->removeAllHardwareBuffers();
m_reset_HW_buffer_counter = 0;
}
+
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 */
@@ -3960,59 +4025,85 @@ inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
graph->put(values);
}
+/****************************************************************************
+ * Shadows
+ *****************************************************************************/
+void Game::updateShadows()
+{
+ ShadowRenderer *shadow = RenderingEngine::get_shadow_renderer();
+ if (!shadow)
+ return;
+
+ float in_timeofday = fmod(runData.time_of_day_smooth, 1.0f);
+
+ float timeoftheday = fmod(getWickedTimeOfDay(in_timeofday) + 0.75f, 0.5f) + 0.25f;
+ const float offset_constant = 10000.0f;
+
+ v3f light(0.0f, 0.0f, -1.0f);
+ light.rotateXZBy(90);
+ light.rotateXYBy(timeoftheday * 360 - 90);
+ light.rotateYZBy(sky->getSkyBodyOrbitTilt());
+ v3f sun_pos = light * offset_constant;
+
+ if (shadow->getDirectionalLightCount() == 0)
+ shadow->addDirectionalLight();
+ shadow->getDirectionalLight().setDirection(sun_pos);
+ shadow->setTimeOfDay(in_timeofday);
+
+ shadow->getDirectionalLight().update_frustum(camera, client, m_camera_offset_changed);
+}
/****************************************************************************
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)
{
const wchar_t *wmsg = wgettext(msg);
- RenderingEngine::draw_load_screen(wmsg, guienv, texture_src, dtime, percent,
+ m_rendering_engine->draw_load_screen(wmsg, guienv, texture_src, dtime, percent,
draw_clouds);
delete[] wmsg;
}
@@ -4057,27 +4148,6 @@ void Game::readSettings()
****************************************************************************/
/****************************************************************************/
-void Game::extendedResourceCleanup()
-{
- // Extended resource accounting
- infostream << "Irrlicht resources after cleanup:" << std::endl;
- infostream << "\tRemaining meshes : "
- << RenderingEngine::get_mesh_cache()->getMeshCount() << std::endl;
- infostream << "\tRemaining textures : "
- << driver->getTextureCount() << std::endl;
-
- for (unsigned int i = 0; i < driver->getTextureCount(); i++) {
- irr::video::ITexture *texture = driver->getTextureByIndex(i);
- infostream << "\t\t" << i << ":" << texture->getName().getPath().c_str()
- << std::endl;
- }
-
- clearTextureNameCache();
- infostream << "\tRemaining materials: "
- << driver-> getMaterialRendererCount()
- << " (note: irrlicht doesn't support removing renderers)" << std::endl;
-}
-
void Game::showDeathFormspec()
{
static std::string formspec_str =
@@ -4095,15 +4165,15 @@ void Game::showDeathFormspec()
LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
auto *&formspec = m_game_ui->getFormspecGUI();
- GUIFormSpecMenu::create(formspec, client, &input->joystick,
- fs_src, txt_dst, client->getFormspecPrepend(), sound);
+ GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
+ &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
formspec->setFocus("btn_respawn");
}
#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"
@@ -4203,16 +4273,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())
@@ -4229,10 +4301,13 @@ void Game::showPauseMenu()
LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
auto *&formspec = m_game_ui->getFormspecGUI();
- GUIFormSpecMenu::create(formspec, client, &input->joystick,
- fs_src, txt_dst, client->getFormspecPrepend(), sound);
+ GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
+ &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
formspec->setFocus("btn_continue");
formspec->doPause = true;
+
+ if (simple_singleplayer_mode)
+ pauseAnimation();
}
/****************************************************************************/
@@ -4243,6 +4318,7 @@ void Game::showPauseMenu()
void the_game(bool *kill,
InputHandler *input,
+ RenderingEngine *rendering_engine,
const GameStartData &start_data,
std::string &error_message,
ChatBackend &chat_backend,
@@ -4257,20 +4333,21 @@ void the_game(bool *kill,
try {
- if (game.startup(kill, input, start_data, error_message,
- reconnect_requested, &chat_backend)) {
+ if (game.startup(kill, input, rendering_engine, start_data,
+ error_message, reconnect_requested, &chat_backend)) {
game.run();
}
} 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 d04153271..d87e747c5 100644
--- a/src/client/game.h
+++ b/src/client/game.h
@@ -23,7 +23,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <string>
class InputHandler;
-class ChatBackend; /* to avoid having to include chat.h */
+class ChatBackend;
+class RenderingEngine;
struct SubgameSpec;
struct GameStartData;
@@ -32,7 +33,7 @@ struct Jitter {
};
struct RunStats {
- u32 drawtime;
+ u64 drawtime; // (us)
Jitter dtime_jitter, busy_time_jitter;
};
@@ -45,6 +46,7 @@ struct CameraOrientation {
void the_game(bool *kill,
InputHandler *input,
+ RenderingEngine *rendering_engine,
const GameStartData &start_data,
std::string &error_message,
ChatBackend &chat_backend,
diff --git a/src/client/gameui.cpp b/src/client/gameui.cpp
index 0c08efeb5..8505ea3ae 100644
--- a/src/client/gameui.cpp
+++ b/src/client/gameui.cpp
@@ -71,11 +71,14 @@ void GameUI::init()
chat_font_size, FM_Unspecified));
}
- // At the middle of the screen
- // Object infos are shown in this
+
+ // Infotext of nodes and objects.
+ // If in debug mode, object debug infos shown here, too.
+ // Located on the left on the screen, below chat.
u32 chat_font_height = m_guitext_chat->getActiveFont()->getDimension(L"Ay").Height;
m_guitext_info = gui::StaticText::add(guienv, L"",
- core::rect<s32>(0, 0, 400, g_fontengine->getTextHeight() * 5 + 5) +
+ // Size is limited; text will be truncated after 6 lines.
+ core::rect<s32>(0, 0, 400, g_fontengine->getTextHeight() * 6) +
v2s32(100, chat_font_height *
(g_settings->getU16("recent_chat_messages") + 3)),
false, true, guiroot);
@@ -97,19 +100,20 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_
const CameraOrientation &cam, const PointedThing &pointed_old,
const GUIChatConsole *chat_console, float dtime)
{
- v2u32 screensize = RenderingEngine::get_instance()->getWindowSize();
+ v2u32 screensize = RenderingEngine::getWindowSize();
- if (m_flags.show_debug) {
- static float drawtime_avg = 0;
- drawtime_avg = drawtime_avg * 0.95 + stats.drawtime * 0.05;
- u16 fps = 1.0 / stats.dtime_jitter.avg;
+ // Minimal debug text must only contain info that can't give a gameplay advantage
+ if (m_flags.show_minimal_debug) {
+ 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) << "%"
@@ -125,9 +129,10 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_
}
// Finally set the guitext visible depending on the flag
- m_guitext->setVisible(m_flags.show_debug);
+ m_guitext->setVisible(m_flags.show_minimal_debug);
- if (m_flags.show_debug) {
+ // Basic debug text also shows info that might give a gameplay advantage
+ if (m_flags.show_basic_debug) {
LocalPlayer *player = client->getEnv().getLocalPlayer();
v3f player_position = player->getPosition();
@@ -160,7 +165,7 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_
));
}
- m_guitext2->setVisible(m_flags.show_debug);
+ m_guitext2->setVisible(m_flags.show_basic_debug);
setStaticText(m_guitext_info, m_infotext.c_str());
m_guitext_info->setVisible(m_flags.show_hud && g_menumgr.menuCount() == 0);
@@ -204,7 +209,7 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_
void GameUI::initFlags()
{
m_flags = GameUI::Flags();
- m_flags.show_debug = g_settings->getBool("show_debug");
+ m_flags.show_minimal_debug = g_settings->getBool("show_debug");
}
void GameUI::showMinimap(bool show)
@@ -221,24 +226,32 @@ 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
s32 chat_y = 5;
- if (m_flags.show_debug)
- chat_y += 2 * g_fontengine->getLineHeight();
+ if (m_flags.show_minimal_debug)
+ chat_y += g_fontengine->getLineHeight();
+ if (m_flags.show_basic_debug)
+ chat_y += g_fontengine->getLineHeight();
- const v2u32 &window_size = RenderingEngine::get_instance()->getWindowSize();
+ const v2u32 &window_size = RenderingEngine::getWindowSize();
- 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()
@@ -260,7 +273,7 @@ void GameUI::updateProfiler()
core::position2di upper_left(6, 50);
core::position2di lower_right = upper_left;
lower_right.X += size.Width + 10;
- lower_right.Y += size.Height;
+ lower_right.Y += size.Height;
m_guitext_profiler->setRelativePosition(core::rect<s32>(upper_left, lower_right));
}
@@ -294,12 +307,9 @@ void GameUI::toggleProfiler()
updateProfiler();
if (m_profiler_current_page != 0) {
- wchar_t buf[255];
- const wchar_t* str = wgettext("Profiler shown (page %d of %d)");
- swprintf(buf, sizeof(buf) / sizeof(wchar_t), str,
- m_profiler_current_page, m_profiler_max_page);
- delete[] str;
- showStatusText(buf);
+ std::wstring msg = fwgettext("Profiler shown (page %d of %d)",
+ m_profiler_current_page, m_profiler_max_page);
+ showStatusText(msg);
} else {
showTranslatedStatusText("Profiler hidden");
}
diff --git a/src/client/gameui.h b/src/client/gameui.h
index b6c8a224d..cc9377bdc 100644
--- a/src/client/gameui.h
+++ b/src/client/gameui.h
@@ -58,7 +58,8 @@ public:
bool show_chat = true;
bool show_hud = true;
bool show_minimap = false;
- bool show_debug = true;
+ bool show_minimal_debug = false;
+ bool show_basic_debug = false;
bool show_profiler_graph = false;
};
@@ -83,11 +84,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();
@@ -108,6 +110,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
@@ -121,6 +125,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 406c096e6..232219237 100644
--- a/src/client/guiscalingfilter.cpp
+++ b/src/client/guiscalingfilter.cpp
@@ -23,7 +23,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "util/numeric.h"
#include <cstdio>
#include "client/renderingengine.h"
-#include "client/tile.h" // hasNPotSupport()
/* Maintain a static cache to store the images that correspond to textures
* in a format that's manipulable by code. Some platforms exhibit issues
@@ -102,7 +101,7 @@ video::ITexture *guiScalingResizeCached(video::IVideoDriver *driver,
if (!g_settings->getBool("gui_scaling_filter_txr2img"))
return src;
srcimg = driver->createImageFromData(src->getColorFormat(),
- src->getSize(), src->lock(), false);
+ src->getSize(), src->lock(video::ETLM_READ_ONLY), false);
src->unlock();
g_imgCache[origname] = srcimg;
}
@@ -117,7 +116,7 @@ video::ITexture *guiScalingResizeCached(video::IVideoDriver *driver,
#if ENABLE_GLES
// Some platforms are picky about textures being powers of 2, so expand
// the image dimensions to the next power of 2, if necessary.
- if (!hasNPotSupport()) {
+ if (!driver->queryFeature(video::EVDF_TEXTURE_NPOT)) {
video::IImage *po2img = driver->createImage(src->getColorFormat(),
core::dimension2d<u32>(npot2((u32)destrect.getWidth()),
npot2((u32)destrect.getHeight())));
@@ -129,7 +128,7 @@ video::ITexture *guiScalingResizeCached(video::IVideoDriver *driver,
#endif
// Convert the scaled image back into a texture.
- scaled = driver->addTexture(scalename, destimg, NULL);
+ scaled = driver->addTexture(scalename, destimg);
destimg->drop();
g_txrCache[scalename] = scaled;
diff --git a/src/client/hud.cpp b/src/client/hud.cpp
index 3de116c06..6011a8cff 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"
@@ -45,11 +47,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#define OBJECT_CROSSHAIR_LINE_SIZE 8
#define CROSSHAIR_LINE_SIZE 10
-Hud::Hud(gui::IGUIEnvironment *guienv, Client *client, LocalPlayer *player,
+Hud::Hud(Client *client, LocalPlayer *player,
Inventory *inventory)
{
driver = RenderingEngine::get_video_driver();
- this->guienv = guienv;
this->client = client;
this->player = player;
this->inventory = inventory;
@@ -223,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)
{
@@ -270,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;
@@ -315,7 +318,7 @@ bool Hud::calculateScreenPos(const v3s16 &camera_offset, HudElement *e, v2s32 *p
{
v3f w_pos = e->world_pos * BS;
scene::ICameraSceneNode* camera =
- RenderingEngine::get_scene_manager()->getActiveCamera();
+ client->getSceneManager()->getActiveCamera();
w_pos -= intToFloat(camera_offset, BS);
core::matrix4 trans = camera->getProjectionMatrix();
trans *= camera->getViewMatrix();
@@ -332,8 +335,8 @@ bool Hud::calculateScreenPos(const v3s16 &camera_offset, HudElement *e, v2s32 *p
void Hud::drawLuaElements(const v3s16 &camera_offset)
{
- u32 text_height = g_fontengine->getTextHeight();
- irr::gui::IGUIFont* font = g_fontengine->getFont();
+ const u32 text_height = g_fontengine->getTextHeight();
+ gui::IGUIFont *const font = g_fontengine->getFont();
// Reorder elements by z_index
std::vector<HudElement*> elems;
@@ -357,40 +360,40 @@ void Hud::drawLuaElements(const v3s16 &camera_offset)
floor(e->pos.Y * (float) m_screensize.Y + 0.5));
switch (e->type) {
case HUD_ELEM_TEXT: {
- irr::gui::IGUIFont *textfont = font;
unsigned int font_size = g_fontengine->getDefaultFontSize();
if (e->size.X > 0)
font_size *= e->size.X;
- if (font_size != g_fontengine->getDefaultFontSize())
- textfont = g_fontengine->getFont(font_size);
+#ifdef __ANDROID__
+ // The text size on Android is not proportional with the actual scaling
+ // FIXME: why do we have such a weird unportable hack??
+ if (font_size > 3 && e->offset.X < -20)
+ font_size -= 3;
+#endif
+ auto textfont = g_fontengine->getFont(FontSpec(font_size,
+ (e->style & HUD_STYLE_MONO) ? FM_Mono : FM_Unspecified,
+ e->style & HUD_STYLE_BOLD, e->style & HUD_STYLE_ITALIC));
video::SColor color(255, (e->number >> 16) & 0xFF,
(e->number >> 8) & 0xFF,
(e->number >> 0) & 0xFF);
std::wstring text = unescape_translate(utf8_to_wide(e->text));
core::dimension2d<u32> textsize = textfont->getDimension(text.c_str());
-#ifdef __ANDROID__
- // The text size on Android is not proportional with the actual scaling
- irr::gui::IGUIFont *font_scaled = font_size <= 3 ?
- textfont : g_fontengine->getFont(font_size - 3);
- if (e->offset.X < -20)
- textsize = font_scaled->getDimension(text.c_str());
-#endif
- 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);
-#ifdef __ANDROID__
- if (e->offset.X < -20)
- font_scaled->draw(text.c_str(), size + pos + offset + offs, color);
- else
-#endif
+ 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: {
@@ -400,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; }
@@ -475,7 +480,7 @@ void Hud::drawLuaElements(const v3s16 &camera_offset)
// Angle according to camera view
v3f fore(0.f, 0.f, 1.f);
- scene::ICameraSceneNode *cam = RenderingEngine::get_scene_manager()->getActiveCamera();
+ scene::ICameraSceneNode *cam = client->getSceneManager()->getActiveCamera();
cam->getAbsoluteTransformation().rotateVect(fore);
int angle = - fore.getHorizontalAngle().Y;
@@ -747,7 +752,7 @@ void Hud::drawHotbar(u16 playeritem) {
s32 width = hotbar_itemcount * (m_hotbar_imagesize + m_padding * 2);
v2s32 pos = centerlowerpos - v2s32(width / 2, m_hotbar_imagesize + m_padding * 3);
- const v2u32 &window_size = RenderingEngine::get_instance()->getWindowSize();
+ const v2u32 &window_size = RenderingEngine::getWindowSize();
if ((float) width / (float) window_size.X <=
g_settings->getFloat("hud_hotbar_max_width")) {
if (player->hud_flags & HUD_FLAG_HOTBAR_VISIBLE) {
@@ -862,6 +867,60 @@ void Hud::drawSelectionMesh()
}
}
+enum Hud::BlockBoundsMode Hud::toggleBlockBounds()
+{
+ m_block_bounds_mode = static_cast<BlockBoundsMode>(m_block_bounds_mode + 1);
+
+ if (m_block_bounds_mode >= BLOCK_BOUNDS_MAX) {
+ m_block_bounds_mode = BLOCK_BOUNDS_OFF;
+ }
+ return m_block_bounds_mode;
+}
+
+void Hud::disableBlockBounds()
+{
+ m_block_bounds_mode = BLOCK_BOUNDS_OFF;
+}
+
+void Hud::drawBlockBounds()
+{
+ if (m_block_bounds_mode == BLOCK_BOUNDS_OFF) {
+ return;
+ }
+
+ video::SMaterial old_material = driver->getMaterial2D();
+ driver->setMaterial(m_selection_material);
+
+ v3s16 pos = player->getStandingNodePos();
+
+ v3s16 blockPos(
+ floorf((float) pos.X / MAP_BLOCKSIZE),
+ floorf((float) pos.Y / MAP_BLOCKSIZE),
+ floorf((float) pos.Z / MAP_BLOCKSIZE)
+ );
+
+ v3f offset = intToFloat(client->getCamera()->getOffset(), BS);
+
+ s8 radius = m_block_bounds_mode == BLOCK_BOUNDS_NEAR ? 2 : 0;
+
+ v3f halfNode = v3f(BS, BS, BS) / 2.0f;
+
+ for (s8 x = -radius; x <= radius; x++)
+ for (s8 y = -radius; y <= radius; y++)
+ for (s8 z = -radius; z <= radius; z++) {
+ v3s16 blockOffset(x, y, z);
+
+ aabb3f box(
+ intToFloat((blockPos + blockOffset) * MAP_BLOCKSIZE, BS) - offset - halfNode,
+ intToFloat(((blockPos + blockOffset) * MAP_BLOCKSIZE) + (MAP_BLOCKSIZE - 1), BS) - offset + halfNode
+ );
+
+ driver->draw3DBox(box, video::SColor(255, 255, 0, 0));
+ }
+
+ driver->setMaterial(old_material);
+}
+
void Hud::updateSelectionMesh(const v3s16 &camera_offset)
{
m_camera_offset = camera_offset;
@@ -908,7 +967,7 @@ void Hud::updateSelectionMesh(const v3s16 &camera_offset)
}
void Hud::resizeHotbar() {
- const v2u32 &window_size = RenderingEngine::get_instance()->getWindowSize();
+ const v2u32 &window_size = RenderingEngine::getWindowSize();
if (m_screensize != window_size) {
m_hotbar_imagesize = floor(HOTBAR_IMAGE_SIZE *
@@ -945,12 +1004,24 @@ void drawItemStack(
return;
}
+ const static thread_local bool enable_animations =
+ g_settings->getBool("inventory_items_animations");
+
const ItemDefinition &def = item.getDefinition(client->idef());
- ItemMesh *imesh = client->idef()->getWieldMesh(def.name, client);
- if (imesh && imesh->mesh) {
+ bool draw_overlay = false;
+
+ bool has_mesh = false;
+ ItemMesh *imesh;
+
+ // Render as mesh if animated or no inventory image
+ if ((enable_animations && rotation_kind < IT_ROT_NONE) || def.inventory_image.empty()) {
+ imesh = client->idef()->getWieldMesh(def.name, client);
+ has_mesh = imesh && imesh->mesh;
+ }
+ if (has_mesh) {
scene::IMesh *mesh = imesh->mesh;
- driver->clearZBuffer();
+ driver->clearBuffers(video::ECBF_DEPTH);
s32 delta = 0;
if (rotation_kind < IT_ROT_NONE) {
MeshTimeInfo &ti = rotation_time_infos[rotation_kind];
@@ -992,9 +1063,6 @@ void drawItemStack(
core::matrix4 matrix;
matrix.makeIdentity();
- static thread_local bool enable_animations =
- g_settings->getBool("inventory_items_animations");
-
if (enable_animations) {
float timer_f = (float) delta / 5000.f;
matrix.setRotationDegrees(v3f(
@@ -1040,15 +1108,36 @@ void drawItemStack(
driver->setTransform(video::ETS_PROJECTION, oldProjMat);
driver->setViewPort(oldViewPort);
- // draw the inventory_overlay
- if (def.type == ITEM_NODE && def.inventory_image.empty() &&
- !def.inventory_overlay.empty()) {
+ 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);
+ video::SColor color;
+ if (texture) {
+ color = client->idef()->getItemstackColor(item, client);
+ } else {
+ color = video::SColor(255, 255, 255, 255);
ITextureSource *tsrc = client->getTextureSource();
- video::ITexture *overlay_texture = tsrc->getTexture(def.inventory_overlay);
- core::dimension2d<u32> dimens = overlay_texture->getOriginalSize();
- core::rect<s32> srcrect(0, 0, dimens.Width, dimens.Height);
- draw2DImageFilterScaled(driver, overlay_texture, rect, srcrect, clip, 0, true);
+ texture = tsrc->getTexture("no_texture.png");
+ if (!texture)
+ return;
}
+
+ const video::SColor colors[] = { color, color, color, color };
+
+ draw2DImageFilterScaled(driver, texture, rect,
+ core::rect<s32>({0, 0}, core::dimension2di(texture->getOriginalSize())),
+ clip, colors, true);
+
+ draw_overlay = true;
+ }
+
+ // draw the inventory_overlay
+ if (!def.inventory_overlay.empty() && draw_overlay) {
+ ITextureSource *tsrc = client->getTextureSource();
+ video::ITexture *overlay_texture = tsrc->getTexture(def.inventory_overlay);
+ core::dimension2d<u32> dimens = overlay_texture->getOriginalSize();
+ core::rect<s32> srcrect(0, 0, dimens.Width, dimens.Height);
+ draw2DImageFilterScaled(driver, overlay_texture, rect, srcrect, clip, 0, true);
}
if (def.type == ITEM_TOOL && item.wear != 0) {
diff --git a/src/client/hud.h b/src/client/hud.h
index d46545d71..fd79183a0 100644
--- a/src/client/hud.h
+++ b/src/client/hud.h
@@ -35,13 +35,13 @@ struct ItemStack;
class Hud
{
public:
- video::IVideoDriver *driver;
- scene::ISceneManager *smgr;
- gui::IGUIEnvironment *guienv;
- Client *client;
- LocalPlayer *player;
- Inventory *inventory;
- ITextureSource *tsrc;
+ enum BlockBoundsMode
+ {
+ BLOCK_BOUNDS_OFF,
+ BLOCK_BOUNDS_CURRENT,
+ BLOCK_BOUNDS_NEAR,
+ BLOCK_BOUNDS_MAX
+ } m_block_bounds_mode = BLOCK_BOUNDS_OFF;
video::SColor crosshair_argb;
video::SColor selectionbox_argb;
@@ -55,10 +55,14 @@ public:
bool pointing_at_object = false;
- Hud(gui::IGUIEnvironment *guienv, Client *client, LocalPlayer *player,
+ Hud(Client *client, LocalPlayer *player,
Inventory *inventory);
~Hud();
+ enum BlockBoundsMode toggleBlockBounds();
+ void disableBlockBounds();
+ void drawBlockBounds();
+
void drawHotbar(u16 playeritem);
void resizeHotbar();
void drawCrosshair();
@@ -103,6 +107,12 @@ private:
void drawCompassRotate(HudElement *e, video::ITexture *texture,
const core::rect<s32> &rect, int way);
+ Client *client = nullptr;
+ video::IVideoDriver *driver = nullptr;
+ LocalPlayer *player = nullptr;
+ Inventory *inventory = nullptr;
+ ITextureSource *tsrc = nullptr;
+
float m_hud_scaling; // cached minetest setting
float m_scale_factor;
v3s16 m_camera_offset;
diff --git a/src/client/imagefilters.cpp b/src/client/imagefilters.cpp
index 0fa501410..b62e336f7 100644
--- a/src/client/imagefilters.cpp
+++ b/src/client/imagefilters.cpp
@@ -19,63 +19,140 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "imagefilters.h"
#include "util/numeric.h"
#include <cmath>
+#include <cassert>
+#include <vector>
+#include <algorithm>
+
+// Simple 2D bitmap class with just the functionality needed here
+class Bitmap {
+ u32 linesize, lines;
+ std::vector<u8> data;
+
+ static inline u32 bytepos(u32 index) { return index >> 3; }
+ static inline u8 bitpos(u32 index) { return index & 7; }
+
+public:
+ Bitmap(u32 width, u32 height) : linesize(width), lines(height),
+ data(bytepos(width * height) + 1) {}
+
+ inline bool get(u32 x, u32 y) const {
+ u32 index = y * linesize + x;
+ return data[bytepos(index)] & (1 << bitpos(index));
+ }
+
+ inline void set(u32 x, u32 y) {
+ u32 index = y * linesize + x;
+ data[bytepos(index)] |= 1 << bitpos(index);
+ }
+
+ inline bool all() const {
+ for (u32 i = 0; i < data.size() - 1; i++) {
+ if (data[i] != 0xff)
+ return false;
+ }
+ // last byte not entirely filled
+ for (u8 i = 0; i < bitpos(linesize * lines); i++) {
+ bool value_of_bit = data.back() & (1 << i);
+ if (!value_of_bit)
+ return false;
+ }
+ return true;
+ }
+
+ inline void copy(Bitmap &to) const {
+ assert(to.linesize == linesize && to.lines == lines);
+ to.data = data;
+ }
+};
/* Fill in RGB values for transparent pixels, to correct for odd colors
* appearing at borders when blending. This is because many PNG optimizers
* like to discard RGB values of transparent pixels, but when blending then
- * with non-transparent neighbors, their RGB values will shpw up nonetheless.
+ * with non-transparent neighbors, their RGB values will show up nonetheless.
*
* This function modifies the original image in-place.
*
* Parameter "threshold" is the alpha level below which pixels are considered
- * transparent. Should be 127 for 3d where alpha is threshold, but 0 for
- * 2d where alpha is blended.
+ * transparent. Should be 127 when the texture is used with ALPHA_CHANNEL_REF,
+ * 0 when alpha blending is used.
*/
void imageCleanTransparent(video::IImage *src, u32 threshold)
{
core::dimension2d<u32> dim = src->getDimension();
- // Walk each pixel looking for fully transparent ones.
+ Bitmap bitmap(dim.Width, dim.Height);
+
+ // First pass: Mark all opaque pixels
// Note: loop y around x for better cache locality.
for (u32 ctry = 0; ctry < dim.Height; ctry++)
for (u32 ctrx = 0; ctrx < dim.Width; ctrx++) {
+ if (src->getPixel(ctrx, ctry).getAlpha() > threshold)
+ bitmap.set(ctrx, ctry);
+ }
+
+ // Exit early if all pixels opaque
+ if (bitmap.all())
+ return;
+
+ Bitmap newmap = bitmap;
+
+ // Cap iterations to keep runtime reasonable, for higher-res textures we can
+ // get away with filling less pixels.
+ int iter_max = 11 - std::max(dim.Width, dim.Height) / 16;
+ iter_max = std::max(iter_max, 2);
- // Ignore opaque pixels.
- irr::video::SColor c = src->getPixel(ctrx, ctry);
- if (c.getAlpha() > threshold)
+ // Then repeatedly look for transparent pixels, filling them in until
+ // we're finished.
+ for (int iter = 0; iter < iter_max; iter++) {
+
+ for (u32 ctry = 0; ctry < dim.Height; ctry++)
+ for (u32 ctrx = 0; ctrx < dim.Width; ctrx++) {
+ // Skip pixels we have already processed
+ if (bitmap.get(ctrx, ctry))
continue;
- // Sample size and total weighted r, g, b values.
+ video::SColor c = src->getPixel(ctrx, ctry);
+
+ // Sample size and total weighted r, g, b values
u32 ss = 0, sr = 0, sg = 0, sb = 0;
- // Walk each neighbor pixel (clipped to image bounds).
+ // Walk each neighbor pixel (clipped to image bounds)
for (u32 sy = (ctry < 1) ? 0 : (ctry - 1);
sy <= (ctry + 1) && sy < dim.Height; sy++)
for (u32 sx = (ctrx < 1) ? 0 : (ctrx - 1);
sx <= (ctrx + 1) && sx < dim.Width; sx++) {
-
- // Ignore transparent pixels.
- irr::video::SColor d = src->getPixel(sx, sy);
- if (d.getAlpha() <= threshold)
+ // Ignore pixels we haven't processed
+ if (!bitmap.get(sx, sy))
continue;
-
- // Add RGB values weighted by alpha.
- u32 a = d.getAlpha();
+
+ // 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);
+ u32 a = d.getAlpha() <= threshold ? 255 : d.getAlpha();
ss += a;
sr += a * d.getRed();
sg += a * d.getGreen();
sb += a * d.getBlue();
}
- // If we found any neighbor RGB data, set pixel to average
- // weighted by alpha.
+ // Set pixel to average weighted by alpha
if (ss > 0) {
c.setRed(sr / ss);
c.setGreen(sg / ss);
c.setBlue(sb / ss);
src->setPixel(ctrx, ctry, c);
+ newmap.set(ctrx, ctry);
}
}
+
+ if (newmap.all())
+ return;
+
+ // Apply changes to bitmap for next run. This is done so we don't introduce
+ // a bias in color propagation in the direction pixels are processed.
+ newmap.copy(bitmap);
+
+ }
}
/* Scale a region of an image into another image, using nearest-neighbor with
diff --git a/src/client/imagefilters.h b/src/client/imagefilters.h
index 5676faf85..c9bdefbb6 100644
--- a/src/client/imagefilters.h
+++ b/src/client/imagefilters.h
@@ -23,13 +23,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
/* Fill in RGB values for transparent pixels, to correct for odd colors
* appearing at borders when blending. This is because many PNG optimizers
* like to discard RGB values of transparent pixels, but when blending then
- * with non-transparent neighbors, their RGB values will shpw up nonetheless.
+ * with non-transparent neighbors, their RGB values will show up nonetheless.
*
* This function modifies the original image in-place.
*
* Parameter "threshold" is the alpha level below which pixels are considered
- * transparent. Should be 127 for 3d where alpha is threshold, but 0 for
- * 2d where alpha is blended.
+ * transparent. Should be 127 when the texture is used with ALPHA_CHANNEL_REF,
+ * 0 when alpha blending is used.
*/
void imageCleanTransparent(video::IImage *src, u32 threshold);
diff --git a/src/client/inputhandler.cpp b/src/client/inputhandler.cpp
index 978baa320..a6ba87e8d 100644
--- a/src/client/inputhandler.cpp
+++ b/src/client/inputhandler.cpp
@@ -35,7 +35,7 @@ void KeyCache::populate()
key[KeyType::LEFT] = getKeySetting("keymap_left");
key[KeyType::RIGHT] = getKeySetting("keymap_right");
key[KeyType::JUMP] = getKeySetting("keymap_jump");
- key[KeyType::SPECIAL1] = getKeySetting("keymap_special1");
+ key[KeyType::AUX1] = getKeySetting("keymap_aux1");
key[KeyType::SNEAK] = getKeySetting("keymap_sneak");
key[KeyType::DIG] = getKeySetting("keymap_dig");
key[KeyType::PLACE] = getKeySetting("keymap_place");
@@ -60,6 +60,7 @@ void KeyCache::populate()
key[KeyType::DEC_VOLUME] = getKeySetting("keymap_decrease_volume");
key[KeyType::CINEMATIC] = getKeySetting("keymap_cinematic");
key[KeyType::SCREENSHOT] = getKeySetting("keymap_screenshot");
+ key[KeyType::TOGGLE_BLOCK_BOUNDS] = getKeySetting("keymap_toggle_block_bounds");
key[KeyType::TOGGLE_HUD] = getKeySetting("keymap_toggle_hud");
key[KeyType::TOGGLE_CHAT] = getKeySetting("keymap_toggle_chat");
key[KeyType::TOGGLE_FOG] = getKeySetting("keymap_toggle_fog");
@@ -137,11 +138,8 @@ bool MyEventReceiver::OnEvent(const SEvent &event)
#endif
} else if (event.EventType == irr::EET_JOYSTICK_INPUT_EVENT) {
- /* TODO add a check like:
- if (event.JoystickEvent != joystick_we_listen_for)
- return false;
- */
- return joystick->handleEvent(event.JoystickEvent);
+ // joystick may be nullptr if game is launched with '--random-input' parameter
+ return joystick && joystick->handleEvent(event.JoystickEvent);
} else if (event.EventType == irr::EET_MOUSE_INPUT_EVENT) {
// Handle mouse events
KeyPress key;
@@ -219,7 +217,7 @@ void RandomInputHandler::step(float dtime)
{
static RandomInputHandlerSimData rnd_data[] = {
{ "keymap_jump", 0.0f, 40 },
- { "keymap_special1", 0.0f, 40 },
+ { "keymap_aux1", 0.0f, 40 },
{ "keymap_forward", 0.0f, 40 },
{ "keymap_left", 0.0f, 40 },
{ "keymap_dig", 0.0f, 30 },
@@ -242,4 +240,39 @@ void RandomInputHandler::step(float dtime)
}
}
mousepos += mousespeed;
+ static bool useJoystick = false;
+ {
+ static float counterUseJoystick = 0;
+ counterUseJoystick -= dtime;
+ if (counterUseJoystick < 0.0) {
+ counterUseJoystick = 5.0; // switch between joystick and keyboard direction input
+ useJoystick = !useJoystick;
+ }
+ }
+ if (useJoystick) {
+ static float counterMovement = 0;
+ counterMovement -= dtime;
+ if (counterMovement < 0.0) {
+ counterMovement = 0.1 * Rand(1, 40);
+ movementSpeed = Rand(0,100)*0.01;
+ movementDirection = Rand(-100, 100)*0.01 * M_PI;
+ }
+ } else {
+ bool f = keydown[keycache.key[KeyType::FORWARD]],
+ l = keydown[keycache.key[KeyType::LEFT]];
+ if (f || l) {
+ movementSpeed = 1.0f;
+ if (f && !l)
+ movementDirection = 0.0;
+ else if (!f && l)
+ movementDirection = -M_PI_2;
+ else if (f && l)
+ movementDirection = -M_PI_4;
+ else
+ movementDirection = 0.0;
+ } else {
+ movementSpeed = 0.0;
+ movementDirection = 0.0;
+ }
+ }
}
diff --git a/src/client/inputhandler.h b/src/client/inputhandler.h
index 1fb4cf0ec..3db105c51 100644
--- a/src/client/inputhandler.h
+++ b/src/client/inputhandler.h
@@ -152,8 +152,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()
{
@@ -189,8 +195,6 @@ public:
#endif
}
- s32 mouse_wheel = 0;
-
JoystickController *joystick = nullptr;
#ifdef HAVE_TOUCHSCREENGUI
@@ -198,6 +202,8 @@ public:
#endif
private:
+ s32 mouse_wheel = 0;
+
// The current state of keys
KeyList keyIsDown;
@@ -240,6 +246,9 @@ public:
virtual bool wasKeyReleased(GameKeyType k) = 0;
virtual bool cancelPressed() = 0;
+ virtual float getMovementSpeed() = 0;
+ virtual float getMovementDirection() = 0;
+
virtual void clearWasKeyPressed() {}
virtual void clearWasKeyReleased() {}
@@ -269,6 +278,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);
@@ -285,10 +300,52 @@ public:
{
return m_receiver->WasKeyReleased(keycache.key[k]) || joystick.wasKeyReleased(k);
}
+
+ virtual float getMovementSpeed()
+ {
+ bool f = m_receiver->IsKeyDown(keycache.key[KeyType::FORWARD]),
+ b = m_receiver->IsKeyDown(keycache.key[KeyType::BACKWARD]),
+ l = m_receiver->IsKeyDown(keycache.key[KeyType::LEFT]),
+ r = m_receiver->IsKeyDown(keycache.key[KeyType::RIGHT]);
+ if (f || b || l || r)
+ {
+ // if contradictory keys pressed, stay still
+ if (f && b && l && r)
+ return 0.0f;
+ else if (f && b && !l && !r)
+ return 0.0f;
+ else if (!f && !b && l && r)
+ return 0.0f;
+ return 1.0f; // If there is a keyboard event, assume maximum speed
+ }
+ return joystick.getMovementSpeed();
+ }
+
+ virtual float getMovementDirection()
+ {
+ float x = 0, z = 0;
+
+ /* Check keyboard for input */
+ if (m_receiver->IsKeyDown(keycache.key[KeyType::FORWARD]))
+ z += 1;
+ if (m_receiver->IsKeyDown(keycache.key[KeyType::BACKWARD]))
+ z -= 1;
+ if (m_receiver->IsKeyDown(keycache.key[KeyType::RIGHT]))
+ x += 1;
+ if (m_receiver->IsKeyDown(keycache.key[KeyType::LEFT]))
+ x -= 1;
+
+ if (x != 0 || z != 0) /* If there is a keyboard event, it takes priority */
+ return atan2(x, z);
+ else
+ return joystick.getMovementDirection();
+ }
+
virtual bool cancelPressed()
{
return wasKeyDown(KeyType::ESC) || m_receiver->WasKeyDown(CancelKey);
}
+
virtual void clearWasKeyPressed()
{
m_receiver->clearWasKeyPressed();
@@ -297,17 +354,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;
@@ -315,16 +376,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()
{
@@ -352,6 +415,8 @@ public:
virtual bool wasKeyPressed(GameKeyType k) { return false; }
virtual bool wasKeyReleased(GameKeyType k) { return false; }
virtual bool cancelPressed() { return false; }
+ virtual float getMovementSpeed() { return movementSpeed; }
+ virtual float getMovementDirection() { return movementDirection; }
virtual v2s32 getMousePos() { return mousepos; }
virtual void setMousePos(s32 x, s32 y) { mousepos = v2s32(x, y); }
@@ -365,4 +430,6 @@ private:
KeyList keydown;
v2s32 mousepos;
v2s32 mousespeed;
+ float movementSpeed;
+ float movementDirection;
};
diff --git a/src/client/joystick_controller.cpp b/src/client/joystick_controller.cpp
index f61ae4ae6..aae73c62d 100644
--- a/src/client/joystick_controller.cpp
+++ b/src/client/joystick_controller.cpp
@@ -79,7 +79,7 @@ JoystickLayout create_default_layout()
// Accessible without any modifier pressed
JLO_B_PB(KeyType::JUMP, bm | 1 << 0, 1 << 0);
- JLO_B_PB(KeyType::SPECIAL1, bm | 1 << 1, 1 << 1);
+ JLO_B_PB(KeyType::AUX1, bm | 1 << 1, 1 << 1);
// Accessible with start button not pressed, but four pressed
// TODO find usage for button 0
@@ -126,11 +126,11 @@ JoystickLayout create_xbox_layout()
// 4 Buttons
JLO_B_PB(KeyType::JUMP, 1 << 0, 1 << 0); // A/green
JLO_B_PB(KeyType::ESC, 1 << 1, 1 << 1); // B/red
- JLO_B_PB(KeyType::SPECIAL1, 1 << 2, 1 << 2); // X/blue
+ JLO_B_PB(KeyType::AUX1, 1 << 2, 1 << 2); // X/blue
JLO_B_PB(KeyType::INVENTORY, 1 << 3, 1 << 3); // Y/yellow
// Analog Sticks
- JLO_B_PB(KeyType::SPECIAL1, 1 << 11, 1 << 11); // left
+ JLO_B_PB(KeyType::AUX1, 1 << 11, 1 << 11); // left
JLO_B_PB(KeyType::SNEAK, 1 << 12, 1 << 12); // right
// Triggers
@@ -154,12 +154,61 @@ 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"))
{
for (float &i : m_past_pressed_time) {
i = 0;
}
+ m_layout.axes_deadzone = 0;
clear();
}
@@ -187,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();
}
@@ -251,10 +302,27 @@ void JoystickController::clear()
memset(m_axes_vals, 0, sizeof(m_axes_vals));
}
-s16 JoystickController::getAxisWithoutDead(JoystickAxis axis)
+float JoystickController::getAxisWithoutDead(JoystickAxis axis)
{
s16 v = m_axes_vals[axis];
+
if (abs(v) < m_layout.axes_deadzone)
- return 0;
- return v;
+ return 0.0f;
+
+ v += (v < 0 ? m_layout.axes_deadzone : -m_layout.axes_deadzone);
+
+ return (float)v / ((float)(INT16_MAX - m_layout.axes_deadzone));
+}
+
+float JoystickController::getMovementDirection()
+{
+ return atan2(getAxisWithoutDead(JA_SIDEWARD_MOVE), -getAxisWithoutDead(JA_FORWARD_MOVE));
+}
+
+float JoystickController::getMovementSpeed()
+{
+ float speed = sqrt(pow(getAxisWithoutDead(JA_FORWARD_MOVE), 2) + pow(getAxisWithoutDead(JA_SIDEWARD_MOVE), 2));
+ if (speed > 1.0f)
+ speed = 1.0f;
+ return speed;
}
diff --git a/src/client/joystick_controller.h b/src/client/joystick_controller.h
index 3f361e4ef..cbc60886c 100644
--- a/src/client/joystick_controller.h
+++ b/src/client/joystick_controller.h
@@ -144,7 +144,10 @@ public:
return m_axes_vals[axis];
}
- s16 getAxisWithoutDead(JoystickAxis axis);
+ float getAxisWithoutDead(JoystickAxis axis);
+
+ float getMovementDirection();
+ float getMovementSpeed();
f32 doubling_dtime;
diff --git a/src/client/keycode.cpp b/src/client/keycode.cpp
index ce5214f54..fac077f0f 100644
--- a/src/client/keycode.cpp
+++ b/src/client/keycode.cpp
@@ -197,7 +197,6 @@ static const struct table_key table[] = {
DEFINEKEY1(KEY_MODECHANGE, N_("IME Mode Change"))
DEFINEKEY1(KEY_APPS, N_("Apps"))
DEFINEKEY1(KEY_SLEEP, N_("Sleep"))
-#if !(IRRLICHT_VERSION_MAJOR <= 1 && IRRLICHT_VERSION_MINOR <= 7 && IRRLICHT_VERSION_REVISION < 3)
DEFINEKEY1(KEY_OEM_1, "OEM 1") // KEY_OEM_[0-9] and KEY_OEM_102 are assigned to multiple
DEFINEKEY1(KEY_OEM_2, "OEM 2") // different chars (on different platforms too) and thus w/o char
DEFINEKEY1(KEY_OEM_3, "OEM 3")
@@ -208,7 +207,6 @@ static const struct table_key table[] = {
DEFINEKEY1(KEY_OEM_8, "OEM 8")
DEFINEKEY1(KEY_OEM_AX, "OEM AX")
DEFINEKEY1(KEY_OEM_102, "OEM 102")
-#endif
DEFINEKEY1(KEY_ATTN, "Attn")
DEFINEKEY1(KEY_CRSEL, "CrSel")
DEFINEKEY1(KEY_EXSEL, "ExSel")
diff --git a/src/client/keys.h b/src/client/keys.h
index 60a7a3c45..e120a2d92 100644
--- a/src/client/keys.h
+++ b/src/client/keys.h
@@ -32,7 +32,7 @@ public:
LEFT,
RIGHT,
JUMP,
- SPECIAL1,
+ AUX1,
SNEAK,
AUTOFORWARD,
DIG,
@@ -59,6 +59,7 @@ public:
DEC_VOLUME,
CINEMATIC,
SCREENSHOT,
+ TOGGLE_BLOCK_BOUNDS,
TOGGLE_HUD,
TOGGLE_CHAT,
TOGGLE_FOG,
diff --git a/src/client/localplayer.cpp b/src/client/localplayer.cpp
index f3eb1a2dd..4f1ea7bda 100644
--- a/src/client/localplayer.cpp
+++ b/src/client/localplayer.cpp
@@ -227,8 +227,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;
}
@@ -238,8 +239,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;
}
@@ -252,7 +254,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;
}
@@ -566,23 +568,7 @@ void LocalPlayer::applyControl(float dtime, Environment *env)
}
}
- if (control.up)
- speedH += v3f(0.0f, 0.0f, 1.0f);
-
- if (control.down)
- speedH -= v3f(0.0f, 0.0f, 1.0f);
-
- if (!control.up && !control.down)
- speedH -= v3f(0.0f, 0.0f, 1.0f) * (control.forw_move_joystick_axis / 32767.f);
-
- if (control.left)
- speedH += v3f(-1.0f, 0.0f, 0.0f);
-
- if (control.right)
- speedH += v3f(1.0f, 0.0f, 0.0f);
-
- if (!control.left && !control.right)
- speedH += v3f(1.0f, 0.0f, 0.0f) * (control.sidew_move_joystick_axis / 32767.f);
+ speedH = v3f(sin(control.movement_direction), 0.0f, cos(control.movement_direction));
if (m_autojump) {
// release autojump after a given time
@@ -639,6 +625,8 @@ void LocalPlayer::applyControl(float dtime, Environment *env)
else
speedH = speedH.normalize() * movement_speed_walk;
+ speedH *= control.movement_speed; /* Apply analog input */
+
// Acceleration increase
f32 incH = 0.0f; // Horizontal (X, Z)
f32 incV = 0.0f; // Vertical (Y)
@@ -814,8 +802,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;
}
@@ -824,8 +813,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;
}
@@ -837,7 +827,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;
@@ -1106,12 +1096,8 @@ void LocalPlayer::handleAutojump(f32 dtime, Environment *env,
if (m_autojump)
return;
- bool control_forward = control.up ||
- (!control.up && !control.down &&
- control.forw_move_joystick_axis < -0.05f);
-
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 345aec9d9..577be2803 100644
--- a/src/client/localplayer.h
+++ b/src/client/localplayer.h
@@ -55,8 +55,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;
@@ -86,7 +86,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;
diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp
index 167e1e3ec..249a56087 100644
--- a/src/client/mapblock_mesh.cpp
+++ b/src/client/mapblock_mesh.cpp
@@ -407,20 +407,20 @@ static void getNodeTextureCoords(v3f base, const v3f &scale, const v3s16 &dir, f
if (dir.X > 0 || dir.Y != 0 || dir.Z < 0)
base -= scale;
if (dir == v3s16(0,0,1)) {
- *u = -base.X - 1;
- *v = -base.Y - 1;
+ *u = -base.X;
+ *v = -base.Y;
} else if (dir == v3s16(0,0,-1)) {
*u = base.X + 1;
- *v = -base.Y - 2;
+ *v = -base.Y - 1;
} else if (dir == v3s16(1,0,0)) {
*u = base.Z + 1;
- *v = -base.Y - 2;
- } else if (dir == v3s16(-1,0,0)) {
- *u = -base.Z - 1;
*v = -base.Y - 1;
+ } else if (dir == v3s16(-1,0,0)) {
+ *u = -base.Z;
+ *v = -base.Y;
} else if (dir == v3s16(0,1,0)) {
*u = base.X + 1;
- *v = -base.Z - 2;
+ *v = -base.Z - 1;
} else if (dir == v3s16(0,-1,0)) {
*u = base.X + 1;
*v = base.Z + 1;
@@ -860,6 +860,9 @@ static void updateFastFaceRow(
g_settings->getBool("enable_shaders") &&
g_settings->getBool("enable_waving_water");
+ static thread_local const bool force_not_tiling =
+ false && g_settings->getBool("enable_dynamic_shadows");
+
v3s16 p = startpos;
u16 continuous_tiles_count = 1;
@@ -898,7 +901,8 @@ static void updateFastFaceRow(
waving,
next_tile);
- if (next_makes_face == makes_face
+ if (!force_not_tiling
+ && next_makes_face == makes_face
&& next_p_corrected == p_corrected + translate_dir
&& next_face_dir_corrected == face_dir_corrected
&& memcmp(next_lights, lights, sizeof(lights)) == 0
@@ -1072,8 +1076,8 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
*/
{
- MapblockMeshGenerator generator(data, &collector);
- generator.generate();
+ MapblockMeshGenerator(data, &collector,
+ data->m_client->getSceneManager()->getMeshManipulator()).generate();
}
/*
diff --git a/src/client/mesh.cpp b/src/client/mesh.cpp
index 2400a374c..c56eba2e2 100644
--- a/src/client/mesh.cpp
+++ b/src/client/mesh.cpp
@@ -27,14 +27,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <SAnimatedMesh.h>
#include <IAnimatedMeshSceneNode.h>
-// In Irrlicht 1.8 the signature of ITexture::lock was changed from
-// (bool, u32) to (E_TEXTURE_LOCK_MODE, u32).
-#if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 7
-#define MY_ETLM_READ_ONLY true
-#else
-#define MY_ETLM_READ_ONLY video::ETLM_READ_ONLY
-#endif
-
inline static void applyShadeFactor(video::SColor& color, float factor)
{
color.setRed(core::clamp(core::round32(color.getRed()*factor), 0, 255));
@@ -506,592 +498,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 4371b8390..1b734bc06 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 dd810ee0a..f26aa1c70 100644
--- a/src/client/minimap.cpp
+++ b/src/client/minimap.cpp
@@ -491,7 +491,8 @@ video::ITexture *Minimap::getMinimapTexture()
// Want to use texture source, to : 1 find texture, 2 cache it
video::ITexture* texture = m_tsrc->getTexture(data->mode.texture);
video::IImage* image = driver->createImageFromData(
- texture->getColorFormat(), texture->getSize(), texture->lock(), true, false);
+ texture->getColorFormat(), texture->getSize(),
+ texture->lock(video::ETLM_READ_ONLY), true, false);
texture->unlock();
auto dim = image->getDimension();
@@ -576,7 +577,7 @@ scene::SMeshBuffer *Minimap::getMinimapMeshBuffer()
void Minimap::drawMinimap()
{
// Non hud managed minimap drawing (legacy minimap)
- v2u32 screensize = RenderingEngine::get_instance()->getWindowSize();
+ v2u32 screensize = RenderingEngine::getWindowSize();
const u32 size = 0.25 * screensize.Y;
drawMinimap(core::rect<s32>(
diff --git a/src/client/particles.cpp b/src/client/particles.cpp
index 7acd996dc..288826a5f 100644
--- a/src/client/particles.cpp
+++ b/src/client/particles.cpp
@@ -64,8 +64,8 @@ Particle::Particle(
v2f texsize,
video::SColor color
):
- scene::ISceneNode(RenderingEngine::get_scene_manager()->getRootSceneNode(),
- RenderingEngine::get_scene_manager())
+ scene::ISceneNode(((Client *)gamedef)->getSceneManager()->getRootSceneNode(),
+ ((Client *)gamedef)->getSceneManager())
{
// Misc
m_gamedef = gamedef;
diff --git a/src/client/render/anaglyph.cpp b/src/client/render/anaglyph.cpp
index 9ba4464a2..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 |
@@ -40,7 +41,7 @@ void RenderingCoreAnaglyph::setupMaterial(int color_mask)
void RenderingCoreAnaglyph::useEye(bool right)
{
RenderingCoreStereo::useEye(right);
- driver->clearZBuffer();
+ driver->clearBuffers(video::ECBF_DEPTH);
setupMaterial(right ? video::ECP_GREEN | video::ECP_BLUE : video::ECP_RED);
}
diff --git a/src/client/render/core.cpp b/src/client/render/core.cpp
index 92a7137ea..44ef1c98c 100644
--- a/src/client/render/core.cpp
+++ b/src/client/render/core.cpp
@@ -24,25 +24,35 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "client/clientmap.h"
#include "client/hud.h"
#include "client/minimap.h"
+#include "client/shadows/dynamicshadowsrender.h"
RenderingCore::RenderingCore(IrrlichtDevice *_device, Client *_client, Hud *_hud)
: device(_device), driver(device->getVideoDriver()), smgr(device->getSceneManager()),
guienv(device->getGUIEnvironment()), client(_client), camera(client->getCamera()),
- mapper(client->getMinimap()), hud(_hud)
+ mapper(client->getMinimap()), hud(_hud),
+ shadow_renderer(nullptr)
{
screensize = driver->getScreenSize();
virtual_size = screensize;
+
+ if (g_settings->getBool("enable_shaders") &&
+ false && g_settings->getBool("enable_dynamic_shadows")) {
+ shadow_renderer = new ShadowRenderer(device, client);
+ }
}
RenderingCore::~RenderingCore()
{
clearTextures();
+ delete shadow_renderer;
}
void RenderingCore::initialize()
{
// have to be called late as the VMT is not ready in the constructor:
initTextures();
+ if (shadow_renderer)
+ shadow_renderer->initialize();
}
void RenderingCore::updateScreenSize()
@@ -66,6 +76,9 @@ void RenderingCore::draw(video::SColor _skycolor, bool _show_hud, bool _show_min
draw_wield_tool = _draw_wield_tool;
draw_crosshair = _draw_crosshair;
+ if (shadow_renderer)
+ shadow_renderer->update();
+
beforeDraw();
drawAll();
}
@@ -73,9 +86,13 @@ void RenderingCore::draw(video::SColor _skycolor, bool _show_hud, bool _show_min
void RenderingCore::draw3D()
{
smgr->drawAll();
+ if (shadow_renderer)
+ shadow_renderer->drawDebug();
+
driver->setTransform(video::ETS_WORLD, core::IdentityMatrix);
if (!show_hud)
return;
+ hud->drawBlockBounds();
hud->drawSelectionMesh();
if (draw_wield_tool)
camera->drawWieldedTool();
diff --git a/src/client/render/core.h b/src/client/render/core.h
index 52ea8f99f..cabfbbfad 100644
--- a/src/client/render/core.h
+++ b/src/client/render/core.h
@@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#pragma once
#include "irrlichttypes_extrabloated.h"
+class ShadowRenderer;
class Camera;
class Client;
class Hud;
@@ -47,6 +48,8 @@ protected:
Minimap *mapper;
Hud *hud;
+ ShadowRenderer *shadow_renderer;
+
void updateScreenSize();
virtual void initTextures() {}
virtual void clearTextures() {}
@@ -72,4 +75,6 @@ public:
bool _draw_wield_tool, bool _draw_crosshair);
inline v2u32 getVirtualSize() const { return virtual_size; }
+
+ ShadowRenderer *get_shadow_renderer() { return shadow_renderer; };
};
diff --git a/src/client/render/interlaced.cpp b/src/client/render/interlaced.cpp
index ce8e92f21..3f79a8eb5 100644
--- a/src/client/render/interlaced.cpp
+++ b/src/client/render/interlaced.cpp
@@ -35,7 +35,11 @@ void RenderingCoreInterlaced::initMaterial()
IShaderSource *s = client->getShaderSource();
mat.UseMipMaps = false;
mat.ZBuffer = false;
+#if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR > 8
+ mat.ZWriteEnable = video::EZW_OFF;
+#else
mat.ZWriteEnable = false;
+#endif
u32 shader = s->getShader("3d_interlaced_merge", TILE_MATERIAL_BASIC);
mat.MaterialType = s->getShaderInfo(shader).material;
for (int k = 0; k < 3; ++k) {
diff --git a/src/client/renderingengine.cpp b/src/client/renderingengine.cpp
index 99ff8c1ee..723865db4 100644
--- a/src/client/renderingengine.cpp
+++ b/src/client/renderingengine.cpp
@@ -19,7 +19,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
*/
#include <IrrlichtDevice.h>
-#include <irrlicht.h>
#include "fontengine.h"
#include "client.h"
#include "clouds.h"
@@ -92,7 +91,6 @@ RenderingEngine::RenderingEngine(IEventReceiver *receiver)
// bpp, fsaa, vsync
bool vsync = g_settings->getBool("vsync");
- u16 bits = g_settings->getU16("fullscreen_bpp");
u16 fsaa = g_settings->getU16("fsaa");
// stereo buffer required for pageflip stereo
@@ -106,7 +104,7 @@ RenderingEngine::RenderingEngine(IEventReceiver *receiver)
u32 i;
for (i = 0; i != drivers.size(); i++) {
if (!strcasecmp(driverstring.c_str(),
- RenderingEngine::getVideoDriverName(drivers[i]))) {
+ RenderingEngine::getVideoDriverInfo(drivers[i]).name.c_str())) {
driverType = drivers[i];
break;
}
@@ -118,17 +116,17 @@ RenderingEngine::RenderingEngine(IEventReceiver *receiver)
}
SIrrlichtCreationParameters params = SIrrlichtCreationParameters();
+ if (g_logger.getTraceEnabled())
+ params.LoggingLevel = irr::ELL_DEBUG;
params.DriverType = driverType;
params.WindowSize = core::dimension2d<u32>(screen_w, screen_h);
- params.Bits = bits;
params.AntiAlias = fsaa;
params.Fullscreen = fullscreen;
params.Stencilbuffer = false;
params.Stereobuffer = stereo_buffer;
params.Vsync = vsync;
params.EventReceiver = receiver;
- params.HighPrecisionFPU = g_settings->getBool("high_precision_fpu");
- params.ZBufferBits = 24;
+ params.HighPrecisionFPU = true;
#ifdef __ANDROID__
params.PrivateData = porting::app_global;
#endif
@@ -157,7 +155,7 @@ RenderingEngine::~RenderingEngine()
s_singleton = nullptr;
}
-v2u32 RenderingEngine::getWindowSize() const
+v2u32 RenderingEngine::_getWindowSize() const
{
if (core)
return core->getVirtualSize();
@@ -169,58 +167,18 @@ void RenderingEngine::setResizable(bool resize)
m_device->setResizable(resize);
}
-bool RenderingEngine::print_video_modes()
+void RenderingEngine::removeMesh(const scene::IMesh* mesh)
{
- IrrlichtDevice *nulldevice;
-
- bool vsync = g_settings->getBool("vsync");
- u16 fsaa = g_settings->getU16("fsaa");
- MyEventReceiver *receiver = new MyEventReceiver();
-
- SIrrlichtCreationParameters params = SIrrlichtCreationParameters();
- params.DriverType = video::EDT_NULL;
- params.WindowSize = core::dimension2d<u32>(640, 480);
- params.Bits = 24;
- params.AntiAlias = fsaa;
- params.Fullscreen = false;
- params.Stencilbuffer = false;
- params.Vsync = vsync;
- params.EventReceiver = receiver;
- params.HighPrecisionFPU = g_settings->getBool("high_precision_fpu");
-
- nulldevice = createDeviceEx(params);
-
- if (!nulldevice) {
- delete receiver;
- return false;
- }
-
- std::cout << _("Available video modes (WxHxD):") << std::endl;
-
- video::IVideoModeList *videomode_list = nulldevice->getVideoModeList();
-
- if (videomode_list != NULL) {
- s32 videomode_count = videomode_list->getVideoModeCount();
- core::dimension2d<u32> videomode_res;
- s32 videomode_depth;
- for (s32 i = 0; i < videomode_count; ++i) {
- videomode_res = videomode_list->getVideoModeResolution(i);
- videomode_depth = videomode_list->getVideoModeDepth(i);
- std::cout << videomode_res.Width << "x" << videomode_res.Height
- << "x" << videomode_depth << std::endl;
- }
+ m_device->getSceneManager()->getMeshCache()->removeMesh(mesh);
+}
- std::cout << _("Active video mode (WxHxD):") << std::endl;
- videomode_res = videomode_list->getDesktopResolution();
- videomode_depth = videomode_list->getDesktopDepth();
- std::cout << videomode_res.Width << "x" << videomode_res.Height << "x"
- << videomode_depth << std::endl;
+void RenderingEngine::cleanupMeshCache()
+{
+ auto mesh_cache = m_device->getSceneManager()->getMeshCache();
+ while (mesh_cache->getMeshCount() != 0) {
+ if (scene::IAnimatedMesh *mesh = mesh_cache->getMeshByIndex(0))
+ mesh_cache->removeMesh(mesh);
}
-
- nulldevice->drop();
- delete receiver;
-
- return videomode_list != NULL;
}
bool RenderingEngine::setupTopLevelWindow(const std::string &name)
@@ -294,7 +252,7 @@ void RenderingEngine::setupTopLevelXorgWindow(const std::string &name)
// force a shutdown of an application if it doesn't respond to the destroy
// window message.
- verbosestream << "Client: Setting Xorg _NET_WM_PID extened window manager property"
+ verbosestream << "Client: Setting Xorg _NET_WM_PID extended window manager property"
<< std::endl;
Atom NET_WM_PID = XInternAtom(x11_dpl, "_NET_WM_PID", false);
@@ -325,12 +283,10 @@ static bool getWindowHandle(irr::video::IVideoDriver *driver, HWND &hWnd)
const video::SExposedVideoData exposedData = driver->getExposedVideoData();
switch (driver->getDriverType()) {
- case video::EDT_DIRECT3D8:
- hWnd = reinterpret_cast<HWND>(exposedData.D3D8.HWnd);
- break;
- case video::EDT_DIRECT3D9:
- hWnd = reinterpret_cast<HWND>(exposedData.D3D9.HWnd);
- break;
+#if ENABLE_GLES
+ case video::EDT_OGLES1:
+ case video::EDT_OGLES2:
+#endif
case video::EDT_OPENGL:
hWnd = reinterpret_cast<HWND>(exposedData.OpenGLWin32.HWnd);
break;
@@ -471,11 +427,11 @@ bool RenderingEngine::setXorgWindowIconFromPath(const std::string &icon_file)
Text will be removed when the screen is drawn the next time.
Additionally, a progressbar can be drawn when percent is set between 0 and 100.
*/
-void RenderingEngine::_draw_load_screen(const std::wstring &text,
+void RenderingEngine::draw_load_screen(const std::wstring &text,
gui::IGUIEnvironment *guienv, ITextureSource *tsrc, float dtime,
int percent, bool clouds)
{
- v2u32 screensize = RenderingEngine::get_instance()->getWindowSize();
+ v2u32 screensize = getWindowSize();
v2s32 textsize(g_fontengine->getTextWidth(text), g_fontengine->getLineHeight());
v2s32 center(screensize.X / 2, screensize.Y / 2);
@@ -543,7 +499,7 @@ void RenderingEngine::_draw_load_screen(const std::wstring &text,
/*
Draws the menu scene including (optional) cloud background.
*/
-void RenderingEngine::_draw_menu_scene(gui::IGUIEnvironment *guienv,
+void RenderingEngine::draw_menu_scene(gui::IGUIEnvironment *guienv,
float dtime, bool clouds)
{
bool cloud_menu_background = clouds && g_settings->getBool("menu_clouds");
@@ -560,85 +516,53 @@ void RenderingEngine::_draw_menu_scene(gui::IGUIEnvironment *guienv,
get_video_driver()->endScene();
}
-std::vector<core::vector3d<u32>> RenderingEngine::getSupportedVideoModes()
-{
- IrrlichtDevice *nulldevice = createDevice(video::EDT_NULL);
- sanity_check(nulldevice);
-
- std::vector<core::vector3d<u32>> mlist;
- video::IVideoModeList *modelist = nulldevice->getVideoModeList();
-
- s32 num_modes = modelist->getVideoModeCount();
- for (s32 i = 0; i != num_modes; i++) {
- core::dimension2d<u32> mode_res = modelist->getVideoModeResolution(i);
- u32 mode_depth = (u32)modelist->getVideoModeDepth(i);
- mlist.emplace_back(mode_res.Width, mode_res.Height, mode_depth);
- }
-
- nulldevice->drop();
- return mlist;
-}
-
std::vector<irr::video::E_DRIVER_TYPE> RenderingEngine::getSupportedVideoDrivers()
{
+ // Only check these drivers.
+ // We do not support software and D3D in any capacity.
+ static const irr::video::E_DRIVER_TYPE glDrivers[4] = {
+ irr::video::EDT_NULL,
+ irr::video::EDT_OPENGL,
+ irr::video::EDT_OGLES1,
+ irr::video::EDT_OGLES2,
+ };
std::vector<irr::video::E_DRIVER_TYPE> drivers;
- for (int i = 0; i != irr::video::EDT_COUNT; i++) {
- if (irr::IrrlichtDevice::isDriverSupported((irr::video::E_DRIVER_TYPE)i))
- drivers.push_back((irr::video::E_DRIVER_TYPE)i);
+ for (int i = 0; i < 4; i++) {
+ if (irr::IrrlichtDevice::isDriverSupported(glDrivers[i]))
+ drivers.push_back(glDrivers[i]);
}
return drivers;
}
-void RenderingEngine::_initialize(Client *client, Hud *hud)
+void RenderingEngine::initialize(Client *client, Hud *hud)
{
const std::string &draw_mode = g_settings->get("3d_mode");
core.reset(createRenderingCore(draw_mode, m_device, client, hud));
core->initialize();
}
-void RenderingEngine::_finalize()
+void RenderingEngine::finalize()
{
core.reset();
}
-void RenderingEngine::_draw_scene(video::SColor skycolor, bool show_hud,
+void RenderingEngine::draw_scene(video::SColor skycolor, bool show_hud,
bool show_minimap, bool draw_wield_tool, bool draw_crosshair)
{
core->draw(skycolor, show_hud, show_minimap, draw_wield_tool, draw_crosshair);
}
-const char *RenderingEngine::getVideoDriverName(irr::video::E_DRIVER_TYPE type)
-{
- static const char *driver_ids[] = {
- "null",
- "software",
- "burningsvideo",
- "direct3d8",
- "direct3d9",
- "opengl",
- "ogles1",
- "ogles2",
- };
-
- return driver_ids[type];
-}
-
-const char *RenderingEngine::getVideoDriverFriendlyName(irr::video::E_DRIVER_TYPE type)
+const VideoDriverInfo &RenderingEngine::getVideoDriverInfo(irr::video::E_DRIVER_TYPE type)
{
- static const char *driver_names[] = {
- "NULL Driver",
- "Software Renderer",
- "Burning's Video",
- "Direct3D 8",
- "Direct3D 9",
- "OpenGL",
- "OpenGL ES1",
- "OpenGL ES2",
+ static const std::unordered_map<int, VideoDriverInfo> driver_info_map = {
+ {(int)video::EDT_NULL, {"null", "NULL Driver"}},
+ {(int)video::EDT_OPENGL, {"opengl", "OpenGL"}},
+ {(int)video::EDT_OGLES1, {"ogles1", "OpenGL ES1"}},
+ {(int)video::EDT_OGLES2, {"ogles2", "OpenGL ES2"}},
};
-
- return driver_names[type];
+ return driver_info_map.at((int)type);
}
#ifndef __ANDROID__
@@ -674,7 +598,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)
@@ -702,14 +626,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/renderingengine.h b/src/client/renderingengine.h
index 34cc60630..a0ddb0d9a 100644
--- a/src/client/renderingengine.h
+++ b/src/client/renderingengine.h
@@ -25,6 +25,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <string>
#include "irrlichttypes_extrabloated.h"
#include "debug.h"
+#include "client/render/core.h"
+// include the shadow mapper classes too
+#include "client/shadows/dynamicshadowsrender.h"
+
+struct VideoDriverInfo {
+ std::string name;
+ std::string friendly_name;
+};
class ITextureSource;
class Camera;
@@ -41,13 +49,11 @@ public:
RenderingEngine(IEventReceiver *eventReceiver);
~RenderingEngine();
- v2u32 getWindowSize() const;
void setResizable(bool resize);
video::IVideoDriver *getVideoDriver() { return driver; }
- static const char *getVideoDriverName(irr::video::E_DRIVER_TYPE type);
- static const char *getVideoDriverFriendlyName(irr::video::E_DRIVER_TYPE type);
+ static const VideoDriverInfo &getVideoDriverInfo(irr::video::E_DRIVER_TYPE type);
static float getDisplayDensity();
static v2u32 getDisplaySize();
@@ -56,31 +62,30 @@ public:
bool setWindowIcon();
bool setXorgWindowIconFromPath(const std::string &icon_file);
static bool print_video_modes();
+ void cleanupMeshCache();
- static RenderingEngine *get_instance() { return s_singleton; }
+ void removeMesh(const scene::IMesh* mesh);
- static io::IFileSystem *get_filesystem()
+ static v2u32 getWindowSize()
{
- sanity_check(s_singleton && s_singleton->m_device);
- return s_singleton->m_device->getFileSystem();
+ sanity_check(s_singleton);
+ return s_singleton->_getWindowSize();
}
- static video::IVideoDriver *get_video_driver()
+ io::IFileSystem *get_filesystem()
{
- sanity_check(s_singleton && s_singleton->m_device);
- return s_singleton->m_device->getVideoDriver();
+ return m_device->getFileSystem();
}
- static scene::IMeshCache *get_mesh_cache()
+ static video::IVideoDriver *get_video_driver()
{
sanity_check(s_singleton && s_singleton->m_device);
- return s_singleton->m_device->getSceneManager()->getMeshCache();
+ return s_singleton->m_device->getVideoDriver();
}
- static scene::ISceneManager *get_scene_manager()
+ scene::ISceneManager *get_scene_manager()
{
- sanity_check(s_singleton && s_singleton->m_device);
- return s_singleton->m_device->getSceneManager();
+ return m_device->getSceneManager();
}
static irr::IrrlichtDevice *get_raw_device()
@@ -89,70 +94,43 @@ public:
return s_singleton->m_device;
}
- static u32 get_timer_time()
+ u32 get_timer_time()
{
- sanity_check(s_singleton && s_singleton->m_device &&
- s_singleton->m_device->getTimer());
- return s_singleton->m_device->getTimer()->getTime();
+ return m_device->getTimer()->getTime();
}
- static gui::IGUIEnvironment *get_gui_env()
+ gui::IGUIEnvironment *get_gui_env()
{
- sanity_check(s_singleton && s_singleton->m_device);
- return s_singleton->m_device->getGUIEnvironment();
+ return m_device->getGUIEnvironment();
}
- inline static void draw_load_screen(const std::wstring &text,
+ void draw_load_screen(const std::wstring &text,
gui::IGUIEnvironment *guienv, ITextureSource *tsrc,
- float dtime = 0, int percent = 0, bool clouds = true)
- {
- s_singleton->_draw_load_screen(
- text, guienv, tsrc, dtime, percent, clouds);
- }
+ float dtime = 0, int percent = 0, bool clouds = true);
- inline static void draw_menu_scene(
- gui::IGUIEnvironment *guienv, float dtime, bool clouds)
- {
- s_singleton->_draw_menu_scene(guienv, dtime, clouds);
- }
+ void draw_menu_scene(gui::IGUIEnvironment *guienv, float dtime, bool clouds);
+ void draw_scene(video::SColor skycolor, bool show_hud,
+ bool show_minimap, bool draw_wield_tool, bool draw_crosshair);
- inline static void draw_scene(video::SColor skycolor, bool show_hud,
- bool show_minimap, bool draw_wield_tool, bool draw_crosshair)
- {
- s_singleton->_draw_scene(skycolor, show_hud, show_minimap,
- draw_wield_tool, draw_crosshair);
- }
+ void initialize(Client *client, Hud *hud);
+ void finalize();
- inline static void initialize(Client *client, Hud *hud)
+ bool run()
{
- s_singleton->_initialize(client, hud);
+ return m_device->run();
}
- inline static void finalize() { s_singleton->_finalize(); }
-
- static bool run()
+ // FIXME: this is still global when it shouldn't be
+ static ShadowRenderer *get_shadow_renderer()
{
- sanity_check(s_singleton && s_singleton->m_device);
- return s_singleton->m_device->run();
+ //if (s_singleton && s_singleton->core)
+ // return s_singleton->core->get_shadow_renderer();
+ return nullptr;
}
-
- static std::vector<core::vector3d<u32>> getSupportedVideoModes();
static std::vector<irr::video::E_DRIVER_TYPE> getSupportedVideoDrivers();
private:
- void _draw_load_screen(const std::wstring &text, gui::IGUIEnvironment *guienv,
- ITextureSource *tsrc, float dtime = 0, int percent = 0,
- bool clouds = true);
-
- void _draw_menu_scene(gui::IGUIEnvironment *guienv, float dtime = 0,
- bool clouds = true);
-
- void _draw_scene(video::SColor skycolor, bool show_hud, bool show_minimap,
- bool draw_wield_tool, bool draw_crosshair);
-
- void _initialize(Client *client, Hud *hud);
-
- void _finalize();
+ v2u32 _getWindowSize() const;
std::unique_ptr<RenderingCore> core;
irr::IrrlichtDevice *m_device = nullptr;
diff --git a/src/client/shader.cpp b/src/client/shader.cpp
index b3e4911f4..c04a25862 100644
--- a/src/client/shader.cpp
+++ b/src/client/shader.cpp
@@ -225,6 +225,16 @@ class MainShaderConstantSetter : public IShaderConstantSetter
{
CachedVertexShaderSetting<float, 16> m_world_view_proj;
CachedVertexShaderSetting<float, 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<s32> m_shadow_texture;
+
#if ENABLE_GLES
// Modelview matrix
CachedVertexShaderSetting<float, 16> m_world_view;
@@ -243,6 +253,13 @@ public:
, 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_shadow_texture("ShadowMapSampler")
{}
~MainShaderConstantSetter() = default;
@@ -280,6 +297,36 @@ public:
};
m_normal.set(m, services);
#endif
+
+ // Set uniforms for Shadow shader
+ if (ShadowRenderer *shadow = RenderingEngine::get_shadow_renderer()) {
+ const auto &light = shadow->getDirectionalLight();
+
+ core::matrix4 shadowViewProj = light.getProjectionMatrix();
+ shadowViewProj *= light.getViewMatrix();
+ m_shadow_view_proj.set(shadowViewProj.pointer(), services);
+
+ float v_LightDirection[3];
+ light.getDirection().getAs3Values(v_LightDirection);
+ m_light_direction.set(v_LightDirection, services);
+
+ float TextureResolution = light.getMapResolution();
+ m_texture_res.set(&TextureResolution, services);
+
+ float ShadowStrength = shadow->getShadowStrength();
+ m_shadow_strength.set(&ShadowStrength, services);
+
+ float timeOfDay = shadow->getTimeOfDay();
+ m_time_of_day.set(&timeOfDay, services);
+
+ float shadowFar = shadow->getMaxShadowFar();
+ m_shadowfar.set(&shadowFar, 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);
+ }
}
};
@@ -579,8 +626,10 @@ ShaderInfo ShaderSource::generateShader(const std::string &name,
if (use_gles) {
shaders_header << R"(
#version 100
- )";
+ )";
vertex_header = R"(
+ precision mediump float;
+
uniform highp mat4 mWorldView;
uniform highp mat4 mWorldViewProj;
uniform mediump mat4 mTexture;
@@ -592,17 +641,17 @@ ShaderInfo ShaderSource::generateShader(const std::string &name,
attribute mediump vec3 inVertexNormal;
attribute mediump vec4 inVertexTangent;
attribute mediump vec4 inVertexBinormal;
- )";
+ )";
fragment_header = R"(
precision mediump float;
- )";
+ )";
} else {
shaders_header << R"(
#version 120
#define lowp
#define mediump
#define highp
- )";
+ )";
vertex_header = R"(
#define mWorldView gl_ModelViewMatrix
#define mWorldViewProj gl_ModelViewProjectionMatrix
@@ -615,7 +664,7 @@ ShaderInfo ShaderSource::generateShader(const std::string &name,
#define inVertexNormal gl_Normal
#define inVertexTangent gl_MultiTexCoord1
#define inVertexBinormal gl_MultiTexCoord2
- )";
+ )";
}
bool use_discard = use_gles;
@@ -625,8 +674,12 @@ ShaderInfo ShaderSource::generateShader(const std::string &name,
if (strstr(gl_renderer, "GC7000"))
use_discard = true;
#endif
- if (use_discard && shaderinfo.base_material != video::EMT_SOLID)
- shaders_header << "#define USE_DISCARD 1\n";
+ if (use_discard) {
+ if (shaderinfo.base_material == video::EMT_TRANSPARENT_ALPHA_CHANNEL)
+ shaders_header << "#define USE_DISCARD 1\n";
+ else if (shaderinfo.base_material == video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF)
+ shaders_header << "#define USE_DISCARD_REF 1\n";
+ }
#define PROVIDE(constant) shaders_header << "#define " #constant " " << (int)constant << "\n"
@@ -680,6 +733,23 @@ ShaderInfo ShaderSource::generateShader(const std::string &name,
shaders_header << "#define FOG_START " << core::clamp(g_settings->getFloat("fog_start"), 0.0f, 0.99f) << "\n";
+ if (false && g_settings->getBool("enable_dynamic_shadows")) {
+ shaders_header << "#define ENABLE_DYNAMIC_SHADOWS 1\n";
+ if (g_settings->getBool("shadow_map_color"))
+ shaders_header << "#define COLORED_SHADOWS 1\n";
+
+ if (g_settings->getBool("shadow_poisson_filter"))
+ shaders_header << "#define POISSON_FILTER 1\n";
+
+ s32 shadow_filter = g_settings->getS32("shadow_filters");
+ shaders_header << "#define SHADOW_FILTER " << shadow_filter << "\n";
+
+ float shadow_soft_radius = g_settings->getFloat("shadow_soft_radius");
+ if (shadow_soft_radius < 1.0f)
+ shadow_soft_radius = 1.0f;
+ shaders_header << "#define SOFTSHADOWRADIUS " << shadow_soft_radius << "\n";
+ }
+
std::string common_header = shaders_header.str();
std::string vertex_shader = m_sourcecache.getOrLoad(name, "opengl_vertex.glsl");
diff --git a/src/client/shader.h b/src/client/shader.h
index 38ab76704..49a563115 100644
--- a/src/client/shader.h
+++ b/src/client/shader.h
@@ -96,9 +96,10 @@ public:
if (has_been_set && std::equal(m_sent, m_sent + count, value))
return;
if (is_pixel)
- services->setPixelShaderConstant(m_name, value, count);
+ services->setPixelShaderConstant(services->getPixelShaderConstantID(m_name), value, count);
else
- services->setVertexShaderConstant(m_name, value, count);
+ services->setVertexShaderConstant(services->getVertexShaderConstantID(m_name), value, count);
+
std::copy(value, value + count, m_sent);
has_been_set = true;
}
diff --git a/src/client/shadows/dynamicshadows.cpp b/src/client/shadows/dynamicshadows.cpp
new file mode 100644
index 000000000..6ef5a4f1d
--- /dev/null
+++ b/src/client/shadows/dynamicshadows.cpp
@@ -0,0 +1,165 @@
+/*
+Minetest
+Copyright (C) 2021 Liso <anlismon@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include <cmath>
+
+#include "client/shadows/dynamicshadows.h"
+#include "client/client.h"
+#include "client/clientenvironment.h"
+#include "client/clientmap.h"
+#include "client/camera.h"
+
+using m4f = core::matrix4;
+
+void DirectionalLight::createSplitMatrices(const Camera *cam)
+{
+ float radius;
+ v3f newCenter;
+ v3f look = cam->getDirection();
+
+ // camera view tangents
+ float tanFovY = tanf(cam->getFovY() * 0.5f);
+ float tanFovX = tanf(cam->getFovX() * 0.5f);
+
+ // adjusted frustum boundaries
+ float sfNear = future_frustum.zNear;
+ 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;
+
+ // 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);
+
+ // 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;
+ // Compute the frustumBoundingSphere radius
+ v3f boundVec = (camPos + farCorner * sfFar) - newCenter;
+ radius = boundVec.getLength() * 2.0f;
+ // boundVec.getLength();
+ float vvolume = radius * 2.0f;
+
+ v3f frustumCenter = newCenter;
+ // probar radius multipliacdor en funcion del I, a menor I mas multiplicador
+ v3f eye_displacement = direction * vvolume;
+
+ // 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);
+ future_frustum.camera_offset = cam->getOffset();
+}
+
+DirectionalLight::DirectionalLight(const u32 shadowMapResolution,
+ const v3f &position, video::SColorf lightColor,
+ f32 farValue) :
+ diffuseColor(lightColor),
+ farPlane(farValue), mapRes(shadowMapResolution), pos(position)
+{}
+
+void DirectionalLight::update_frustum(const Camera *cam, Client *client, bool force)
+{
+ if (dirty && !force)
+ return;
+
+ float zNear = cam->getCameraNode()->getNearValue();
+ float zFar = getMaxFarValue();
+
+ ///////////////////////////////////
+ // update splits near and fars
+ future_frustum.zNear = zNear;
+ future_frustum.zFar = zFar;
+
+ // update shadow frustum
+ createSplitMatrices(cam);
+ // get the draw list for shadows
+ client->getEnv().getClientMap().updateDrawListShadow(
+ getPosition(), getDirection(), future_frustum.length);
+ should_update_map_shadow = true;
+ dirty = true;
+
+ // when camera offset changes, adjust the current frustum view matrix to avoid flicker
+ v3s16 cam_offset = cam->getOffset();
+ if (cam_offset != shadow_frustum.camera_offset) {
+ 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.camera_offset = cam_offset;
+ }
+}
+
+void DirectionalLight::commitFrustum()
+{
+ if (!dirty)
+ return;
+
+ shadow_frustum = future_frustum;
+ dirty = false;
+}
+
+void DirectionalLight::setDirection(v3f dir)
+{
+ direction = -dir;
+ direction.normalize();
+}
+
+v3f DirectionalLight::getPosition() const
+{
+ return shadow_frustum.position;
+}
+
+const m4f &DirectionalLight::getViewMatrix() const
+{
+ return shadow_frustum.ViewMat;
+}
+
+const m4f &DirectionalLight::getProjectionMatrix() const
+{
+ return shadow_frustum.ProjOrthMat;
+}
+
+const m4f &DirectionalLight::getFutureViewMatrix() const
+{
+ return future_frustum.ViewMat;
+}
+
+const m4f &DirectionalLight::getFutureProjectionMatrix() const
+{
+ return future_frustum.ProjOrthMat;
+}
+
+m4f DirectionalLight::getViewProjMatrix()
+{
+ return shadow_frustum.ProjOrthMat * shadow_frustum.ViewMat;
+}
diff --git a/src/client/shadows/dynamicshadows.h b/src/client/shadows/dynamicshadows.h
new file mode 100644
index 000000000..d8be66be8
--- /dev/null
+++ b/src/client/shadows/dynamicshadows.h
@@ -0,0 +1,109 @@
+/*
+Minetest
+Copyright (C) 2021 Liso <anlismon@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "irrlichttypes_bloated.h"
+#include <matrix4.h>
+#include "util/basic_macros.h"
+
+class Camera;
+class Client;
+
+struct shadowFrustum
+{
+ float zNear{0.0f};
+ float zFar{0.0f};
+ float length{0.0f};
+ core::matrix4 ProjOrthMat;
+ core::matrix4 ViewMat;
+ v3f position;
+ v3s16 camera_offset;
+};
+
+class DirectionalLight
+{
+public:
+ DirectionalLight(const u32 shadowMapResolution,
+ const v3f &position,
+ video::SColorf lightColor = video::SColor(0xffffffff),
+ f32 farValue = 100.0f);
+ ~DirectionalLight() = default;
+
+ //DISABLE_CLASS_COPY(DirectionalLight)
+
+ void update_frustum(const Camera *cam, Client *client, bool force = false);
+
+ // when set direction is updated to negative normalized(direction)
+ void setDirection(v3f dir);
+ v3f getDirection() const{
+ return direction;
+ };
+ v3f getPosition() const;
+
+ /// Gets the light's matrices.
+ const core::matrix4 &getViewMatrix() const;
+ const core::matrix4 &getProjectionMatrix() const;
+ const core::matrix4 &getFutureViewMatrix() const;
+ const core::matrix4 &getFutureProjectionMatrix() const;
+ core::matrix4 getViewProjMatrix();
+
+ /// Gets the light's far value.
+ f32 getMaxFarValue() const
+ {
+ return farPlane;
+ }
+
+
+ /// Gets the light's color.
+ const video::SColorf &getLightColor() const
+ {
+ return diffuseColor;
+ }
+
+ /// Sets the light's color.
+ void setLightColor(const video::SColorf &lightColor)
+ {
+ diffuseColor = lightColor;
+ }
+
+ /// Gets the shadow map resolution for this light.
+ u32 getMapResolution() const
+ {
+ return mapRes;
+ }
+
+ bool should_update_map_shadow{true};
+
+ void commitFrustum();
+
+private:
+ void createSplitMatrices(const Camera *cam);
+
+ video::SColorf diffuseColor;
+
+ f32 farPlane;
+ u32 mapRes;
+
+ v3f pos;
+ v3f direction{0};
+ shadowFrustum shadow_frustum;
+ shadowFrustum future_frustum;
+ bool dirty{false};
+};
diff --git a/src/client/shadows/dynamicshadowsrender.cpp b/src/client/shadows/dynamicshadowsrender.cpp
new file mode 100644
index 000000000..a913a9290
--- /dev/null
+++ b/src/client/shadows/dynamicshadowsrender.cpp
@@ -0,0 +1,630 @@
+/*
+Minetest
+Copyright (C) 2021 Liso <anlismon@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include <cstring>
+#include "client/shadows/dynamicshadowsrender.h"
+#include "client/shadows/shadowsScreenQuad.h"
+#include "client/shadows/shadowsshadercallbacks.h"
+#include "settings.h"
+#include "filesys.h"
+#include "util/string.h"
+#include "client/shader.h"
+#include "client/client.h"
+#include "client/clientmap.h"
+#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_shadows_enabled = true;
+
+ m_shadow_strength = g_settings->getFloat("shadow_strength");
+
+ m_shadow_map_max_distance = g_settings->getFloat("shadow_map_max_distance");
+
+ m_shadow_map_texture_size = g_settings->getFloat("shadow_map_texture_size");
+
+ m_shadow_map_texture_32bit = g_settings->getBool("shadow_map_texture_32bit");
+ m_shadow_map_colored = g_settings->getBool("shadow_map_color");
+ m_shadow_samples = g_settings->getS32("shadow_filters");
+ m_map_shadow_update_frames = g_settings->getS16("shadow_update_frames");
+}
+
+ShadowRenderer::~ShadowRenderer()
+{
+ if (m_shadow_depth_cb)
+ delete m_shadow_depth_cb;
+ if (m_shadow_mix_cb)
+ delete m_shadow_mix_cb;
+ m_shadow_node_array.clear();
+ m_light_list.clear();
+
+ if (shadowMapTextureDynamicObjects)
+ m_driver->removeTexture(shadowMapTextureDynamicObjects);
+
+ if (shadowMapTextureFinal)
+ m_driver->removeTexture(shadowMapTextureFinal);
+
+ if (shadowMapTextureColors)
+ m_driver->removeTexture(shadowMapTextureColors);
+
+ if (shadowMapClientMap)
+ m_driver->removeTexture(shadowMapClientMap);
+
+ if (shadowMapClientMapFuture)
+ m_driver->removeTexture(shadowMapClientMapFuture);
+}
+
+void ShadowRenderer::initialize()
+{
+ auto *gpu = m_driver->getGPUProgrammingServices();
+
+ // we need glsl
+ if (m_shadows_enabled && gpu && m_driver->queryFeature(video::EVDF_ARB_GLSL)) {
+ createShaders();
+ } else {
+ m_shadows_enabled = false;
+
+ warningstream << "Shadows: GLSL Shader not supported on this system."
+ << std::endl;
+ return;
+ }
+
+ m_texture_format = m_shadow_map_texture_32bit
+ ? video::ECOLOR_FORMAT::ECF_R32F
+ : video::ECOLOR_FORMAT::ECF_R16F;
+
+ m_texture_format_color = m_shadow_map_texture_32bit
+ ? video::ECOLOR_FORMAT::ECF_G32R32F
+ : video::ECOLOR_FORMAT::ECF_G16R16F;
+}
+
+
+size_t ShadowRenderer::addDirectionalLight()
+{
+ m_light_list.emplace_back(m_shadow_map_texture_size,
+ v3f(0.f, 0.f, 0.f),
+ video::SColor(255, 255, 255, 255), m_shadow_map_max_distance);
+ return m_light_list.size() - 1;
+}
+
+DirectionalLight &ShadowRenderer::getDirectionalLight(u32 index)
+{
+ return m_light_list[index];
+}
+
+size_t ShadowRenderer::getDirectionalLightCount() const
+{
+ return m_light_list.size();
+}
+
+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;
+ }
+ return 0.0f;
+}
+
+void ShadowRenderer::addNodeToShadowList(
+ scene::ISceneNode *node, E_SHADOW_MODE shadowMode)
+{
+ m_shadow_node_array.emplace_back(NodeToApply(node, shadowMode));
+}
+
+void ShadowRenderer::removeNodeFromShadowList(scene::ISceneNode *node)
+{
+ for (auto it = m_shadow_node_array.begin(); it != m_shadow_node_array.end();) {
+ if (it->node == node) {
+ it = m_shadow_node_array.erase(it);
+ break;
+ } else {
+ ++it;
+ }
+ }
+}
+
+void ShadowRenderer::updateSMTextures()
+{
+ if (!m_shadows_enabled || m_smgr->getActiveCamera() == nullptr) {
+ return;
+ }
+
+ if (!shadowMapTextureDynamicObjects) {
+
+ shadowMapTextureDynamicObjects = getSMTexture(
+ std::string("shadow_dynamic_") + itos(m_shadow_map_texture_size),
+ m_texture_format, true);
+ }
+
+ if (!shadowMapClientMap) {
+
+ shadowMapClientMap = getSMTexture(
+ std::string("shadow_clientmap_") + itos(m_shadow_map_texture_size),
+ m_shadow_map_colored ? m_texture_format_color : m_texture_format,
+ true);
+ }
+
+ if (!shadowMapClientMapFuture && m_map_shadow_update_frames > 1) {
+ shadowMapClientMapFuture = getSMTexture(
+ std::string("shadow_clientmap_bb_") + itos(m_shadow_map_texture_size),
+ m_shadow_map_colored ? m_texture_format_color : m_texture_format,
+ true);
+ }
+
+ if (m_shadow_map_colored && !shadowMapTextureColors) {
+ shadowMapTextureColors = getSMTexture(
+ std::string("shadow_colored_") + itos(m_shadow_map_texture_size),
+ m_shadow_map_colored ? m_texture_format_color : m_texture_format,
+ true);
+ }
+
+ // The merge all shadowmaps texture
+ if (!shadowMapTextureFinal) {
+ video::ECOLOR_FORMAT frt;
+ if (m_shadow_map_texture_32bit) {
+ if (m_shadow_map_colored)
+ frt = video::ECOLOR_FORMAT::ECF_A32B32G32R32F;
+ else
+ frt = video::ECOLOR_FORMAT::ECF_R32F;
+ } else {
+ if (m_shadow_map_colored)
+ frt = video::ECOLOR_FORMAT::ECF_A16B16G16R16F;
+ else
+ frt = video::ECOLOR_FORMAT::ECF_R16F;
+ }
+ shadowMapTextureFinal = getSMTexture(
+ std::string("shadowmap_final_") + itos(m_shadow_map_texture_size),
+ frt, true);
+ }
+
+ if (!m_shadow_node_array.empty() && !m_light_list.empty()) {
+ bool reset_sm_texture = false;
+
+ // detect if SM should be regenerated
+ for (DirectionalLight &light : m_light_list) {
+ if (light.should_update_map_shadow) {
+ light.should_update_map_shadow = false;
+ m_current_frame = 0;
+ reset_sm_texture = true;
+ }
+ }
+
+ video::ITexture* shadowMapTargetTexture = shadowMapClientMapFuture;
+ if (shadowMapTargetTexture == nullptr)
+ shadowMapTargetTexture = shadowMapClientMap;
+
+ // 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;
+
+ // set the Render Target
+ // right now we can only render in usual RTT, not
+ // Depth texture is available in irrlicth maybe we
+ // should put some gl* fn here
+
+
+ if (m_current_frame < m_map_shadow_update_frames) {
+ m_driver->setRenderTarget(shadowMapTargetTexture, reset_sm_texture, true,
+ video::SColor(255, 255, 255, 255));
+ renderShadowMap(shadowMapTargetTexture, light);
+
+ // Render transparent part in one pass.
+ // This is also handled in ClientMap.
+ if (m_current_frame == m_map_shadow_update_frames - 1) {
+ if (m_shadow_map_colored) {
+ m_driver->setRenderTarget(0, false, false);
+ m_driver->setRenderTarget(shadowMapTextureColors,
+ true, false, video::SColor(255, 255, 255, 255));
+ }
+ renderShadowMap(shadowMapTextureColors, light,
+ scene::ESNRP_TRANSPARENT);
+ }
+ m_driver->setRenderTarget(0, false, false);
+ }
+
+ reset_sm_texture = false;
+ } // end for lights
+
+ // move to the next section
+ if (m_current_frame <= m_map_shadow_update_frames)
+ ++m_current_frame;
+
+ // pass finished, swap textures and commit light changes
+ if (m_current_frame == m_map_shadow_update_frames) {
+ if (shadowMapClientMapFuture != nullptr)
+ std::swap(shadowMapClientMapFuture, shadowMapClientMap);
+
+ // Let all lights know that maps are updated
+ for (DirectionalLight &light : m_light_list)
+ light.commitFrustum();
+ }
+ }
+}
+
+void ShadowRenderer::update(video::ITexture *outputTarget)
+{
+ if (!m_shadows_enabled || m_smgr->getActiveCamera() == nullptr) {
+ return;
+ }
+
+ updateSMTextures();
+
+ 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;
+
+ // render shadows for the n0n-map objects.
+ m_driver->setRenderTarget(shadowMapTextureDynamicObjects, true,
+ true, video::SColor(255, 255, 255, 255));
+ renderShadowObjects(shadowMapTextureDynamicObjects, light);
+ // clear the Render Target
+ m_driver->setRenderTarget(0, false, false);
+
+ // in order to avoid too many map shadow renders,
+ // we should make a second pass to mix clientmap shadows and
+ // entities shadows :(
+ m_screen_quad->getMaterial().setTexture(0, shadowMapClientMap);
+ // dynamic objs shadow texture.
+ if (m_shadow_map_colored)
+ m_screen_quad->getMaterial().setTexture(1, shadowMapTextureColors);
+ m_screen_quad->getMaterial().setTexture(2, shadowMapTextureDynamicObjects);
+
+ m_driver->setRenderTarget(shadowMapTextureFinal, false, false,
+ video::SColor(255, 255, 255, 255));
+ m_screen_quad->render(m_driver);
+ m_driver->setRenderTarget(0, false, false);
+
+ } // end for lights
+ }
+}
+
+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()));
+
+ 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 (m_shadow_map_colored) {
+
+ m_driver->draw2DImage(shadowMapTextureColors,
+ core::rect<s32>(128,128 + 50 + 128 + 128,
+ 128 + 128, 128 + 50 + 128 + 128 + 128),
+ core::rect<s32>({0, 0}, shadowMapTextureColors->getSize()));
+ }
+ #endif
+}
+
+
+video::ITexture *ShadowRenderer::getSMTexture(const std::string &shadow_map_name,
+ video::ECOLOR_FORMAT texture_format, bool force_creation)
+{
+ if (force_creation) {
+ return m_driver->addRenderTargetTexture(
+ core::dimension2du(m_shadow_map_texture_size,
+ m_shadow_map_texture_size),
+ shadow_map_name.c_str(), texture_format);
+ }
+
+ return m_driver->getTexture(shadow_map_name.c_str());
+}
+
+void ShadowRenderer::renderShadowMap(video::ITexture *target,
+ DirectionalLight &light, scene::E_SCENE_NODE_RENDER_PASS pass)
+{
+ m_driver->setTransform(video::ETS_VIEW, light.getFutureViewMatrix());
+ m_driver->setTransform(video::ETS_PROJECTION, light.getFutureProjectionMatrix());
+
+ // Operate on the client map
+ for (const auto &shadow_node : m_shadow_node_array) {
+ if (strcmp(shadow_node.node->getName(), "ClientMap") != 0)
+ continue;
+
+ ClientMap *map_node = static_cast<ClientMap *>(shadow_node.node);
+
+ video::SMaterial material;
+ if (map_node->getMaterialCount() > 0) {
+ // we only want the first material, which is the one with the albedo info
+ material = map_node->getMaterial(0);
+ }
+
+ 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;
+ }
+ else {
+ material.MaterialType = (video::E_MATERIAL_TYPE) depth_shader;
+ 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());
+
+ map_node->renderMapShadows(m_driver, material, pass, m_current_frame, m_map_shadow_update_frames);
+ break;
+ }
+}
+
+void ShadowRenderer::renderShadowObjects(
+ video::ITexture *target, DirectionalLight &light)
+{
+ m_driver->setTransform(video::ETS_VIEW, light.getViewMatrix());
+ m_driver->setTransform(video::ETS_PROJECTION, light.getProjectionMatrix());
+
+ for (const auto &shadow_node : m_shadow_node_array) {
+ // we only take care of the shadow casters
+ if (shadow_node.shadowMode == ESM_RECEIVE ||
+ strcmp(shadow_node.node->getName(), "ClientMap") == 0)
+ continue;
+
+ // render other objects
+ u32 n_node_materials = shadow_node.node->getMaterialCount();
+ std::vector<s32> BufferMaterialList;
+ std::vector<std::pair<bool, bool>> BufferMaterialCullingList;
+ std::vector<video::E_BLEND_OPERATION> BufferBlendOperationList;
+ BufferMaterialList.reserve(n_node_materials);
+ BufferMaterialCullingList.reserve(n_node_materials);
+ BufferBlendOperationList.reserve(n_node_materials);
+
+ // backup materialtype for each material
+ // (aka shader)
+ // and replace it by our "depth" shader
+ for (u32 m = 0; m < n_node_materials; m++) {
+ auto &current_mat = shadow_node.node->getMaterial(m);
+
+ BufferMaterialList.push_back(current_mat.MaterialType);
+ current_mat.MaterialType =
+ (video::E_MATERIAL_TYPE)depth_shader_entities;
+
+ BufferMaterialCullingList.emplace_back(
+ (bool)current_mat.BackfaceCulling, (bool)current_mat.FrontfaceCulling);
+ BufferBlendOperationList.push_back(current_mat.BlendOperation);
+
+ 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,
+ shadow_node.node->getAbsoluteTransformation());
+ shadow_node.node->render();
+
+ // restore the material.
+
+ for (u32 m = 0; m < n_node_materials; m++) {
+ auto &current_mat = shadow_node.node->getMaterial(m);
+
+ current_mat.MaterialType = (video::E_MATERIAL_TYPE) BufferMaterialList[m];
+
+ current_mat.BackfaceCulling = BufferMaterialCullingList[m].first;
+ current_mat.FrontfaceCulling = BufferMaterialCullingList[m].second;
+ current_mat.BlendOperation = BufferBlendOperationList[m];
+ }
+
+ } // end for caster shadow nodes
+}
+
+void ShadowRenderer::mixShadowsQuad()
+{
+}
+
+/*
+ * @Liso's disclaimer ;) This function loads the Shadow Mapping Shaders.
+ * I used a custom loader because I couldn't figure out how to use the base
+ * Shaders system with custom IShaderConstantSetCallBack without messing up the
+ * code too much. If anyone knows how to integrate this with the standard MT
+ * shaders, please feel free to change it.
+ */
+
+void ShadowRenderer::createShaders()
+{
+ video::IGPUProgrammingServices *gpu = m_driver->getGPUProgrammingServices();
+
+ if (depth_shader == -1) {
+ std::string depth_shader_vs = getShaderPath("shadow_shaders", "pass1_vertex.glsl");
+ if (depth_shader_vs.empty()) {
+ m_shadows_enabled = 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;
+ errorstream << "Error shadow mapping fs shader not found." << std::endl;
+ return;
+ }
+ m_shadow_depth_cb = new ShadowDepthShaderCB();
+
+ depth_shader = 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::EMT_ONETEXTURE_BLEND);
+
+ if (depth_shader == -1) {
+ // upsi, something went wrong loading shader.
+ delete m_shadow_depth_cb;
+ m_shadows_enabled = false;
+ errorstream << "Error compiling shadow mapping shader." << std::endl;
+ return;
+ }
+
+ // HACK, TODO: investigate this better
+ // Grab the material renderer once more so minetest doesn't crash
+ // on exit
+ m_driver->getMaterialRenderer(depth_shader)->grab();
+ }
+
+ // This creates a clone of depth_shader with base material set to EMT_SOLID,
+ // because entities won't render shadows with base material EMP_ONETEXTURE_BLEND
+ 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;
+ 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;
+ errorstream << "Error shadow mapping fs shader not found." << std::endl;
+ return;
+ }
+
+ 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);
+
+ if (depth_shader_entities == -1) {
+ // upsi, something went wrong loading shader.
+ m_shadows_enabled = false;
+ errorstream << "Error compiling shadow mapping shader (dynamic)." << std::endl;
+ return;
+ }
+
+ // HACK, TODO: investigate this better
+ // Grab the material renderer once more so minetest doesn't crash
+ // on exit
+ m_driver->getMaterialRenderer(depth_shader_entities)->grab();
+ }
+
+ if (mixcsm_shader == -1) {
+ std::string depth_shader_vs = getShaderPath("shadow_shaders", "pass2_vertex.glsl");
+ if (depth_shader_vs.empty()) {
+ m_shadows_enabled = 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;
+ errorstream << "Error cascade shadow mapping fs shader not found." << std::endl;
+ return;
+ }
+ m_shadow_mix_cb = new shadowScreenQuadCB();
+ m_screen_quad = new shadowScreenQuad();
+ mixcsm_shader = 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_mix_cb);
+
+ m_screen_quad->getMaterial().MaterialType =
+ (video::E_MATERIAL_TYPE)mixcsm_shader;
+
+ if (mixcsm_shader == -1) {
+ // upsi, something went wrong loading shader.
+ delete m_shadow_mix_cb;
+ delete m_screen_quad;
+ m_shadows_enabled = false;
+ errorstream << "Error compiling cascade shadow mapping shader." << std::endl;
+ return;
+ }
+
+ // HACK, TODO: investigate this better
+ // Grab the material renderer once more so minetest doesn't crash
+ // on exit
+ m_driver->getMaterialRenderer(mixcsm_shader)->grab();
+ }
+
+ 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;
+ 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;
+ errorstream << "Error shadow mapping fs shader not found." << std::endl;
+ return;
+ }
+ m_shadow_depth_trans_cb = new ShadowDepthShaderCB();
+
+ depth_shader_trans = 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_trans_cb);
+
+ if (depth_shader_trans == -1) {
+ // upsi, something went wrong loading shader.
+ delete m_shadow_depth_trans_cb;
+ m_shadow_map_colored = false;
+ m_shadows_enabled = false;
+ errorstream << "Error compiling colored shadow mapping shader." << std::endl;
+ return;
+ }
+
+ // HACK, TODO: investigate this better
+ // Grab the material renderer once more so minetest doesn't crash
+ // on exit
+ m_driver->getMaterialRenderer(depth_shader_trans)->grab();
+ }
+}
+
+std::string ShadowRenderer::readShaderFile(const std::string &path)
+{
+ std::string prefix;
+ if (m_shadow_map_colored)
+ prefix.append("#define COLORED_SHADOWS 1\n");
+
+ std::string content;
+ fs::ReadFile(path, content);
+
+ return prefix + content;
+}
diff --git a/src/client/shadows/dynamicshadowsrender.h b/src/client/shadows/dynamicshadowsrender.h
new file mode 100644
index 000000000..e4b3c3e22
--- /dev/null
+++ b/src/client/shadows/dynamicshadowsrender.h
@@ -0,0 +1,147 @@
+/*
+Minetest
+Copyright (C) 2021 Liso <anlismon@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include <string>
+#include <vector>
+#include "irrlichttypes_extrabloated.h"
+#include "client/shadows/dynamicshadows.h"
+
+class ShadowDepthShaderCB;
+class shadowScreenQuad;
+class shadowScreenQuadCB;
+
+enum E_SHADOW_MODE : u8
+{
+ ESM_RECEIVE = 0,
+ ESM_BOTH,
+};
+
+struct NodeToApply
+{
+ NodeToApply(scene::ISceneNode *n,
+ E_SHADOW_MODE m = E_SHADOW_MODE::ESM_BOTH) :
+ node(n),
+ shadowMode(m){};
+ bool operator<(const NodeToApply &other) const { return node < other.node; };
+
+ scene::ISceneNode *node;
+
+ E_SHADOW_MODE shadowMode{E_SHADOW_MODE::ESM_BOTH};
+ bool dirty{false};
+};
+
+class ShadowRenderer
+{
+public:
+ ShadowRenderer(IrrlichtDevice *device, Client *client);
+
+ ~ShadowRenderer();
+
+ void initialize();
+
+ /// Adds a directional light shadow map (Usually just one (the sun) except in
+ /// Tattoine ).
+ size_t addDirectionalLight();
+ DirectionalLight &getDirectionalLight(u32 index = 0);
+ size_t getDirectionalLightCount() const;
+ f32 getMaxShadowFar() const;
+
+ /// Adds a shadow to the scene node.
+ /// The shadow mode can be ESM_BOTH, or ESM_RECEIVE.
+ /// ESM_BOTH casts and receives shadows
+ /// ESM_RECEIVE only receives but does not cast shadows.
+ ///
+ void addNodeToShadowList(scene::ISceneNode *node,
+ E_SHADOW_MODE shadowMode = ESM_BOTH);
+ void removeNodeFromShadowList(scene::ISceneNode *node);
+
+ void update(video::ITexture *outputTarget = nullptr);
+ void drawDebug();
+
+ video::ITexture *get_texture()
+ {
+ return shadowMapTextureFinal;
+ }
+
+
+ bool is_active() const { return m_shadows_enabled; }
+ void setTimeOfDay(float isDay) { m_time_day = isDay; };
+
+ s32 getShadowSamples() const { return m_shadow_samples; }
+ float getShadowStrength() const { return m_shadow_strength; }
+ float getTimeOfDay() const { return m_time_day; }
+
+private:
+ video::ITexture *getSMTexture(const std::string &shadow_map_name,
+ video::ECOLOR_FORMAT texture_format,
+ bool force_creation = false);
+
+ void renderShadowMap(video::ITexture *target, DirectionalLight &light,
+ scene::E_SCENE_NODE_RENDER_PASS pass =
+ scene::ESNRP_SOLID);
+ void renderShadowObjects(video::ITexture *target, DirectionalLight &light);
+ void mixShadowsQuad();
+ void updateSMTextures();
+
+ // a bunch of variables
+ IrrlichtDevice *m_device{nullptr};
+ scene::ISceneManager *m_smgr{nullptr};
+ video::IVideoDriver *m_driver{nullptr};
+ Client *m_client{nullptr};
+ video::ITexture *shadowMapClientMap{nullptr};
+ video::ITexture *shadowMapClientMapFuture{nullptr};
+ video::ITexture *shadowMapTextureFinal{nullptr};
+ video::ITexture *shadowMapTextureDynamicObjects{nullptr};
+ video::ITexture *shadowMapTextureColors{nullptr};
+
+ std::vector<DirectionalLight> m_light_list;
+ std::vector<NodeToApply> m_shadow_node_array;
+
+ float m_shadow_strength;
+ 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_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 */
+
+ video::ECOLOR_FORMAT m_texture_format{video::ECOLOR_FORMAT::ECF_R16F};
+ video::ECOLOR_FORMAT m_texture_format_color{video::ECOLOR_FORMAT::ECF_R16G16};
+
+ // Shadow Shader stuff
+
+ void createShaders();
+ std::string readShaderFile(const std::string &path);
+
+ s32 depth_shader{-1};
+ s32 depth_shader_entities{-1};
+ s32 depth_shader_trans{-1};
+ s32 mixcsm_shader{-1};
+
+ ShadowDepthShaderCB *m_shadow_depth_cb{nullptr};
+ ShadowDepthShaderCB *m_shadow_depth_trans_cb{nullptr};
+
+ shadowScreenQuad *m_screen_quad{nullptr};
+ shadowScreenQuadCB *m_shadow_mix_cb{nullptr};
+};
diff --git a/src/client/shadows/shadowsScreenQuad.cpp b/src/client/shadows/shadowsScreenQuad.cpp
new file mode 100644
index 000000000..5f6d38157
--- /dev/null
+++ b/src/client/shadows/shadowsScreenQuad.cpp
@@ -0,0 +1,61 @@
+/*
+Minetest
+Copyright (C) 2021 Liso <anlismon@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "shadowsScreenQuad.h"
+
+shadowScreenQuad::shadowScreenQuad()
+{
+ Material.Wireframe = false;
+ Material.Lighting = false;
+
+ video::SColor color(0x0);
+ Vertices[0] = video::S3DVertex(
+ -1.0f, -1.0f, 0.0f, 0, 0, 1, color, 0.0f, 1.0f);
+ Vertices[1] = video::S3DVertex(
+ -1.0f, 1.0f, 0.0f, 0, 0, 1, color, 0.0f, 0.0f);
+ Vertices[2] = video::S3DVertex(
+ 1.0f, 1.0f, 0.0f, 0, 0, 1, color, 1.0f, 0.0f);
+ Vertices[3] = video::S3DVertex(
+ 1.0f, -1.0f, 0.0f, 0, 0, 1, color, 1.0f, 1.0f);
+ Vertices[4] = video::S3DVertex(
+ -1.0f, -1.0f, 0.0f, 0, 0, 1, color, 0.0f, 1.0f);
+ Vertices[5] = video::S3DVertex(
+ 1.0f, 1.0f, 0.0f, 0, 0, 1, color, 1.0f, 0.0f);
+}
+
+void shadowScreenQuad::render(video::IVideoDriver *driver)
+{
+ u16 indices[6] = {0, 1, 2, 3, 4, 5};
+ driver->setMaterial(Material);
+ driver->setTransform(video::ETS_WORLD, core::matrix4());
+ driver->drawIndexedTriangleList(&Vertices[0], 6, &indices[0], 2);
+}
+
+void shadowScreenQuadCB::OnSetConstants(
+ video::IMaterialRendererServices *services, s32 userData)
+{
+ s32 TextureId = 0;
+ m_sm_client_map_setting.set(&TextureId, services);
+
+ TextureId = 1;
+ m_sm_client_map_trans_setting.set(&TextureId, services);
+
+ TextureId = 2;
+ m_sm_dynamic_sampler_setting.set(&TextureId, services);
+}
diff --git a/src/client/shadows/shadowsScreenQuad.h b/src/client/shadows/shadowsScreenQuad.h
new file mode 100644
index 000000000..c18be9a2b
--- /dev/null
+++ b/src/client/shadows/shadowsScreenQuad.h
@@ -0,0 +1,54 @@
+/*
+Minetest
+Copyright (C) 2021 Liso <anlismon@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+#include "irrlichttypes_extrabloated.h"
+#include <IMaterialRendererServices.h>
+#include <IShaderConstantSetCallBack.h>
+#include "client/shader.h"
+
+class shadowScreenQuad
+{
+public:
+ shadowScreenQuad();
+
+ void render(video::IVideoDriver *driver);
+ video::SMaterial &getMaterial() { return Material; }
+
+private:
+ video::S3DVertex Vertices[6];
+ video::SMaterial Material;
+};
+
+class shadowScreenQuadCB : public video::IShaderConstantSetCallBack
+{
+public:
+ shadowScreenQuadCB() :
+ m_sm_client_map_setting("ShadowMapClientMap"),
+ m_sm_client_map_trans_setting("ShadowMapClientMapTraslucent"),
+ m_sm_dynamic_sampler_setting("ShadowMapSamplerdynamic")
+ {}
+
+ virtual void OnSetConstants(video::IMaterialRendererServices *services,
+ s32 userData);
+private:
+ CachedPixelShaderSetting<s32> m_sm_client_map_setting;
+ CachedPixelShaderSetting<s32> m_sm_client_map_trans_setting;
+ CachedPixelShaderSetting<s32> m_sm_dynamic_sampler_setting;
+};
diff --git a/src/client/shadows/shadowsshadercallbacks.cpp b/src/client/shadows/shadowsshadercallbacks.cpp
new file mode 100644
index 000000000..65a63f49c
--- /dev/null
+++ b/src/client/shadows/shadowsshadercallbacks.cpp
@@ -0,0 +1,36 @@
+/*
+Minetest
+Copyright (C) 2021 Liso <anlismon@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "client/shadows/shadowsshadercallbacks.h"
+
+void ShadowDepthShaderCB::OnSetConstants(
+ video::IMaterialRendererServices *services, s32 userData)
+{
+ video::IVideoDriver *driver = services->getVideoDriver();
+
+ core::matrix4 lightMVP = driver->getTransform(video::ETS_PROJECTION);
+ lightMVP *= driver->getTransform(video::ETS_VIEW);
+ lightMVP *= driver->getTransform(video::ETS_WORLD);
+
+ m_light_mvp_setting.set(lightMVP.pointer(), services);
+ m_map_resolution_setting.set(&MapRes, services);
+ m_max_far_setting.set(&MaxFar, services);
+ s32 TextureId = 0;
+ m_color_map_sampler_setting.set(&TextureId, services);
+}
diff --git a/src/client/shadows/shadowsshadercallbacks.h b/src/client/shadows/shadowsshadercallbacks.h
new file mode 100644
index 000000000..3549567c3
--- /dev/null
+++ b/src/client/shadows/shadowsshadercallbacks.h
@@ -0,0 +1,48 @@
+/*
+Minetest
+Copyright (C) 2021 Liso <anlismon@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+#include "irrlichttypes_extrabloated.h"
+#include <IMaterialRendererServices.h>
+#include <IShaderConstantSetCallBack.h>
+#include "client/shader.h"
+
+class ShadowDepthShaderCB : public video::IShaderConstantSetCallBack
+{
+public:
+ ShadowDepthShaderCB() :
+ m_light_mvp_setting("LightMVP"),
+ m_map_resolution_setting("MapResolution"),
+ m_max_far_setting("MaxFar"),
+ m_color_map_sampler_setting("ColorMapSampler")
+ {}
+
+ void OnSetMaterial(const video::SMaterial &material) override {}
+
+ void OnSetConstants(video::IMaterialRendererServices *services,
+ s32 userData) override;
+
+ f32 MaxFar{2048.0f}, MapRes{1024.0f};
+
+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;
+};
diff --git a/src/client/sky.cpp b/src/client/sky.cpp
index 2512a0e23..7fe90c6cd 100644
--- a/src/client/sky.cpp
+++ b/src/client/sky.cpp
@@ -18,50 +18,65 @@ 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()
{
video::SMaterial mat;
mat.Lighting = false;
-#if ENABLE_GLES
+#if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR > 8
mat.ZBuffer = video::ECFN_DISABLED;
+ mat.ZWriteEnable = video::EZW_OFF;
#else
+ mat.ZWriteEnable = false;
mat.ZBuffer = video::ECFN_NEVER;
#endif
- mat.ZWriteEnable = false;
mat.AntiAliasing = 0;
mat.TextureLayer[0].TextureWrapU = video::ETC_CLAMP_TO_EDGE;
mat.TextureLayer[0].TextureWrapV = video::ETC_CLAMP_TO_EDGE;
mat.BackfaceCulling = false;
return mat;
-};
+}
-Sky::Sky(s32 id, ITextureSource *tsrc, IShaderSource *ssrc) :
- scene::ISceneNode(RenderingEngine::get_scene_manager()->getRootSceneNode(),
- RenderingEngine::get_scene_manager(), id)
+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(),
+ rendering_engine->get_scene_manager(), id)
+{
+ m_seed = (u64)myrand() << 32 | myrand();
+
setAutomaticCulling(scene::EAC_OFF);
m_box.MaxEdge.set(0, 0, 0);
m_box.MinEdge.set(0, 0, 0);
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();
@@ -70,57 +85,30 @@ Sky::Sky(s32 id, ITextureSource *tsrc, IShaderSource *ssrc) :
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();
m_materials[i].Lighting = true;
m_materials[i].MaterialType = video::EMT_SOLID;
}
+
m_directional_colored_fog = g_settings->getBool("directional_colored_fog");
- setStarCount(1000, true);
+
+ if (false && g_settings->getBool("enable_dynamic_shadows")) {
+ float val = g_settings->getFloat("shadow_sky_body_orbit_tilt");
+ m_sky_body_orbit_tilt = rangelim(val, 0.0f, 60.0f);
+ }
+
+ setStarCount(1000);
}
void Sky::OnRegisterSceneNode()
@@ -172,17 +160,7 @@ void Sky::render()
video::SColorf mooncolor_f(0.50, 0.57, 0.65, 1);
video::SColorf mooncolor2_f(0.85, 0.875, 0.9, 1);
- float nightlength = 0.415;
- float wn = nightlength / 2;
- float wicked_time_of_day = 0;
- if (m_time_of_day > wn && m_time_of_day < 1.0 - wn)
- wicked_time_of_day = (m_time_of_day - wn) / (1.0 - wn * 2) * 0.5 + 0.25;
- else if (m_time_of_day < 0.5)
- wicked_time_of_day = m_time_of_day / wn * 0.25;
- else
- wicked_time_of_day = 1.0 - ((1.0 - m_time_of_day) / wn * 0.25);
- /*std::cerr<<"time_of_day="<<m_time_of_day<<" -> "
- <<"wicked_time_of_day="<<wicked_time_of_day<<std::endl;*/
+ float wicked_time_of_day = getWickedTimeOfDay(m_time_of_day);
video::SColor suncolor = suncolor_f.toSColor();
video::SColor suncolor2 = suncolor2_f.toSColor();
@@ -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;
@@ -736,10 +700,15 @@ void Sky::place_sky_body(
* day_position: turn the body around the Z axis, to place it depending of the time of the day
*/
{
+ v3f centrum(0, 0, -1);
+ centrum.rotateXZBy(horizon_position);
+ centrum.rotateXYBy(day_position);
+ centrum.rotateYZBy(m_sky_body_orbit_tilt);
for (video::S3DVertex &vertex : vertices) {
// Body is directed to -Z (south) by default
vertex.Pos.rotateXZBy(horizon_position);
vertex.Pos.rotateXYBy(day_position);
+ vertex.Pos.Z += centrum.Z;
}
}
@@ -749,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;
}
}
@@ -797,42 +762,37 @@ 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;
- m_seed = (u64)myrand() << 32 | myrand();
updateStars();
}
}
@@ -920,12 +880,16 @@ 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()
+float getWickedTimeOfDay(float time_of_day)
{
- 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 nightlength = 0.415f;
+ float wn = nightlength / 2;
+ float wicked_time_of_day = 0;
+ if (time_of_day > wn && time_of_day < 1.0f - wn)
+ wicked_time_of_day = (time_of_day - wn) / (1.0f - wn * 2) * 0.5f + 0.25f;
+ else if (time_of_day < 0.5f)
+ wicked_time_of_day = time_of_day / wn * 0.25f;
+ else
+ wicked_time_of_day = 1.0f - ((1.0f - time_of_day) / wn * 0.25f);
+ return wicked_time_of_day;
}
diff --git a/src/client/sky.h b/src/client/sky.h
index dc7da5021..3dc057b70 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;
@@ -36,7 +36,7 @@ class Sky : public scene::ISceneNode
{
public:
//! constructor
- Sky(s32 id, ITextureSource *tsrc, IShaderSource *ssrc);
+ Sky(s32 id, RenderingEngine *rendering_engine, ITextureSource *tsrc, IShaderSource *ssrc);
virtual void OnRegisterSceneNode();
@@ -77,7 +77,7 @@ public:
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(); }
@@ -105,6 +105,8 @@ public:
ITextureSource *tsrc);
const video::SColorf &getCurrentStarColor() const { return m_star_color; }
+ float getSkyBodyOrbitTilt() const { return m_sky_body_orbit_tilt; }
+
private:
aabb3f m_box;
video::SMaterial m_materials[SKY_MATERIAL_COUNT];
@@ -148,7 +150,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;
@@ -159,6 +161,7 @@ private:
bool m_directional_colored_fog;
bool m_in_clouds = true; // Prevent duplicating bools to remember old values
bool m_enable_shaders = false;
+ float m_sky_body_orbit_tilt = 0.0f;
video::SColorf m_bgcolor_bright_f = video::SColorf(1.0f, 1.0f, 1.0f, 1.0f);
video::SColorf m_skycolor_bright_f = video::SColorf(1.0f, 1.0f, 1.0f, 1.0f);
@@ -203,5 +206,8 @@ 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
+// this is used to draw both Sun/Moon and shadows
+float getWickedTimeOfDay(float time_of_day);
diff --git a/src/client/sound_openal.cpp b/src/client/sound_openal.cpp
index f4e61f93e..0eda8842b 100644
--- a/src/client/sound_openal.cpp
+++ b/src/client/sound_openal.cpp
@@ -362,6 +362,14 @@ public:
for (auto &buffer : m_buffers) {
for (SoundBuffer *sb : buffer.second) {
+ alDeleteBuffers(1, &sb->buffer_id);
+
+ ALenum error = alGetError();
+ if (error != AL_NO_ERROR) {
+ warningstream << "Audio: Failed to free stream for "
+ << buffer.first << ": " << alErrorString(error) << std::endl;
+ }
+
delete sb;
}
buffer.second.clear();
@@ -671,8 +679,8 @@ public:
alSourcei(sound->source_id, AL_SOURCE_RELATIVE, false);
alSource3f(sound->source_id, AL_POSITION, pos.X, pos.Y, pos.Z);
- alSource3f(sound->source_id, AL_VELOCITY, 0, 0, 0);
- alSourcef(sound->source_id, AL_REFERENCE_DISTANCE, 30.0);
+ alSource3f(sound->source_id, AL_VELOCITY, 0.0f, 0.0f, 0.0f);
+ alSourcef(sound->source_id, AL_REFERENCE_DISTANCE, 10.0f);
}
bool updateSoundGain(int id, float gain)
diff --git a/src/client/tile.cpp b/src/client/tile.cpp
index f2639757e..da03ff5c8 100644
--- a/src/client/tile.cpp
+++ b/src/client/tile.cpp
@@ -33,15 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "imagefilters.h"
#include "guiscalingfilter.h"
#include "renderingengine.h"
-
-
-#if ENABLE_GLES
-#ifdef _IRR_COMPILE_WITH_OGLES1_
-#include <GLES/gl.h>
-#else
-#include <GLES2/gl2.h>
-#endif
-#endif
+#include "util/base64.h"
/*
A cache from texture name to texture path
@@ -90,12 +82,8 @@ static bool replace_ext(std::string &path, const char *ext)
std::string getImagePath(std::string path)
{
// A NULL-ended list of possible image extensions
- const char *extensions[] = {
- "png", "jpg", "bmp", "tga",
- "pcx", "ppm", "psd", "wal", "rgb",
- NULL
- };
- // If there is no extension, add one
+ const char *extensions[] = { "png", "jpg", "bmp", "tga", NULL };
+ // If there is no extension, assume PNG
if (removeStringEnd(path, extensions).empty())
path = path + ".png";
// Check paths until something is found to exist
@@ -427,6 +415,7 @@ private:
std::unordered_map<std::string, Palette> m_palettes;
// Cached settings needed for making textures from meshes
+ bool m_setting_mipmap;
bool m_setting_trilinear_filter;
bool m_setting_bilinear_filter;
};
@@ -447,6 +436,7 @@ TextureSource::TextureSource()
// Cache some settings
// Note: Since this is only done once, the game must be restarted
// for these settings to take effect
+ m_setting_mipmap = g_settings->getBool("mip_map");
m_setting_trilinear_filter = g_settings->getBool("trilinear_filter");
m_setting_bilinear_filter = g_settings->getBool("bilinear_filter");
}
@@ -667,7 +657,7 @@ video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *id)
{
static thread_local bool filter_needed =
- g_settings->getBool("texture_clean_transparent") ||
+ g_settings->getBool("texture_clean_transparent") || m_setting_mipmap ||
((m_setting_trilinear_filter || m_setting_bilinear_filter) &&
g_settings->getS32("texture_min_size") > 1);
// Avoid duplicating texture if it won't actually change
@@ -773,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);
@@ -837,17 +830,16 @@ static video::IImage *createInventoryCubeImage(
image = scaled;
}
sanity_check(image->getPitch() == 4 * size);
- return reinterpret_cast<u32 *>(image->lock());
+ return reinterpret_cast<u32 *>(image->getData());
};
auto free_image = [] (video::IImage *image) -> void {
- image->unlock();
image->drop();
};
video::IImage *result = driver->createImage(video::ECF_A8R8G8B8, {cube_size, cube_size});
sanity_check(result->getPitch() == 4 * cube_size);
result->fill(video::SColor(0x00000000u));
- u32 *target = reinterpret_cast<u32 *>(result->lock());
+ u32 *target = reinterpret_cast<u32 *>(result->getData());
// Draws single cube face
// `shade_factor` is face brightness, in range [0.0, 1.0]
@@ -906,7 +898,6 @@ static video::IImage *createInventoryCubeImage(
{0, 5}, {1, 5},
});
- result->unlock();
return result;
}
@@ -1013,42 +1004,19 @@ video::IImage* TextureSource::generateImage(const std::string &name)
#if ENABLE_GLES
-
-static inline u16 get_GL_major_version()
-{
- const GLubyte *gl_version = glGetString(GL_VERSION);
- return (u16) (gl_version[0] - '0');
-}
-
-/**
- * Check if hardware requires npot2 aligned textures
- * @return true if alignment NOT(!) requires, false otherwise
- */
-
-bool hasNPotSupport()
-{
- // Only GLES2 is trusted to correctly report npot support
- // Note: we cache the boolean result, the GL context will never change.
- static const bool supported = get_GL_major_version() > 1 &&
- glGetString(GL_EXTENSIONS) &&
- strstr((char *)glGetString(GL_EXTENSIONS), "GL_OES_texture_npot");
- return supported;
-}
-
/**
* Check and align image to npot2 if required by hardware
* @param image image to check for npot2 alignment
* @param driver driver to use for image operations
* @return image or copy of image aligned to npot2
*/
-
-video::IImage * Align2Npot2(video::IImage * image,
- video::IVideoDriver* driver)
+video::IImage *Align2Npot2(video::IImage *image,
+ video::IVideoDriver *driver)
{
if (image == NULL)
return image;
- if (hasNPotSupport())
+ if (driver->queryFeature(video::EVDF_TEXTURE_NPOT))
return image;
core::dimension2d<u32> dim = image->getDimension();
@@ -1092,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)
{
@@ -1155,41 +1162,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();
@@ -1638,8 +1611,8 @@ bool TextureSource::generateImagePart(std::string part_of_name,
return false;
}
- // Apply the "clean transparent" filter, if configured.
- if (g_settings->getBool("texture_clean_transparent"))
+ // Apply the "clean transparent" filter, if needed
+ if (m_setting_mipmap || g_settings->getBool("texture_clean_transparent"))
imageCleanTransparent(baseimg, 127);
/* Upscale textures to user's requested minimum size. This is a trick to make
@@ -1817,6 +1790,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 "
@@ -2219,6 +2229,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();
@@ -2233,9 +2285,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)
@@ -2245,17 +2295,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 49c46f749..fcdc46460 100644
--- a/src/client/tile.h
+++ b/src/client/tile.h
@@ -134,8 +134,7 @@ public:
IWritableTextureSource *createTextureSource();
#if ENABLE_GLES
-bool hasNPotSupport();
-video::IImage * Align2Npot2(video::IImage * image, irr::video::IVideoDriver* driver);
+video::IImage *Align2Npot2(video::IImage *image, video::IVideoDriver *driver);
#endif
enum MaterialType{
diff --git a/src/client/wieldmesh.cpp b/src/client/wieldmesh.cpp
index ad583210a..0a4cb3b86 100644
--- a/src/client/wieldmesh.cpp
+++ b/src/client/wieldmesh.cpp
@@ -33,6 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "util/numeric.h"
#include <map>
#include <IMeshManipulator.h>
+#include "client/renderingengine.h"
#define WIELD_SCALE_FACTOR 30.0
#define WIELD_SCALE_FACTOR_EXTRUDED 40.0
@@ -220,11 +221,18 @@ WieldMeshSceneNode::WieldMeshSceneNode(scene::ISceneManager *mgr, s32 id, bool l
m_meshnode->setReadOnlyMaterials(false);
m_meshnode->setVisible(false);
dummymesh->drop(); // m_meshnode grabbed it
+
+ m_shadow = RenderingEngine::get_shadow_renderer();
}
WieldMeshSceneNode::~WieldMeshSceneNode()
{
sanity_check(g_extrusion_mesh_cache);
+
+ // Remove node from shadow casters. m_shadow might be an invalid pointer!
+ if (auto shadow = RenderingEngine::get_shadow_renderer())
+ shadow->removeNodeFromShadowList(m_meshnode);
+
if (g_extrusion_mesh_cache->drop())
g_extrusion_mesh_cache = nullptr;
}
@@ -294,32 +302,36 @@ void WieldMeshSceneNode::setExtruded(const std::string &imagename,
}
material.setFlag(video::EMF_ANISOTROPIC_FILTER, m_anisotropic_filter);
// mipmaps cause "thin black line" artifacts
-#if (IRRLICHT_VERSION_MAJOR >= 1 && IRRLICHT_VERSION_MINOR >= 8) || IRRLICHT_VERSION_MAJOR >= 2
material.setFlag(video::EMF_USE_MIP_MAPS, false);
-#endif
if (m_enable_shaders) {
material.setTexture(2, tsrc->getShaderFlagsTexture(false));
}
}
}
-scene::SMesh *createSpecialNodeMesh(Client *client, content_t id, std::vector<ItemPartColor> *colors, const ContentFeatures &f)
+static scene::SMesh *createSpecialNodeMesh(Client *client, MapNode n,
+ std::vector<ItemPartColor> *colors, const ContentFeatures &f)
{
MeshMakeData mesh_make_data(client, false);
MeshCollector collector;
mesh_make_data.setSmoothLighting(false);
- MapblockMeshGenerator gen(&mesh_make_data, &collector);
- u8 param2 = 0;
- if (f.param_type_2 == CPT2_WALLMOUNTED ||
+ MapblockMeshGenerator gen(&mesh_make_data, &collector,
+ client->getSceneManager()->getMeshManipulator());
+
+ if (n.getParam2()) {
+ // keep it
+ } else if (f.param_type_2 == CPT2_WALLMOUNTED ||
f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
- if (f.drawtype == NDT_TORCHLIKE)
- param2 = 1;
- else if (f.drawtype == NDT_SIGNLIKE ||
+ if (f.drawtype == NDT_TORCHLIKE ||
+ f.drawtype == NDT_SIGNLIKE ||
f.drawtype == NDT_NODEBOX ||
- f.drawtype == NDT_MESH)
- param2 = 4;
+ f.drawtype == NDT_MESH) {
+ n.setParam2(4);
+ }
+ } else if (f.drawtype == NDT_SIGNLIKE || f.drawtype == NDT_TORCHLIKE) {
+ n.setParam2(1);
}
- gen.renderSingle(id, param2);
+ gen.renderSingle(n.getContent(), n.getParam2());
colors->clear();
scene::SMesh *mesh = new scene::SMesh();
@@ -392,7 +404,6 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che
case NDT_TORCHLIKE:
case NDT_RAILLIKE:
case NDT_PLANTLIKE:
- case NDT_PLANTLIKE_ROOTED:
case NDT_FLOWINGLIQUID: {
v3f wscale = def.wield_scale;
if (f.drawtype == NDT_FLOWINGLIQUID)
@@ -408,14 +419,26 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che
m_colors.emplace_back(l1.has_color, l1.color);
break;
}
+ case NDT_PLANTLIKE_ROOTED: {
+ setExtruded(tsrc->getTextureName(f.special_tiles[0].layers[0].texture_id),
+ "", def.wield_scale, tsrc,
+ f.special_tiles[0].layers[0].animation_frame_count);
+ // Add color
+ const TileLayer &l0 = f.special_tiles[0].layers[0];
+ m_colors.emplace_back(l0.has_color, l0.color);
+ break;
+ }
case NDT_NORMAL:
case NDT_ALLFACES:
case NDT_LIQUID:
setCube(f, def.wield_scale);
break;
- default:
+ default: {
// Render non-trivial drawtypes like the actual node
- mesh = createSpecialNodeMesh(client, id, &m_colors, f);
+ MapNode n(id);
+ n.setParam2(def.place_param2);
+
+ mesh = createSpecialNodeMesh(client, n, &m_colors, f);
changeToMesh(mesh);
mesh->drop();
m_meshnode->setScale(
@@ -423,6 +446,7 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che
/ (BS * f.visual_scale));
break;
}
+ }
u32 material_count = m_meshnode->getMaterialCount();
for (u32 i = 0; i < material_count; ++i) {
@@ -434,9 +458,14 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che
material.setFlag(video::EMF_TRILINEAR_FILTER, m_trilinear_filter);
}
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));
@@ -511,6 +540,10 @@ void WieldMeshSceneNode::changeToMesh(scene::IMesh *mesh)
// need to normalize normals when lighting is enabled (because of setScale())
m_meshnode->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, m_lighting);
m_meshnode->setVisible(true);
+
+ // Add mesh to shadow caster
+ if (m_shadow)
+ m_shadow->addNodeToShadowList(m_meshnode);
}
void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result)
@@ -523,7 +556,7 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result)
content_t id = ndef->getId(def.name);
FATAL_ERROR_IF(!g_extrusion_mesh_cache, "Extrusion mesh cache is not yet initialized");
-
+
scene::SMesh *mesh = nullptr;
// Shading is on by default
@@ -585,12 +618,16 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result)
result->buffer_colors.emplace_back(l0.has_color, l0.color);
break;
}
- default:
+ default: {
// Render non-trivial drawtypes like the actual node
- mesh = createSpecialNodeMesh(client, id, &result->buffer_colors, f);
+ MapNode n(id);
+ n.setParam2(def.place_param2);
+
+ mesh = createSpecialNodeMesh(client, n, &result->buffer_colors, f);
scaleMesh(mesh, v3f(0.12, 0.12, 0.12));
break;
}
+ }
u32 mc = mesh->getMeshBufferCount();
for (u32 i = 0; i < mc; ++i) {
diff --git a/src/client/wieldmesh.h b/src/client/wieldmesh.h
index 933097230..d1eeb64f5 100644
--- a/src/client/wieldmesh.h
+++ b/src/client/wieldmesh.h
@@ -27,6 +27,7 @@ struct ItemStack;
class Client;
class ITextureSource;
struct ContentFeatures;
+class ShadowRenderer;
/*!
* Holds color information of an item mesh's buffer.
@@ -124,6 +125,8 @@ private:
// so this variable is just required so we can implement
// getBoundingBox() and is set to an empty box.
aabb3f m_bounding_box;
+
+ ShadowRenderer *m_shadow;
};
void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result);