summaryrefslogtreecommitdiff
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.cpp16
-rw-r--r--src/client/camera.h3
-rw-r--r--src/client/client.cpp64
-rw-r--r--src/client/client.h11
-rw-r--r--src/client/clientenvironment.cpp1
-rw-r--r--src/client/clientevent.h1
-rw-r--r--src/client/clientlauncher.cpp3
-rw-r--r--src/client/clientmap.cpp315
-rw-r--r--src/client/clientmap.h74
-rw-r--r--src/client/clouds.cpp3
-rw-r--r--src/client/content_cao.cpp111
-rw-r--r--src/client/content_cao.h12
-rw-r--r--src/client/content_mapblock.cpp143
-rw-r--r--src/client/content_mapblock.h5
-rw-r--r--src/client/fontengine.cpp12
-rw-r--r--src/client/game.cpp181
-rw-r--r--src/client/gameui.cpp12
-rw-r--r--src/client/guiscalingfilter.cpp49
-rw-r--r--src/client/guiscalingfilter.h8
-rw-r--r--src/client/hud.cpp71
-rw-r--r--src/client/imagefilters.cpp2
-rw-r--r--src/client/joystick_controller.cpp12
-rw-r--r--src/client/localplayer.cpp23
-rw-r--r--src/client/localplayer.h5
-rw-r--r--src/client/mapblock_mesh.cpp326
-rw-r--r--src/client/mapblock_mesh.h125
-rw-r--r--src/client/mesh.cpp1
-rw-r--r--src/client/mesh_generator_thread.cpp22
-rw-r--r--src/client/mesh_generator_thread.h7
-rw-r--r--src/client/minimap.cpp2
-rw-r--r--src/client/particles.cpp440
-rw-r--r--src/client/particles.h68
-rw-r--r--src/client/render/core.cpp16
-rw-r--r--src/client/render/stereo.cpp2
-rw-r--r--src/client/renderingengine.cpp32
-rw-r--r--src/client/renderingengine.h5
-rw-r--r--src/client/shader.cpp97
-rw-r--r--src/client/shadows/dynamicshadows.cpp80
-rw-r--r--src/client/shadows/dynamicshadows.h21
-rw-r--r--src/client/shadows/dynamicshadowsrender.cpp201
-rw-r--r--src/client/shadows/dynamicshadowsrender.h21
-rw-r--r--src/client/shadows/shadowsshadercallbacks.cpp12
-rw-r--r--src/client/shadows/shadowsshadercallbacks.h12
-rw-r--r--src/client/sky.cpp9
-rw-r--r--src/client/sky.h3
-rw-r--r--src/client/sound.h28
-rw-r--r--src/client/sound_openal.cpp82
-rw-r--r--src/client/tile.cpp2
-rw-r--r--src/client/tile.h14
-rw-r--r--src/client/wieldmesh.cpp21
51 files changed, 1989 insertions, 801 deletions
diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt
index 8d058852a..656ad45ce 100644
--- a/src/client/CMakeLists.txt
+++ b/src/client/CMakeLists.txt
@@ -60,7 +60,7 @@ set(client_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/wieldmesh.cpp
${CMAKE_CURRENT_SOURCE_DIR}/shadows/dynamicshadows.cpp
${CMAKE_CURRENT_SOURCE_DIR}/shadows/dynamicshadowsrender.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/shadows/shadowsshadercallbacks.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/shadows/shadowsScreenQuad.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/shadows/shadowsshadercallbacks.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/shadows/shadowsScreenQuad.cpp
PARENT_SCOPE
)
diff --git a/src/client/camera.cpp b/src/client/camera.cpp
index d1f19adb3..df75c52d6 100644
--- a/src/client/camera.cpp
+++ b/src/client/camera.cpp
@@ -47,7 +47,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
Camera::Camera(MapDrawControl &draw_control, Client *client, RenderingEngine *rendering_engine):
m_draw_control(draw_control),
- m_client(client)
+ m_client(client),
+ m_player_light_color(0xFFFFFFFF)
{
auto smgr = rendering_engine->get_scene_manager();
// note: making the camera node a child of the player node
@@ -74,11 +75,11 @@ Camera::Camera(MapDrawControl &draw_control, Client *client, RenderingEngine *re
* (as opposed to the this local caching). This can be addressed in
* a later release.
*/
- m_cache_fall_bobbing_amount = g_settings->getFloat("fall_bobbing_amount");
- m_cache_view_bobbing_amount = g_settings->getFloat("view_bobbing_amount");
+ m_cache_fall_bobbing_amount = g_settings->getFloat("fall_bobbing_amount", 0.0f, 100.0f);
+ m_cache_view_bobbing_amount = g_settings->getFloat("view_bobbing_amount", 0.0f, 7.9f);
// 45 degrees is the lowest FOV that doesn't cause the server to treat this
// as a zoom FOV and load world beyond the set server limits.
- m_cache_fov = std::fmax(g_settings->getFloat("fov"), 45.0f);
+ m_cache_fov = g_settings->getFloat("fov", 45.0f, 160.0f);
m_arm_inertia = g_settings->getBool("arm_inertia");
m_nametags.clear();
m_show_nametag_backgrounds = g_settings->getBool("show_nametag_backgrounds");
@@ -153,8 +154,10 @@ void Camera::step(f32 dtime)
bool was_under_zero = m_wield_change_timer < 0;
m_wield_change_timer = MYMIN(m_wield_change_timer + dtime, 0.125);
- if (m_wield_change_timer >= 0 && was_under_zero)
+ if (m_wield_change_timer >= 0 && was_under_zero) {
m_wieldnode->setItem(m_wield_item_next, m_client);
+ m_wieldnode->setNodeLightColor(m_player_light_color);
+ }
if (m_view_bobbing_state != 0)
{
@@ -555,7 +558,8 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 tool_reload_ratio)
m_wieldnode->setPosition(wield_position);
m_wieldnode->setRotation(wield_rotation);
- m_wieldnode->setNodeLightColor(player->light_color);
+ m_player_light_color = player->light_color;
+ m_wieldnode->setNodeLightColor(m_player_light_color);
// Set render distance
updateViewingRange();
diff --git a/src/client/camera.h b/src/client/camera.h
index 403d6024c..cbf248d97 100644
--- a/src/client/camera.h
+++ b/src/client/camera.h
@@ -264,4 +264,7 @@ private:
std::list<Nametag *> m_nametags;
bool m_show_nametag_backgrounds;
+
+ // Last known light color of the player
+ video::SColor m_player_light_color;
};
diff --git a/src/client/client.cpp b/src/client/client.cpp
index 0dd8a61d2..31bbf2463 100644
--- a/src/client/client.cpp
+++ b/src/client/client.cpp
@@ -58,6 +58,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "game.h"
#include "chatmessage.h"
#include "translation.h"
+#include "content/mod_configuration.h"
extern gui::IGUIEnvironment* guienv;
@@ -100,7 +101,8 @@ Client::Client(
MtEventManager *event,
RenderingEngine *rendering_engine,
bool ipv6,
- GameUI *game_ui
+ GameUI *game_ui,
+ ELoginRegister allow_login_or_register
):
m_tsrc(tsrc),
m_shsrc(shsrc),
@@ -117,6 +119,7 @@ Client::Client(
m_particle_manager(&m_env),
m_con(new con::Connection(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, ipv6, this)),
m_address_name(address_name),
+ m_allow_login_or_register(allow_login_or_register),
m_server_ser_ver(SER_FMT_VER_INVALID),
m_last_chat_message_sent(time(NULL)),
m_password(password),
@@ -194,7 +197,21 @@ void Client::loadMods()
scanModIntoMemory(BUILTIN_MOD_NAME, getBuiltinLuaPath());
m_script->loadModFromMemory(BUILTIN_MOD_NAME);
- ClientModConfiguration modconf(getClientModsLuaPath());
+ ModConfiguration modconf;
+ {
+ std::unordered_map<std::string, std::string> paths;
+ std::string path_user = porting::path_user + DIR_DELIM + "clientmods";
+ const auto modsPath = getClientModsLuaPath();
+ if (modsPath != path_user) {
+ paths["share"] = modsPath;
+ }
+ paths["mods"] = path_user;
+
+ std::string settings_path = path_user + DIR_DELIM + "mods.conf";
+ modconf.addModsFromConfig(settings_path, paths);
+ modconf.checkConflictsAndDeps();
+ }
+
m_mods = modconf.getMods();
// complain about mods with unsatisfied dependencies
if (!modconf.isConsistent()) {
@@ -396,10 +413,6 @@ void Client::step(float dtime)
initial_step = false;
}
else if(m_state == LC_Created) {
- if (m_is_registration_confirmation_state) {
- // Waiting confirmation
- return;
- }
float &counter = m_connection_reinit_timer;
counter -= dtime;
if(counter <= 0.0) {
@@ -426,7 +439,7 @@ void Client::step(float dtime)
if(m_map_timer_and_unload_interval.step(dtime, map_timer_and_unload_dtime)) {
std::vector<v3s16> deleted_blocks;
m_env.getMap().timerUpdate(map_timer_and_unload_dtime,
- g_settings->getFloat("client_unload_unused_data_timeout"),
+ std::max(g_settings->getFloat("client_unload_unused_data_timeout"), 0.0f),
g_settings->getS32("client_mapblock_limit"),
&deleted_blocks);
@@ -495,6 +508,7 @@ void Client::step(float dtime)
ClientEvent *event = new ClientEvent();
event->type = CE_PLAYER_DAMAGE;
event->player_damage.amount = damage;
+ event->player_damage.effect = true;
m_client_event_queue.push(event);
}
}
@@ -530,6 +544,7 @@ void Client::step(float dtime)
{
int num_processed_meshes = 0;
std::vector<v3s16> blocks_to_ack;
+ bool force_update_shadows = false;
while (!m_mesh_update_thread.m_queue_out.empty())
{
num_processed_meshes++;
@@ -556,9 +571,11 @@ void Client::step(float dtime)
if (is_empty)
delete r.mesh;
- else
+ else {
// Replace with the new mesh
block->mesh = r.mesh;
+ force_update_shadows = true;
+ }
}
} else {
delete r.mesh;
@@ -583,6 +600,10 @@ void Client::step(float dtime)
if (num_processed_meshes > 0)
g_profiler->graphAdd("num_processed_meshes", num_processed_meshes);
+
+ auto shadow_renderer = RenderingEngine::get_shadow_renderer();
+ if (shadow_renderer && force_update_shadows)
+ shadow_renderer->setForceUpdateShadowMap();
}
/*
@@ -786,16 +807,18 @@ void Client::peerAdded(con::Peer *peer)
infostream << "Client::peerAdded(): peer->id="
<< peer->id << std::endl;
}
+
void Client::deletingPeer(con::Peer *peer, bool timeout)
{
infostream << "Client::deletingPeer(): "
"Server Peer is getting deleted "
<< "(timeout=" << timeout << ")" << std::endl;
- if (timeout) {
- m_access_denied = true;
+ m_access_denied = true;
+ if (timeout)
m_access_denied_reason = gettext("Connection timed out.");
- }
+ else if (m_access_denied_reason.empty())
+ m_access_denied_reason = gettext("Connection aborted (protocol error?).");
}
/*
@@ -1069,18 +1092,6 @@ void Client::sendInit(const std::string &playerName)
Send(&pkt);
}
-void Client::promptConfirmRegistration(AuthMechanism chosen_auth_mechanism)
-{
- m_chosen_auth_mech = chosen_auth_mechanism;
- m_is_registration_confirmation_state = true;
-}
-
-void Client::confirmRegistration()
-{
- m_is_registration_confirmation_state = false;
- startAuth(m_chosen_auth_mech);
-}
-
void Client::startAuth(AuthMechanism chosen_auth_mechanism)
{
m_chosen_auth_mech = chosen_auth_mechanism;
@@ -1258,7 +1269,7 @@ void Client::sendChatMessage(const std::wstring &message)
pkt << message;
Send(&pkt);
- } else if (m_out_chat_queue.size() < (u16) max_queue_size || max_queue_size == -1) {
+ } else if (m_out_chat_queue.size() < (u16) max_queue_size || max_queue_size < 0) {
m_out_chat_queue.push(message);
} else {
infostream << "Could not queue chat message because maximum out chat queue size ("
@@ -1812,11 +1823,10 @@ void Client::makeScreenshot()
if (!raw_image)
return;
- time_t t = time(NULL);
- struct tm *tm = localtime(&t);
+ const struct tm tm = mt_localtime();
char timetstamp_c[64];
- strftime(timetstamp_c, sizeof(timetstamp_c), "%Y%m%d_%H%M%S", tm);
+ strftime(timetstamp_c, sizeof(timetstamp_c), "%Y%m%d_%H%M%S", &tm);
std::string screenshot_dir;
diff --git a/src/client/client.h b/src/client/client.h
index 84c85471d..bdcc2a3dd 100644
--- a/src/client/client.h
+++ b/src/client/client.h
@@ -37,6 +37,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "mesh_generator_thread.h"
#include "network/address.h"
#include "network/peerhandler.h"
+#include "gameparams.h"
#include <fstream>
#define CLIENT_CHAT_MESSAGE_LIMIT_PER_10S 10.0f
@@ -127,7 +128,8 @@ public:
MtEventManager *event,
RenderingEngine *rendering_engine,
bool ipv6,
- GameUI *game_ui
+ GameUI *game_ui,
+ ELoginRegister allow_login_or_register
);
~Client();
@@ -227,6 +229,7 @@ public:
void handleCommand_PlayerSpeed(NetworkPacket *pkt);
void handleCommand_MediaPush(NetworkPacket *pkt);
void handleCommand_MinimapModes(NetworkPacket *pkt);
+ void handleCommand_SetLighting(NetworkPacket *pkt);
void ProcessData(NetworkPacket *pkt);
@@ -346,8 +349,6 @@ public:
u16 getProtoVersion()
{ return m_proto_ver; }
- void confirmRegistration();
- bool m_is_registration_confirmation_state = false;
bool m_simple_singleplayer_mode;
float mediaReceiveProgress();
@@ -406,7 +407,7 @@ public:
}
ClientScripting *getScript() { return m_script; }
- const bool modsLoaded() const { return m_mods_loaded; }
+ bool modsLoaded() const { return m_mods_loaded; }
void pushToEventQueue(ClientEvent *event);
@@ -459,7 +460,6 @@ private:
static AuthMechanism choseAuthMech(const u32 mechs);
void sendInit(const std::string &playerName);
- void promptConfirmRegistration(AuthMechanism chosen_auth_mechanism);
void startAuth(AuthMechanism chosen_auth_mechanism);
void sendDeletedBlocks(std::vector<v3s16> &blocks);
void sendGotBlocks(const std::vector<v3s16> &blocks);
@@ -491,6 +491,7 @@ private:
ParticleManager m_particle_manager;
std::unique_ptr<con::Connection> m_con;
std::string m_address_name;
+ ELoginRegister m_allow_login_or_register = ELoginRegister::Any;
Camera *m_camera = nullptr;
Minimap *m_minimap = nullptr;
bool m_minimap_disabled_by_server = false;
diff --git a/src/client/clientenvironment.cpp b/src/client/clientenvironment.cpp
index 448af36c6..183a95007 100644
--- a/src/client/clientenvironment.cpp
+++ b/src/client/clientenvironment.cpp
@@ -305,6 +305,7 @@ void ClientEnvironment::step(float dtime)
node_at_lplayer = m_map->getNode(p);
u16 light = getInteriorLight(node_at_lplayer, 0, m_client->ndef());
+ lplayer->light_color = encode_light(light, 0); // this transfers light.alpha
final_color_blend(&lplayer->light_color, light, day_night_ratio);
}
diff --git a/src/client/clientevent.h b/src/client/clientevent.h
index 17d3aedd6..243a94596 100644
--- a/src/client/clientevent.h
+++ b/src/client/clientevent.h
@@ -87,6 +87,7 @@ struct ClientEvent
struct
{
u16 amount;
+ bool effect;
} player_damage;
struct
{
diff --git a/src/client/clientlauncher.cpp b/src/client/clientlauncher.cpp
index 063154316..60c9525f3 100644
--- a/src/client/clientlauncher.cpp
+++ b/src/client/clientlauncher.cpp
@@ -451,6 +451,7 @@ bool ClientLauncher::launch_game(std::string &error_message,
start_data.name = menudata.name;
start_data.password = menudata.password;
start_data.address = std::move(menudata.address);
+ start_data.allow_login_or_register = menudata.allow_login_or_register;
server_name = menudata.servername;
server_description = menudata.serverdescription;
@@ -564,6 +565,8 @@ void ClientLauncher::speed_tests()
// volatile to avoid some potential compiler optimisations
volatile static s16 temp16;
volatile static f32 tempf;
+ // Silence compiler warning
+ (void)temp16;
static v3f tempv3f1;
static v3f tempv3f2;
static std::string tempstring;
diff --git a/src/client/clientmap.cpp b/src/client/clientmap.cpp
index 1a024e464..c5ba98ff6 100644
--- a/src/client/clientmap.cpp
+++ b/src/client/clientmap.cpp
@@ -97,9 +97,32 @@ ClientMap::ClientMap(
m_cache_trilinear_filter = g_settings->getBool("trilinear_filter");
m_cache_bilinear_filter = g_settings->getBool("bilinear_filter");
m_cache_anistropic_filter = g_settings->getBool("anisotropic_filter");
+ m_cache_transparency_sorting_distance = g_settings->getU16("transparency_sorting_distance");
}
+void ClientMap::updateCamera(v3f pos, v3f dir, f32 fov, v3s16 offset)
+{
+ v3s16 previous_node = floatToInt(m_camera_position, BS) + m_camera_offset;
+ v3s16 previous_block = getContainerPos(previous_node, MAP_BLOCKSIZE);
+
+ m_camera_position = pos;
+ m_camera_direction = dir;
+ m_camera_fov = fov;
+ m_camera_offset = offset;
+
+ v3s16 current_node = floatToInt(m_camera_position, BS) + m_camera_offset;
+ v3s16 current_block = getContainerPos(current_node, MAP_BLOCKSIZE);
+
+ // reorder the blocks when camera crosses block boundary
+ if (previous_block != current_block)
+ m_needs_update_drawlist = true;
+
+ // reorder transparent meshes when camera crosses node boundary
+ if (previous_node != current_node)
+ m_needs_update_transparent_meshes = true;
+}
+
MapSector * ClientMap::emergeSector(v2s16 p2d)
{
// Check that it doesn't exist already
@@ -196,13 +219,11 @@ void ClientMap::updateDrawList()
// Number of blocks occlusion culled
u32 blocks_occlusion_culled = 0;
- // No occlusion culling when free_move is on and camera is
- // inside ground
+ // No occlusion culling when free_move is on and camera is inside ground
bool occlusion_culling_enabled = true;
- if (g_settings->getBool("free_move") && g_settings->getBool("noclip")) {
+ if (m_control.allow_noclip) {
MapNode n = getNode(cam_pos_nodes);
- if (n.getContent() == CONTENT_IGNORE ||
- m_nodedef->get(n).solidness == 2)
+ if (n.getContent() == CONTENT_IGNORE || m_nodedef->get(n).solidness == 2)
occlusion_culling_enabled = false;
}
@@ -324,21 +345,16 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
//u32 mesh_animate_count_far = 0;
/*
+ Update transparent meshes
+ */
+ if (is_transparent_pass)
+ updateTransparentMeshBuffers();
+
+ /*
Draw the selected MapBlocks
*/
MeshBufListList 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;
@@ -375,7 +391,15 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
/*
Get the meshbuffers of the block
*/
- {
+ if (is_transparent_pass) {
+ // In transparent pass, the mesh will give us
+ // the partial buffers in the correct order
+ for (auto &buffer : block->mesh->getTransparentBuffers())
+ draw_order.emplace_back(block_pos, &buffer);
+ }
+ else {
+ // otherwise, group buffers across meshes
+ // using MeshBufListList
MapBlockMesh *mapBlockMesh = block->mesh;
assert(mapBlockMesh);
@@ -389,35 +413,14 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
video::SMaterial& material = buf->getMaterial();
video::IMaterialRenderer* rnd =
- driver->getMaterialRenderer(material.MaterialType);
+ driver->getMaterialRenderer(material.MaterialType);
bool transparent = (rnd && rnd->isTransparent());
- if (transparent == is_transparent_pass) {
+ if (!transparent) {
if (buf->getVertexCount() == 0)
errorstream << "Block [" << analyze_block(block)
- << "] contains an empty meshbuf" << std::endl;
-
- material.setFlag(video::EMF_TRILINEAR_FILTER,
- m_cache_trilinear_filter);
- material.setFlag(video::EMF_BILINEAR_FILTER,
- m_cache_bilinear_filter);
- material.setFlag(video::EMF_ANISOTROPIC_FILTER,
- m_cache_anistropic_filter);
- material.setFlag(video::EMF_WIREFRAME,
- m_control.show_wireframe);
-
- 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);
- }
+ << "] contains an empty meshbuf" << std::endl;
+
+ grouped_buffers.add(buf, block_pos, layer);
}
}
}
@@ -442,8 +445,9 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
// Render all mesh buffers in order
drawcall_count += draw_order.size();
+
for (auto &descriptor : draw_order) {
- scene::IMeshBuffer *buf = descriptor.m_buffer;
+ scene::IMeshBuffer *buf = descriptor.getBuffer();
// Check and abort if the machine is swapping a lot
if (draw.getTimerTime() > 2000) {
@@ -454,28 +458,42 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
if (!descriptor.m_reuse_material) {
auto &material = buf->getMaterial();
+
+ // Apply filter settings
+ material.setFlag(video::EMF_TRILINEAR_FILTER,
+ m_cache_trilinear_filter);
+ material.setFlag(video::EMF_BILINEAR_FILTER,
+ m_cache_bilinear_filter);
+ material.setFlag(video::EMF_ANISOTROPIC_FILTER,
+ m_cache_anistropic_filter);
+ material.setFlag(video::EMF_WIREFRAME,
+ m_control.show_wireframe);
+
// pass the shadow map texture to the buffer texture
ShadowRenderer *shadow = m_rendering_engine->get_shadow_renderer();
if (shadow && shadow->is_active()) {
- auto &layer = material.TextureLayer[3];
+ auto &layer = material.TextureLayer[ShadowRenderer::TEXTURE_LAYER_SHADOW];
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.BilinearFilter = false;
+ layer.AnisotropicFilter = false;
layer.TrilinearFilter = false;
}
driver->setMaterial(material);
++material_swaps;
+ material.TextureLayer[ShadowRenderer::TEXTURE_LAYER_SHADOW].Texture = nullptr;
}
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();
+ descriptor.draw(driver);
+ vertex_count += buf->getIndexCount();
}
g_profiler->avg(prefix + "draw meshes [ms]", draw.stop(true));
@@ -659,19 +677,17 @@ void ClientMap::renderPostFx(CameraMode cam_mode)
MapNode n = getNode(floatToInt(m_camera_position, BS));
- // - If the player is in a solid node, make everything black.
- // - If the player is in liquid, draw a semi-transparent overlay.
- // - Do not if player is in third person mode
const ContentFeatures& features = m_nodedef->get(n);
video::SColor post_effect_color = features.post_effect_color;
- if(features.solidness == 2 && !(g_settings->getBool("noclip") &&
- m_client->checkLocalPrivilege("noclip")) &&
- cam_mode == CAMERA_MODE_FIRST)
- {
+
+ // If the camera is in a solid node, make everything black.
+ // (first person mode only)
+ if (features.solidness == 2 && cam_mode == CAMERA_MODE_FIRST &&
+ !m_control.allow_noclip) {
post_effect_color = video::SColor(255, 0, 0, 0);
}
- if (post_effect_color.getAlpha() != 0)
- {
+
+ if (post_effect_color.getAlpha() != 0) {
// Draw a full-screen rectangle
video::IVideoDriver* driver = SceneManager->getVideoDriver();
v2u32 ss = driver->getScreenSize();
@@ -698,7 +714,9 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
u32 drawcall_count = 0;
u32 vertex_count = 0;
- MeshBufListList drawbufs;
+ MeshBufListList grouped_buffers;
+ std::vector<DrawDescriptor> draw_order;
+
int count = 0;
int low_bound = is_transparent_pass ? 0 : m_drawlist_shadow.size() / total_frames * frame;
@@ -727,7 +745,15 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
/*
Get the meshbuffers of the block
*/
- {
+ if (is_transparent_pass) {
+ // In transparent pass, the mesh will give us
+ // the partial buffers in the correct order
+ for (auto &buffer : block->mesh->getTransparentBuffers())
+ draw_order.emplace_back(block_pos, &buffer);
+ }
+ else {
+ // otherwise, group buffers across meshes
+ // using MeshBufListList
MapBlockMesh *mapBlockMesh = block->mesh;
assert(mapBlockMesh);
@@ -742,79 +768,91 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
video::SMaterial &mat = buf->getMaterial();
auto rnd = driver->getMaterialRenderer(mat.MaterialType);
bool transparent = rnd && rnd->isTransparent();
- if (transparent == is_transparent_pass)
- drawbufs.add(buf, block_pos, layer);
+ if (!transparent)
+ grouped_buffers.add(buf, block_pos, layer);
}
}
}
}
+ u32 buffer_count = 0;
+ for (auto &lists : grouped_buffers.lists)
+ for (MeshBufList &list : lists)
+ buffer_count += list.bufs.size();
+
+ draw_order.reserve(draw_order.size() + buffer_count);
+
+ // Capture draw order for all solid meshes
+ for (auto &lists : grouped_buffers.lists) {
+ for (MeshBufList &list : lists) {
+ // iterate in reverse to draw closest blocks first
+ for (auto it = list.bufs.rbegin(); it != list.bufs.rend(); ++it)
+ draw_order.emplace_back(it->first, it->second, it != list.bufs.rbegin());
+ }
+ }
+
TimeTaker draw("Drawing shadow mesh buffers");
core::matrix4 m; // Model matrix
v3f offset = intToFloat(m_camera_offset, BS);
+ u32 material_swaps = 0;
- // Render all layers in order
- for (auto &lists : drawbufs.lists) {
- for (MeshBufList &list : lists) {
- // Check and abort if the machine is swapping a lot
- if (draw.getTimerTime() > 1000) {
- infostream << "ClientMap::renderMapShadows(): Rendering "
- "took >1s, returning." << std::endl;
- break;
- }
- for (auto &pair : list.bufs) {
- scene::IMeshBuffer *buf = pair.second;
-
- // override some material properties
- video::SMaterial local_material = buf->getMaterial();
- local_material.MaterialType = material.MaterialType;
- local_material.BackfaceCulling = material.BackfaceCulling;
- local_material.FrontfaceCulling = material.FrontfaceCulling;
- local_material.BlendOperation = material.BlendOperation;
- local_material.Lighting = false;
- driver->setMaterial(local_material);
-
- v3f block_wpos = intToFloat(pair.first * MAP_BLOCKSIZE, BS);
- m.setTranslation(block_wpos - offset);
-
- driver->setTransform(video::ETS_WORLD, m);
- driver->drawMeshBuffer(buf);
- vertex_count += buf->getVertexCount();
- }
+ // Render all mesh buffers in order
+ drawcall_count += draw_order.size();
+
+ for (auto &descriptor : draw_order) {
+ scene::IMeshBuffer *buf = descriptor.getBuffer();
- drawcall_count += list.bufs.size();
+ // Check and abort if the machine is swapping a lot
+ if (draw.getTimerTime() > 1000) {
+ infostream << "ClientMap::renderMapShadows(): Rendering "
+ "took >1s, returning." << std::endl;
+ break;
+ }
+
+ if (!descriptor.m_reuse_material) {
+ // override some material properties
+ video::SMaterial local_material = buf->getMaterial();
+ local_material.MaterialType = material.MaterialType;
+ local_material.BackfaceCulling = material.BackfaceCulling;
+ local_material.FrontfaceCulling = material.FrontfaceCulling;
+ local_material.BlendOperation = material.BlendOperation;
+ local_material.Lighting = false;
+ driver->setMaterial(local_material);
+ ++material_swaps;
}
+
+ v3f block_wpos = intToFloat(descriptor.m_pos * MAP_BLOCKSIZE, BS);
+ m.setTranslation(block_wpos - offset);
+
+ driver->setTransform(video::ETS_WORLD, m);
+ descriptor.draw(driver);
+ vertex_count += buf->getIndexCount();
}
- // restore the driver material state
+ // restore the driver material state
video::SMaterial clean;
clean.BlendOperation = video::EBO_ADD;
driver->setMaterial(clean); // reset material to defaults
driver->draw3DLine(v3f(), v3f(), video::SColor(0));
-
+
g_profiler->avg(prefix + "draw meshes [ms]", draw.stop(true));
g_profiler->avg(prefix + "vertices drawn [#]", vertex_count);
g_profiler->avg(prefix + "drawcalls [#]", drawcall_count);
+ g_profiler->avg(prefix + "material swaps [#]", material_swaps);
}
/*
Custom update draw list for the pov of shadow light.
*/
-void ClientMap::updateDrawListShadow(const v3f &shadow_light_pos, const v3f &shadow_light_dir, float shadow_range)
+void ClientMap::updateDrawListShadow(v3f shadow_light_pos, v3f shadow_light_dir, float radius, float length)
{
ScopeProfiler sp(g_profiler, "CM::updateDrawListShadow()", SPT_AVG);
- const v3f camera_position = shadow_light_pos;
- const v3f camera_direction = shadow_light_dir;
- // I "fake" fov just to avoid creating a new function to handle orthographic
- // projection.
- const f32 camera_fov = m_camera_fov * 1.9f;
-
- v3s16 cam_pos_nodes = floatToInt(camera_position, BS);
+ v3s16 cam_pos_nodes = floatToInt(shadow_light_pos, BS);
v3s16 p_blocks_min;
v3s16 p_blocks_max;
- getBlocksInViewRange(cam_pos_nodes, &p_blocks_min, &p_blocks_max, shadow_range);
+ getBlocksInViewRange(cam_pos_nodes, &p_blocks_min, &p_blocks_max, radius + length);
std::vector<v2s16> blocks_in_range;
@@ -824,15 +862,6 @@ void ClientMap::updateDrawListShadow(const v3f &shadow_light_pos, const v3f &sha
}
m_drawlist_shadow.clear();
- // We need to append the blocks from the camera POV because sometimes
- // they are not inside the light frustum and it creates glitches.
- // FIXME: This could be removed if we figure out why they are missing
- // from the light frustum.
- for (auto &i : m_drawlist) {
- i.second->refGrab();
- m_drawlist_shadow[i.first] = i.second;
- }
-
// Number of blocks currently loaded by the client
u32 blocks_loaded = 0;
// Number of blocks with mesh in rendering range
@@ -858,23 +887,13 @@ void ClientMap::updateDrawListShadow(const v3f &shadow_light_pos, const v3f &sha
continue;
}
- float range = shadow_range;
-
- float d = 0.0;
- if (!isBlockInSight(block->getPos(), camera_position,
- camera_direction, camera_fov, range, &d))
+ v3f block_pos = intToFloat(block->getPos() * MAP_BLOCKSIZE, BS);
+ v3f projection = shadow_light_pos + shadow_light_dir * shadow_light_dir.dotProduct(block_pos - shadow_light_pos);
+ if (projection.getDistanceFrom(block_pos) > radius)
continue;
blocks_in_range_with_mesh++;
- /*
- Occlusion culling
- */
- if (isBlockOccluded(block, cam_pos_nodes)) {
- blocks_occlusion_culled++;
- continue;
- }
-
// This block is in range. Reset usage timer.
block->resetUsageTimer();
@@ -891,3 +910,55 @@ void ClientMap::updateDrawListShadow(const v3f &shadow_light_pos, const v3f &sha
g_profiler->avg("SHADOW MapBlocks drawn [#]", m_drawlist_shadow.size());
g_profiler->avg("SHADOW MapBlocks loaded [#]", blocks_loaded);
}
+
+void ClientMap::updateTransparentMeshBuffers()
+{
+ ScopeProfiler sp(g_profiler, "CM::updateTransparentMeshBuffers", SPT_AVG);
+ u32 sorted_blocks = 0;
+ u32 unsorted_blocks = 0;
+ f32 sorting_distance_sq = pow(m_cache_transparency_sorting_distance * BS, 2.0f);
+
+
+ // Update the order of transparent mesh buffers in each mesh
+ for (auto it = m_drawlist.begin(); it != m_drawlist.end(); it++) {
+ MapBlock* block = it->second;
+ if (!block->mesh)
+ continue;
+
+ if (m_needs_update_transparent_meshes ||
+ block->mesh->getTransparentBuffers().size() == 0) {
+
+ v3s16 block_pos = block->getPos();
+ v3f block_pos_f = intToFloat(block_pos * MAP_BLOCKSIZE + MAP_BLOCKSIZE / 2, BS);
+ f32 distance = m_camera_position.getDistanceFromSQ(block_pos_f);
+ if (distance <= sorting_distance_sq) {
+ block->mesh->updateTransparentBuffers(m_camera_position, block_pos);
+ ++sorted_blocks;
+ }
+ else {
+ block->mesh->consolidateTransparentBuffers();
+ ++unsorted_blocks;
+ }
+ }
+ }
+
+ g_profiler->avg("CM::Transparent Buffers - Sorted", sorted_blocks);
+ g_profiler->avg("CM::Transparent Buffers - Unsorted", unsorted_blocks);
+ m_needs_update_transparent_meshes = false;
+}
+
+scene::IMeshBuffer* ClientMap::DrawDescriptor::getBuffer()
+{
+ return m_use_partial_buffer ? m_partial_buffer->getBuffer() : m_buffer;
+}
+
+void ClientMap::DrawDescriptor::draw(video::IVideoDriver* driver)
+{
+ if (m_use_partial_buffer) {
+ m_partial_buffer->beforeDraw();
+ driver->drawMeshBuffer(m_partial_buffer->getBuffer());
+ m_partial_buffer->afterDraw();
+ } else {
+ driver->drawMeshBuffer(m_buffer);
+ }
+}
diff --git a/src/client/clientmap.h b/src/client/clientmap.h
index b4dc42395..8c45b5382 100644
--- a/src/client/clientmap.h
+++ b/src/client/clientmap.h
@@ -27,10 +27,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
struct MapDrawControl
{
- // Overrides limits by drawing everything
- bool range_all = false;
// Wanted drawing range
float wanted_range = 0.0f;
+ // Overrides limits by drawing everything
+ bool range_all = false;
+ // Allow rendering out of bounds
+ bool allow_noclip = false;
// show a wire frame for debugging
bool show_wireframe = false;
};
@@ -56,6 +58,7 @@ struct MeshBufListList
class Client;
class ITextureSource;
+class PartialMeshBuffer;
/*
ClientMap
@@ -75,53 +78,37 @@ public:
virtual ~ClientMap() = default;
- s32 mapType() const
+ bool maySaveBlocks() override
{
- return MAPTYPE_CLIENT;
+ return false;
}
- void drop()
+ void drop() override
{
- ISceneNode::drop();
+ ISceneNode::drop(); // calls destructor
}
- void updateCamera(const v3f &pos, const v3f &dir, f32 fov, const v3s16 &offset)
- {
- 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;
- }
+ void updateCamera(v3f pos, v3f dir, f32 fov, v3s16 offset);
/*
Forcefully get a sector from somewhere
*/
- MapSector * emergeSector(v2s16 p);
-
- //void deSerializeSector(v2s16 p2d, std::istream &is);
+ MapSector * emergeSector(v2s16 p) override;
/*
ISceneNode methods
*/
- virtual void OnRegisterSceneNode();
+ virtual void OnRegisterSceneNode() override;
- virtual void render()
+ virtual void render() override
{
video::IVideoDriver* driver = SceneManager->getVideoDriver();
driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
renderMap(driver, SceneManager->getSceneNodeRenderPass());
}
- virtual const aabb3f &getBoundingBox() const
+ virtual const aabb3f &getBoundingBox() const override
{
return m_box;
}
@@ -129,7 +116,7 @@ public:
void getBlocksInViewRange(v3s16 cam_pos_nodes,
v3s16 *p_blocks_min, v3s16 *p_blocks_max, float range=-1.0f);
void updateDrawList();
- void updateDrawListShadow(const v3f &shadow_light_pos, const v3f &shadow_light_dir, float shadow_range);
+ void updateDrawListShadow(v3f shadow_light_pos, v3f shadow_light_dir, float radius, float length);
// Returns true if draw list needs updating before drawing the next frame.
bool needsUpdateDrawList() { return m_needs_update_drawlist; }
void renderMap(video::IVideoDriver* driver, s32 pass);
@@ -143,13 +130,17 @@ public:
void renderPostFx(CameraMode cam_mode);
// For debug printing
- virtual void PrintInfo(std::ostream &out);
+ void PrintInfo(std::ostream &out) override;
const MapDrawControl & getControl() const { return m_control; }
f32 getWantedRange() const { return m_control.wanted_range; }
f32 getCameraFov() const { return m_camera_fov; }
private:
+
+ // update the vertex order in transparent mesh buffers
+ void updateTransparentMeshBuffers();
+
// Orders blocks by distance to the camera
class MapBlockComparer
{
@@ -167,6 +158,29 @@ private:
v3s16 m_camera_block;
};
+
+ // reference to a mesh buffer used when rendering the map.
+ struct DrawDescriptor {
+ v3s16 m_pos;
+ union {
+ scene::IMeshBuffer *m_buffer;
+ const PartialMeshBuffer *m_partial_buffer;
+ };
+ bool m_reuse_material:1;
+ bool m_use_partial_buffer:1;
+
+ DrawDescriptor(v3s16 pos, scene::IMeshBuffer *buffer, bool reuse_material) :
+ m_pos(pos), m_buffer(buffer), m_reuse_material(reuse_material), m_use_partial_buffer(false)
+ {}
+
+ DrawDescriptor(v3s16 pos, const PartialMeshBuffer *buffer) :
+ m_pos(pos), m_partial_buffer(buffer), m_reuse_material(false), m_use_partial_buffer(true)
+ {}
+
+ scene::IMeshBuffer* getBuffer();
+ void draw(video::IVideoDriver* driver);
+ };
+
Client *m_client;
RenderingEngine *m_rendering_engine;
@@ -179,6 +193,7 @@ private:
v3f m_camera_direction = v3f(0,0,1);
f32 m_camera_fov = M_PI;
v3s16 m_camera_offset;
+ bool m_needs_update_transparent_meshes = true;
std::map<v3s16, MapBlock*, MapBlockComparer> m_drawlist;
std::map<v3s16, MapBlock*> m_drawlist_shadow;
@@ -190,4 +205,5 @@ private:
bool m_cache_bilinear_filter;
bool m_cache_anistropic_filter;
bool m_added_to_shadow_renderer{false};
+ u16 m_cache_transparency_sorting_distance;
};
diff --git a/src/client/clouds.cpp b/src/client/clouds.cpp
index 383a1d799..c84c03034 100644
--- a/src/client/clouds.cpp
+++ b/src/client/clouds.cpp
@@ -366,7 +366,8 @@ void Clouds::update(const v3f &camera_p, const video::SColorf &color_diffuse)
void Clouds::readSettings()
{
- m_cloud_radius_i = g_settings->getU16("cloud_radius");
+ // Upper limit was chosen due to posible render bugs
+ m_cloud_radius_i = rangelim(g_settings->getU16("cloud_radius"), 1, 62);
m_enable_3d = g_settings->getBool("enable_3d_clouds");
}
diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp
index 1d4636a08..568d25fb7 100644
--- a/src/client/content_cao.cpp
+++ b/src/client/content_cao.cpp
@@ -38,9 +38,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "map.h"
#include "mesh.h"
#include "nodedef.h"
-#include "serialization.h" // For decompressZlib
#include "settings.h"
-#include "sound.h"
#include "tool.h"
#include "wieldmesh.h"
#include <algorithm>
@@ -435,7 +433,7 @@ const v3f GenericCAO::getPosition() const
return m_position;
}
-const bool GenericCAO::isImmortal()
+bool GenericCAO::isImmortal() const
{
return itemgroup_get(getGroups(), "immortal");
}
@@ -745,9 +743,6 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr)
m_meshnode = m_smgr->addMeshSceneNode(mesh, m_matrixnode);
m_meshnode->grab();
mesh->drop();
- // Set it to use the materials of the meshbuffers directly.
- // This is needed for changing the texture in the future
- m_meshnode->setReadOnlyMaterials(true);
} else if (m_prop.visual == "cube") {
grabMatrixNode();
scene::IMesh *mesh = createCubeMesh(v3f(BS,BS,BS));
@@ -861,10 +856,11 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr)
void GenericCAO::updateLight(u32 day_night_ratio)
{
- if (m_glow < 0)
+ if (m_prop.glow < 0)
return;
- u8 light_at_pos = 0;
+ u16 light_at_pos = 0;
+ u8 light_at_pos_intensity = 0;
bool pos_ok = false;
v3s16 pos[3];
@@ -873,28 +869,33 @@ void GenericCAO::updateLight(u32 day_night_ratio)
bool this_ok;
MapNode n = m_env->getMap().getNode(pos[i], &this_ok);
if (this_ok) {
- u8 this_light = n.getLightBlend(day_night_ratio, m_client->ndef());
- light_at_pos = MYMAX(light_at_pos, this_light);
+ u16 this_light = getInteriorLight(n, 0, m_client->ndef());
+ u8 this_light_intensity = MYMAX(this_light & 0xFF, this_light >> 8);
+ if (this_light_intensity > light_at_pos_intensity) {
+ light_at_pos = this_light;
+ light_at_pos_intensity = this_light_intensity;
+ }
pos_ok = true;
}
}
if (!pos_ok)
- light_at_pos = blend_light(day_night_ratio, LIGHT_SUN, 0);
+ light_at_pos = LIGHT_SUN;
+
+ video::SColor light = encode_light(light_at_pos, m_prop.glow);
+ if (!m_enable_shaders)
+ final_color_blend(&light, light_at_pos, day_night_ratio);
- u8 light = decode_light(light_at_pos + m_glow);
if (light != m_last_light) {
m_last_light = light;
setNodeLight(light);
}
}
-void GenericCAO::setNodeLight(u8 light)
+void GenericCAO::setNodeLight(const video::SColor &light_color)
{
- video::SColor color(255, light, light, light);
-
if (m_prop.visual == "wielditem" || m_prop.visual == "item") {
if (m_wield_meshnode)
- m_wield_meshnode->setNodeLightColor(color);
+ m_wield_meshnode->setNodeLightColor(light_color);
return;
}
@@ -902,12 +903,8 @@ void GenericCAO::setNodeLight(u8 light)
if (m_prop.visual == "upright_sprite") {
if (!m_meshnode)
return;
-
- scene::IMesh *mesh = m_meshnode->getMesh();
- for (u32 i = 0; i < mesh->getMeshBufferCount(); ++i) {
- scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
- buf->getMaterial().EmissiveColor = color;
- }
+ for (u32 i = 0; i < m_meshnode->getMaterialCount(); ++i)
+ m_meshnode->getMaterial(i).EmissiveColor = light_color;
} else {
scene::ISceneNode *node = getSceneNode();
if (!node)
@@ -915,16 +912,16 @@ void GenericCAO::setNodeLight(u8 light)
for (u32 i = 0; i < node->getMaterialCount(); ++i) {
video::SMaterial &material = node->getMaterial(i);
- material.EmissiveColor = color;
+ material.EmissiveColor = light_color;
}
}
} else {
if (m_meshnode) {
- setMeshColor(m_meshnode->getMesh(), color);
+ setMeshColor(m_meshnode->getMesh(), light_color);
} else if (m_animated_meshnode) {
- setAnimatedMeshColor(m_animated_meshnode, color);
+ setAnimatedMeshColor(m_animated_meshnode, light_color);
} else if (m_spritenode) {
- m_spritenode->setColor(color);
+ m_spritenode->setColor(light_color);
}
}
}
@@ -1175,7 +1172,7 @@ void GenericCAO::step(float dtime, ClientEnvironment *env)
// Reduce footstep gain, as non-local-player footsteps are
// somehow louder.
spec.gain *= 0.6f;
- m_client->sound()->playSoundAt(spec, false, getPosition());
+ m_client->sound()->playSoundAt(spec, getPosition());
}
}
}
@@ -1200,7 +1197,7 @@ void GenericCAO::step(float dtime, ClientEnvironment *env)
}
}
- if (!getParent() && node && fabs(m_prop.automatic_rotate) > 0.001f) {
+ if (node && fabs(m_prop.automatic_rotate) > 0.001f) {
// This is the child node's rotation. It is only used for automatic_rotate.
v3f local_rot = node->getRotation();
local_rot.Y = modulo360f(local_rot.Y - dtime * core::RADTODEG *
@@ -1320,7 +1317,6 @@ void GenericCAO::updateTextures(std::string mod)
m_previous_texture_modifier = m_current_texture_modifier;
m_current_texture_modifier = mod;
- m_glow = m_prop.glow;
if (m_spritenode) {
if (m_prop.visual == "sprite") {
@@ -1440,22 +1436,22 @@ void GenericCAO::updateTextures(std::string mod)
if (!m_prop.textures.empty())
tname = m_prop.textures[0];
tname += mod;
- scene::IMeshBuffer *buf = mesh->getMeshBuffer(0);
- buf->getMaterial().setTexture(0,
+ auto& material = m_meshnode->getMaterial(0);
+ material.setTexture(0,
tsrc->getTextureForMesh(tname));
// This allows setting per-material colors. However, until a real lighting
// system is added, the code below will have no effect. Once MineTest
// has directional lighting, it should work automatically.
if(!m_prop.colors.empty()) {
- buf->getMaterial().AmbientColor = m_prop.colors[0];
- buf->getMaterial().DiffuseColor = m_prop.colors[0];
- buf->getMaterial().SpecularColor = m_prop.colors[0];
+ material.AmbientColor = m_prop.colors[0];
+ material.DiffuseColor = m_prop.colors[0];
+ material.SpecularColor = m_prop.colors[0];
}
- buf->getMaterial().setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter);
- buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter);
- buf->getMaterial().setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter);
+ material.setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter);
+ material.setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter);
+ material.setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter);
}
{
std::string tname = "no_texture.png";
@@ -1464,29 +1460,29 @@ void GenericCAO::updateTextures(std::string mod)
else if (!m_prop.textures.empty())
tname = m_prop.textures[0];
tname += mod;
- scene::IMeshBuffer *buf = mesh->getMeshBuffer(1);
- buf->getMaterial().setTexture(0,
+ auto& material = m_meshnode->getMaterial(1);
+ material.setTexture(0,
tsrc->getTextureForMesh(tname));
// This allows setting per-material colors. However, until a real lighting
// system is added, the code below will have no effect. Once MineTest
// has directional lighting, it should work automatically.
if (m_prop.colors.size() >= 2) {
- buf->getMaterial().AmbientColor = m_prop.colors[1];
- buf->getMaterial().DiffuseColor = m_prop.colors[1];
- buf->getMaterial().SpecularColor = m_prop.colors[1];
+ material.AmbientColor = m_prop.colors[1];
+ material.DiffuseColor = m_prop.colors[1];
+ material.SpecularColor = m_prop.colors[1];
} else if (!m_prop.colors.empty()) {
- buf->getMaterial().AmbientColor = m_prop.colors[0];
- buf->getMaterial().DiffuseColor = m_prop.colors[0];
- buf->getMaterial().SpecularColor = m_prop.colors[0];
+ material.AmbientColor = m_prop.colors[0];
+ material.DiffuseColor = m_prop.colors[0];
+ material.SpecularColor = m_prop.colors[0];
}
- buf->getMaterial().setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter);
- buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter);
- buf->getMaterial().setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter);
+ material.setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter);
+ material.setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter);
+ material.setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter);
}
// Set mesh color (only if lighting is disabled)
- if (!m_prop.colors.empty() && m_glow < 0)
+ if (!m_prop.colors.empty() && m_prop.glow < 0)
setMeshColor(mesh, m_prop.colors[0]);
}
}
@@ -1960,20 +1956,17 @@ void GenericCAO::updateMeshCulling()
const bool hidden = m_client->getCamera()->getCameraMode() == CAMERA_MODE_FIRST;
- if (m_meshnode && m_prop.visual == "upright_sprite") {
- u32 buffers = m_meshnode->getMesh()->getMeshBufferCount();
- for (u32 i = 0; i < buffers; i++) {
- video::SMaterial &mat = m_meshnode->getMesh()->getMeshBuffer(i)->getMaterial();
- // upright sprite has no backface culling
- mat.setFlag(video::EMF_FRONT_FACE_CULLING, hidden);
- }
- return;
- }
-
scene::ISceneNode *node = getSceneNode();
+
if (!node)
return;
+ if (m_prop.visual == "upright_sprite") {
+ // upright sprite has no backface culling
+ node->setMaterialFlag(video::EMF_FRONT_FACE_CULLING, hidden);
+ return;
+ }
+
if (hidden) {
// Hide the mesh by culling both front and
// back faces. Serious hackyness but it works for our
diff --git a/src/client/content_cao.h b/src/client/content_cao.h
index 4bbba9134..5a8116c71 100644
--- a/src/client/content_cao.h
+++ b/src/client/content_cao.h
@@ -125,9 +125,8 @@ private:
std::string m_current_texture_modifier = "";
bool m_visuals_expired = false;
float m_step_distance_counter = 0.0f;
- u8 m_last_light = 255;
+ video::SColor m_last_light = video::SColor(0xFFFFFFFF);
bool m_is_visible = false;
- s8 m_glow = 0;
// Material
video::E_MATERIAL_TYPE m_material_type;
// Settings
@@ -165,14 +164,9 @@ public:
const v3f getPosition() const;
- void setPosition(const v3f &pos)
- {
- pos_translator.val_current = pos;
- }
-
inline const v3f &getRotation() const { return m_rotation; }
- const bool isImmortal();
+ bool isImmortal() const;
inline const ObjectProperties &getProperties() const { return m_prop; }
@@ -245,7 +239,7 @@ public:
void updateLight(u32 day_night_ratio);
- void setNodeLight(u8 light);
+ void setNodeLight(const video::SColor &light);
/* Get light position(s).
* returns number of positions written into pos[], which must have space
diff --git a/src/client/content_mapblock.cpp b/src/client/content_mapblock.cpp
index bb2d6398f..0bac5e827 100644
--- a/src/client/content_mapblock.cpp
+++ b/src/client/content_mapblock.cpp
@@ -150,8 +150,10 @@ void MapblockMeshGenerator::drawQuad(v3f *coords, const v3s16 &normal,
// should be (2+2)*6=24 values in the list. The order of
// the faces in the list is up-down-right-left-back-front
// (compatible with ContentFeatures).
+// mask - a bit mask that suppresses drawing of tiles.
+// tile i will not be drawn if mask & (1 << i) is 1
void MapblockMeshGenerator::drawCuboid(const aabb3f &box,
- TileSpec *tiles, int tilecount, const LightInfo *lights, const f32 *txc)
+ TileSpec *tiles, int tilecount, const LightInfo *lights, const f32 *txc, u8 mask)
{
assert(tilecount >= 1 && tilecount <= 6); // pre-condition
@@ -274,6 +276,8 @@ void MapblockMeshGenerator::drawCuboid(const aabb3f &box,
// Add to mesh collector
for (int k = 0; k < 6; ++k) {
+ if (mask & (1 << k))
+ continue;
int tileindex = MYMIN(k, tilecount - 1);
collector->append(tiles[tileindex], vertices + 4 * k, 4, quad_indices, 6);
}
@@ -363,7 +367,7 @@ void MapblockMeshGenerator::generateCuboidTextureCoords(const aabb3f &box, f32 *
}
void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, const f32 *txc,
- TileSpec *tiles, int tile_count)
+ TileSpec *tiles, int tile_count, u8 mask)
{
bool scale = std::fabs(f->visual_scale - 1.0f) > 1e-3f;
f32 texture_coord_buf[24];
@@ -400,12 +404,49 @@ void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, const f32 *txc,
d.Z = (j & 1) ? dz2 : dz1;
lights[j] = blendLight(d);
}
- drawCuboid(box, tiles, tile_count, lights, txc);
+ drawCuboid(box, tiles, tile_count, lights, txc, mask);
} else {
- drawCuboid(box, tiles, tile_count, nullptr, txc);
+ drawCuboid(box, tiles, tile_count, nullptr, txc, mask);
}
}
+u8 MapblockMeshGenerator::getNodeBoxMask(aabb3f box, u8 solid_neighbors, u8 sametype_neighbors) const
+{
+ const f32 NODE_BOUNDARY = 0.5 * BS;
+
+ // For an oversized nodebox, return immediately
+ if (box.MaxEdge.X > NODE_BOUNDARY ||
+ box.MinEdge.X < -NODE_BOUNDARY ||
+ box.MaxEdge.Y > NODE_BOUNDARY ||
+ box.MinEdge.Y < -NODE_BOUNDARY ||
+ box.MaxEdge.Z > NODE_BOUNDARY ||
+ box.MinEdge.Z < -NODE_BOUNDARY)
+ return 0;
+
+ // We can skip faces at node boundary if the matching neighbor is solid
+ u8 solid_mask =
+ (box.MaxEdge.Y == NODE_BOUNDARY ? 1 : 0) |
+ (box.MinEdge.Y == -NODE_BOUNDARY ? 2 : 0) |
+ (box.MaxEdge.X == NODE_BOUNDARY ? 4 : 0) |
+ (box.MinEdge.X == -NODE_BOUNDARY ? 8 : 0) |
+ (box.MaxEdge.Z == NODE_BOUNDARY ? 16 : 0) |
+ (box.MinEdge.Z == -NODE_BOUNDARY ? 32 : 0);
+
+ u8 sametype_mask = 0;
+ if (f->alpha == AlphaMode::ALPHAMODE_OPAQUE) {
+ // In opaque nodeboxes, faces on opposite sides can cancel
+ // each other out if there is a matching neighbor of the same type
+ sametype_mask =
+ ((solid_mask & 3) == 3 ? 3 : 0) |
+ ((solid_mask & 12) == 12 ? 12 : 0) |
+ ((solid_mask & 48) == 48 ? 48 : 0);
+ }
+
+ // Combine masks with actual neighbors to get the faces to be skipped
+ return (solid_mask & solid_neighbors) | (sametype_mask & sametype_neighbors);
+}
+
+
void MapblockMeshGenerator::prepareLiquidNodeDrawing()
{
getSpecialTile(0, &tile_liquid_top);
@@ -1363,13 +1404,38 @@ void MapblockMeshGenerator::drawNodeboxNode()
getTile(nodebox_tile_dirs[face], &tiles[face]);
}
+ bool param2_is_rotation =
+ f->param_type_2 == CPT2_COLORED_FACEDIR ||
+ f->param_type_2 == CPT2_COLORED_WALLMOUNTED ||
+ f->param_type_2 == CPT2_FACEDIR ||
+ f->param_type_2 == CPT2_WALLMOUNTED;
+
+ bool param2_is_level =
+ f->param_type_2 == CPT2_LEVELED;
+
// locate possible neighboring nodes to connect to
u8 neighbors_set = 0;
- if (f->node_box.type == NODEBOX_CONNECTED) {
- for (int dir = 0; dir != 6; dir++) {
- u8 flag = 1 << dir;
- v3s16 p2 = blockpos_nodes + p + nodebox_connection_dirs[dir];
- MapNode n2 = data->m_vmanip.getNodeNoEx(p2);
+ u8 solid_neighbors = 0;
+ u8 sametype_neighbors = 0;
+ for (int dir = 0; dir != 6; dir++) {
+ u8 flag = 1 << dir;
+ v3s16 p2 = blockpos_nodes + p + nodebox_tile_dirs[dir];
+ MapNode n2 = data->m_vmanip.getNodeNoEx(p2);
+
+ // mark neighbors that are the same node type
+ // and have the same rotation or higher level stored as param2
+ if (n2.param0 == n.param0 &&
+ (!param2_is_rotation || n.param2 == n2.param2) &&
+ (!param2_is_level || n.param2 <= n2.param2))
+ sametype_neighbors |= flag;
+
+ // mark neighbors that are simple solid blocks
+ if (nodedef->get(n2).drawtype == NDT_NORMAL)
+ solid_neighbors |= flag;
+
+ if (f->node_box.type == NODEBOX_CONNECTED) {
+ p2 = blockpos_nodes + p + nodebox_connection_dirs[dir];
+ n2 = data->m_vmanip.getNodeNoEx(p2);
if (nodedef->nodeboxConnects(n, n2, flag))
neighbors_set |= flag;
}
@@ -1377,8 +1443,63 @@ void MapblockMeshGenerator::drawNodeboxNode()
std::vector<aabb3f> boxes;
n.getNodeBoxes(nodedef, &boxes, neighbors_set);
- for (auto &box : boxes)
- drawAutoLightedCuboid(box, nullptr, tiles, 6);
+
+ bool isTransparent = false;
+
+ for (const TileSpec &tile : tiles) {
+ if (tile.layers[0].isTransparent()) {
+ isTransparent = true;
+ break;
+ }
+ }
+
+ if (isTransparent) {
+ std::vector<float> sections;
+ // Preallocate 8 default splits + Min&Max for each nodebox
+ sections.reserve(8 + 2 * boxes.size());
+
+ for (int axis = 0; axis < 3; axis++) {
+ // identify sections
+
+ if (axis == 0) {
+ // Default split at node bounds, up to 3 nodes in each direction
+ for (float s = -3.5f * BS; s < 4.0f * BS; s += 1.0f * BS)
+ sections.push_back(s);
+ }
+ else {
+ // Avoid readding the same 8 default splits for Y and Z
+ sections.resize(8);
+ }
+
+ // Add edges of existing node boxes, rounded to 1E-3
+ for (size_t i = 0; i < boxes.size(); i++) {
+ sections.push_back(std::floor(boxes[i].MinEdge[axis] * 1E3) * 1E-3);
+ sections.push_back(std::floor(boxes[i].MaxEdge[axis] * 1E3) * 1E-3);
+ }
+
+ // split the boxes at recorded sections
+ // limit splits to avoid runaway crash if inner loop adds infinite splits
+ // due to e.g. precision problems.
+ // 100 is just an arbitrary, reasonably high number.
+ for (size_t i = 0; i < boxes.size() && i < 100; i++) {
+ aabb3f *box = &boxes[i];
+ for (float section : sections) {
+ if (box->MinEdge[axis] < section && box->MaxEdge[axis] > section) {
+ aabb3f copy(*box);
+ copy.MinEdge[axis] = section;
+ box->MaxEdge[axis] = section;
+ boxes.push_back(copy);
+ box = &boxes[i]; // find new address of the box in case of reallocation
+ }
+ }
+ }
+ }
+ }
+
+ for (auto &box : boxes) {
+ u8 mask = getNodeBoxMask(box, solid_neighbors, sametype_neighbors);
+ drawAutoLightedCuboid(box, nullptr, tiles, 6, mask);
+ }
}
void MapblockMeshGenerator::drawMeshNode()
diff --git a/src/client/content_mapblock.h b/src/client/content_mapblock.h
index 7344f05ee..b13748cbc 100644
--- a/src/client/content_mapblock.h
+++ b/src/client/content_mapblock.h
@@ -100,10 +100,11 @@ public:
// cuboid drawing!
void drawCuboid(const aabb3f &box, TileSpec *tiles, int tilecount,
- const LightInfo *lights , const f32 *txc);
+ const LightInfo *lights , const f32 *txc, u8 mask = 0);
void generateCuboidTextureCoords(aabb3f const &box, f32 *coords);
void drawAutoLightedCuboid(aabb3f box, const f32 *txc = NULL,
- TileSpec *tiles = NULL, int tile_count = 0);
+ TileSpec *tiles = NULL, int tile_count = 0, u8 mask = 0);
+ u8 getNodeBoxMask(aabb3f box, u8 solid_neighbors, u8 sametype_neighbors) const;
// liquid-specific
bool top_is_same_liquid;
diff --git a/src/client/fontengine.cpp b/src/client/fontengine.cpp
index ad8305b45..0ae50dfe2 100644
--- a/src/client/fontengine.cpp
+++ b/src/client/fontengine.cpp
@@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "filesys.h"
#include "gettext.h"
#include "irrlicht_changes/CGUITTFont.h"
+#include "util/numeric.h" // rangelim
/** maximum size distance for getting a "similar" font size */
#define MAX_FONT_SIZE_OFFSET 10
@@ -172,9 +173,9 @@ unsigned int FontEngine::getFontSize(FontMode mode)
/******************************************************************************/
void FontEngine::readSettings()
{
- 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_Standard] = rangelim(g_settings->getU16("font_size"), 5, 72);
+ m_default_size[_FM_Fallback] = m_default_size[FM_Standard];
+ m_default_size[FM_Mono] = rangelim(g_settings->getU16("mono_font_size"), 5, 72);
m_default_bold = g_settings->getBool("font_bold");
m_default_italic = g_settings->getBool("font_italic");
@@ -217,8 +218,9 @@ gui::IGUIFont *FontEngine::initFont(const FontSpec &spec)
if (spec.italic)
setting_suffix.append("_italic");
- u32 size = std::max<u32>(spec.size * RenderingEngine::getDisplayDensity() *
- g_settings->getFloat("gui_scaling"), 1);
+ // Font size in pixels for FreeType
+ u32 size = rangelim(spec.size * RenderingEngine::getDisplayDensity() *
+ g_settings->getFloat("gui_scaling"), 1U, 500U);
// Constrain the font size to a certain multiple, if necessary
u16 divisible_by = g_settings->getU16(setting_prefix + "font_size_divisible_by");
diff --git a/src/client/game.cpp b/src/client/game.cpp
index 768b1abad..c34e3a415 100644
--- a/src/client/game.cpp
+++ b/src/client/game.cpp
@@ -43,7 +43,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "gameparams.h"
#include "gettext.h"
#include "gui/guiChatConsole.h"
-#include "gui/guiConfirmRegistration.h"
#include "gui/guiFormSpecMenu.h"
#include "gui/guiKeyChangeMenu.h"
#include "gui/guiPasswordChange.h"
@@ -283,7 +282,7 @@ public:
if (m_player_step_timer <= 0 && m_player_step_sound.exists()) {
m_player_step_timer = 0.03;
if (makes_footstep_sound)
- m_sound->playSound(m_player_step_sound, false);
+ m_sound->playSound(m_player_step_sound);
}
}
@@ -291,7 +290,7 @@ public:
{
if (m_player_jump_timer <= 0.0f) {
m_player_jump_timer = 0.2f;
- m_sound->playSound(SimpleSoundSpec("player_jump", 0.5f), false);
+ m_sound->playSound(SimpleSoundSpec("player_jump", 0.5f));
}
}
@@ -316,32 +315,32 @@ public:
static void cameraPunchLeft(MtEvent *e, void *data)
{
SoundMaker *sm = (SoundMaker *)data;
- sm->m_sound->playSound(sm->m_player_leftpunch_sound, false);
+ sm->m_sound->playSound(sm->m_player_leftpunch_sound);
}
static void cameraPunchRight(MtEvent *e, void *data)
{
SoundMaker *sm = (SoundMaker *)data;
- sm->m_sound->playSound(sm->m_player_rightpunch_sound, false);
+ sm->m_sound->playSound(sm->m_player_rightpunch_sound);
}
static void nodeDug(MtEvent *e, void *data)
{
SoundMaker *sm = (SoundMaker *)data;
NodeDugEvent *nde = (NodeDugEvent *)e;
- sm->m_sound->playSound(sm->m_ndef->get(nde->n).sound_dug, false);
+ sm->m_sound->playSound(sm->m_ndef->get(nde->n).sound_dug);
}
static void playerDamage(MtEvent *e, void *data)
{
SoundMaker *sm = (SoundMaker *)data;
- sm->m_sound->playSound(SimpleSoundSpec("player_damage", 0.5), false);
+ sm->m_sound->playSound(SimpleSoundSpec("player_damage", 0.5));
}
static void playerFallingDamage(MtEvent *e, void *data)
{
SoundMaker *sm = (SoundMaker *)data;
- sm->m_sound->playSound(SimpleSoundSpec("player_falling_damage", 0.5), false);
+ sm->m_sound->playSound(SimpleSoundSpec("player_falling_damage", 0.5));
}
void registerReceiver(MtEventManager *mgr)
@@ -422,6 +421,7 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter
CachedPixelShaderSetting<float, 3> m_camera_offset_vertex;
CachedPixelShaderSetting<SamplerLayer_t> m_base_texture;
CachedPixelShaderSetting<SamplerLayer_t> m_normal_texture;
+ CachedPixelShaderSetting<SamplerLayer_t> m_texture_flags;
Client *m_client;
public:
@@ -456,6 +456,7 @@ public:
m_camera_offset_vertex("cameraOffset"),
m_base_texture("baseTexture"),
m_normal_texture("normalTexture"),
+ m_texture_flags("textureFlags"),
m_client(client)
{
g_settings->registerChangedCallback("enable_fog", settingsCallback, this);
@@ -525,9 +526,12 @@ public:
m_camera_offset_pixel.set(camera_offset_array, services);
m_camera_offset_vertex.set(camera_offset_array, services);
- SamplerLayer_t base_tex = 0, normal_tex = 1;
+ SamplerLayer_t base_tex = 0,
+ normal_tex = 1,
+ flags_tex = 2;
m_base_texture.set(&base_tex, services);
m_normal_texture.set(&normal_tex, services);
+ m_texture_flags.set(&flags_tex, services);
}
};
@@ -723,7 +727,7 @@ protected:
void processClientEvents(CameraOrientation *cam);
void updateCamera(f32 dtime);
void updateSound(f32 dtime);
- void processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug);
+ void processPlayerInteraction(f32 dtime, bool show_hud);
/*!
* Returns the object or node the player is pointing at.
* Also updates the selected thing in the Hud.
@@ -842,7 +846,6 @@ private:
EventManager *eventmgr = nullptr;
QuicktuneShortcutter *quicktune = nullptr;
- bool registration_confirmation_shown = false;
std::unique_ptr<GameUI> m_game_ui;
GUIChatConsole *gui_chat_console = nullptr; // Free using ->Drop()
@@ -1063,9 +1066,9 @@ bool Game::startup(bool *kill,
void Game::run()
{
ProfilerGraph graph;
- RunStats stats = { 0 };
- CameraOrientation cam_view_target = { 0 };
- CameraOrientation cam_view = { 0 };
+ RunStats stats = {};
+ CameraOrientation cam_view_target = {};
+ CameraOrientation cam_view = {};
FpsControl draw_times;
f32 dtime; // in seconds
@@ -1137,8 +1140,7 @@ void Game::run()
updateDebugState();
updateCamera(dtime);
updateSound(dtime);
- processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud,
- m_game_ui->m_flags.show_basic_debug);
+ processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud);
updateFrame(&graph, &stats, dtime, cam_view);
updateProfilerGraphs(&graph);
@@ -1481,7 +1483,8 @@ bool Game::connectToServer(const GameStartData &start_data,
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());
+ m_rendering_engine, connect_address.isIPv6(), m_game_ui.get(),
+ start_data.allow_login_or_register);
client->migrateModStorage();
} catch (const BaseException &e) {
*error_message = fmtgettext("Error creating client: %s", e.what());
@@ -1492,7 +1495,7 @@ bool Game::connectToServer(const GameStartData &start_data,
client->m_simple_singleplayer_mode = simple_singleplayer_mode;
infostream << "Connecting to server at ";
- connect_address.print(&infostream);
+ connect_address.print(infostream);
infostream << std::endl;
client->connect(connect_address,
@@ -1544,28 +1547,16 @@ bool Game::connectToServer(const GameStartData &start_data,
break;
}
- if (client->m_is_registration_confirmation_state) {
- if (registration_confirmation_shown) {
- // Keep drawing the GUI
- m_rendering_engine->draw_menu_scene(guienv, dtime, true);
- } else {
- registration_confirmation_shown = true;
- (new GUIConfirmRegistration(guienv, guienv->getRootGUIElement(), -1,
- &g_menumgr, client, start_data.name, start_data.password,
- connection_aborted, texture_src))->drop();
- }
- } else {
- 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 = gettext("Connection timed out.");
- errorstream << *error_message << std::endl;
- break;
- }
-
- // Update status
- showOverlayMessage(N_("Connecting to server..."), dtime, 20);
+ 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 = gettext("Connection timed out.");
+ errorstream << *error_message << std::endl;
+ break;
}
+
+ // Update status
+ showOverlayMessage(N_("Connecting to server..."), dtime, 20);
}
} catch (con::PeerNotFoundException &e) {
// TODO: Should something be done here? At least an info/error
@@ -1743,22 +1734,26 @@ void Game::processQueues()
void Game::updateDebugState()
{
- const bool has_basic_debug = true;
+ LocalPlayer *player = client->getEnv().getLocalPlayer();
+
+ // debug UI and wireframe
bool has_debug = client->checkPrivilege("debug");
+ bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
if (m_game_ui->m_flags.show_basic_debug) {
- if (!has_basic_debug) {
+ if (!has_basic_debug)
m_game_ui->m_flags.show_basic_debug = false;
- }
} else if (m_game_ui->m_flags.show_minimal_debug) {
- if (has_basic_debug) {
+ if (has_basic_debug)
m_game_ui->m_flags.show_basic_debug = true;
- }
}
if (!has_basic_debug)
hud->disableBlockBounds();
if (!has_debug)
draw_control->show_wireframe = false;
+
+ // noclip
+ draw_control->allow_noclip = m_cache_enable_noclip && client->checkPrivilege("noclip");
}
void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
@@ -1914,7 +1909,7 @@ void Game::processKeyInput()
if (client->modsLoaded())
openConsole(0.2, L".");
else
- m_game_ui->showStatusText(wgettext("Client side scripting is disabled"));
+ m_game_ui->showTranslatedStatusText("Client side scripting is disabled");
} else if (wasKeyDown(KeyType::CONSOLE)) {
openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f));
} else if (wasKeyDown(KeyType::FREEMOVE)) {
@@ -1941,7 +1936,7 @@ 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);
+ float new_volume = g_settings->getFloat("sound_volume", 0.0f, 0.9f) + 0.1f;
g_settings->setFloat("sound_volume", new_volume);
std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
m_game_ui->showStatusText(msg);
@@ -1950,7 +1945,7 @@ void Game::processKeyInput()
}
} 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);
+ float new_volume = g_settings->getFloat("sound_volume", 0.1f, 1.0f) - 0.1f;
g_settings->setFloat("sound_volume", new_volume);
std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
m_game_ui->showStatusText(msg);
@@ -2214,27 +2209,27 @@ void Game::toggleCinematic()
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)");
+ LocalPlayer *player = client->getEnv().getLocalPlayer();
+ if (!(client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG))) {
+ m_game_ui->showTranslatedStatusText("Can't show block bounds (disabled by mod or game)");
+ return;
+ }
+ enum Hud::BlockBoundsMode newmode = hud->toggleBlockBounds();
+ switch (newmode) {
+ case Hud::BLOCK_BOUNDS_OFF:
+ m_game_ui->showTranslatedStatusText("Block bounds hidden");
+ break;
+ case Hud::BLOCK_BOUNDS_CURRENT:
+ m_game_ui->showTranslatedStatusText("Block bounds shown for current block");
+ break;
+ case Hud::BLOCK_BOUNDS_NEAR:
+ m_game_ui->showTranslatedStatusText("Block bounds shown for nearby blocks");
+ break;
+ case Hud::BLOCK_BOUNDS_MAX:
+ m_game_ui->showTranslatedStatusText("Block bounds shown for all blocks");
+ break;
+ default:
+ break;
}
}
@@ -2301,6 +2296,9 @@ void Game::toggleFog()
void Game::toggleDebug()
{
+ LocalPlayer *player = client->getEnv().getLocalPlayer();
+ bool has_debug = client->checkPrivilege("debug");
+ bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
// Initial: No debug info
// 1x toggle: Debug text
// 2x toggle: Debug text with profiler graph
@@ -2310,9 +2308,8 @@ void Game::toggleDebug()
// The debug text can be in 2 modes: minimal and basic.
// * Minimal: Only technical client info that not gameplay-relevant
// * Basic: Info that might give gameplay advantage, e.g. pos, angle
- // Basic mode is always used.
-
- const bool has_basic_debug = true;
+ // Basic mode is used when player has the debug HUD flag set,
+ // otherwise the Minimal mode is used.
if (!m_game_ui->m_flags.show_minimal_debug) {
m_game_ui->m_flags.show_minimal_debug = true;
if (has_basic_debug)
@@ -2336,7 +2333,7 @@ void Game::toggleDebug()
m_game_ui->m_flags.show_basic_debug = false;
m_game_ui->m_flags.show_profiler_graph = false;
draw_control->show_wireframe = false;
- if (client->checkPrivilege("debug")) {
+ if (has_debug) {
m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
} else {
m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
@@ -2512,11 +2509,13 @@ void Game::updatePlayerControl(const CameraOrientation &cam)
input->getMovementDirection()
);
- // autoforward if set: move towards pointed position at maximum speed
+ // autoforward if set: move at maximum speed
if (player->getPlayerSettings().continuous_forward &&
client->activeObjectsReceived() && !player->isDead()) {
control.movement_speed = 1.0f;
- control.movement_direction = 0.0f;
+ // sideways movement only
+ float dx = sin(control.movement_direction);
+ control.movement_direction = atan2(dx, 1.0f);
}
#ifdef HAVE_TOUCHSCREENGUI
@@ -2612,6 +2611,9 @@ void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation
if (client->modsLoaded())
client->getScript()->on_damage_taken(event->player_damage.amount);
+ if (!event->player_damage.effect)
+ return;
+
// Damage flash and hurt tilt are not used at death
if (client->getHP() > 0) {
LocalPlayer *player = client->getEnv().getLocalPlayer();
@@ -2880,6 +2882,7 @@ void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam
sky->setStarCount(event->star_params->count);
sky->setStarColor(event->star_params->starcolor);
sky->setStarScale(event->star_params->scale);
+ sky->setStarDayOpacity(event->star_params->day_opacity);
delete event->star_params;
}
@@ -3042,7 +3045,7 @@ void Game::updateSound(f32 dtime)
}
-void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug)
+void Game::processPlayerInteraction(f32 dtime, bool show_hud)
{
LocalPlayer *player = client->getEnv().getLocalPlayer();
@@ -3162,7 +3165,9 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug)
handlePointingAtNode(pointed, selected_item, hand_item, dtime);
} else if (pointed.type == POINTEDTHING_OBJECT) {
v3f player_position = player->getPosition();
- handlePointingAtObject(pointed, tool_item, player_position, show_debug);
+ bool basic_debug_allowed = client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
+ handlePointingAtObject(pointed, tool_item, player_position,
+ m_game_ui->m_flags.show_basic_debug && basic_debug_allowed);
} else if (isKeyDown(KeyType::DIG)) {
// When button is held down in air, show continuous animation
runData.punching = true;
@@ -3760,7 +3765,10 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
float direct_brightness;
bool sunlight_seen;
- if (m_cache_enable_noclip && m_cache_enable_free_move) {
+ // When in noclip mode force same sky brightness as above ground so you
+ // can see properly
+ if (draw_control->allow_noclip && m_cache_enable_free_move &&
+ client->checkPrivilege("fly")) {
direct_brightness = time_brightness;
sunlight_seen = true;
} else {
@@ -4043,7 +4051,12 @@ void Game::updateShadows()
float in_timeofday = fmod(runData.time_of_day_smooth, 1.0f);
- float timeoftheday = fmod(getWickedTimeOfDay(in_timeofday) + 0.75f, 0.5f) + 0.25f;
+ float timeoftheday = getWickedTimeOfDay(in_timeofday);
+ bool is_day = timeoftheday > 0.25 && timeoftheday < 0.75;
+ bool is_shadow_visible = is_day ? sky->getSunVisible() : sky->getMoonVisible();
+ shadow->setShadowIntensity(is_shadow_visible ? client->getEnv().getLocalPlayer()->getLighting().shadow_intensity : 0.0f);
+
+ timeoftheday = fmod(timeoftheday + 0.75f, 0.5f) + 0.25f;
const float offset_constant = 10000.0f;
v3f light(0.0f, 0.0f, -1.0f);
@@ -4075,10 +4088,10 @@ void FpsControl::reset()
*/
void FpsControl::limit(IrrlichtDevice *device, f32 *dtime)
{
- const u64 frametime_min = 1000000.0f / (
- device->isWindowFocused() && !g_menumgr.pausesGame()
+ const float fps_limit = (device->isWindowFocused() && !g_menumgr.pausesGame())
? g_settings->getFloat("fps_max")
- : g_settings->getFloat("fps_max_unfocused"));
+ : g_settings->getFloat("fps_max_unfocused");
+ const u64 frametime_min = 1000000.0f / std::max(fps_limit, 1.0f);
u64 time = porting::getTimeUs();
@@ -4127,9 +4140,9 @@ void Game::readSettings()
m_cache_enable_joysticks = g_settings->getBool("enable_joysticks");
m_cache_enable_particles = g_settings->getBool("enable_particles");
m_cache_enable_fog = g_settings->getBool("enable_fog");
- m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity");
- m_cache_joystick_frustum_sensitivity = g_settings->getFloat("joystick_frustum_sensitivity");
- m_repeat_place_time = g_settings->getFloat("repeat_place_time");
+ m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity", 0.001f, 10.0f);
+ m_cache_joystick_frustum_sensitivity = std::max(g_settings->getFloat("joystick_frustum_sensitivity"), 0.001f);
+ m_repeat_place_time = g_settings->getFloat("repeat_place_time", 0.25f, 2.0);
m_cache_enable_noclip = g_settings->getBool("noclip");
m_cache_enable_free_move = g_settings->getBool("free_move");
diff --git a/src/client/gameui.cpp b/src/client/gameui.cpp
index 8505ea3ae..909719bbe 100644
--- a/src/client/gameui.cpp
+++ b/src/client/gameui.cpp
@@ -68,7 +68,7 @@ void GameUI::init()
u16 chat_font_size = g_settings->getU16("chat_font_size");
if (chat_font_size != 0) {
m_guitext_chat->setOverrideFont(g_fontengine->getFont(
- chat_font_size, FM_Unspecified));
+ rangelim(chat_font_size, 5, 72), FM_Unspecified));
}
@@ -151,9 +151,13 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_
const NodeDefManager *nodedef = client->getNodeDefManager();
MapNode n = map.getNode(pointed_old.node_undersurface);
- if (n.getContent() != CONTENT_IGNORE && nodedef->get(n).name != "unknown") {
- os << ", pointed: " << nodedef->get(n).name
- << ", param2: " << (u64) n.getParam2();
+ if (n.getContent() != CONTENT_IGNORE) {
+ if (nodedef->get(n).name == "unknown") {
+ os << ", pointed: <unknown node>";
+ } else {
+ os << ", pointed: " << nodedef->get(n).name;
+ }
+ os << ", param2: " << (u64) n.getParam2();
}
}
diff --git a/src/client/guiscalingfilter.cpp b/src/client/guiscalingfilter.cpp
index de122becf..42508259f 100644
--- a/src/client/guiscalingfilter.cpp
+++ b/src/client/guiscalingfilter.cpp
@@ -176,52 +176,61 @@ void draw2DImageFilterScaled(video::IVideoDriver *driver, video::ITexture *txr,
}
void draw2DImage9Slice(video::IVideoDriver *driver, video::ITexture *texture,
- const core::rect<s32> &rect, const core::rect<s32> &middle,
- const core::rect<s32> *cliprect, const video::SColor *const colors)
+ const core::rect<s32> &destrect, const core::rect<s32> &srcrect,
+ const core::rect<s32> &middlerect, const core::rect<s32> *cliprect,
+ const video::SColor *const colors)
{
- auto originalSize = texture->getOriginalSize();
- core::vector2di lowerRightOffset = core::vector2di(originalSize.Width, originalSize.Height) - middle.LowerRightCorner;
+ // `-x` is interpreted as `w - x`
+ core::rect<s32> middle = middlerect;
+
+ if (middlerect.LowerRightCorner.X < 0)
+ middle.LowerRightCorner.X += srcrect.getWidth();
+ if (middlerect.LowerRightCorner.Y < 0)
+ middle.LowerRightCorner.Y += srcrect.getHeight();
+
+ core::vector2di lower_right_offset = core::vector2di(srcrect.getWidth(),
+ srcrect.getHeight()) - middle.LowerRightCorner;
for (int y = 0; y < 3; ++y) {
for (int x = 0; x < 3; ++x) {
- core::rect<s32> src({0, 0}, originalSize);
- core::rect<s32> dest = rect;
+ core::rect<s32> src = srcrect;
+ core::rect<s32> dest = destrect;
switch (x) {
case 0:
- dest.LowerRightCorner.X = rect.UpperLeftCorner.X + middle.UpperLeftCorner.X;
- src.LowerRightCorner.X = middle.UpperLeftCorner.X;
+ dest.LowerRightCorner.X = destrect.UpperLeftCorner.X + middle.UpperLeftCorner.X;
+ src.LowerRightCorner.X = srcrect.UpperLeftCorner.X + middle.UpperLeftCorner.X;
break;
case 1:
dest.UpperLeftCorner.X += middle.UpperLeftCorner.X;
- dest.LowerRightCorner.X -= lowerRightOffset.X;
- src.UpperLeftCorner.X = middle.UpperLeftCorner.X;
- src.LowerRightCorner.X = middle.LowerRightCorner.X;
+ dest.LowerRightCorner.X -= lower_right_offset.X;
+ src.UpperLeftCorner.X += middle.UpperLeftCorner.X;
+ src.LowerRightCorner.X -= lower_right_offset.X;
break;
case 2:
- dest.UpperLeftCorner.X = rect.LowerRightCorner.X - lowerRightOffset.X;
- src.UpperLeftCorner.X = middle.LowerRightCorner.X;
+ dest.UpperLeftCorner.X = destrect.LowerRightCorner.X - lower_right_offset.X;
+ src.UpperLeftCorner.X = srcrect.LowerRightCorner.X - lower_right_offset.X;
break;
}
switch (y) {
case 0:
- dest.LowerRightCorner.Y = rect.UpperLeftCorner.Y + middle.UpperLeftCorner.Y;
- src.LowerRightCorner.Y = middle.UpperLeftCorner.Y;
+ dest.LowerRightCorner.Y = destrect.UpperLeftCorner.Y + middle.UpperLeftCorner.Y;
+ src.LowerRightCorner.Y = srcrect.UpperLeftCorner.Y + middle.UpperLeftCorner.Y;
break;
case 1:
dest.UpperLeftCorner.Y += middle.UpperLeftCorner.Y;
- dest.LowerRightCorner.Y -= lowerRightOffset.Y;
- src.UpperLeftCorner.Y = middle.UpperLeftCorner.Y;
- src.LowerRightCorner.Y = middle.LowerRightCorner.Y;
+ dest.LowerRightCorner.Y -= lower_right_offset.Y;
+ src.UpperLeftCorner.Y += middle.UpperLeftCorner.Y;
+ src.LowerRightCorner.Y -= lower_right_offset.Y;
break;
case 2:
- dest.UpperLeftCorner.Y = rect.LowerRightCorner.Y - lowerRightOffset.Y;
- src.UpperLeftCorner.Y = middle.LowerRightCorner.Y;
+ dest.UpperLeftCorner.Y = destrect.LowerRightCorner.Y - lower_right_offset.Y;
+ src.UpperLeftCorner.Y = srcrect.LowerRightCorner.Y - lower_right_offset.Y;
break;
}
diff --git a/src/client/guiscalingfilter.h b/src/client/guiscalingfilter.h
index 379a4bdb0..f2d2fce10 100644
--- a/src/client/guiscalingfilter.h
+++ b/src/client/guiscalingfilter.h
@@ -46,13 +46,13 @@ video::ITexture *guiScalingImageButton(video::IVideoDriver *driver, video::IText
*/
void draw2DImageFilterScaled(video::IVideoDriver *driver, video::ITexture *txr,
const core::rect<s32> &destrect, const core::rect<s32> &srcrect,
- const core::rect<s32> *cliprect = 0, const video::SColor *const colors = 0,
- bool usealpha = false);
+ const core::rect<s32> *cliprect = nullptr,
+ const video::SColor *const colors = nullptr, bool usealpha = false);
/*
* 9-slice / segment drawing
*/
void draw2DImage9Slice(video::IVideoDriver *driver, video::ITexture *texture,
- const core::rect<s32> &rect, const core::rect<s32> &middle,
- const core::rect<s32> *cliprect = nullptr,
+ const core::rect<s32> &destrect, const core::rect<s32> &srcrect,
+ const core::rect<s32> &middlerect, const core::rect<s32> *cliprect = nullptr,
const video::SColor *const colors = nullptr);
diff --git a/src/client/hud.cpp b/src/client/hud.cpp
index 259a18ab9..c0c289608 100644
--- a/src/client/hud.cpp
+++ b/src/client/hud.cpp
@@ -55,7 +55,7 @@ Hud::Hud(Client *client, LocalPlayer *player,
this->player = player;
this->inventory = inventory;
- m_hud_scaling = g_settings->getFloat("hud_scaling");
+ m_hud_scaling = g_settings->getFloat("hud_scaling", 0.5f, 20.0f);
m_scale_factor = m_hud_scaling * RenderingEngine::getDisplayDensity();
m_hotbar_imagesize = std::floor(HOTBAR_IMAGE_SIZE *
RenderingEngine::getDisplayDensity() + 0.5f);
@@ -1013,6 +1013,10 @@ void drawItemStack(
bool has_mesh = false;
ItemMesh *imesh;
+ core::rect<s32> viewrect = rect;
+ if (clip != nullptr)
+ viewrect.clipAgainst(*clip);
+
// Render as mesh if animated or no inventory image
if ((enable_animations && rotation_kind < IT_ROT_NONE) || def.inventory_image.empty()) {
imesh = client->idef()->getWieldMesh(def.name, client);
@@ -1034,9 +1038,6 @@ void drawItemStack(
core::rect<s32> oldViewPort = driver->getViewPort();
core::matrix4 oldProjMat = driver->getTransform(video::ETS_PROJECTION);
core::matrix4 oldViewMat = driver->getTransform(video::ETS_VIEW);
- core::rect<s32> viewrect = rect;
- if (clip)
- viewrect.clipAgainst(*clip);
core::matrix4 ProjMatrix;
ProjMatrix.buildProjectionMatrixOrthoLH(2.0f, 2.0f, -1.0f, 100.0f);
@@ -1180,24 +1181,68 @@ void drawItemStack(
driver->draw2DRectangle(color, progressrect2, clip);
}
- if (font != NULL && item.count >= 2) {
+ const std::string &count_text = item.metadata.getString("count_meta");
+ if (font != nullptr && (item.count >= 2 || !count_text.empty())) {
// Get the item count as a string
- std::string text = itos(item.count);
- v2u32 dim = font->getDimension(utf8_to_wide(text).c_str());
+ std::string text = count_text.empty() ? itos(item.count) : count_text;
+ v2u32 dim = font->getDimension(utf8_to_wide(unescape_enriched(text)).c_str());
v2s32 sdim(dim.X, dim.Y);
core::rect<s32> rect2(
- /*rect.UpperLeftCorner,
- core::dimension2d<u32>(rect.getWidth(), 15)*/
rect.LowerRightCorner - sdim,
- sdim
+ rect.LowerRightCorner
);
- video::SColor bgcolor(128, 0, 0, 0);
- driver->draw2DRectangle(bgcolor, rect2, clip);
+ // get the count alignment
+ s32 count_alignment = stoi(item.metadata.getString("count_alignment"));
+ if (count_alignment != 0) {
+ s32 a_x = count_alignment & 3;
+ s32 a_y = (count_alignment >> 2) & 3;
+
+ s32 x1, x2, y1, y2;
+ switch (a_x) {
+ case 1: // left
+ x1 = rect.UpperLeftCorner.X;
+ x2 = x1 + sdim.X;
+ break;
+ case 2: // middle
+ x1 = (rect.UpperLeftCorner.X + rect.LowerRightCorner.X - sdim.X) / 2;
+ x2 = x1 + sdim.X;
+ break;
+ case 3: // right
+ x2 = rect.LowerRightCorner.X;
+ x1 = x2 - sdim.X;
+ break;
+ default: // 0 = default
+ x1 = rect2.UpperLeftCorner.X;
+ x2 = rect2.LowerRightCorner.X;
+ break;
+ }
+
+ switch (a_y) {
+ case 1: // up
+ y1 = rect.UpperLeftCorner.Y;
+ y2 = y1 + sdim.Y;
+ break;
+ case 2: // middle
+ y1 = (rect.UpperLeftCorner.Y + rect.LowerRightCorner.Y - sdim.Y) / 2;
+ y2 = y1 + sdim.Y;
+ break;
+ case 3: // down
+ y2 = rect.LowerRightCorner.Y;
+ y1 = y2 - sdim.Y;
+ break;
+ default: // 0 = default
+ y1 = rect2.UpperLeftCorner.Y;
+ y2 = rect2.LowerRightCorner.Y;
+ break;
+ }
+
+ rect2 = core::rect<s32>(x1, y1, x2, y2);
+ }
video::SColor color(255, 255, 255, 255);
- font->draw(text.c_str(), rect2, color, false, false, clip);
+ font->draw(utf8_to_wide(text).c_str(), rect2, color, false, false, &viewrect);
}
}
diff --git a/src/client/imagefilters.cpp b/src/client/imagefilters.cpp
index b62e336f7..c9d1504ad 100644
--- a/src/client/imagefilters.cpp
+++ b/src/client/imagefilters.cpp
@@ -124,7 +124,7 @@ void imageCleanTransparent(video::IImage *src, u32 threshold)
// Ignore pixels we haven't processed
if (!bitmap.get(sx, sy))
continue;
-
+
// Add RGB values weighted by alpha IF the pixel is opaque, otherwise
// use full weight since we want to propagate colors.
video::SColor d = src->getPixel(sx, sy);
diff --git a/src/client/joystick_controller.cpp b/src/client/joystick_controller.cpp
index aae73c62d..9e58b9f62 100644
--- a/src/client/joystick_controller.cpp
+++ b/src/client/joystick_controller.cpp
@@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "gettime.h"
#include "porting.h"
#include "util/string.h"
+#include "util/numeric.h"
bool JoystickButtonCmb::isTriggered(const irr::SEvent::SJoystickEvent &ev) const
{
@@ -202,9 +203,9 @@ JoystickLayout create_dragonrise_gamecube_layout()
}
-JoystickController::JoystickController() :
- doubling_dtime(g_settings->getFloat("repeat_joystick_button_time"))
+JoystickController::JoystickController()
{
+ doubling_dtime = std::max(g_settings->getFloat("repeat_joystick_button_time"), 0.001f);
for (float &i : m_past_pressed_time) {
i = 0;
}
@@ -217,19 +218,20 @@ void JoystickController::onJoystickConnect(const std::vector<irr::SJoystickInfo>
s32 id = g_settings->getS32("joystick_id");
std::string layout = g_settings->get("joystick_type");
- if (id < 0 || (u16)id >= joystick_infos.size()) {
+ if (id < 0 || id >= (s32)joystick_infos.size()) {
// TODO: auto detection
id = 0;
}
- if (id >= 0 && (u16)id < joystick_infos.size()) {
+ if (id >= 0 && id < (s32)joystick_infos.size()) {
if (layout.empty() || layout == "auto")
setLayoutFromControllerName(joystick_infos[id].Name.c_str());
else
setLayoutFromControllerName(layout);
}
- m_joystick_id = id;
+ // Irrlicht restriction.
+ m_joystick_id = rangelim(id, 0, UINT8_MAX);
}
void JoystickController::setLayoutFromControllerName(const std::string &name)
diff --git a/src/client/localplayer.cpp b/src/client/localplayer.cpp
index 279efafe9..79fe2cb11 100644
--- a/src/client/localplayer.cpp
+++ b/src/client/localplayer.cpp
@@ -426,16 +426,6 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
camera_impact = getSpeed().Y * -1;
}
- {
- camera_barely_in_ceiling = false;
- v3s16 camera_np = floatToInt(getEyePosition(), BS);
- MapNode n = map->getNode(camera_np);
- if (n.getContent() != CONTENT_IGNORE) {
- if (nodemgr->get(n).walkable && nodemgr->get(n).solidness == 2)
- camera_barely_in_ceiling = true;
- }
- }
-
/*
Check properties of the node on which the player is standing
*/
@@ -696,8 +686,7 @@ v3s16 LocalPlayer::getLightPosition() const
v3f LocalPlayer::getEyeOffset() const
{
- float eye_height = camera_barely_in_ceiling ? m_eye_height - 0.125f : m_eye_height;
- return v3f(0.0f, BS * eye_height, 0.0f);
+ return v3f(0.0f, BS * m_eye_height, 0.0f);
}
ClientActiveObject *LocalPlayer::getParent() const
@@ -1025,16 +1014,6 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d,
camera_impact = getSpeed().Y * -1.0f;
}
- {
- camera_barely_in_ceiling = false;
- v3s16 camera_np = floatToInt(getEyePosition(), BS);
- MapNode n = map->getNode(camera_np);
- if (n.getContent() != CONTENT_IGNORE) {
- if (nodemgr->get(n).walkable && nodemgr->get(n).solidness == 2)
- camera_barely_in_ceiling = true;
- }
- }
-
/*
Update the node last under the player
*/
diff --git a/src/client/localplayer.h b/src/client/localplayer.h
index 577be2803..650a01574 100644
--- a/src/client/localplayer.h
+++ b/src/client/localplayer.h
@@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "environment.h"
#include "constants.h"
#include "settings.h"
+#include "lighting.h"
#include <list>
class Client;
@@ -158,6 +159,8 @@ public:
added_velocity += vel;
}
+ inline Lighting& getLighting() { return m_lighting; }
+
private:
void accelerate(const v3f &target_speed, const f32 max_increase_H,
const f32 max_increase_V, const bool use_pitch);
@@ -196,7 +199,6 @@ private:
u16 m_breath = PLAYER_MAX_BREATH_DEFAULT;
f32 m_yaw = 0.0f;
f32 m_pitch = 0.0f;
- bool camera_barely_in_ceiling = false;
aabb3f m_collisionbox = aabb3f(-BS * 0.30f, 0.0f, -BS * 0.30f, BS * 0.30f,
BS * 1.75f, BS * 0.30f);
float m_eye_height = 1.625f;
@@ -209,4 +211,5 @@ private:
GenericCAO *m_cao = nullptr;
Client *m_client;
+ Lighting m_lighting;
};
diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp
index e077011cc..c730b9bf9 100644
--- a/src/client/mapblock_mesh.cpp
+++ b/src/client/mapblock_mesh.cpp
@@ -30,6 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "client/meshgen/collector.h"
#include "client/renderingengine.h"
#include <array>
+#include <algorithm>
/*
MeshMakeData
@@ -861,7 +862,7 @@ static void updateFastFaceRow(
g_settings->getBool("enable_waving_water");
static thread_local const bool force_not_tiling =
- false && g_settings->getBool("enable_dynamic_shadows");
+ g_settings->getBool("enable_dynamic_shadows");
v3s16 p = startpos;
@@ -1004,6 +1005,179 @@ static void applyTileColor(PreMeshBuffer &pmb)
}
/*
+ MapBlockBspTree
+*/
+
+void MapBlockBspTree::buildTree(const std::vector<MeshTriangle> *triangles)
+{
+ this->triangles = triangles;
+
+ nodes.clear();
+
+ // assert that triangle index can fit into s32
+ assert(triangles->size() <= 0x7FFFFFFFL);
+ std::vector<s32> indexes;
+ indexes.reserve(triangles->size());
+ for (u32 i = 0; i < triangles->size(); i++)
+ indexes.push_back(i);
+
+ if (!indexes.empty()) {
+ // Start in the center of the block with increment of one quarter in each direction
+ root = buildTree(v3f(1, 0, 0), v3f((MAP_BLOCKSIZE + 1) * 0.5f * BS), MAP_BLOCKSIZE * 0.25f * BS, indexes, 0);
+ } else {
+ root = -1;
+ }
+}
+
+/**
+ * @brief Find a candidate plane to split a set of triangles in two
+ *
+ * The candidate plane is represented by one of the triangles from the set.
+ *
+ * @param list Vector of indexes of the triangles in the set
+ * @param triangles Vector of all triangles in the BSP tree
+ * @return Address of the triangle that represents the proposed split plane
+ */
+static const MeshTriangle *findSplitCandidate(const std::vector<s32> &list, const std::vector<MeshTriangle> &triangles)
+{
+ // find the center of the cluster.
+ v3f center(0, 0, 0);
+ size_t n = list.size();
+ for (s32 i : list) {
+ center += triangles[i].centroid / n;
+ }
+
+ // find the triangle with the largest area and closest to the center
+ const MeshTriangle *candidate_triangle = &triangles[list[0]];
+ const MeshTriangle *ith_triangle;
+ for (s32 i : list) {
+ ith_triangle = &triangles[i];
+ if (ith_triangle->areaSQ > candidate_triangle->areaSQ ||
+ (ith_triangle->areaSQ == candidate_triangle->areaSQ &&
+ ith_triangle->centroid.getDistanceFromSQ(center) < candidate_triangle->centroid.getDistanceFromSQ(center))) {
+ candidate_triangle = ith_triangle;
+ }
+ }
+ return candidate_triangle;
+}
+
+s32 MapBlockBspTree::buildTree(v3f normal, v3f origin, float delta, const std::vector<s32> &list, u32 depth)
+{
+ // if the list is empty, don't bother
+ if (list.empty())
+ return -1;
+
+ // if there is only one triangle, or the delta is insanely small, this is a leaf node
+ if (list.size() == 1 || delta < 0.01) {
+ nodes.emplace_back(normal, origin, list, -1, -1);
+ return nodes.size() - 1;
+ }
+
+ std::vector<s32> front_list;
+ std::vector<s32> back_list;
+ std::vector<s32> node_list;
+
+ // split the list
+ for (s32 i : list) {
+ const MeshTriangle &triangle = (*triangles)[i];
+ float factor = normal.dotProduct(triangle.centroid - origin);
+ if (factor == 0)
+ node_list.push_back(i);
+ else if (factor > 0)
+ front_list.push_back(i);
+ else
+ back_list.push_back(i);
+ }
+
+ // define the new split-plane
+ v3f candidate_normal(normal.Z, normal.X, normal.Y);
+ float candidate_delta = delta;
+ if (depth % 3 == 2)
+ candidate_delta /= 2;
+
+ s32 front_index = -1;
+ s32 back_index = -1;
+
+ if (!front_list.empty()) {
+ v3f next_normal = candidate_normal;
+ v3f next_origin = origin + delta * normal;
+ float next_delta = candidate_delta;
+ if (next_delta < 5) {
+ const MeshTriangle *candidate = findSplitCandidate(front_list, *triangles);
+ next_normal = candidate->getNormal();
+ next_origin = candidate->centroid;
+ }
+ front_index = buildTree(next_normal, next_origin, next_delta, front_list, depth + 1);
+
+ // if there are no other triangles, don't create a new node
+ if (back_list.empty() && node_list.empty())
+ return front_index;
+ }
+
+ if (!back_list.empty()) {
+ v3f next_normal = candidate_normal;
+ v3f next_origin = origin - delta * normal;
+ float next_delta = candidate_delta;
+ if (next_delta < 5) {
+ const MeshTriangle *candidate = findSplitCandidate(back_list, *triangles);
+ next_normal = candidate->getNormal();
+ next_origin = candidate->centroid;
+ }
+
+ back_index = buildTree(next_normal, next_origin, next_delta, back_list, depth + 1);
+
+ // if there are no other triangles, don't create a new node
+ if (front_list.empty() && node_list.empty())
+ return back_index;
+ }
+
+ nodes.emplace_back(normal, origin, node_list, front_index, back_index);
+
+ return nodes.size() - 1;
+}
+
+void MapBlockBspTree::traverse(s32 node, v3f viewpoint, std::vector<s32> &output) const
+{
+ if (node < 0) return; // recursion break;
+
+ const TreeNode &n = nodes[node];
+ float factor = n.normal.dotProduct(viewpoint - n.origin);
+
+ if (factor > 0)
+ traverse(n.back_ref, viewpoint, output);
+ else
+ traverse(n.front_ref, viewpoint, output);
+
+ if (factor != 0)
+ for (s32 i : n.triangle_refs)
+ output.push_back(i);
+
+ if (factor > 0)
+ traverse(n.front_ref, viewpoint, output);
+ else
+ traverse(n.back_ref, viewpoint, output);
+}
+
+
+
+/*
+ PartialMeshBuffer
+*/
+
+void PartialMeshBuffer::beforeDraw() const
+{
+ // Patch the indexes in the mesh buffer before draw
+ m_buffer->Indices = std::move(m_vertex_indexes);
+ m_buffer->setDirty(scene::EBT_INDEX);
+}
+
+void PartialMeshBuffer::afterDraw() const
+{
+ // Take the data back
+ m_vertex_indexes = m_buffer->Indices.steal();
+}
+
+/*
MapBlockMesh
*/
@@ -1084,6 +1258,9 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
Convert MeshCollector to SMesh
*/
+ const bool desync_animations = g_settings->getBool(
+ "desynchronize_mapblock_texture_animation");
+
for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) {
for(u32 i = 0; i < collector.prebuffers[layer].size(); i++)
{
@@ -1113,18 +1290,18 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
// - Texture animation
if (p.layer.material_flags & MATERIAL_FLAG_ANIMATION) {
// Add to MapBlockMesh in order to animate these tiles
- m_animation_tiles[std::pair<u8, u32>(layer, i)] = p.layer;
- m_animation_frames[std::pair<u8, u32>(layer, i)] = 0;
- if (g_settings->getBool(
- "desynchronize_mapblock_texture_animation")) {
+ auto &info = m_animation_info[{layer, i}];
+ info.tile = p.layer;
+ info.frame = 0;
+ if (desync_animations) {
// Get starting position from noise
- m_animation_frame_offsets[std::pair<u8, u32>(layer, i)] =
+ info.frame_offset =
100000 * (2.0 + noise3d(
data->m_blockpos.X, data->m_blockpos.Y,
data->m_blockpos.Z, 0));
} else {
// Play all synchronized
- m_animation_frame_offsets[std::pair<u8, u32>(layer, i)] = 0;
+ info.frame_offset = 0;
}
// Replace tile texture with the first animation frame
p.layer.texture = (*p.layer.frames)[0].texture;
@@ -1135,19 +1312,23 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
// Dummy sunlight to handle non-sunlit areas
video::SColorf sunlight;
get_sunlight_color(&sunlight, 0);
- u32 vertex_count = p.vertices.size();
+
+ std::map<u32, video::SColor> colors;
+ const u32 vertex_count = p.vertices.size();
for (u32 j = 0; j < vertex_count; j++) {
video::SColor *vc = &p.vertices[j].Color;
video::SColor copy = *vc;
if (vc->getAlpha() == 0) // No sunlight - no need to animate
final_color_blend(vc, copy, sunlight); // Finalize color
else // Record color to animate
- m_daynight_diffs[std::pair<u8, u32>(layer, i)][j] = copy;
+ colors[j] = copy;
// The sunlight ratio has been stored,
// delete alpha (for the final rendering).
vc->setAlpha(255);
}
+ if (!colors.empty())
+ m_daynight_diffs[{layer, i}] = std::move(colors);
}
// Create material
@@ -1173,8 +1354,23 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
scene::SMeshBuffer *buf = new scene::SMeshBuffer();
buf->Material = material;
- buf->append(&p.vertices[0], p.vertices.size(),
- &p.indices[0], p.indices.size());
+ if (p.layer.isTransparent()) {
+ buf->append(&p.vertices[0], p.vertices.size(), nullptr, 0);
+
+ MeshTriangle t;
+ t.buffer = buf;
+ m_transparent_triangles.reserve(p.indices.size() / 3);
+ for (u32 i = 0; i < p.indices.size(); i += 3) {
+ t.p1 = p.indices[i];
+ t.p2 = p.indices[i + 1];
+ t.p3 = p.indices[i + 2];
+ t.updateAttributes();
+ m_transparent_triangles.push_back(t);
+ }
+ } else {
+ buf->append(&p.vertices[0], p.vertices.size(),
+ &p.indices[0], p.indices.size());
+ }
mesh->addMeshBuffer(buf);
buf->drop();
}
@@ -1187,12 +1383,13 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
}
//std::cout<<"added "<<fastfaces.getSize()<<" faces."<<std::endl;
+ m_bsp_tree.buildTree(&m_transparent_triangles);
// Check if animation is required for this mesh
m_has_animation =
!m_crack_materials.empty() ||
!m_daynight_diffs.empty() ||
- !m_animation_tiles.empty();
+ !m_animation_info.empty();
}
MapBlockMesh::~MapBlockMesh()
@@ -1226,25 +1423,22 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack,
for (auto &crack_material : m_crack_materials) {
scene::IMeshBuffer *buf = m_mesh[crack_material.first.first]->
getMeshBuffer(crack_material.first.second);
- std::string basename = crack_material.second;
// Create new texture name from original
- std::ostringstream os;
- os << basename << crack;
+ std::string s = crack_material.second + itos(crack);
u32 new_texture_id = 0;
video::ITexture *new_texture =
- m_tsrc->getTextureForMesh(os.str(), &new_texture_id);
+ m_tsrc->getTextureForMesh(s, &new_texture_id);
buf->getMaterial().setTexture(0, new_texture);
- // If the current material is also animated,
- // update animation info
- auto anim_iter = m_animation_tiles.find(crack_material.first);
- if (anim_iter != m_animation_tiles.end()) {
- TileLayer &tile = anim_iter->second;
+ // If the current material is also animated, update animation info
+ auto anim_it = m_animation_info.find(crack_material.first);
+ if (anim_it != m_animation_info.end()) {
+ TileLayer &tile = anim_it->second.tile;
tile.texture = new_texture;
tile.texture_id = new_texture_id;
// force animation update
- m_animation_frames[crack_material.first] = -1;
+ anim_it->second.frame = -1;
}
}
@@ -1252,28 +1446,25 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack,
}
// Texture animation
- for (auto &animation_tile : m_animation_tiles) {
- const TileLayer &tile = animation_tile.second;
+ for (auto &it : m_animation_info) {
+ const TileLayer &tile = it.second.tile;
// Figure out current frame
- int frameoffset = m_animation_frame_offsets[animation_tile.first];
- int frame = (int)(time * 1000 / tile.animation_frame_length_ms
- + frameoffset) % tile.animation_frame_count;
+ int frameno = (int)(time * 1000 / tile.animation_frame_length_ms
+ + it.second.frame_offset) % tile.animation_frame_count;
// If frame doesn't change, skip
- if (frame == m_animation_frames[animation_tile.first])
+ if (frameno == it.second.frame)
continue;
- m_animation_frames[animation_tile.first] = frame;
+ it.second.frame = frameno;
- scene::IMeshBuffer *buf = m_mesh[animation_tile.first.first]->
- getMeshBuffer(animation_tile.first.second);
+ scene::IMeshBuffer *buf = m_mesh[it.first.first]->getMeshBuffer(it.first.second);
- const FrameSpec &animation_frame = (*tile.frames)[frame];
- buf->getMaterial().setTexture(0, animation_frame.texture);
+ const FrameSpec &frame = (*tile.frames)[frameno];
+ buf->getMaterial().setTexture(0, frame.texture);
if (m_enable_shaders) {
- if (animation_frame.normal_texture)
- buf->getMaterial().setTexture(1,
- animation_frame.normal_texture);
- buf->getMaterial().setTexture(2, animation_frame.flags_texture);
+ if (frame.normal_texture)
+ buf->getMaterial().setTexture(1, frame.normal_texture);
+ buf->getMaterial().setTexture(2, frame.flags_texture);
}
}
@@ -1300,6 +1491,67 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack,
return true;
}
+void MapBlockMesh::updateTransparentBuffers(v3f camera_pos, v3s16 block_pos)
+{
+ // nothing to do if the entire block is opaque
+ if (m_transparent_triangles.empty())
+ return;
+
+ v3f block_posf = intToFloat(block_pos * MAP_BLOCKSIZE, BS);
+ v3f rel_camera_pos = camera_pos - block_posf;
+
+ std::vector<s32> triangle_refs;
+ m_bsp_tree.traverse(rel_camera_pos, triangle_refs);
+
+ // arrange index sequences into partial buffers
+ m_transparent_buffers.clear();
+
+ scene::SMeshBuffer *current_buffer = nullptr;
+ std::vector<u16> current_strain;
+ for (auto i : triangle_refs) {
+ const auto &t = m_transparent_triangles[i];
+ if (current_buffer != t.buffer) {
+ if (current_buffer) {
+ m_transparent_buffers.emplace_back(current_buffer, std::move(current_strain));
+ current_strain.clear();
+ }
+ current_buffer = t.buffer;
+ }
+ current_strain.push_back(t.p1);
+ current_strain.push_back(t.p2);
+ current_strain.push_back(t.p3);
+ }
+
+ if (!current_strain.empty())
+ m_transparent_buffers.emplace_back(current_buffer, std::move(current_strain));
+}
+
+void MapBlockMesh::consolidateTransparentBuffers()
+{
+ m_transparent_buffers.clear();
+
+ scene::SMeshBuffer *current_buffer = nullptr;
+ std::vector<u16> current_strain;
+
+ // use the fact that m_transparent_triangles is already arranged by buffer
+ for (const auto &t : m_transparent_triangles) {
+ if (current_buffer != t.buffer) {
+ if (current_buffer != nullptr) {
+ this->m_transparent_buffers.emplace_back(current_buffer, std::move(current_strain));
+ current_strain.clear();
+ }
+ current_buffer = t.buffer;
+ }
+ current_strain.push_back(t.p1);
+ current_strain.push_back(t.p2);
+ current_strain.push_back(t.p3);
+ }
+
+ if (!current_strain.empty()) {
+ this->m_transparent_buffers.emplace_back(current_buffer, std::move(current_strain));
+ }
+}
+
video::SColor encode_light(u16 light, u8 emissive_light)
{
// Get components
diff --git a/src/client/mapblock_mesh.h b/src/client/mapblock_mesh.h
index 3b17c4af9..169b3a8c1 100644
--- a/src/client/mapblock_mesh.h
+++ b/src/client/mapblock_mesh.h
@@ -71,6 +71,102 @@ struct MeshMakeData
void setSmoothLighting(bool smooth_lighting);
};
+// represents a triangle as indexes into the vertex buffer in SMeshBuffer
+class MeshTriangle
+{
+public:
+ scene::SMeshBuffer *buffer;
+ u16 p1, p2, p3;
+ v3f centroid;
+ float areaSQ;
+
+ void updateAttributes()
+ {
+ v3f v1 = buffer->getPosition(p1);
+ v3f v2 = buffer->getPosition(p2);
+ v3f v3 = buffer->getPosition(p3);
+
+ centroid = (v1 + v2 + v3) / 3;
+ areaSQ = (v2-v1).crossProduct(v3-v1).getLengthSQ() / 4;
+ }
+
+ v3f getNormal() const {
+ v3f v1 = buffer->getPosition(p1);
+ v3f v2 = buffer->getPosition(p2);
+ v3f v3 = buffer->getPosition(p3);
+
+ return (v2-v1).crossProduct(v3-v1);
+ }
+};
+
+/**
+ * Implements a binary space partitioning tree
+ * See also: https://en.wikipedia.org/wiki/Binary_space_partitioning
+ */
+class MapBlockBspTree
+{
+public:
+ MapBlockBspTree() {}
+
+ void buildTree(const std::vector<MeshTriangle> *triangles);
+
+ void traverse(v3f viewpoint, std::vector<s32> &output) const
+ {
+ traverse(root, viewpoint, output);
+ }
+
+private:
+ // Tree node definition;
+ struct TreeNode
+ {
+ v3f normal;
+ v3f origin;
+ std::vector<s32> triangle_refs;
+ s32 front_ref;
+ s32 back_ref;
+
+ TreeNode() = default;
+ TreeNode(v3f normal, v3f origin, const std::vector<s32> &triangle_refs, s32 front_ref, s32 back_ref) :
+ normal(normal), origin(origin), triangle_refs(triangle_refs), front_ref(front_ref), back_ref(back_ref)
+ {}
+ };
+
+
+ s32 buildTree(v3f normal, v3f origin, float delta, const std::vector<s32> &list, u32 depth);
+ void traverse(s32 node, v3f viewpoint, std::vector<s32> &output) const;
+
+ const std::vector<MeshTriangle> *triangles = nullptr; // this reference is managed externally
+ std::vector<TreeNode> nodes; // list of nodes
+ s32 root = -1; // index of the root node
+};
+
+/*
+ * PartialMeshBuffer
+ *
+ * Attach alternate `Indices` to an existing mesh buffer, to make it possible to use different
+ * indices with the same vertex buffer.
+ *
+ * Irrlicht does not currently support this: `CMeshBuffer` ties together a single vertex buffer
+ * and a single index buffer. There's no way to share these between mesh buffers.
+ *
+ */
+class PartialMeshBuffer
+{
+public:
+ PartialMeshBuffer(scene::SMeshBuffer *buffer, std::vector<u16> &&vertex_indexes) :
+ m_buffer(buffer), m_vertex_indexes(std::move(vertex_indexes))
+ {}
+
+ scene::IMeshBuffer *getBuffer() const { return m_buffer; }
+ const std::vector<u16> &getVertexIndexes() const { return m_vertex_indexes; }
+
+ void beforeDraw() const;
+ void afterDraw() const;
+private:
+ scene::SMeshBuffer *m_buffer;
+ mutable std::vector<u16> m_vertex_indexes;
+};
+
/*
Holds a mesh for a mapblock.
@@ -125,7 +221,23 @@ public:
m_animation_force_timer--;
}
+ /// update transparent buffers to render towards the camera
+ void updateTransparentBuffers(v3f camera_pos, v3s16 block_pos);
+ void consolidateTransparentBuffers();
+
+ /// get the list of transparent buffers
+ const std::vector<PartialMeshBuffer> &getTransparentBuffers() const
+ {
+ return this->m_transparent_buffers;
+ }
+
private:
+ struct AnimationInfo {
+ int frame; // last animation frame
+ int frame_offset;
+ TileLayer tile;
+ };
+
scene::IMesh *m_mesh[MAX_TILE_LAYERS];
MinimapMapblock *m_minimap_mapblock;
ITextureSource *m_tsrc;
@@ -144,12 +256,10 @@ private:
// Maps mesh and mesh buffer (i.e. material) indices to base texture names
std::map<std::pair<u8, u32>, std::string> m_crack_materials;
- // Animation info: texture animationi
+ // Animation info: texture animation
// Maps mesh and mesh buffer indices to TileSpecs
// Keys are pairs of (mesh index, buffer index in the mesh)
- std::map<std::pair<u8, u32>, TileLayer> m_animation_tiles;
- std::map<std::pair<u8, u32>, int> m_animation_frames; // last animation frame
- std::map<std::pair<u8, u32>, int> m_animation_frame_offsets;
+ std::map<std::pair<u8, u32>, AnimationInfo> m_animation_info;
// Animation info: day/night transitions
// Last daynight_ratio value passed to animate()
@@ -158,6 +268,13 @@ private:
// of sunlit vertices
// Keys are pairs of (mesh index, buffer index in the mesh)
std::map<std::pair<u8, u32>, std::map<u32, video::SColor > > m_daynight_diffs;
+
+ // list of all semitransparent triangles in the mapblock
+ std::vector<MeshTriangle> m_transparent_triangles;
+ // Binary Space Partitioning tree for the block
+ MapBlockBspTree m_bsp_tree;
+ // Ordered list of references to parts of transparent buffers to draw
+ std::vector<PartialMeshBuffer> m_transparent_buffers;
};
/*!
diff --git a/src/client/mesh.cpp b/src/client/mesh.cpp
index c56eba2e2..4bf07effa 100644
--- a/src/client/mesh.cpp
+++ b/src/client/mesh.cpp
@@ -20,7 +20,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "mesh.h"
#include "debug.h"
#include "log.h"
-#include "irrMap.h"
#include <cmath>
#include <iostream>
#include <IAnimatedMesh.h>
diff --git a/src/client/mesh_generator_thread.cpp b/src/client/mesh_generator_thread.cpp
index 5c3f4180b..c1bd7388e 100644
--- a/src/client/mesh_generator_thread.cpp
+++ b/src/client/mesh_generator_thread.cpp
@@ -50,7 +50,8 @@ QueuedMeshUpdate::~QueuedMeshUpdate()
*/
MeshUpdateQueue::MeshUpdateQueue(Client *client):
- m_client(client)
+ m_client(client),
+ m_next_cache_cleanup(0)
{
m_cache_enable_shaders = g_settings->getBool("enable_shaders");
m_cache_smooth_lighting = g_settings->getBool("smooth_lighting");
@@ -157,8 +158,7 @@ CachedMapBlockData* MeshUpdateQueue::cacheBlock(Map *map, v3s16 p, UpdateMode mo
size_t *cache_hit_counter)
{
CachedMapBlockData *cached_block = nullptr;
- std::map<v3s16, CachedMapBlockData*>::iterator it =
- m_cache.find(p);
+ auto it = m_cache.find(p);
if (it != m_cache.end()) {
cached_block = it->second;
@@ -192,7 +192,7 @@ CachedMapBlockData* MeshUpdateQueue::cacheBlock(Map *map, v3s16 p, UpdateMode mo
CachedMapBlockData* MeshUpdateQueue::getCachedBlock(const v3s16 &p)
{
- std::map<v3s16, CachedMapBlockData*>::iterator it = m_cache.find(p);
+ auto it = m_cache.find(p);
if (it != m_cache.end()) {
return it->second;
}
@@ -231,6 +231,15 @@ void MeshUpdateQueue::cleanupCache()
g_profiler->avg("MeshUpdateQueue MapBlock cache size kB",
mapblock_kB * m_cache.size());
+ // Iterating the entire cache can get pretty expensive so don't do it too often
+ {
+ constexpr int cleanup_interval = 250;
+ const u64 now = porting::getTimeMs();
+ if (m_next_cache_cleanup > now)
+ return;
+ m_next_cache_cleanup = now + cleanup_interval;
+ }
+
// The cache size is kept roughly below cache_soft_max_size, not letting
// anything get older than cache_seconds_max or deleted before 2 seconds.
const int cache_seconds_max = 10;
@@ -240,12 +249,11 @@ void MeshUpdateQueue::cleanupCache()
int t_now = time(0);
- for (std::map<v3s16, CachedMapBlockData*>::iterator it = m_cache.begin();
- it != m_cache.end(); ) {
+ for (auto it = m_cache.begin(); it != m_cache.end(); ) {
CachedMapBlockData *cached_block = it->second;
if (cached_block->refcount_from_queue == 0 &&
cached_block->last_used_timestamp < t_now - cache_seconds) {
- m_cache.erase(it++);
+ it = m_cache.erase(it);
delete cached_block;
} else {
++it;
diff --git a/src/client/mesh_generator_thread.h b/src/client/mesh_generator_thread.h
index 1b734bc06..552b2a9f0 100644
--- a/src/client/mesh_generator_thread.h
+++ b/src/client/mesh_generator_thread.h
@@ -21,6 +21,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <ctime>
#include <mutex>
+#include <unordered_map>
+#include <unordered_set>
#include "mapblock_mesh.h"
#include "threading/mutex_auto_lock.h"
#include "util/thread.h"
@@ -81,8 +83,9 @@ public:
private:
Client *m_client;
std::vector<QueuedMeshUpdate *> m_queue;
- std::set<v3s16> m_urgents;
- std::map<v3s16, CachedMapBlockData *> m_cache;
+ std::unordered_set<v3s16> m_urgents;
+ std::unordered_map<v3s16, CachedMapBlockData *> m_cache;
+ u64 m_next_cache_cleanup; // milliseconds
std::mutex m_mutex;
// TODO: Add callback to update these when g_settings changes
diff --git a/src/client/minimap.cpp b/src/client/minimap.cpp
index f26aa1c70..320621d91 100644
--- a/src/client/minimap.cpp
+++ b/src/client/minimap.cpp
@@ -304,7 +304,7 @@ void Minimap::setModeIndex(size_t index)
data->mode = m_modes[index];
m_current_mode_index = index;
} else {
- data->mode = MinimapModeDef{MINIMAP_TYPE_OFF, gettext("Minimap hidden"), 0, 0, ""};
+ data->mode = {MINIMAP_TYPE_OFF, gettext("Minimap hidden"), 0, 0, "", 0};
m_current_mode_index = 0;
}
diff --git a/src/client/particles.cpp b/src/client/particles.cpp
index 288826a5f..818cdc8cc 100644
--- a/src/client/particles.cpp
+++ b/src/client/particles.cpp
@@ -34,23 +34,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "settings.h"
/*
- Utility
-*/
-
-static f32 random_f32(f32 min, f32 max)
-{
- return rand() / (float)RAND_MAX * (max - min) + min;
-}
-
-static v3f random_v3f(v3f min, v3f max)
-{
- return v3f(
- random_f32(min.X, max.X),
- random_f32(min.Y, max.Y),
- random_f32(min.Z, max.Z));
-}
-
-/*
Particle
*/
@@ -59,25 +42,69 @@ Particle::Particle(
LocalPlayer *player,
ClientEnvironment *env,
const ParticleParameters &p,
- video::ITexture *texture,
+ const ClientTexRef& texture,
v2f texpos,
v2f texsize,
video::SColor color
):
scene::ISceneNode(((Client *)gamedef)->getSceneManager()->getRootSceneNode(),
- ((Client *)gamedef)->getSceneManager())
+ ((Client *)gamedef)->getSceneManager()),
+ m_texture(texture)
{
// Misc
m_gamedef = gamedef;
m_env = env;
+ // translate blend modes to GL blend functions
+ video::E_BLEND_FACTOR bfsrc, bfdst;
+ video::E_BLEND_OPERATION blendop;
+ const auto blendmode = texture.tex != nullptr
+ ? texture.tex -> blendmode
+ : ParticleParamTypes::BlendMode::alpha;
+
+ switch (blendmode) {
+ case ParticleParamTypes::BlendMode::add:
+ bfsrc = video::EBF_SRC_ALPHA;
+ bfdst = video::EBF_DST_ALPHA;
+ blendop = video::EBO_ADD;
+ break;
+
+ case ParticleParamTypes::BlendMode::sub:
+ bfsrc = video::EBF_SRC_ALPHA;
+ bfdst = video::EBF_DST_ALPHA;
+ blendop = video::EBO_REVSUBTRACT;
+ break;
+
+ case ParticleParamTypes::BlendMode::screen:
+ bfsrc = video::EBF_ONE;
+ bfdst = video::EBF_ONE_MINUS_SRC_COLOR;
+ blendop = video::EBO_ADD;
+ break;
+
+ default: // includes ParticleParamTypes::BlendMode::alpha
+ bfsrc = video::EBF_SRC_ALPHA;
+ bfdst = video::EBF_ONE_MINUS_SRC_ALPHA;
+ blendop = video::EBO_ADD;
+ break;
+ }
+
// Texture
m_material.setFlag(video::EMF_LIGHTING, false);
m_material.setFlag(video::EMF_BACK_FACE_CULLING, false);
m_material.setFlag(video::EMF_BILINEAR_FILTER, false);
m_material.setFlag(video::EMF_FOG_ENABLE, true);
- m_material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
- m_material.setTexture(0, texture);
+
+ // correctly render layered transparent particles -- see #10398
+ m_material.setFlag(video::EMF_ZWRITE_ENABLE, true);
+
+ // enable alpha blending and set blend mode
+ m_material.MaterialType = video::EMT_ONETEXTURE_BLEND;
+ m_material.MaterialTypeParam = video::pack_textureBlendFunc(
+ bfsrc, bfdst,
+ video::EMFN_MODULATE_1X,
+ video::EAS_TEXTURE | video::EAS_VERTEX_COLOR);
+ m_material.BlendOperation = blendop;
+ m_material.setTexture(0, m_texture.ref);
m_texpos = texpos;
m_texsize = texsize;
m_animation = p.animation;
@@ -90,6 +117,9 @@ Particle::Particle(
m_pos = p.pos;
m_velocity = p.vel;
m_acceleration = p.acc;
+ m_drag = p.drag;
+ m_jitter = p.jitter;
+ m_bounce = p.bounce;
m_expiration = p.expirationtime;
m_player = player;
m_size = p.size;
@@ -98,6 +128,8 @@ Particle::Particle(
m_object_collision = p.object_collision;
m_vertical = p.vertical;
m_glow = p.glow;
+ m_alpha = 0;
+ m_parent = nullptr;
// Irrlicht stuff
const float c = p.size / 2;
@@ -111,6 +143,14 @@ Particle::Particle(
updateVertices();
}
+Particle::~Particle()
+{
+ /* if our textures aren't owned by a particlespawner, we need to clean
+ * them up ourselves when the particle dies */
+ if (m_parent == nullptr)
+ delete m_texture.tex;
+}
+
void Particle::OnRegisterSceneNode()
{
if (IsVisible)
@@ -134,6 +174,12 @@ void Particle::render()
void Particle::step(float dtime)
{
m_time += dtime;
+
+ // apply drag (not handled by collisionMoveSimple) and brownian motion
+ v3f av = vecAbsolute(m_velocity);
+ av -= av * (m_drag * dtime);
+ m_velocity = av*vecSign(m_velocity) + v3f(m_jitter.pickWithin())*dtime;
+
if (m_collisiondetection) {
aabb3f box = m_collisionbox;
v3f p_pos = m_pos * BS;
@@ -141,17 +187,41 @@ void Particle::step(float dtime)
collisionMoveResult r = collisionMoveSimple(m_env, m_gamedef, BS * 0.5f,
box, 0.0f, dtime, &p_pos, &p_velocity, m_acceleration * BS, nullptr,
m_object_collision);
- if (m_collision_removal && r.collides) {
- // force expiration of the particle
- m_expiration = -1.0;
+
+ f32 bounciness = m_bounce.pickWithin();
+ if (r.collides && (m_collision_removal || bounciness > 0)) {
+ if (m_collision_removal) {
+ // force expiration of the particle
+ m_expiration = -1.0f;
+ } else if (bounciness > 0) {
+ /* cheap way to get a decent bounce effect is to only invert the
+ * largest component of the velocity vector, so e.g. you don't
+ * have a rock immediately bounce back in your face when you try
+ * to skip it across the water (as would happen if we simply
+ * downscaled and negated the velocity vector). this means
+ * bounciness will work properly for cubic objects, but meshes
+ * with diagonal angles and entities will not yield the correct
+ * visual. this is probably unavoidable */
+ if (av.Y > av.X && av.Y > av.Z) {
+ m_velocity.Y = -(m_velocity.Y * bounciness);
+ } else if (av.X > av.Y && av.X > av.Z) {
+ m_velocity.X = -(m_velocity.X * bounciness);
+ } else if (av.Z > av.Y && av.Z > av.X) {
+ m_velocity.Z = -(m_velocity.Z * bounciness);
+ } else { // well now we're in a bit of a pickle
+ m_velocity = -(m_velocity * bounciness);
+ }
+ }
} else {
- m_pos = p_pos / BS;
m_velocity = p_velocity / BS;
}
+ m_pos = p_pos / BS;
} else {
+ // apply acceleration
m_velocity += m_acceleration * dtime;
m_pos += m_velocity * dtime;
}
+
if (m_animation.type != TAT_NONE) {
m_animation_time += dtime;
int frame_length_i, frame_count;
@@ -165,11 +235,21 @@ void Particle::step(float dtime)
}
}
+ // animate particle alpha in accordance with settings
+ if (m_texture.tex != nullptr)
+ m_alpha = m_texture.tex -> alpha.blend(m_time / (m_expiration+0.1f));
+ else
+ m_alpha = 1.f;
+
// Update lighting
updateLight();
// Update model
updateVertices();
+
+ // Update position -- see #10398
+ v3s16 camera_offset = m_env->getCameraOffset();
+ setPosition(m_pos*BS - intToFloat(camera_offset, BS));
}
void Particle::updateLight()
@@ -189,7 +269,7 @@ void Particle::updateLight()
light = blend_light(m_env->getDayNightRatio(), LIGHT_SUN, 0);
u8 m_light = decode_light(light + m_glow);
- m_color.set(255,
+ m_color.set(m_alpha*255,
m_light * m_base_color.getRed() / 255,
m_light * m_base_color.getGreen() / 255,
m_light * m_base_color.getBlue() / 255);
@@ -198,6 +278,12 @@ void Particle::updateLight()
void Particle::updateVertices()
{
f32 tx0, tx1, ty0, ty1;
+ v2f scale;
+
+ if (m_texture.tex != nullptr)
+ scale = m_texture.tex -> scale.blend(m_time / (m_expiration+0.1));
+ else
+ scale = v2f(1.f, 1.f);
if (m_animation.type != TAT_NONE) {
const v2u32 texsize = m_material.getTexture(0)->getSize();
@@ -218,16 +304,24 @@ void Particle::updateVertices()
ty1 = m_texpos.Y + m_texsize.Y;
}
- m_vertices[0] = video::S3DVertex(-m_size / 2, -m_size / 2,
+ auto half = m_size * .5f,
+ hx = half * scale.X,
+ hy = half * scale.Y;
+ m_vertices[0] = video::S3DVertex(-hx, -hy,
0, 0, 0, 0, m_color, tx0, ty1);
- m_vertices[1] = video::S3DVertex(m_size / 2, -m_size / 2,
+ m_vertices[1] = video::S3DVertex(hx, -hy,
0, 0, 0, 0, m_color, tx1, ty1);
- m_vertices[2] = video::S3DVertex(m_size / 2, m_size / 2,
+ m_vertices[2] = video::S3DVertex(hx, hy,
0, 0, 0, 0, m_color, tx1, ty0);
- m_vertices[3] = video::S3DVertex(-m_size / 2, m_size / 2,
+ m_vertices[3] = video::S3DVertex(-hx, hy,
0, 0, 0, 0, m_color, tx0, ty0);
- v3s16 camera_offset = m_env->getCameraOffset();
+
+ // see #10398
+ // v3s16 camera_offset = m_env->getCameraOffset();
+ // particle position is now handled by step()
+ m_box.reset(v3f());
+
for (video::S3DVertex &vertex : m_vertices) {
if (m_vertical) {
v3f ppos = m_player->getPosition()/BS;
@@ -238,7 +332,6 @@ void Particle::updateVertices()
vertex.Pos.rotateXZBy(m_player->getYaw());
}
m_box.addInternalPoint(vertex.Pos);
- vertex.Pos += m_pos*BS - intToFloat(camera_offset, BS);
}
}
@@ -251,7 +344,8 @@ ParticleSpawner::ParticleSpawner(
LocalPlayer *player,
const ParticleSpawnerParameters &p,
u16 attached_id,
- video::ITexture *texture,
+ std::unique_ptr<ClientTexture[]>& texpool,
+ size_t texcount,
ParticleManager *p_manager
):
m_particlemanager(p_manager), p(p)
@@ -259,21 +353,66 @@ ParticleSpawner::ParticleSpawner(
m_gamedef = gamedef;
m_player = player;
m_attached_id = attached_id;
- m_texture = texture;
+ m_texpool = std::move(texpool);
+ m_texcount = texcount;
m_time = 0;
+ m_active = 0;
+ m_dying = false;
m_spawntimes.reserve(p.amount + 1);
for (u16 i = 0; i <= p.amount; i++) {
- float spawntime = rand() / (float)RAND_MAX * p.time;
+ float spawntime = myrand_float() * p.time;
m_spawntimes.push_back(spawntime);
}
+
+ size_t max_particles = 0; // maximum number of particles likely to be visible at any given time
+ if (p.time != 0) {
+ auto maxGenerations = p.time / std::min(p.exptime.start.min, p.exptime.end.min);
+ max_particles = p.amount / maxGenerations;
+ } else {
+ auto longestLife = std::max(p.exptime.start.max, p.exptime.end.max);
+ max_particles = p.amount * longestLife;
+ }
+
+ p_manager->reserveParticleSpace(max_particles * 1.2);
+}
+
+namespace {
+ GenericCAO *findObjectByID(ClientEnvironment *env, u16 id) {
+ if (id == 0)
+ return nullptr;
+ return env->getGenericCAO(id);
+ }
}
void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
const core::matrix4 *attached_absolute_pos_rot_matrix)
{
+ float fac = 0;
+ if (p.time != 0) { // ensure safety from divide-by-zeroes
+ fac = m_time / (p.time+0.1f);
+ }
+
+ auto r_pos = p.pos.blend(fac);
+ auto r_vel = p.vel.blend(fac);
+ auto r_acc = p.acc.blend(fac);
+ auto r_drag = p.drag.blend(fac);
+ auto r_radius = p.radius.blend(fac);
+ auto r_jitter = p.jitter.blend(fac);
+ auto r_bounce = p.bounce.blend(fac);
+ v3f attractor_origin = p.attractor_origin.blend(fac);
+ v3f attractor_direction = p.attractor_direction.blend(fac);
+ auto attractor_obj = findObjectByID(env, p.attractor_attachment);
+ auto attractor_direction_obj = findObjectByID(env, p.attractor_direction_attachment);
+
+ auto r_exp = p.exptime.blend(fac);
+ auto r_size = p.size.blend(fac);
+ auto r_attract = p.attract.blend(fac);
+ auto attract = r_attract.pickWithin();
+
v3f ppos = m_player->getPosition() / BS;
- v3f pos = random_v3f(p.minpos, p.maxpos);
+ v3f pos = r_pos.pickWithin();
+ v3f sphere_radius = r_radius.pickWithin();
// Need to apply this first or the following check
// will be wrong for attached spawners
@@ -287,15 +426,18 @@ void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
pos.Z += camera_offset.Z;
}
- if (pos.getDistanceFrom(ppos) > radius)
+ if (pos.getDistanceFromSQ(ppos) > radius*radius)
return;
// Parameters for the single particle we're about to spawn
ParticleParameters pp;
pp.pos = pos;
- pp.vel = random_v3f(p.minvel, p.maxvel);
- pp.acc = random_v3f(p.minacc, p.maxacc);
+ pp.vel = r_vel.pickWithin();
+ pp.acc = r_acc.pickWithin();
+ pp.drag = r_drag.pickWithin();
+ pp.jitter = r_jitter;
+ pp.bounce = r_bounce;
if (attached_absolute_pos_rot_matrix) {
// Apply attachment rotation
@@ -303,30 +445,137 @@ void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
attached_absolute_pos_rot_matrix->rotateVect(pp.acc);
}
- pp.expirationtime = random_f32(p.minexptime, p.maxexptime);
+ if (attractor_obj)
+ attractor_origin += attractor_obj->getPosition() / BS;
+ if (attractor_direction_obj) {
+ auto *attractor_absolute_pos_rot_matrix = attractor_direction_obj->getAbsolutePosRotMatrix();
+ if (attractor_absolute_pos_rot_matrix)
+ attractor_absolute_pos_rot_matrix->rotateVect(attractor_direction);
+ }
+
+ pp.expirationtime = r_exp.pickWithin();
+
+ if (sphere_radius != v3f()) {
+ f32 l = sphere_radius.getLength();
+ v3f mag = sphere_radius;
+ mag.normalize();
+
+ v3f ofs = v3f(l,0,0);
+ ofs.rotateXZBy(myrand_range(0.f,360.f));
+ ofs.rotateYZBy(myrand_range(0.f,360.f));
+ ofs.rotateXYBy(myrand_range(0.f,360.f));
+
+ pp.pos += ofs * mag;
+ }
+
+ if (p.attractor_kind != ParticleParamTypes::AttractorKind::none && attract != 0) {
+ v3f dir;
+ f32 dist = 0; /* =0 necessary to silence warning */
+ switch (p.attractor_kind) {
+ case ParticleParamTypes::AttractorKind::none:
+ break;
+
+ case ParticleParamTypes::AttractorKind::point: {
+ dist = pp.pos.getDistanceFrom(attractor_origin);
+ dir = pp.pos - attractor_origin;
+ dir.normalize();
+ break;
+ }
+
+ case ParticleParamTypes::AttractorKind::line: {
+ // https://github.com/minetest/minetest/issues/11505#issuecomment-915612700
+ const auto& lorigin = attractor_origin;
+ v3f ldir = attractor_direction;
+ ldir.normalize();
+ auto origin_to_point = pp.pos - lorigin;
+ auto scalar_projection = origin_to_point.dotProduct(ldir);
+ auto point_on_line = lorigin + (ldir * scalar_projection);
+
+ dist = pp.pos.getDistanceFrom(point_on_line);
+ dir = (point_on_line - pp.pos);
+ dir.normalize();
+ dir *= -1; // flip it around so strength=1 attracts, not repulses
+ break;
+ }
+
+ case ParticleParamTypes::AttractorKind::plane: {
+ // https://github.com/minetest/minetest/issues/11505#issuecomment-915612700
+ const v3f& porigin = attractor_origin;
+ v3f normal = attractor_direction;
+ normal.normalize();
+ v3f point_to_origin = porigin - pp.pos;
+ f32 factor = normal.dotProduct(point_to_origin);
+ if (numericAbsolute(factor) == 0.0f) {
+ dir = normal;
+ } else {
+ factor = numericSign(factor);
+ dir = normal * factor;
+ }
+ dist = numericAbsolute(normal.dotProduct(pp.pos - porigin));
+ dir *= -1; // flip it around so strength=1 attracts, not repulses
+ break;
+ }
+ }
+
+ f32 speedTowards = numericAbsolute(attract) * dist;
+ v3f avel = dir * speedTowards;
+ if (attract > 0 && speedTowards > 0) {
+ avel *= -1;
+ if (p.attractor_kill) {
+ // make sure the particle dies after crossing the attractor threshold
+ f32 timeToCenter = dist / speedTowards;
+ if (timeToCenter < pp.expirationtime)
+ pp.expirationtime = timeToCenter;
+ }
+ }
+ pp.vel += avel;
+ }
+
p.copyCommon(pp);
- video::ITexture *texture;
+ ClientTexRef texture;
v2f texpos, texsize;
video::SColor color(0xFFFFFFFF);
if (p.node.getContent() != CONTENT_IGNORE) {
const ContentFeatures &f =
m_particlemanager->m_env->getGameDef()->ndef()->get(p.node);
- if (!ParticleManager::getNodeParticleParams(p.node, f, pp, &texture,
+ if (!ParticleManager::getNodeParticleParams(p.node, f, pp, &texture.ref,
texpos, texsize, &color, p.node_tile))
return;
} else {
- texture = m_texture;
+ if (m_texcount == 0)
+ return;
+ texture = decltype(texture)(m_texpool[m_texcount == 1 ? 0 : myrand_range(0,m_texcount-1)]);
texpos = v2f(0.0f, 0.0f);
texsize = v2f(1.0f, 1.0f);
+ if (texture.tex->animated)
+ pp.animation = texture.tex->animation;
+ }
+
+ // synchronize animation length with particle life if desired
+ if (pp.animation.type != TAT_NONE) {
+ if (pp.animation.type == TAT_VERTICAL_FRAMES &&
+ pp.animation.vertical_frames.length < 0) {
+ auto& a = pp.animation.vertical_frames;
+ // we add a tiny extra value to prevent the first frame
+ // from flickering back on just before the particle dies
+ a.length = (pp.expirationtime / -a.length) + 0.1;
+ } else if (pp.animation.type == TAT_SHEET_2D &&
+ pp.animation.sheet_2d.frame_length < 0) {
+ auto& a = pp.animation.sheet_2d;
+ auto frames = a.frames_w * a.frames_h;
+ auto runtime = (pp.expirationtime / -a.frame_length) + 0.1;
+ pp.animation.sheet_2d.frame_length = frames / runtime;
+ }
}
// Allow keeping default random size
- if (p.maxsize > 0.0f)
- pp.size = random_f32(p.minsize, p.maxsize);
+ if (p.size.start.max > 0.0f || p.size.end.max > 0.0f)
+ pp.size = r_size.pickWithin();
- m_particlemanager->addParticle(new Particle(
+ ++m_active;
+ auto pa = new Particle(
m_gamedef,
m_player,
env,
@@ -335,7 +584,9 @@ void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
texpos,
texsize,
color
- ));
+ );
+ pa->m_parent = this;
+ m_particlemanager->addParticle(pa);
}
void ParticleSpawner::step(float dtime, ClientEnvironment *env)
@@ -348,7 +599,7 @@ void ParticleSpawner::step(float dtime, ClientEnvironment *env)
bool unloaded = false;
const core::matrix4 *attached_absolute_pos_rot_matrix = nullptr;
if (m_attached_id) {
- if (GenericCAO *attached = dynamic_cast<GenericCAO *>(env->getActiveObject(m_attached_id))) {
+ if (GenericCAO *attached = env->getGenericCAO(m_attached_id)) {
attached_absolute_pos_rot_matrix = attached->getAbsolutePosRotMatrix();
} else {
unloaded = true;
@@ -379,7 +630,7 @@ void ParticleSpawner::step(float dtime, ClientEnvironment *env)
return;
for (int i = 0; i <= p.amount; i++) {
- if (rand() / (float)RAND_MAX < dtime)
+ if (myrand_float() < dtime)
spawnParticle(env, radius, attached_absolute_pos_rot_matrix);
}
}
@@ -408,9 +659,15 @@ void ParticleManager::stepSpawners(float dtime)
{
MutexAutoLock lock(m_spawner_list_lock);
for (auto i = m_particle_spawners.begin(); i != m_particle_spawners.end();) {
- if (i->second->get_expired()) {
- delete i->second;
- m_particle_spawners.erase(i++);
+ if (i->second->getExpired()) {
+ // the particlespawner owns the textures, so we need to make
+ // sure there are no active particles before we free it
+ if (i->second->m_active == 0) {
+ delete i->second;
+ m_particle_spawners.erase(i++);
+ } else {
+ ++i;
+ }
} else {
i->second->step(dtime, m_env);
++i;
@@ -423,6 +680,10 @@ void ParticleManager::stepParticles(float dtime)
MutexAutoLock lock(m_particle_list_lock);
for (auto i = m_particles.begin(); i != m_particles.end();) {
if ((*i)->get_expired()) {
+ if ((*i)->m_parent) {
+ assert((*i)->m_parent->m_active != 0);
+ --(*i)->m_parent->m_active;
+ }
(*i)->remove();
delete *i;
i = m_particles.erase(i);
@@ -464,13 +725,29 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
const ParticleSpawnerParameters &p = *event->add_particlespawner.p;
- video::ITexture *texture =
- client->tsrc()->getTextureForMesh(p.texture);
+ // texture pool
+ std::unique_ptr<ClientTexture[]> texpool = nullptr;
+ size_t txpsz = 0;
+ if (!p.texpool.empty()) {
+ txpsz = p.texpool.size();
+ texpool = decltype(texpool)(new ClientTexture [txpsz]);
+
+ for (size_t i = 0; i < txpsz; ++i) {
+ texpool[i] = ClientTexture(p.texpool[i], client->tsrc());
+ }
+ } else {
+ // no texpool in use, use fallback texture
+ txpsz = 1;
+ texpool = decltype(texpool)(new ClientTexture[1] {
+ ClientTexture(p.texture, client->tsrc())
+ });
+ }
auto toadd = new ParticleSpawner(client, player,
p,
event->add_particlespawner.attached_id,
- texture,
+ texpool,
+ txpsz,
this);
addParticleSpawner(event->add_particlespawner.id, toadd);
@@ -481,7 +758,7 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
case CE_SPAWN_PARTICLE: {
ParticleParameters &p = *event->spawn_particle;
- video::ITexture *texture;
+ ClientTexRef texture;
v2f texpos, texsize;
video::SColor color(0xFFFFFFFF);
@@ -489,11 +766,15 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
if (p.node.getContent() != CONTENT_IGNORE) {
const ContentFeatures &f = m_env->getGameDef()->ndef()->get(p.node);
- if (!getNodeParticleParams(p.node, f, p, &texture, texpos,
- texsize, &color, p.node_tile))
- texture = nullptr;
+ getNodeParticleParams(p.node, f, p, &texture.ref, texpos,
+ texsize, &color, p.node_tile);
} else {
- texture = client->tsrc()->getTextureForMesh(p.texture);
+ /* with no particlespawner to own the texture, we need
+ * to save it on the heap. it will be freed when the
+ * particle is destroyed */
+ auto texstore = new ClientTexture(p.texture, client->tsrc());
+
+ texture = ClientTexRef(*texstore);
texpos = v2f(0.0f, 0.0f);
texsize = v2f(1.0f, 1.0f);
}
@@ -502,7 +783,7 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
if (oldsize > 0.0f)
p.size = oldsize;
- if (texture) {
+ if (texture.ref) {
Particle *toadd = new Particle(client, player, m_env,
p, texture, texpos, texsize, color);
@@ -529,7 +810,7 @@ bool ParticleManager::getNodeParticleParams(const MapNode &n,
if (tilenum > 0 && tilenum <= 6)
texid = tilenum - 1;
else
- texid = rand() % 6;
+ texid = myrand_range(0,5);
const TileLayer &tile = f.tiles[texid].layers[0];
p.animation.type = TAT_NONE;
@@ -539,13 +820,13 @@ bool ParticleManager::getNodeParticleParams(const MapNode &n,
else
*texture = tile.texture;
- float size = (rand() % 8) / 64.0f;
+ float size = (myrand_range(0,8)) / 64.0f;
p.size = BS * size;
if (tile.scale)
size /= tile.scale;
texsize = v2f(size * 2.0f, size * 2.0f);
- texpos.X = (rand() % 64) / 64.0f - texsize.X;
- texpos.Y = (rand() % 64) / 64.0f - texsize.Y;
+ texpos.X = (myrand_range(0,64)) / 64.0f - texsize.X;
+ texpos.Y = (myrand_range(0,64)) / 64.0f - texsize.Y;
if (tile.has_color)
*color = tile.color;
@@ -577,20 +858,20 @@ void ParticleManager::addNodeParticle(IGameDef *gamedef,
LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
{
ParticleParameters p;
- video::ITexture *texture;
+ video::ITexture *ref = nullptr;
v2f texpos, texsize;
video::SColor color;
- if (!getNodeParticleParams(n, f, p, &texture, texpos, texsize, &color))
+ if (!getNodeParticleParams(n, f, p, &ref, texpos, texsize, &color))
return;
- p.expirationtime = (rand() % 100) / 100.0f;
+ p.expirationtime = myrand_range(0, 100) / 100.0f;
// Physics
p.vel = v3f(
- (rand() % 150) / 50.0f - 1.5f,
- (rand() % 150) / 50.0f,
- (rand() % 150) / 50.0f - 1.5f
+ myrand_range(-1.5f,1.5f),
+ myrand_range(0.f,3.f),
+ myrand_range(-1.5f,1.5f)
);
p.acc = v3f(
0.0f,
@@ -598,9 +879,9 @@ void ParticleManager::addNodeParticle(IGameDef *gamedef,
0.0f
);
p.pos = v3f(
- (f32)pos.X + (rand() % 100) / 200.0f - 0.25f,
- (f32)pos.Y + (rand() % 100) / 200.0f - 0.25f,
- (f32)pos.Z + (rand() % 100) / 200.0f - 0.25f
+ (f32)pos.X + myrand_range(0.f, .5f) - .25f,
+ (f32)pos.Y + myrand_range(0.f, .5f) - .25f,
+ (f32)pos.Z + myrand_range(0.f, .5f) - .25f
);
Particle *toadd = new Particle(
@@ -608,7 +889,7 @@ void ParticleManager::addNodeParticle(IGameDef *gamedef,
player,
m_env,
p,
- texture,
+ ClientTexRef(ref),
texpos,
texsize,
color);
@@ -616,6 +897,12 @@ void ParticleManager::addNodeParticle(IGameDef *gamedef,
addParticle(toadd);
}
+void ParticleManager::reserveParticleSpace(size_t max_estimate)
+{
+ MutexAutoLock lock(m_particle_list_lock);
+ m_particles.reserve(m_particles.size() + max_estimate);
+}
+
void ParticleManager::addParticle(Particle *toadd)
{
MutexAutoLock lock(m_particle_list_lock);
@@ -634,7 +921,6 @@ void ParticleManager::deleteParticleSpawner(u64 id)
MutexAutoLock lock(m_spawner_list_lock);
auto it = m_particle_spawners.find(id);
if (it != m_particle_spawners.end()) {
- delete it->second;
- m_particle_spawners.erase(it);
+ it->second->setDying();
}
}
diff --git a/src/client/particles.h b/src/client/particles.h
index 2011f0262..0818b796b 100644
--- a/src/client/particles.h
+++ b/src/client/particles.h
@@ -31,20 +31,51 @@ class ClientEnvironment;
struct MapNode;
struct ContentFeatures;
+struct ClientTexture
+{
+ /* per-spawner structure used to store the ParticleTexture structs
+ * that spawned particles will refer to through ClientTexRef */
+ ParticleTexture tex;
+ video::ITexture *ref = nullptr;
+
+ ClientTexture() = default;
+ ClientTexture(const ServerParticleTexture& p, ITextureSource *t):
+ tex(p),
+ ref(t->getTextureForMesh(p.string)) {};
+};
+
+struct ClientTexRef
+{
+ /* per-particle structure used to avoid massively duplicating the
+ * fairly large ParticleTexture struct */
+ ParticleTexture* tex = nullptr;
+ video::ITexture* ref = nullptr;
+ ClientTexRef() = default;
+
+ /* constructor used by particles spawned from a spawner */
+ ClientTexRef(ClientTexture& t):
+ tex(&t.tex), ref(t.ref) {};
+
+ /* constructor used for node particles */
+ ClientTexRef(decltype(ref) tp): ref(tp) {};
+};
+
+class ParticleSpawner;
+
class Particle : public scene::ISceneNode
{
- public:
+public:
Particle(
- IGameDef* gamedef,
+ IGameDef *gamedef,
LocalPlayer *player,
ClientEnvironment *env,
const ParticleParameters &p,
- video::ITexture *texture,
+ const ClientTexRef &texture,
v2f texpos,
v2f texsize,
video::SColor color
);
- ~Particle() = default;
+ ~Particle();
virtual const aabb3f &getBoundingBox() const
{
@@ -69,9 +100,12 @@ class Particle : public scene::ISceneNode
bool get_expired ()
{ return m_expiration < m_time; }
+ ParticleSpawner *m_parent;
+
private:
void updateLight();
void updateVertices();
+ void setVertexAlpha(float a);
video::S3DVertex m_vertices[4];
float m_time = 0.0f;
@@ -81,14 +115,19 @@ private:
IGameDef *m_gamedef;
aabb3f m_box;
aabb3f m_collisionbox;
+ ClientTexRef m_texture;
video::SMaterial m_material;
v2f m_texpos;
v2f m_texsize;
v3f m_pos;
v3f m_velocity;
v3f m_acceleration;
+ v3f m_drag;
+ ParticleParamTypes::v3fRange m_jitter;
+ ParticleParamTypes::f32Range m_bounce;
LocalPlayer *m_player;
float m_size;
+
//! Color without lighting
video::SColor m_base_color;
//! Final rendered color
@@ -102,24 +141,27 @@ private:
float m_animation_time = 0.0f;
int m_animation_frame = 0;
u8 m_glow;
+ float m_alpha = 0.0f;
};
class ParticleSpawner
{
public:
- ParticleSpawner(IGameDef* gamedef,
+ ParticleSpawner(IGameDef *gamedef,
LocalPlayer *player,
const ParticleSpawnerParameters &p,
u16 attached_id,
- video::ITexture *texture,
+ std::unique_ptr<ClientTexture[]> &texpool,
+ size_t texcount,
ParticleManager* p_manager);
- ~ParticleSpawner() = default;
-
void step(float dtime, ClientEnvironment *env);
- bool get_expired ()
- { return p.amount <= 0 && p.time != 0; }
+ size_t m_active;
+
+ bool getExpired() const
+ { return m_dying || (p.amount <= 0 && p.time != 0); }
+ void setDying() { m_dying = true; }
private:
void spawnParticle(ClientEnvironment *env, float radius,
@@ -127,10 +169,12 @@ private:
ParticleManager *m_particlemanager;
float m_time;
+ bool m_dying;
IGameDef *m_gamedef;
LocalPlayer *m_player;
ParticleSpawnerParameters p;
- video::ITexture *m_texture;
+ std::unique_ptr<ClientTexture[]> m_texpool;
+ size_t m_texcount;
std::vector<float> m_spawntimes;
u16 m_attached_id;
};
@@ -156,6 +200,8 @@ public:
void addNodeParticle(IGameDef *gamedef, LocalPlayer *player, v3s16 pos,
const MapNode &n, const ContentFeatures &f);
+ void reserveParticleSpace(size_t max_estimate);
+
/**
* This function is only used by client particle spawners
*
diff --git a/src/client/render/core.cpp b/src/client/render/core.cpp
index 44ef1c98c..ca5d3c614 100644
--- a/src/client/render/core.cpp
+++ b/src/client/render/core.cpp
@@ -35,8 +35,15 @@ RenderingCore::RenderingCore(IrrlichtDevice *_device, Client *_client, Hud *_hud
screensize = driver->getScreenSize();
virtual_size = screensize;
+ // disable if unsupported
+ if (g_settings->getBool("enable_dynamic_shadows") && (
+ g_settings->get("video_driver") != "opengl" ||
+ !g_settings->getBool("enable_shaders"))) {
+ g_settings->setBool("enable_dynamic_shadows", false);
+ }
+
if (g_settings->getBool("enable_shaders") &&
- false && g_settings->getBool("enable_dynamic_shadows")) {
+ g_settings->getBool("enable_dynamic_shadows")) {
shadow_renderer = new ShadowRenderer(device, client);
}
}
@@ -76,8 +83,11 @@ 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)
+ if (shadow_renderer) {
+ // This is necessary to render shadows for animations correctly
+ smgr->getRootSceneNode()->OnAnimate(device->getTimer()->getTime());
shadow_renderer->update();
+ }
beforeDraw();
drawAll();
@@ -103,7 +113,7 @@ void RenderingCore::drawHUD()
if (show_hud) {
if (draw_crosshair)
hud->drawCrosshair();
-
+
hud->drawHotbar(client->getEnv().getLocalPlayer()->getWieldIndex());
hud->drawLuaElements(camera->getOffset());
camera->drawNametags();
diff --git a/src/client/render/stereo.cpp b/src/client/render/stereo.cpp
index 967b5a78f..0f54e166e 100644
--- a/src/client/render/stereo.cpp
+++ b/src/client/render/stereo.cpp
@@ -27,7 +27,7 @@ RenderingCoreStereo::RenderingCoreStereo(
IrrlichtDevice *_device, Client *_client, Hud *_hud)
: RenderingCore(_device, _client, _hud)
{
- eye_offset = BS * g_settings->getFloat("3d_paralax_strength");
+ eye_offset = BS * g_settings->getFloat("3d_paralax_strength", -0.087f, 0.087f);
}
void RenderingCoreStereo::beforeDraw()
diff --git a/src/client/renderingengine.cpp b/src/client/renderingengine.cpp
index 723865db4..9698b63bb 100644
--- a/src/client/renderingengine.cpp
+++ b/src/client/renderingengine.cpp
@@ -86,8 +86,12 @@ RenderingEngine::RenderingEngine(IEventReceiver *receiver)
// Resolution selection
bool fullscreen = g_settings->getBool("fullscreen");
- u16 screen_w = g_settings->getU16("screen_w");
- u16 screen_h = g_settings->getU16("screen_h");
+#ifdef __ANDROID__
+ u16 screen_w = 0, screen_h = 0;
+#else
+ u16 screen_w = std::max<u16>(g_settings->getU16("screen_w"), 1);
+ u16 screen_h = std::max<u16>(g_settings->getU16("screen_h"), 1);
+#endif
// bpp, fsaa, vsync
bool vsync = g_settings->getBool("vsync");
@@ -116,7 +120,7 @@ RenderingEngine::RenderingEngine(IEventReceiver *receiver)
}
SIrrlichtCreationParameters params = SIrrlichtCreationParameters();
- if (g_logger.getTraceEnabled())
+ if (tracestream)
params.LoggingLevel = irr::ELL_DEBUG;
params.DriverType = driverType;
params.WindowSize = core::dimension2d<u32>(screen_w, screen_h);
@@ -598,7 +602,7 @@ static float calcDisplayDensity()
float RenderingEngine::getDisplayDensity()
{
static float cached_display_density = calcDisplayDensity();
- return cached_display_density * g_settings->getFloat("display_density_factor");
+ return std::max(cached_display_density * g_settings->getFloat("display_density_factor"), 0.5f);
}
#elif defined(_WIN32)
@@ -626,37 +630,23 @@ float RenderingEngine::getDisplayDensity()
display_density = calcDisplayDensity(get_video_driver());
cached = true;
}
- return display_density * g_settings->getFloat("display_density_factor");
+ return std::max(display_density * g_settings->getFloat("display_density_factor"), 0.5f);
}
#else
float RenderingEngine::getDisplayDensity()
{
- return (g_settings->getFloat("screen_dpi") / 96.0) * g_settings->getFloat("display_density_factor");
+ return std::max(g_settings->getFloat("screen_dpi") / 96.0f *
+ g_settings->getFloat("display_density_factor"), 0.5f);
}
#endif
-v2u32 RenderingEngine::getDisplaySize()
-{
- IrrlichtDevice *nulldevice = createDevice(video::EDT_NULL);
-
- core::dimension2d<u32> deskres =
- nulldevice->getVideoModeList()->getDesktopResolution();
- nulldevice->drop();
-
- return deskres;
-}
-
#else // __ANDROID__
float RenderingEngine::getDisplayDensity()
{
return porting::getDisplayDensity();
}
-v2u32 RenderingEngine::getDisplaySize()
-{
- return porting::getDisplaySize();
-}
#endif // __ANDROID__
diff --git a/src/client/renderingengine.h b/src/client/renderingengine.h
index a0ddb0d9a..38420010f 100644
--- a/src/client/renderingengine.h
+++ b/src/client/renderingengine.h
@@ -55,7 +55,6 @@ public:
static const VideoDriverInfo &getVideoDriverInfo(irr::video::E_DRIVER_TYPE type);
static float getDisplayDensity();
- static v2u32 getDisplaySize();
bool setupTopLevelWindow(const std::string &name);
void setupTopLevelXorgWindow(const std::string &name);
@@ -123,8 +122,8 @@ public:
// FIXME: this is still global when it shouldn't be
static ShadowRenderer *get_shadow_renderer()
{
- //if (s_singleton && s_singleton->core)
- // return s_singleton->core->get_shadow_renderer();
+ if (s_singleton && s_singleton->core)
+ return s_singleton->core->get_shadow_renderer();
return nullptr;
}
static std::vector<irr::video::E_DRIVER_TYPE> getSupportedVideoDrivers();
diff --git a/src/client/shader.cpp b/src/client/shader.cpp
index c04a25862..009a4b3d7 100644
--- a/src/client/shader.cpp
+++ b/src/client/shader.cpp
@@ -40,20 +40,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "client/tile.h"
#include "config.h"
-#if ENABLE_GLES
-#ifdef _IRR_COMPILE_WITH_OGLES1_
-#include <GLES/gl.h>
-#else
-#include <GLES2/gl2.h>
-#endif
-#else
-#ifndef __APPLE__
-#include <GL/gl.h>
-#else
-#define GL_SILENCE_DEPRECATION
-#include <OpenGL/gl.h>
-#endif
-#endif
+#include <mt_opengl.h>
/*
A cache from shader name to shader path
@@ -223,17 +210,24 @@ public:
class MainShaderConstantSetter : public IShaderConstantSetter
{
- CachedVertexShaderSetting<float, 16> m_world_view_proj;
- CachedVertexShaderSetting<float, 16> m_world;
+ CachedVertexShaderSetting<f32, 16> m_world_view_proj;
+ CachedVertexShaderSetting<f32, 16> m_world;
// Shadow-related
- CachedPixelShaderSetting<float, 16> m_shadow_view_proj;
- CachedPixelShaderSetting<float, 3> m_light_direction;
- CachedPixelShaderSetting<float> m_texture_res;
- CachedPixelShaderSetting<float> m_shadow_strength;
- CachedPixelShaderSetting<float> m_time_of_day;
- CachedPixelShaderSetting<float> m_shadowfar;
+ CachedPixelShaderSetting<f32, 16> m_shadow_view_proj;
+ CachedPixelShaderSetting<f32, 3> m_light_direction;
+ CachedPixelShaderSetting<f32> m_texture_res;
+ CachedPixelShaderSetting<f32> m_shadow_strength;
+ CachedPixelShaderSetting<f32> m_time_of_day;
+ CachedPixelShaderSetting<f32> m_shadowfar;
+ CachedPixelShaderSetting<f32, 4> m_camera_pos;
CachedPixelShaderSetting<s32> m_shadow_texture;
+ CachedVertexShaderSetting<f32> m_perspective_bias0_vertex;
+ CachedPixelShaderSetting<f32> m_perspective_bias0_pixel;
+ CachedVertexShaderSetting<f32> m_perspective_bias1_vertex;
+ CachedPixelShaderSetting<f32> m_perspective_bias1_pixel;
+ CachedVertexShaderSetting<f32> m_perspective_zbias_vertex;
+ CachedPixelShaderSetting<f32> m_perspective_zbias_pixel;
#if ENABLE_GLES
// Modelview matrix
@@ -248,18 +242,25 @@ public:
MainShaderConstantSetter() :
m_world_view_proj("mWorldViewProj")
, m_world("mWorld")
-#if ENABLE_GLES
- , m_world_view("mWorldView")
- , m_texture("mTexture")
- , m_normal("mNormal")
-#endif
, m_shadow_view_proj("m_ShadowViewProj")
, m_light_direction("v_LightDirection")
, m_texture_res("f_textureresolution")
, m_shadow_strength("f_shadow_strength")
, m_time_of_day("f_timeofday")
, m_shadowfar("f_shadowfar")
+ , m_camera_pos("CameraPos")
, m_shadow_texture("ShadowMapSampler")
+ , m_perspective_bias0_vertex("xyPerspectiveBias0")
+ , m_perspective_bias0_pixel("xyPerspectiveBias0")
+ , m_perspective_bias1_vertex("xyPerspectiveBias1")
+ , m_perspective_bias1_pixel("xyPerspectiveBias1")
+ , m_perspective_zbias_vertex("zPerspectiveBias")
+ , m_perspective_zbias_pixel("zPerspectiveBias")
+#if ENABLE_GLES
+ , m_world_view("mWorldView")
+ , m_texture("mTexture")
+ , m_normal("mNormal")
+#endif
{}
~MainShaderConstantSetter() = default;
@@ -306,26 +307,40 @@ public:
shadowViewProj *= light.getViewMatrix();
m_shadow_view_proj.set(shadowViewProj.pointer(), services);
- float v_LightDirection[3];
+ f32 v_LightDirection[3];
light.getDirection().getAs3Values(v_LightDirection);
m_light_direction.set(v_LightDirection, services);
- float TextureResolution = light.getMapResolution();
+ f32 TextureResolution = light.getMapResolution();
m_texture_res.set(&TextureResolution, services);
- float ShadowStrength = shadow->getShadowStrength();
+ f32 ShadowStrength = shadow->getShadowStrength();
m_shadow_strength.set(&ShadowStrength, services);
- float timeOfDay = shadow->getTimeOfDay();
+ f32 timeOfDay = shadow->getTimeOfDay();
m_time_of_day.set(&timeOfDay, services);
- float shadowFar = shadow->getMaxShadowFar();
+ f32 shadowFar = shadow->getMaxShadowFar();
m_shadowfar.set(&shadowFar, services);
+ f32 cam_pos[4];
+ shadowViewProj.transformVect(cam_pos, light.getPlayerPos());
+ m_camera_pos.set(cam_pos, services);
+
// I dont like using this hardcoded value. maybe something like
// MAX_TEXTURE - 1 or somthing like that??
s32 TextureLayerID = 3;
m_shadow_texture.set(&TextureLayerID, services);
+
+ f32 bias0 = shadow->getPerspectiveBiasXY();
+ m_perspective_bias0_vertex.set(&bias0, services);
+ m_perspective_bias0_pixel.set(&bias0, services);
+ f32 bias1 = 1.0f - bias0 + 1e-5f;
+ m_perspective_bias1_vertex.set(&bias1, services);
+ m_perspective_bias1_pixel.set(&bias1, services);
+ f32 zbias = shadow->getPerspectiveBiasZ();
+ m_perspective_zbias_vertex.set(&zbias, services);
+ m_perspective_zbias_pixel.set(&zbias, services);
}
}
};
@@ -667,13 +682,19 @@ ShaderInfo ShaderSource::generateShader(const std::string &name,
)";
}
+ // Since this is the first time we're using the GL bindings be extra careful.
+ // This should be removed before 5.6.0 or similar.
+ if (!GL.GetString) {
+ errorstream << "OpenGL procedures were not loaded correctly, "
+ "please open a bug report with details about your platform/OS." << std::endl;
+ abort();
+ }
+
bool use_discard = use_gles;
-#ifdef __unix__
// For renderers that should use discard instead of GL_ALPHA_TEST
- const char* gl_renderer = (const char*)glGetString(GL_RENDERER);
- if (strstr(gl_renderer, "GC7000"))
+ const char *renderer = reinterpret_cast<const char*>(GL.GetString(GL.RENDERER));
+ if (strstr(renderer, "GC7000"))
use_discard = true;
-#endif
if (use_discard) {
if (shaderinfo.base_material == video::EMT_TRANSPARENT_ALPHA_CHANNEL)
shaders_header << "#define USE_DISCARD 1\n";
@@ -733,7 +754,7 @@ 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")) {
+ if (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";
@@ -750,6 +771,8 @@ ShaderInfo ShaderSource::generateShader(const std::string &name,
shaders_header << "#define SOFTSHADOWRADIUS " << shadow_soft_radius << "\n";
}
+ shaders_header << "#line 0\n"; // reset the line counter for meaningful diagnostics
+
std::string common_header = shaders_header.str();
std::string vertex_shader = m_sourcecache.getOrLoad(name, "opengl_vertex.glsl");
diff --git a/src/client/shadows/dynamicshadows.cpp b/src/client/shadows/dynamicshadows.cpp
index 6ef5a4f1d..9f26ba94a 100644
--- a/src/client/shadows/dynamicshadows.cpp
+++ b/src/client/shadows/dynamicshadows.cpp
@@ -27,11 +27,24 @@ with this program; if not, write to the Free Software Foundation, Inc.,
using m4f = core::matrix4;
+static v3f quantizeDirection(v3f direction, float step)
+{
+
+ float yaw = std::atan2(direction.Z, direction.X);
+ float pitch = std::asin(direction.Y); // assume look is normalized
+
+ yaw = std::floor(yaw / step) * step;
+ pitch = std::floor(pitch / step) * step;
+
+ return v3f(std::cos(yaw)*std::cos(pitch), std::sin(pitch), std::sin(yaw)*std::cos(pitch));
+}
+
void DirectionalLight::createSplitMatrices(const Camera *cam)
{
- float radius;
+ const float DISTANCE_STEP = BS * 2.0; // 2 meters
v3f newCenter;
v3f look = cam->getDirection();
+ look = quantizeDirection(look, M_PI / 12.0); // 15 degrees
// camera view tangents
float tanFovY = tanf(cam->getFovY() * 0.5f);
@@ -42,42 +55,42 @@ void DirectionalLight::createSplitMatrices(const Camera *cam)
float sfFar = adjustDist(future_frustum.zFar, cam->getFovY());
// adjusted camera positions
- v3f camPos2 = cam->getPosition();
- v3f camPos = v3f(camPos2.X - cam->getOffset().X * BS,
- camPos2.Y - cam->getOffset().Y * BS,
- camPos2.Z - cam->getOffset().Z * BS);
- camPos += look * sfNear;
- camPos2 += look * sfNear;
+ v3f cam_pos_world = cam->getPosition();
+ cam_pos_world = v3f(
+ floor(cam_pos_world.X / DISTANCE_STEP) * DISTANCE_STEP,
+ floor(cam_pos_world.Y / DISTANCE_STEP) * DISTANCE_STEP,
+ floor(cam_pos_world.Z / DISTANCE_STEP) * DISTANCE_STEP);
+ v3f cam_pos_scene = v3f(cam_pos_world.X - cam->getOffset().X * BS,
+ cam_pos_world.Y - cam->getOffset().Y * BS,
+ cam_pos_world.Z - cam->getOffset().Z * BS);
+ cam_pos_scene += look * sfNear;
+ cam_pos_world += look * sfNear;
// center point of light frustum
- float end = sfNear + sfFar;
- newCenter = camPos + look * (sfNear + 0.05f * end);
- v3f world_center = camPos2 + look * (sfNear + 0.05f * end);
+ v3f center_scene = cam_pos_scene + look * 0.35 * (sfFar - sfNear);
+ v3f center_world = cam_pos_world + look * 0.35 * (sfFar - sfNear);
// Create a vector to the frustum far corner
const v3f &viewUp = cam->getCameraNode()->getUpVector();
v3f viewRight = look.crossProduct(viewUp);
- v3f farCorner = look + viewRight * tanFovX + viewUp * tanFovY;
+ v3f farCorner = (look + viewRight * tanFovX + viewUp * tanFovY).normalize();
// Compute the frustumBoundingSphere radius
- v3f boundVec = (camPos + farCorner * sfFar) - newCenter;
- radius = boundVec.getLength() * 2.0f;
- // boundVec.getLength();
- float vvolume = radius * 2.0f;
-
- v3f frustumCenter = newCenter;
- // probar radius multipliacdor en funcion del I, a menor I mas multiplicador
- v3f eye_displacement = direction * vvolume;
+ v3f boundVec = (cam_pos_scene + farCorner * sfFar) - center_scene;
+ float radius = boundVec.getLength();
+ float length = radius * 3.0f;
+ v3f eye_displacement = quantizeDirection(direction, M_PI / 2880 /*15 seconds*/) * length;
// we must compute the viewmat with the position - the camera offset
// but the future_frustum position must be the actual world position
- v3f eye = frustumCenter - eye_displacement;
- future_frustum.position = world_center - eye_displacement;
- future_frustum.length = vvolume;
- future_frustum.ViewMat.buildCameraLookAtMatrixLH(eye, frustumCenter, v3f(0.0f, 1.0f, 0.0f));
- future_frustum.ProjOrthMat.buildProjectionMatrixOrthoLH(future_frustum.length,
- future_frustum.length, -future_frustum.length,
- future_frustum.length,false);
+ v3f eye = center_scene - eye_displacement;
+ future_frustum.player = cam_pos_scene;
+ future_frustum.position = center_world - eye_displacement;
+ future_frustum.length = length;
+ future_frustum.radius = radius;
+ future_frustum.ViewMat.buildCameraLookAtMatrixLH(eye, center_scene, v3f(0.0f, 1.0f, 0.0f));
+ future_frustum.ProjOrthMat.buildProjectionMatrixOrthoLH(radius, radius,
+ 0.0f, length, false);
future_frustum.camera_offset = cam->getOffset();
}
@@ -95,6 +108,8 @@ void DirectionalLight::update_frustum(const Camera *cam, Client *client, bool fo
float zNear = cam->getCameraNode()->getNearValue();
float zFar = getMaxFarValue();
+ if (!client->getEnv().getClientMap().getControl().range_all)
+ zFar = MYMIN(zFar, client->getEnv().getClientMap().getControl().wanted_range * BS);
///////////////////////////////////
// update splits near and fars
@@ -105,7 +120,7 @@ void DirectionalLight::update_frustum(const Camera *cam, Client *client, bool fo
createSplitMatrices(cam);
// get the draw list for shadows
client->getEnv().getClientMap().updateDrawListShadow(
- getPosition(), getDirection(), future_frustum.length);
+ getPosition(), getDirection(), future_frustum.radius, future_frustum.length);
should_update_map_shadow = true;
dirty = true;
@@ -115,6 +130,7 @@ void DirectionalLight::update_frustum(const Camera *cam, Client *client, bool fo
v3f rotated_offset;
shadow_frustum.ViewMat.rotateVect(rotated_offset, intToFloat(cam_offset - shadow_frustum.camera_offset, BS));
shadow_frustum.ViewMat.setTranslation(shadow_frustum.ViewMat.getTranslation() + rotated_offset);
+ shadow_frustum.player += intToFloat(shadow_frustum.camera_offset - cam->getOffset(), BS);
shadow_frustum.camera_offset = cam_offset;
}
}
@@ -139,6 +155,16 @@ v3f DirectionalLight::getPosition() const
return shadow_frustum.position;
}
+v3f DirectionalLight::getPlayerPos() const
+{
+ return shadow_frustum.player;
+}
+
+v3f DirectionalLight::getFuturePlayerPos() const
+{
+ return future_frustum.player;
+}
+
const m4f &DirectionalLight::getViewMatrix() const
{
return shadow_frustum.ViewMat;
diff --git a/src/client/shadows/dynamicshadows.h b/src/client/shadows/dynamicshadows.h
index d8be66be8..6e9d96b15 100644
--- a/src/client/shadows/dynamicshadows.h
+++ b/src/client/shadows/dynamicshadows.h
@@ -22,18 +22,21 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "irrlichttypes_bloated.h"
#include <matrix4.h>
#include "util/basic_macros.h"
+#include "constants.h"
class Camera;
class Client;
struct shadowFrustum
{
- float zNear{0.0f};
- float zFar{0.0f};
- float length{0.0f};
+ f32 zNear{0.0f};
+ f32 zFar{0.0f};
+ f32 length{0.0f};
+ f32 radius{0.0f};
core::matrix4 ProjOrthMat;
core::matrix4 ViewMat;
v3f position;
+ v3f player;
v3s16 camera_offset;
};
@@ -56,6 +59,8 @@ public:
return direction;
};
v3f getPosition() const;
+ v3f getPlayerPos() const;
+ v3f getFuturePlayerPos() const;
/// Gets the light's matrices.
const core::matrix4 &getViewMatrix() const;
@@ -64,10 +69,16 @@ public:
const core::matrix4 &getFutureProjectionMatrix() const;
core::matrix4 getViewProjMatrix();
- /// Gets the light's far value.
+ /// Gets the light's maximum far value, i.e. the shadow boundary
f32 getMaxFarValue() const
{
- return farPlane;
+ return farPlane * BS;
+ }
+
+ /// Gets the current far value of the light
+ f32 getFarValue() const
+ {
+ return shadow_frustum.zFar;
}
diff --git a/src/client/shadows/dynamicshadowsrender.cpp b/src/client/shadows/dynamicshadowsrender.cpp
index a913a9290..944deb801 100644
--- a/src/client/shadows/dynamicshadowsrender.cpp
+++ b/src/client/shadows/dynamicshadowsrender.cpp
@@ -18,6 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
*/
#include <cstring>
+#include <cmath>
#include "client/shadows/dynamicshadowsrender.h"
#include "client/shadows/shadowsScreenQuad.h"
#include "client/shadows/shadowsshadercallbacks.h"
@@ -30,12 +31,19 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "profiler.h"
ShadowRenderer::ShadowRenderer(IrrlichtDevice *device, Client *client) :
- m_device(device), m_smgr(device->getSceneManager()),
- m_driver(device->getVideoDriver()), m_client(client), m_current_frame(0)
+ m_smgr(device->getSceneManager()), m_driver(device->getVideoDriver()),
+ m_client(client), m_current_frame(0),
+ m_perspective_bias_xy(0.8), m_perspective_bias_z(0.5)
{
+ (void) m_client;
+
+ m_shadows_supported = true; // assume shadows supported. We will check actual support in initialize
m_shadows_enabled = true;
- m_shadow_strength = g_settings->getFloat("shadow_strength");
+ m_shadow_strength_gamma = g_settings->getFloat("shadow_strength_gamma");
+ if (std::isnan(m_shadow_strength_gamma))
+ m_shadow_strength_gamma = 1.0f;
+ m_shadow_strength_gamma = core::clamp(m_shadow_strength_gamma, 0.1f, 10.0f);
m_shadow_map_max_distance = g_settings->getFloat("shadow_map_max_distance");
@@ -49,27 +57,58 @@ ShadowRenderer::ShadowRenderer(IrrlichtDevice *device, Client *client) :
ShadowRenderer::~ShadowRenderer()
{
+ // call to disable releases dynamically allocated resources
+ disable();
+
if (m_shadow_depth_cb)
delete m_shadow_depth_cb;
+ if (m_shadow_depth_entity_cb)
+ delete m_shadow_depth_entity_cb;
+ if (m_shadow_depth_trans_cb)
+ delete m_shadow_depth_trans_cb;
if (m_shadow_mix_cb)
delete m_shadow_mix_cb;
m_shadow_node_array.clear();
m_light_list.clear();
+}
- if (shadowMapTextureDynamicObjects)
+void ShadowRenderer::disable()
+{
+ m_shadows_enabled = false;
+ if (shadowMapTextureFinal) {
+ m_driver->setRenderTarget(shadowMapTextureFinal, true, true,
+ video::SColor(255, 255, 255, 255));
+ m_driver->setRenderTarget(0, false, false);
+ }
+
+ if (shadowMapTextureDynamicObjects) {
m_driver->removeTexture(shadowMapTextureDynamicObjects);
+ shadowMapTextureDynamicObjects = nullptr;
+ }
- if (shadowMapTextureFinal)
+ if (shadowMapTextureFinal) {
m_driver->removeTexture(shadowMapTextureFinal);
+ shadowMapTextureFinal = nullptr;
+ }
- if (shadowMapTextureColors)
+ if (shadowMapTextureColors) {
m_driver->removeTexture(shadowMapTextureColors);
+ shadowMapTextureColors = nullptr;
+ }
- if (shadowMapClientMap)
+ if (shadowMapClientMap) {
m_driver->removeTexture(shadowMapClientMap);
+ shadowMapClientMap = nullptr;
+ }
- if (shadowMapClientMapFuture)
+ if (shadowMapClientMapFuture) {
m_driver->removeTexture(shadowMapClientMapFuture);
+ shadowMapClientMapFuture = nullptr;
+ }
+
+ for (auto node : m_shadow_node_array)
+ if (node.shadowMode & E_SHADOW_MODE::ESM_RECEIVE)
+ node.node->setMaterialTexture(TEXTURE_LAYER_SHADOW, nullptr);
}
void ShadowRenderer::initialize()
@@ -77,10 +116,10 @@ void ShadowRenderer::initialize()
auto *gpu = m_driver->getGPUProgrammingServices();
// we need glsl
- if (m_shadows_enabled && gpu && m_driver->queryFeature(video::EVDF_ARB_GLSL)) {
+ if (m_shadows_supported && gpu && m_driver->queryFeature(video::EVDF_ARB_GLSL)) {
createShaders();
} else {
- m_shadows_enabled = false;
+ m_shadows_supported = false;
warningstream << "Shadows: GLSL Shader not supported on this system."
<< std::endl;
@@ -94,6 +133,8 @@ void ShadowRenderer::initialize()
m_texture_format_color = m_shadow_map_texture_32bit
? video::ECOLOR_FORMAT::ECF_G32R32F
: video::ECOLOR_FORMAT::ECF_G16R16F;
+
+ m_shadows_enabled &= m_shadows_supported;
}
@@ -118,24 +159,36 @@ size_t ShadowRenderer::getDirectionalLightCount() const
f32 ShadowRenderer::getMaxShadowFar() const
{
if (!m_light_list.empty()) {
- float wanted_range = m_client->getEnv().getClientMap().getWantedRange();
-
- float zMax = m_light_list[0].getMaxFarValue() > wanted_range
- ? wanted_range
- : m_light_list[0].getMaxFarValue();
- return zMax * MAP_BLOCKSIZE;
+ float zMax = m_light_list[0].getFarValue();
+ return zMax;
}
return 0.0f;
}
+void ShadowRenderer::setShadowIntensity(float shadow_intensity)
+{
+ m_shadow_strength = pow(shadow_intensity, 1.0f / m_shadow_strength_gamma);
+ if (m_shadow_strength > 1E-2)
+ enable();
+ else
+ disable();
+}
+
void ShadowRenderer::addNodeToShadowList(
scene::ISceneNode *node, E_SHADOW_MODE shadowMode)
{
- m_shadow_node_array.emplace_back(NodeToApply(node, shadowMode));
+ if (!node)
+ return;
+ m_shadow_node_array.emplace_back(node, shadowMode);
+ if (shadowMode == ESM_RECEIVE || shadowMode == ESM_BOTH)
+ node->setMaterialTexture(TEXTURE_LAYER_SHADOW, shadowMapTextureFinal);
}
void ShadowRenderer::removeNodeFromShadowList(scene::ISceneNode *node)
{
+ if (!node)
+ return;
+ node->setMaterialTexture(TEXTURE_LAYER_SHADOW, nullptr);
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);
@@ -157,6 +210,7 @@ void ShadowRenderer::updateSMTextures()
shadowMapTextureDynamicObjects = getSMTexture(
std::string("shadow_dynamic_") + itos(m_shadow_map_texture_size),
m_texture_format, true);
+ assert(shadowMapTextureDynamicObjects != nullptr);
}
if (!shadowMapClientMap) {
@@ -165,6 +219,7 @@ void ShadowRenderer::updateSMTextures()
std::string("shadow_clientmap_") + itos(m_shadow_map_texture_size),
m_shadow_map_colored ? m_texture_format_color : m_texture_format,
true);
+ assert(shadowMapClientMap != nullptr);
}
if (!shadowMapClientMapFuture && m_map_shadow_update_frames > 1) {
@@ -172,6 +227,7 @@ void ShadowRenderer::updateSMTextures()
std::string("shadow_clientmap_bb_") + itos(m_shadow_map_texture_size),
m_shadow_map_colored ? m_texture_format_color : m_texture_format,
true);
+ assert(shadowMapClientMapFuture != nullptr);
}
if (m_shadow_map_colored && !shadowMapTextureColors) {
@@ -179,6 +235,7 @@ void ShadowRenderer::updateSMTextures()
std::string("shadow_colored_") + itos(m_shadow_map_texture_size),
m_shadow_map_colored ? m_texture_format_color : m_texture_format,
true);
+ assert(shadowMapTextureColors != nullptr);
}
// The merge all shadowmaps texture
@@ -198,6 +255,11 @@ void ShadowRenderer::updateSMTextures()
shadowMapTextureFinal = getSMTexture(
std::string("shadowmap_final_") + itos(m_shadow_map_texture_size),
frt, true);
+ assert(shadowMapTextureFinal != nullptr);
+
+ for (auto &node : m_shadow_node_array)
+ if (node.shadowMode == ESM_RECEIVE || node.shadowMode == ESM_BOTH)
+ node.node->setMaterialTexture(TEXTURE_LAYER_SHADOW, shadowMapTextureFinal);
}
if (!m_shadow_node_array.empty() && !m_light_list.empty()) {
@@ -205,7 +267,7 @@ void ShadowRenderer::updateSMTextures()
// detect if SM should be regenerated
for (DirectionalLight &light : m_light_list) {
- if (light.should_update_map_shadow) {
+ if (light.should_update_map_shadow || m_force_update_shadow_map) {
light.should_update_map_shadow = false;
m_current_frame = 0;
reset_sm_texture = true;
@@ -219,23 +281,29 @@ void ShadowRenderer::updateSMTextures()
// Update SM incrementally:
for (DirectionalLight &light : m_light_list) {
// Static shader values.
- m_shadow_depth_cb->MapRes = (f32)m_shadow_map_texture_size;
- m_shadow_depth_cb->MaxFar = (f32)m_shadow_map_max_distance * BS;
-
+ for (auto cb : {m_shadow_depth_cb, m_shadow_depth_entity_cb, m_shadow_depth_trans_cb})
+ if (cb) {
+ cb->MapRes = (f32)m_shadow_map_texture_size;
+ cb->MaxFar = (f32)m_shadow_map_max_distance * BS;
+ cb->PerspectiveBiasXY = getPerspectiveBiasXY();
+ cb->PerspectiveBiasZ = getPerspectiveBiasZ();
+ cb->CameraPos = light.getFuturePlayerPos();
+ }
+
// set the Render Target
// right now we can only render in usual RTT, not
// Depth texture is available in irrlicth maybe we
// should put some gl* fn here
- if (m_current_frame < m_map_shadow_update_frames) {
+ if (m_current_frame < m_map_shadow_update_frames || m_force_update_shadow_map) {
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_current_frame == m_map_shadow_update_frames - 1 || m_force_update_shadow_map) {
if (m_shadow_map_colored) {
m_driver->setRenderTarget(0, false, false);
m_driver->setRenderTarget(shadowMapTextureColors,
@@ -255,7 +323,7 @@ void ShadowRenderer::updateSMTextures()
++m_current_frame;
// pass finished, swap textures and commit light changes
- if (m_current_frame == m_map_shadow_update_frames) {
+ if (m_current_frame == m_map_shadow_update_frames || m_force_update_shadow_map) {
if (shadowMapClientMapFuture != nullptr)
std::swap(shadowMapClientMapFuture, shadowMapClientMap);
@@ -263,6 +331,7 @@ void ShadowRenderer::updateSMTextures()
for (DirectionalLight &light : m_light_list)
light.commitFrustum();
}
+ m_force_update_shadow_map = false;
}
}
@@ -274,12 +343,18 @@ void ShadowRenderer::update(video::ITexture *outputTarget)
updateSMTextures();
+ if (shadowMapTextureFinal == nullptr) {
+ return;
+ }
+
+
if (!m_shadow_node_array.empty() && !m_light_list.empty()) {
for (DirectionalLight &light : m_light_list) {
- // Static shader values.
- m_shadow_depth_cb->MapRes = (f32)m_shadow_map_texture_size;
- m_shadow_depth_cb->MaxFar = (f32)m_shadow_map_max_distance * BS;
+ // Static shader values for entities are set in updateSMTextures
+ // SM texture for entities is not updated incrementally and
+ // must by updated using current player position.
+ m_shadow_depth_entity_cb->CameraPos = light.getPlayerPos();
// render shadows for the n0n-map objects.
m_driver->setRenderTarget(shadowMapTextureDynamicObjects, true,
@@ -311,19 +386,23 @@ void ShadowRenderer::drawDebug()
/* this code just shows shadows textures in screen and in ONLY for debugging*/
#if 0
// this is debug, ignore for now.
- m_driver->draw2DImage(shadowMapTextureFinal,
- core::rect<s32>(0, 50, 128, 128 + 50),
- core::rect<s32>({0, 0}, shadowMapTextureFinal->getSize()));
+ if (shadowMapTextureFinal)
+ m_driver->draw2DImage(shadowMapTextureFinal,
+ core::rect<s32>(0, 50, 128, 128 + 50),
+ core::rect<s32>({0, 0}, shadowMapTextureFinal->getSize()));
- m_driver->draw2DImage(shadowMapClientMap,
- core::rect<s32>(0, 50 + 128, 128, 128 + 50 + 128),
- core::rect<s32>({0, 0}, shadowMapTextureFinal->getSize()));
- m_driver->draw2DImage(shadowMapTextureDynamicObjects,
- core::rect<s32>(0, 128 + 50 + 128, 128,
- 128 + 50 + 128 + 128),
- core::rect<s32>({0, 0}, shadowMapTextureDynamicObjects->getSize()));
+ if (shadowMapClientMap)
+ m_driver->draw2DImage(shadowMapClientMap,
+ core::rect<s32>(0, 50 + 128, 128, 128 + 50 + 128),
+ core::rect<s32>({0, 0}, shadowMapTextureFinal->getSize()));
+
+ if (shadowMapTextureDynamicObjects)
+ m_driver->draw2DImage(shadowMapTextureDynamicObjects,
+ core::rect<s32>(0, 128 + 50 + 128, 128,
+ 128 + 50 + 128 + 128),
+ core::rect<s32>({0, 0}, shadowMapTextureDynamicObjects->getSize()));
- if (m_shadow_map_colored) {
+ if (m_shadow_map_colored && shadowMapTextureColors) {
m_driver->draw2DImage(shadowMapTextureColors,
core::rect<s32>(128,128 + 50 + 128 + 128,
@@ -368,10 +447,6 @@ void ShadowRenderer::renderShadowMap(video::ITexture *target,
material.BackfaceCulling = false;
material.FrontfaceCulling = true;
- material.PolygonOffsetFactor = 4.0f;
- material.PolygonOffsetDirection = video::EPO_BACK;
- //material.PolygonOffsetDepthBias = 1.0f/4.0f;
- //material.PolygonOffsetSlopeScale = -1.f;
if (m_shadow_map_colored && pass != scene::ESNRP_SOLID) {
material.MaterialType = (video::E_MATERIAL_TYPE) depth_shader_trans;
@@ -381,13 +456,13 @@ void ShadowRenderer::renderShadowMap(video::ITexture *target,
material.BlendOperation = video::EBO_MIN;
}
- // FIXME: I don't think this is needed here
- map_node->OnAnimate(m_device->getTimer()->getTime());
-
m_driver->setTransform(video::ETS_WORLD,
map_node->getAbsoluteTransformation());
- map_node->renderMapShadows(m_driver, material, pass, m_current_frame, m_map_shadow_update_frames);
+ int frame = m_force_update_shadow_map ? 0 : m_current_frame;
+ int total_frames = m_force_update_shadow_map ? 1 : m_map_shadow_update_frames;
+
+ map_node->renderMapShadows(m_driver, material, pass, frame, total_frames);
break;
}
}
@@ -429,10 +504,6 @@ void ShadowRenderer::renderShadowObjects(
current_mat.BackfaceCulling = true;
current_mat.FrontfaceCulling = false;
- current_mat.PolygonOffsetFactor = 1.0f/2048.0f;
- current_mat.PolygonOffsetDirection = video::EPO_BACK;
- //current_mat.PolygonOffsetDepthBias = 1.0 * 2.8e-6;
- //current_mat.PolygonOffsetSlopeScale = -1.f;
}
m_driver->setTransform(video::ETS_WORLD,
@@ -473,13 +544,13 @@ void ShadowRenderer::createShaders()
if (depth_shader == -1) {
std::string depth_shader_vs = getShaderPath("shadow_shaders", "pass1_vertex.glsl");
if (depth_shader_vs.empty()) {
- m_shadows_enabled = false;
+ m_shadows_supported = false;
errorstream << "Error shadow mapping vs shader not found." << std::endl;
return;
}
std::string depth_shader_fs = getShaderPath("shadow_shaders", "pass1_fragment.glsl");
if (depth_shader_fs.empty()) {
- m_shadows_enabled = false;
+ m_shadows_supported = false;
errorstream << "Error shadow mapping fs shader not found." << std::endl;
return;
}
@@ -494,7 +565,9 @@ void ShadowRenderer::createShaders()
if (depth_shader == -1) {
// upsi, something went wrong loading shader.
delete m_shadow_depth_cb;
+ m_shadow_depth_cb = nullptr;
m_shadows_enabled = false;
+ m_shadows_supported = false;
errorstream << "Error compiling shadow mapping shader." << std::endl;
return;
}
@@ -510,26 +583,30 @@ void ShadowRenderer::createShaders()
if (depth_shader_entities == -1) {
std::string depth_shader_vs = getShaderPath("shadow_shaders", "pass1_vertex.glsl");
if (depth_shader_vs.empty()) {
- m_shadows_enabled = false;
+ m_shadows_supported = false;
errorstream << "Error shadow mapping vs shader not found." << std::endl;
return;
}
std::string depth_shader_fs = getShaderPath("shadow_shaders", "pass1_fragment.glsl");
if (depth_shader_fs.empty()) {
- m_shadows_enabled = false;
+ m_shadows_supported = false;
errorstream << "Error shadow mapping fs shader not found." << std::endl;
return;
}
+ m_shadow_depth_entity_cb = new ShadowDepthShaderCB();
depth_shader_entities = gpu->addHighLevelShaderMaterial(
readShaderFile(depth_shader_vs).c_str(), "vertexMain",
video::EVST_VS_1_1,
readShaderFile(depth_shader_fs).c_str(), "pixelMain",
- video::EPST_PS_1_2, m_shadow_depth_cb);
+ video::EPST_PS_1_2, m_shadow_depth_entity_cb);
if (depth_shader_entities == -1) {
// upsi, something went wrong loading shader.
+ delete m_shadow_depth_entity_cb;
+ m_shadow_depth_entity_cb = nullptr;
m_shadows_enabled = false;
+ m_shadows_supported = false;
errorstream << "Error compiling shadow mapping shader (dynamic)." << std::endl;
return;
}
@@ -543,14 +620,14 @@ void ShadowRenderer::createShaders()
if (mixcsm_shader == -1) {
std::string depth_shader_vs = getShaderPath("shadow_shaders", "pass2_vertex.glsl");
if (depth_shader_vs.empty()) {
- m_shadows_enabled = false;
+ m_shadows_supported = false;
errorstream << "Error cascade shadow mapping fs shader not found." << std::endl;
return;
}
std::string depth_shader_fs = getShaderPath("shadow_shaders", "pass2_fragment.glsl");
if (depth_shader_fs.empty()) {
- m_shadows_enabled = false;
+ m_shadows_supported = false;
errorstream << "Error cascade shadow mapping fs shader not found." << std::endl;
return;
}
@@ -569,7 +646,7 @@ void ShadowRenderer::createShaders()
// upsi, something went wrong loading shader.
delete m_shadow_mix_cb;
delete m_screen_quad;
- m_shadows_enabled = false;
+ m_shadows_supported = false;
errorstream << "Error compiling cascade shadow mapping shader." << std::endl;
return;
}
@@ -583,13 +660,13 @@ void ShadowRenderer::createShaders()
if (m_shadow_map_colored && depth_shader_trans == -1) {
std::string depth_shader_vs = getShaderPath("shadow_shaders", "pass1_trans_vertex.glsl");
if (depth_shader_vs.empty()) {
- m_shadows_enabled = false;
+ m_shadows_supported = false;
errorstream << "Error shadow mapping vs shader not found." << std::endl;
return;
}
std::string depth_shader_fs = getShaderPath("shadow_shaders", "pass1_trans_fragment.glsl");
if (depth_shader_fs.empty()) {
- m_shadows_enabled = false;
+ m_shadows_supported = false;
errorstream << "Error shadow mapping fs shader not found." << std::endl;
return;
}
@@ -604,8 +681,9 @@ void ShadowRenderer::createShaders()
if (depth_shader_trans == -1) {
// upsi, something went wrong loading shader.
delete m_shadow_depth_trans_cb;
+ m_shadow_depth_trans_cb = nullptr;
m_shadow_map_colored = false;
- m_shadows_enabled = false;
+ m_shadows_supported = false;
errorstream << "Error compiling colored shadow mapping shader." << std::endl;
return;
}
@@ -622,6 +700,7 @@ std::string ShadowRenderer::readShaderFile(const std::string &path)
std::string prefix;
if (m_shadow_map_colored)
prefix.append("#define COLORED_SHADOWS 1\n");
+ prefix.append("#line 0\n");
std::string content;
fs::ReadFile(path, content);
diff --git a/src/client/shadows/dynamicshadowsrender.h b/src/client/shadows/dynamicshadowsrender.h
index e4b3c3e22..bd27f6f20 100644
--- a/src/client/shadows/dynamicshadowsrender.h
+++ b/src/client/shadows/dynamicshadowsrender.h
@@ -51,6 +51,8 @@ struct NodeToApply
class ShadowRenderer
{
public:
+ static const int TEXTURE_LAYER_SHADOW = 3;
+
ShadowRenderer(IrrlichtDevice *device, Client *client);
~ShadowRenderer();
@@ -74,6 +76,7 @@ public:
void removeNodeFromShadowList(scene::ISceneNode *node);
void update(video::ITexture *outputTarget = nullptr);
+ void setForceUpdateShadowMap() { m_force_update_shadow_map = true; }
void drawDebug();
video::ITexture *get_texture()
@@ -82,13 +85,17 @@ public:
}
- bool is_active() const { return m_shadows_enabled; }
+ bool is_active() const { return m_shadows_enabled && shadowMapTextureFinal != nullptr; }
void setTimeOfDay(float isDay) { m_time_day = isDay; };
+ void setShadowIntensity(float shadow_intensity);
s32 getShadowSamples() const { return m_shadow_samples; }
- float getShadowStrength() const { return m_shadow_strength; }
+ float getShadowStrength() const { return m_shadows_enabled ? m_shadow_strength : 0.0f; }
float getTimeOfDay() const { return m_time_day; }
+ f32 getPerspectiveBiasXY() { return m_perspective_bias_xy; }
+ f32 getPerspectiveBiasZ() { return m_perspective_bias_z; }
+
private:
video::ITexture *getSMTexture(const std::string &shadow_map_name,
video::ECOLOR_FORMAT texture_format,
@@ -101,8 +108,10 @@ private:
void mixShadowsQuad();
void updateSMTextures();
+ void disable();
+ void enable() { m_shadows_enabled = m_shadows_supported; }
+
// a bunch of variables
- IrrlichtDevice *m_device{nullptr};
scene::ISceneManager *m_smgr{nullptr};
video::IVideoDriver *m_driver{nullptr};
Client *m_client{nullptr};
@@ -116,15 +125,20 @@ private:
std::vector<NodeToApply> m_shadow_node_array;
float m_shadow_strength;
+ float m_shadow_strength_gamma;
float m_shadow_map_max_distance;
float m_shadow_map_texture_size;
float m_time_day{0.0f};
int m_shadow_samples;
bool m_shadow_map_texture_32bit;
bool m_shadows_enabled;
+ bool m_shadows_supported;
bool m_shadow_map_colored;
+ bool m_force_update_shadow_map;
u8 m_map_shadow_update_frames; /* Use this number of frames to update map shaodw */
u8 m_current_frame{0}; /* Current frame */
+ f32 m_perspective_bias_xy;
+ f32 m_perspective_bias_z;
video::ECOLOR_FORMAT m_texture_format{video::ECOLOR_FORMAT::ECF_R16F};
video::ECOLOR_FORMAT m_texture_format_color{video::ECOLOR_FORMAT::ECF_R16G16};
@@ -140,6 +154,7 @@ private:
s32 mixcsm_shader{-1};
ShadowDepthShaderCB *m_shadow_depth_cb{nullptr};
+ ShadowDepthShaderCB *m_shadow_depth_entity_cb{nullptr};
ShadowDepthShaderCB *m_shadow_depth_trans_cb{nullptr};
shadowScreenQuad *m_screen_quad{nullptr};
diff --git a/src/client/shadows/shadowsshadercallbacks.cpp b/src/client/shadows/shadowsshadercallbacks.cpp
index 65a63f49c..b571ea939 100644
--- a/src/client/shadows/shadowsshadercallbacks.cpp
+++ b/src/client/shadows/shadowsshadercallbacks.cpp
@@ -26,6 +26,10 @@ void ShadowDepthShaderCB::OnSetConstants(
core::matrix4 lightMVP = driver->getTransform(video::ETS_PROJECTION);
lightMVP *= driver->getTransform(video::ETS_VIEW);
+
+ f32 cam_pos[4];
+ lightMVP.transformVect(cam_pos, CameraPos);
+
lightMVP *= driver->getTransform(video::ETS_WORLD);
m_light_mvp_setting.set(lightMVP.pointer(), services);
@@ -33,4 +37,12 @@ void ShadowDepthShaderCB::OnSetConstants(
m_max_far_setting.set(&MaxFar, services);
s32 TextureId = 0;
m_color_map_sampler_setting.set(&TextureId, services);
+ f32 bias0 = PerspectiveBiasXY;
+ m_perspective_bias0.set(&bias0, services);
+ f32 bias1 = 1.0f - bias0 + 1e-5f;
+ m_perspective_bias1.set(&bias1, services);
+ f32 zbias = PerspectiveBiasZ;
+ m_perspective_zbias.set(&zbias, services);
+
+ m_cam_pos_setting.set(cam_pos, services);
}
diff --git a/src/client/shadows/shadowsshadercallbacks.h b/src/client/shadows/shadowsshadercallbacks.h
index 3549567c3..87833c0ec 100644
--- a/src/client/shadows/shadowsshadercallbacks.h
+++ b/src/client/shadows/shadowsshadercallbacks.h
@@ -30,7 +30,11 @@ public:
m_light_mvp_setting("LightMVP"),
m_map_resolution_setting("MapResolution"),
m_max_far_setting("MaxFar"),
- m_color_map_sampler_setting("ColorMapSampler")
+ m_color_map_sampler_setting("ColorMapSampler"),
+ m_perspective_bias0("xyPerspectiveBias0"),
+ m_perspective_bias1("xyPerspectiveBias1"),
+ m_perspective_zbias("zPerspectiveBias"),
+ m_cam_pos_setting("CameraPos")
{}
void OnSetMaterial(const video::SMaterial &material) override {}
@@ -39,10 +43,16 @@ public:
s32 userData) override;
f32 MaxFar{2048.0f}, MapRes{1024.0f};
+ f32 PerspectiveBiasXY {0.9f}, PerspectiveBiasZ {0.5f};
+ v3f CameraPos;
private:
CachedVertexShaderSetting<f32, 16> m_light_mvp_setting;
CachedVertexShaderSetting<f32> m_map_resolution_setting;
CachedVertexShaderSetting<f32> m_max_far_setting;
CachedPixelShaderSetting<s32> m_color_map_sampler_setting;
+ CachedVertexShaderSetting<f32> m_perspective_bias0;
+ CachedVertexShaderSetting<f32> m_perspective_bias1;
+ CachedVertexShaderSetting<f32> m_perspective_zbias;
+ CachedVertexShaderSetting<f32, 4> m_cam_pos_setting;
};
diff --git a/src/client/sky.cpp b/src/client/sky.cpp
index 7fe90c6cd..ca56889b4 100644
--- a/src/client/sky.cpp
+++ b/src/client/sky.cpp
@@ -103,7 +103,7 @@ Sky::Sky(s32 id, RenderingEngine *rendering_engine, ITextureSource *tsrc, IShade
m_directional_colored_fog = g_settings->getBool("directional_colored_fog");
- if (false && g_settings->getBool("enable_dynamic_shadows")) {
+ if (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);
}
@@ -660,9 +660,12 @@ void Sky::draw_stars(video::IVideoDriver * driver, float wicked_time_of_day)
// to time 4000.
float tod = wicked_time_of_day < 0.5f ? wicked_time_of_day : (1.0f - wicked_time_of_day);
- float starbrightness = (0.25f - fabsf(tod)) * 20.0f;
+ float day_opacity = clamp(m_star_params.day_opacity, 0.0f, 1.0f);
+ float starbrightness = (0.25f - fabs(tod)) * 20.0f;
+ float alpha = clamp(starbrightness, day_opacity, 1.0f);
+
m_star_color = m_star_params.starcolor;
- m_star_color.a *= clamp(starbrightness, 0.0f, 1.0f);
+ m_star_color.a *= alpha;
if (m_star_color.a <= 0.0f) // Stars are only drawn when not fully transparent
return;
m_materials[0].DiffuseColor = m_materials[0].EmissiveColor = m_star_color.toSColor();
diff --git a/src/client/sky.h b/src/client/sky.h
index 3dc057b70..cbb1186aa 100644
--- a/src/client/sky.h
+++ b/src/client/sky.h
@@ -65,6 +65,7 @@ public:
}
void setSunVisible(bool sun_visible) { m_sun_params.visible = sun_visible; }
+ bool getSunVisible() const { return m_sun_params.visible; }
void setSunTexture(const std::string &sun_texture,
const std::string &sun_tonemap, ITextureSource *tsrc);
void setSunScale(f32 sun_scale) { m_sun_params.scale = sun_scale; }
@@ -72,6 +73,7 @@ public:
void setSunriseTexture(const std::string &sunglow_texture, ITextureSource* tsrc);
void setMoonVisible(bool moon_visible) { m_moon_params.visible = moon_visible; }
+ bool getMoonVisible() const { return m_moon_params.visible; }
void setMoonTexture(const std::string &moon_texture,
const std::string &moon_tonemap, ITextureSource *tsrc);
void setMoonScale(f32 moon_scale) { m_moon_params.scale = moon_scale; }
@@ -80,6 +82,7 @@ public:
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(); }
+ void setStarDayOpacity(f32 day_opacity) { m_star_params.day_opacity = day_opacity; }
bool getCloudsVisible() const { return m_clouds_visible && m_clouds_enabled; }
const video::SColorf &getCloudColor() const { return m_cloudcolor_f; }
diff --git a/src/client/sound.h b/src/client/sound.h
index c1d3c0cfa..213d20831 100644
--- a/src/client/sound.h
+++ b/src/client/sound.h
@@ -51,10 +51,8 @@ public:
// playSound functions return -1 on failure, otherwise a handle to the
// sound. If name=="", call should be ignored without error.
- virtual int playSound(const std::string &name, bool loop, float volume,
- float fade = 0.0f, float pitch = 1.0f) = 0;
- virtual int playSoundAt(const std::string &name, bool loop, float volume, v3f pos,
- float pitch = 1.0f) = 0;
+ virtual int playSound(const SimpleSoundSpec &spec) = 0;
+ virtual int playSoundAt(const SimpleSoundSpec &spec, const v3f &pos) = 0;
virtual void stopSound(int sound) = 0;
virtual bool soundExists(int sound) = 0;
virtual void updateSoundPosition(int sound, v3f pos) = 0;
@@ -62,15 +60,6 @@ public:
virtual float getSoundGain(int id) = 0;
virtual void step(float dtime) = 0;
virtual void fadeSound(int sound, float step, float gain) = 0;
-
- int playSound(const SimpleSoundSpec &spec, bool loop)
- {
- return playSound(spec.name, loop, spec.gain, spec.fade, spec.pitch);
- }
- int playSoundAt(const SimpleSoundSpec &spec, bool loop, const v3f &pos)
- {
- return playSoundAt(spec.name, loop, spec.gain, pos, spec.pitch);
- }
};
class DummySoundManager : public ISoundManager
@@ -88,16 +77,9 @@ public:
{
}
void setListenerGain(float gain) {}
- int playSound(const std::string &name, bool loop, float volume, float fade,
- float pitch)
- {
- return 0;
- }
- int playSoundAt(const std::string &name, bool loop, float volume, v3f pos,
- float pitch)
- {
- return 0;
- }
+
+ int playSound(const SimpleSoundSpec &spec) { return -1; }
+ int playSoundAt(const SimpleSoundSpec &spec, const v3f &pos) { return -1; }
void stopSound(int sound) {}
bool soundExists(int sound) { return false; }
void updateSoundPosition(int sound, v3f pos) {}
diff --git a/src/client/sound_openal.cpp b/src/client/sound_openal.cpp
index 0eda8842b..b015aba25 100644
--- a/src/client/sound_openal.cpp
+++ b/src/client/sound_openal.cpp
@@ -322,7 +322,7 @@ private:
OnDemandSoundFetcher *m_fetcher;
ALCdevice *m_device;
ALCcontext *m_context;
- int m_next_id;
+ u16 m_last_used_id = 0; // only access within getFreeId() !
std::unordered_map<std::string, std::vector<SoundBuffer*>> m_buffers;
std::unordered_map<int, PlayingSound*> m_sounds_playing;
struct FadeState {
@@ -342,8 +342,7 @@ public:
OpenALSoundManager(SoundManagerSingleton *smg, OnDemandSoundFetcher *fetcher):
m_fetcher(fetcher),
m_device(smg->m_device.get()),
- m_context(smg->m_context.get()),
- m_next_id(1)
+ m_context(smg->m_context.get())
{
infostream << "Audio: Initialized: OpenAL " << std::endl;
}
@@ -379,6 +378,22 @@ public:
infostream << "Audio: Deinitialized." << std::endl;
}
+ u16 getFreeId()
+ {
+ u16 startid = m_last_used_id;
+ while (!isFreeId(++m_last_used_id)) {
+ if (m_last_used_id == startid)
+ return 0;
+ }
+
+ return m_last_used_id;
+ }
+
+ inline bool isFreeId(int id) const
+ {
+ return id > 0 && m_sounds_playing.find(id) == m_sounds_playing.end();
+ }
+
void step(float dtime)
{
doFades(dtime);
@@ -437,7 +452,7 @@ public:
<< std::endl;
assert(buf);
PlayingSound *sound = new PlayingSound;
- assert(sound);
+
warn_if_error(alGetError(), "before createPlayingSoundAt");
alGenSources(1, &sound->source_id);
alSourcei(sound->source_id, AL_BUFFER, buf->buffer_id);
@@ -463,28 +478,17 @@ public:
{
assert(buf);
PlayingSound *sound = createPlayingSound(buf, loop, volume, pitch);
- if(!sound)
+ if (!sound)
return -1;
- int id = m_next_id++;
- m_sounds_playing[id] = sound;
- return id;
- }
- int playSoundRawAt(SoundBuffer *buf, bool loop, float volume, const v3f &pos,
- float pitch)
- {
- assert(buf);
- PlayingSound *sound = createPlayingSoundAt(buf, loop, volume, pos, pitch);
- if(!sound)
- return -1;
- int id = m_next_id++;
- m_sounds_playing[id] = sound;
- return id;
+ int handle = getFreeId();
+ m_sounds_playing[handle] = sound;
+ return handle;
}
void deleteSound(int id)
{
- std::unordered_map<int, PlayingSound*>::iterator i = m_sounds_playing.find(id);
+ auto i = m_sounds_playing.find(id);
if(i == m_sounds_playing.end())
return;
PlayingSound *sound = i->second;
@@ -580,39 +584,46 @@ public:
alListenerf(AL_GAIN, gain);
}
- int playSound(const std::string &name, bool loop, float volume, float fade, float pitch)
+ int playSound(const SimpleSoundSpec &spec)
{
maintain();
- if (name.empty())
+ if (spec.name.empty())
return 0;
- SoundBuffer *buf = getFetchBuffer(name);
+ SoundBuffer *buf = getFetchBuffer(spec.name);
if(!buf){
- infostream << "OpenALSoundManager: \"" << name << "\" not found."
+ infostream << "OpenALSoundManager: \"" << spec.name << "\" not found."
<< std::endl;
return -1;
}
+
int handle = -1;
- if (fade > 0) {
- handle = playSoundRaw(buf, loop, 0.0f, pitch);
- fadeSound(handle, fade, volume);
+ if (spec.fade > 0) {
+ handle = playSoundRaw(buf, spec.loop, 0.0f, spec.pitch);
+ fadeSound(handle, spec.fade, spec.gain);
} else {
- handle = playSoundRaw(buf, loop, volume, pitch);
+ handle = playSoundRaw(buf, spec.loop, spec.gain, spec.pitch);
}
return handle;
}
- int playSoundAt(const std::string &name, bool loop, float volume, v3f pos, float pitch)
+ int playSoundAt(const SimpleSoundSpec &spec, const v3f &pos)
{
maintain();
- if (name.empty())
+ if (spec.name.empty())
return 0;
- SoundBuffer *buf = getFetchBuffer(name);
- if(!buf){
- infostream << "OpenALSoundManager: \"" << name << "\" not found."
+ SoundBuffer *buf = getFetchBuffer(spec.name);
+ if (!buf) {
+ infostream << "OpenALSoundManager: \"" << spec.name << "\" not found."
<< std::endl;
return -1;
}
- return playSoundRawAt(buf, loop, volume, pos, pitch);
+
+ PlayingSound *sound = createPlayingSoundAt(buf, spec.loop, spec.gain, pos, spec.pitch);
+ if (!sound)
+ return -1;
+ int handle = getFreeId();
+ m_sounds_playing[handle] = sound;
+ return handle;
}
void stopSound(int sound)
@@ -624,8 +635,9 @@ public:
void fadeSound(int soundid, float step, float gain)
{
// Ignore the command if step isn't valid.
- if (step == 0)
+ if (step == 0 || soundid < 0)
return;
+
float current_gain = getSoundGain(soundid);
step = gain - current_gain > 0 ? abs(step) : -abs(step);
if (m_sounds_fading.find(soundid) != m_sounds_fading.end()) {
diff --git a/src/client/tile.cpp b/src/client/tile.cpp
index aa78c50f0..87d2818b4 100644
--- a/src/client/tile.cpp
+++ b/src/client/tile.cpp
@@ -1619,7 +1619,7 @@ bool TextureSource::generateImagePart(std::string part_of_name,
* textures that don't have the resources to offer high-res alternatives.
*/
const bool filter = m_setting_trilinear_filter || m_setting_bilinear_filter;
- const s32 scaleto = filter ? g_settings->getS32("texture_min_size") : 1;
+ const s32 scaleto = filter ? g_settings->getU16("texture_min_size") : 1;
if (scaleto > 1) {
const core::dimension2d<u32> dim = baseimg->getDimension();
diff --git a/src/client/tile.h b/src/client/tile.h
index fe96cef58..e55a26e56 100644
--- a/src/client/tile.h
+++ b/src/client/tile.h
@@ -260,6 +260,17 @@ struct TileLayer
&& (material_flags & MATERIAL_FLAG_TILEABLE_VERTICAL);
}
+ bool isTransparent() const
+ {
+ switch (material_type) {
+ case TILE_MATERIAL_ALPHA:
+ case TILE_MATERIAL_LIQUID_TRANSPARENT:
+ case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT:
+ return true;
+ }
+ return false;
+ }
+
// Ordered for size, please do not reorder
video::ITexture *texture = nullptr;
@@ -308,7 +319,8 @@ struct TileSpec
for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) {
if (layers[layer] != other.layers[layer])
return false;
- if (!layers[layer].isTileable())
+ // Only non-transparent tiles can be merged into fast faces
+ if (layers[layer].isTransparent() || !layers[layer].isTileable())
return false;
}
return rotation == 0
diff --git a/src/client/wieldmesh.cpp b/src/client/wieldmesh.cpp
index 0a4cb3b86..0a89e2aa2 100644
--- a/src/client/wieldmesh.cpp
+++ b/src/client/wieldmesh.cpp
@@ -386,6 +386,9 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che
m_colors.emplace_back();
// overlay is white, if present
m_colors.emplace_back(true, video::SColor(0xFFFFFFFF));
+ // initialize the color
+ if (!m_lighting)
+ setColor(video::SColor(0xFFFFFFFF));
return;
}
@@ -457,6 +460,10 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che
material.setFlag(video::EMF_BILINEAR_FILTER, m_bilinear_filter);
material.setFlag(video::EMF_TRILINEAR_FILTER, m_trilinear_filter);
}
+
+ // initialize the color
+ if (!m_lighting)
+ setColor(video::SColor(0xFFFFFFFF));
return;
} else {
if (!def.inventory_image.empty()) {
@@ -469,6 +476,10 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che
m_colors.emplace_back();
// overlay is white, if present
m_colors.emplace_back(true, video::SColor(0xFFFFFFFF));
+
+ // initialize the color
+ if (!m_lighting)
+ setColor(video::SColor(0xFFFFFFFF));
return;
}
@@ -515,8 +526,9 @@ void WieldMeshSceneNode::setNodeLightColor(video::SColor color)
material.EmissiveColor = color;
}
}
-
- setColor(color);
+ else {
+ setColor(color);
+ }
}
void WieldMeshSceneNode::render()
@@ -541,9 +553,10 @@ void WieldMeshSceneNode::changeToMesh(scene::IMesh *mesh)
m_meshnode->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, m_lighting);
m_meshnode->setVisible(true);
- // Add mesh to shadow caster
- if (m_shadow)
+ if (m_shadow) {
+ // Add mesh to shadow caster
m_shadow->addNodeToShadowList(m_meshnode);
+ }
}
void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result)