From: Quentin Bazin Date: Wed, 28 Nov 2018 19:01:49 +0000 (+0100) Subject: Move client-specific files to 'src/client' (#7902) X-Git-Tag: 5.0.0~173 X-Git-Url: https://git.librecmc.org/?a=commitdiff_plain;h=5f1cd55;p=oweals%2Fminetest.git Move client-specific files to 'src/client' (#7902) Update Android.mk Remove 'src/client' from include_directories --- diff --git a/.gitignore b/.gitignore index dd06dfb30..d5cb9b6ea 100644 --- a/.gitignore +++ b/.gitignore @@ -56,6 +56,7 @@ debug.txt ## Other files generated by minetest screenshot_*.png +testbm.txt ## Doxygen files doc/Doxyfile @@ -95,6 +96,7 @@ cmake-build-release/ cmake_config.h cmake_config_githash.h CMakeDoxy* +compile_commands.json ## Android build files build/android/src/main/assets diff --git a/build/android/jni/Android.mk b/build/android/jni/Android.mk index e5cf7349c..364278f68 100644 --- a/build/android/jni/Android.mk +++ b/build/android/jni/Android.mk @@ -113,20 +113,20 @@ LOCAL_C_INCLUDES := \ LOCAL_SRC_FILES := \ jni/src/ban.cpp \ - jni/src/camera.cpp \ + jni/src/client/camera.cpp \ jni/src/mapgen/cavegen.cpp \ jni/src/chat.cpp \ - jni/src/client.cpp \ - jni/src/clientenvironment.cpp \ + jni/src/client/client.cpp \ + jni/src/client/clientenvironment.cpp \ jni/src/clientiface.cpp \ - jni/src/clientmap.cpp \ - jni/src/clientmedia.cpp \ - jni/src/clientobject.cpp \ - jni/src/clouds.cpp \ + jni/src/client/clientmap.cpp \ + jni/src/client/clientmedia.cpp \ + jni/src/client/clientobject.cpp \ + jni/src/client/clouds.cpp \ jni/src/collision.cpp \ - jni/src/content_cao.cpp \ - jni/src/content_cso.cpp \ - jni/src/content_mapblock.cpp \ + jni/src/client/content_cao.cpp \ + jni/src/client/content_cso.cpp \ + jni/src/client/content_mapblock.cpp \ jni/src/content_mapnode.cpp \ jni/src/content_nodemeta.cpp \ jni/src/content_sao.cpp \ @@ -147,10 +147,10 @@ LOCAL_SRC_FILES := \ jni/src/emerge.cpp \ jni/src/environment.cpp \ jni/src/face_position_cache.cpp \ - jni/src/filecache.cpp \ + jni/src/client/filecache.cpp \ jni/src/filesys.cpp \ - jni/src/fontengine.cpp \ - jni/src/game.cpp \ + jni/src/client/fontengine.cpp \ + jni/src/client/game.cpp \ jni/src/genericobject.cpp \ jni/src/gettext.cpp \ jni/src/gui/guiChatConsole.cpp \ @@ -162,7 +162,7 @@ LOCAL_SRC_FILES := \ jni/src/gui/guiKeyChangeMenu.cpp \ jni/src/gui/guiPasswordChange.cpp \ jni/src/gui/guiTable.cpp \ - jni/src/guiscalingfilter.cpp \ + jni/src/client/guiscalingfilter.cpp \ jni/src/gui/guiVolumeChange.cpp \ jni/src/gui/intlGUIEditBox.cpp \ jni/src/gui/modalMenu.cpp \ @@ -170,20 +170,20 @@ LOCAL_SRC_FILES := \ jni/src/gui/touchscreengui.cpp \ jni/src/httpfetch.cpp \ jni/src/hud.cpp \ - jni/src/imagefilters.cpp \ + jni/src/client/imagefilters.cpp \ jni/src/inventory.cpp \ jni/src/inventorymanager.cpp \ jni/src/itemdef.cpp \ jni/src/itemstackmetadata.cpp \ - jni/src/keycode.cpp \ + jni/src/client/keycode.cpp \ jni/src/light.cpp \ - jni/src/localplayer.cpp \ + jni/src/client/localplayer.cpp \ jni/src/log.cpp \ jni/src/main.cpp \ jni/src/map.cpp \ jni/src/map_settings_manager.cpp \ jni/src/mapblock.cpp \ - jni/src/mapblock_mesh.cpp \ + jni/src/client/mapblock_mesh.cpp \ jni/src/mapgen/mapgen.cpp \ jni/src/mapgen/mapgen_carpathian.cpp \ jni/src/mapgen/mapgen_flat.cpp \ @@ -195,14 +195,14 @@ LOCAL_SRC_FILES := \ jni/src/mapgen/mapgen_valleys.cpp \ jni/src/mapnode.cpp \ jni/src/mapsector.cpp \ - jni/src/mesh.cpp \ - jni/src/mesh_generator_thread.cpp \ + jni/src/client/mesh.cpp \ + jni/src/client/mesh_generator_thread.cpp \ jni/src/metadata.cpp \ jni/src/mapgen/mg_biome.cpp \ jni/src/mapgen/mg_decoration.cpp \ jni/src/mapgen/mg_ore.cpp \ jni/src/mapgen/mg_schematic.cpp \ - jni/src/minimap.cpp \ + jni/src/client/minimap.cpp \ jni/src/modchannels.cpp \ jni/src/nameidmapping.cpp \ jni/src/nodedef.cpp \ @@ -211,7 +211,7 @@ LOCAL_SRC_FILES := \ jni/src/noise.cpp \ jni/src/objdef.cpp \ jni/src/object_properties.cpp \ - jni/src/particles.cpp \ + jni/src/client/particles.cpp \ jni/src/pathfinder.cpp \ jni/src/player.cpp \ jni/src/porting_android.cpp \ @@ -229,8 +229,8 @@ LOCAL_SRC_FILES := \ jni/src/serverenvironment.cpp \ jni/src/serverlist.cpp \ jni/src/serverobject.cpp \ - jni/src/shader.cpp \ - jni/src/sky.cpp \ + jni/src/client/shader.cpp \ + jni/src/client/sky.cpp \ jni/src/staticobject.cpp \ jni/src/tileanimation.cpp \ jni/src/translation.cpp \ @@ -275,7 +275,7 @@ LOCAL_SRC_FILES := \ jni/src/unittest/test_voxelalgorithms.cpp \ jni/src/unittest/test_voxelmanipulator.cpp \ jni/src/settings.cpp \ - jni/src/wieldmesh.cpp \ + jni/src/client/wieldmesh.cpp \ jni/src/client/meshgen/collector.cpp \ jni/src/client/clientlauncher.cpp \ jni/src/client/gameui.cpp \ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c0832d0c2..8218d586c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -397,12 +397,14 @@ set(common_SRCS genericobject.cpp gettext.cpp httpfetch.cpp + hud.cpp inventory.cpp inventorymanager.cpp itemdef.cpp itemstackmetadata.cpp light.cpp log.cpp + main.cpp map.cpp map_settings_manager.cpp mapblock.cpp @@ -482,34 +484,6 @@ set(client_SRCS ${gui_SRCS} ${client_network_SRCS} ${client_irrlicht_changes_SRCS} - camera.cpp - client.cpp - clientenvironment.cpp - clientmap.cpp - clientmedia.cpp - clientobject.cpp - clouds.cpp - content_cao.cpp - content_cso.cpp - content_mapblock.cpp - convert_json.cpp - filecache.cpp - fontengine.cpp - game.cpp - guiscalingfilter.cpp - hud.cpp - imagefilters.cpp - keycode.cpp - localplayer.cpp - main.cpp - mapblock_mesh.cpp - mesh.cpp - mesh_generator_thread.cpp - minimap.cpp - particles.cpp - shader.cpp - sky.cpp - wieldmesh.cpp ${client_SCRIPT_SRCS} ${UNITTEST_CLIENT_SRCS} ) @@ -518,7 +492,6 @@ list(SORT client_SRCS) # Server sources set(server_SRCS ${common_SRCS} - main.cpp ) list(SORT server_SRCS) @@ -829,10 +802,10 @@ if(BUILD_CLIENT) endif() if(USE_FREETYPE) - install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../fonts" DESTINATION "${SHAREDIR}" + install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../fonts" DESTINATION "${SHAREDIR}" FILES_MATCHING PATTERN "*.ttf" PATTERN "*.txt") else() - install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../fonts" DESTINATION "${SHAREDIR}" + install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../fonts" DESTINATION "${SHAREDIR}" FILES_MATCHING PATTERN "*.png" PATTERN "*.xml") endif() diff --git a/src/camera.cpp b/src/camera.cpp deleted file mode 100644 index 1bbdb56ea..000000000 --- a/src/camera.cpp +++ /dev/null @@ -1,658 +0,0 @@ -/* -Minetest -Copyright (C) 2010-2013 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include "camera.h" -#include "debug.h" -#include "client.h" -#include "map.h" -#include "clientmap.h" // MapDrawControl -#include "player.h" -#include -#include "client/renderingengine.h" -#include "settings.h" -#include "wieldmesh.h" -#include "noise.h" // easeCurve -#include "sound.h" -#include "event.h" -#include "nodedef.h" -#include "util/numeric.h" -#include "constants.h" -#include "fontengine.h" -#include "script/scripting_client.h" - -#define CAMERA_OFFSET_STEP 200 -#define WIELDMESH_OFFSET_X 55.0f -#define WIELDMESH_OFFSET_Y -35.0f - -Camera::Camera(MapDrawControl &draw_control, Client *client): - m_draw_control(draw_control), - m_client(client) -{ - scene::ISceneManager *smgr = RenderingEngine::get_scene_manager(); - // note: making the camera node a child of the player node - // would lead to unexpected behaviour, so we don't do that. - m_playernode = smgr->addEmptySceneNode(smgr->getRootSceneNode()); - m_headnode = smgr->addEmptySceneNode(m_playernode); - m_cameranode = smgr->addCameraSceneNode(smgr->getRootSceneNode()); - m_cameranode->bindTargetAndRotation(true); - - // This needs to be in its own scene manager. It is drawn after - // all other 3D scene nodes and before the GUI. - m_wieldmgr = smgr->createNewSceneManager(); - m_wieldmgr->addCameraSceneNode(); - m_wieldnode = new WieldMeshSceneNode(m_wieldmgr, -1, false); - m_wieldnode->setItem(ItemStack(), m_client); - m_wieldnode->drop(); // m_wieldmgr grabbed it - - /* TODO: Add a callback function so these can be updated when a setting - * changes. At this point in time it doesn't matter (e.g. /set - * is documented to change server settings only) - * - * TODO: Local caching of settings is not optimal and should at some stage - * be updated to use a global settings object for getting thse values - * (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"); - // 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_arm_inertia = g_settings->getBool("arm_inertia"); - m_nametags.clear(); -} - -Camera::~Camera() -{ - m_wieldmgr->drop(); -} - -bool Camera::successfullyCreated(std::string &error_message) -{ - if (!m_playernode) { - error_message = "Failed to create the player scene node"; - } else if (!m_headnode) { - error_message = "Failed to create the head scene node"; - } else if (!m_cameranode) { - error_message = "Failed to create the camera scene node"; - } else if (!m_wieldmgr) { - error_message = "Failed to create the wielded item scene manager"; - } else if (!m_wieldnode) { - error_message = "Failed to create the wielded item scene node"; - } else { - error_message.clear(); - } - - if (g_settings->getBool("enable_client_modding")) { - m_client->getScript()->on_camera_ready(this); - } - return error_message.empty(); -} - -// Returns the fractional part of x -inline f32 my_modf(f32 x) -{ - double dummy; - return modf(x, &dummy); -} - -void Camera::step(f32 dtime) -{ - if(m_view_bobbing_fall > 0) - { - m_view_bobbing_fall -= 3 * dtime; - if(m_view_bobbing_fall <= 0) - m_view_bobbing_fall = -1; // Mark the effect as finished - } - - 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) - m_wieldnode->setItem(m_wield_item_next, m_client); - - if (m_view_bobbing_state != 0) - { - //f32 offset = dtime * m_view_bobbing_speed * 0.035; - f32 offset = dtime * m_view_bobbing_speed * 0.030; - if (m_view_bobbing_state == 2) { - // Animation is getting turned off - if (m_view_bobbing_anim < 0.25) { - m_view_bobbing_anim -= offset; - } else if (m_view_bobbing_anim > 0.75) { - m_view_bobbing_anim += offset; - } - - if (m_view_bobbing_anim < 0.5) { - m_view_bobbing_anim += offset; - if (m_view_bobbing_anim > 0.5) - m_view_bobbing_anim = 0.5; - } else { - m_view_bobbing_anim -= offset; - if (m_view_bobbing_anim < 0.5) - m_view_bobbing_anim = 0.5; - } - - if (m_view_bobbing_anim <= 0 || m_view_bobbing_anim >= 1 || - fabs(m_view_bobbing_anim - 0.5) < 0.01) { - m_view_bobbing_anim = 0; - m_view_bobbing_state = 0; - } - } - else { - float was = m_view_bobbing_anim; - m_view_bobbing_anim = my_modf(m_view_bobbing_anim + offset); - bool step = (was == 0 || - (was < 0.5f && m_view_bobbing_anim >= 0.5f) || - (was > 0.5f && m_view_bobbing_anim <= 0.5f)); - if(step) { - m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::VIEW_BOBBING_STEP)); - } - } - } - - if (m_digging_button != -1) { - f32 offset = dtime * 3.5f; - float m_digging_anim_was = m_digging_anim; - m_digging_anim += offset; - if (m_digging_anim >= 1) - { - m_digging_anim = 0; - m_digging_button = -1; - } - float lim = 0.15; - if(m_digging_anim_was < lim && m_digging_anim >= lim) - { - if (m_digging_button == 0) { - m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::CAMERA_PUNCH_LEFT)); - } else if(m_digging_button == 1) { - m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::CAMERA_PUNCH_RIGHT)); - } - } - } -} - -static inline v2f dir(const v2f &pos_dist) -{ - f32 x = pos_dist.X - WIELDMESH_OFFSET_X; - f32 y = pos_dist.Y - WIELDMESH_OFFSET_Y; - - f32 x_abs = std::fabs(x); - f32 y_abs = std::fabs(y); - - if (x_abs >= y_abs) { - y *= (1.0f / x_abs); - x /= x_abs; - } - - if (y_abs >= x_abs) { - x *= (1.0f / y_abs); - y /= y_abs; - } - - return v2f(std::fabs(x), std::fabs(y)); -} - -void Camera::addArmInertia(f32 player_yaw) -{ - m_cam_vel.X = std::fabs(rangelim(m_last_cam_pos.X - player_yaw, - -100.0f, 100.0f) / 0.016f) * 0.01f; - m_cam_vel.Y = std::fabs((m_last_cam_pos.Y - m_camera_direction.Y) / 0.016f); - f32 gap_X = std::fabs(WIELDMESH_OFFSET_X - m_wieldmesh_offset.X); - f32 gap_Y = std::fabs(WIELDMESH_OFFSET_Y - m_wieldmesh_offset.Y); - - if (m_cam_vel.X > 1.0f || m_cam_vel.Y > 1.0f) { - /* - The arm moves relative to the camera speed, - with an acceleration factor. - */ - - if (m_cam_vel.X > 1.0f) { - if (m_cam_vel.X > m_cam_vel_old.X) - m_cam_vel_old.X = m_cam_vel.X; - - f32 acc_X = 0.12f * (m_cam_vel.X - (gap_X * 0.1f)); - m_wieldmesh_offset.X += m_last_cam_pos.X < player_yaw ? acc_X : -acc_X; - - if (m_last_cam_pos.X != player_yaw) - m_last_cam_pos.X = player_yaw; - - m_wieldmesh_offset.X = rangelim(m_wieldmesh_offset.X, - WIELDMESH_OFFSET_X - 7.0f, WIELDMESH_OFFSET_X + 7.0f); - } - - if (m_cam_vel.Y > 1.0f) { - if (m_cam_vel.Y > m_cam_vel_old.Y) - m_cam_vel_old.Y = m_cam_vel.Y; - - f32 acc_Y = 0.12f * (m_cam_vel.Y - (gap_Y * 0.1f)); - m_wieldmesh_offset.Y += - m_last_cam_pos.Y > m_camera_direction.Y ? acc_Y : -acc_Y; - - if (m_last_cam_pos.Y != m_camera_direction.Y) - m_last_cam_pos.Y = m_camera_direction.Y; - - m_wieldmesh_offset.Y = rangelim(m_wieldmesh_offset.Y, - WIELDMESH_OFFSET_Y - 10.0f, WIELDMESH_OFFSET_Y + 5.0f); - } - - m_arm_dir = dir(m_wieldmesh_offset); - } else { - /* - Now the arm gets back to its default position when the camera stops, - following a vector, with a smooth deceleration factor. - */ - - f32 dec_X = 0.12f * (m_cam_vel_old.X * (1.0f + - (1.0f - m_arm_dir.X))) * (gap_X / 20.0f); - - f32 dec_Y = 0.06f * (m_cam_vel_old.Y * (1.0f + - (1.0f - m_arm_dir.Y))) * (gap_Y / 15.0f); - - if (gap_X < 0.1f) - m_cam_vel_old.X = 0.0f; - - m_wieldmesh_offset.X -= - m_wieldmesh_offset.X > WIELDMESH_OFFSET_X ? dec_X : -dec_X; - - if (gap_Y < 0.1f) - m_cam_vel_old.Y = 0.0f; - - m_wieldmesh_offset.Y -= - m_wieldmesh_offset.Y > WIELDMESH_OFFSET_Y ? dec_Y : -dec_Y; - } -} - -void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_reload_ratio) -{ - // Get player position - // Smooth the movement when walking up stairs - v3f old_player_position = m_playernode->getPosition(); - v3f player_position = player->getPosition(); - if (player->isAttached && player->parent) - player_position = player->parent->getPosition(); - //if(player->touching_ground && player_position.Y > old_player_position.Y) - if(player->touching_ground && - player_position.Y > old_player_position.Y) - { - f32 oldy = old_player_position.Y; - f32 newy = player_position.Y; - f32 t = std::exp(-23 * frametime); - player_position.Y = oldy * t + newy * (1-t); - } - - // Set player node transformation - m_playernode->setPosition(player_position); - m_playernode->setRotation(v3f(0, -1 * player->getYaw(), 0)); - m_playernode->updateAbsolutePosition(); - - // Get camera tilt timer (hurt animation) - float cameratilt = fabs(fabs(player->hurt_tilt_timer-0.75)-0.75); - - // Fall bobbing animation - float fall_bobbing = 0; - if(player->camera_impact >= 1 && m_camera_mode < CAMERA_MODE_THIRD) - { - if(m_view_bobbing_fall == -1) // Effect took place and has finished - player->camera_impact = m_view_bobbing_fall = 0; - else if(m_view_bobbing_fall == 0) // Initialize effect - m_view_bobbing_fall = 1; - - // Convert 0 -> 1 to 0 -> 1 -> 0 - fall_bobbing = m_view_bobbing_fall < 0.5 ? m_view_bobbing_fall * 2 : -(m_view_bobbing_fall - 0.5) * 2 + 1; - // Smoothen and invert the above - fall_bobbing = sin(fall_bobbing * 0.5 * M_PI) * -1; - // Amplify according to the intensity of the impact - fall_bobbing *= (1 - rangelim(50 / player->camera_impact, 0, 1)) * 5; - - fall_bobbing *= m_cache_fall_bobbing_amount; - } - - // Calculate players eye offset for different camera modes - v3f PlayerEyeOffset = player->getEyeOffset(); - if (m_camera_mode == CAMERA_MODE_FIRST) - PlayerEyeOffset += player->eye_offset_first; - else - PlayerEyeOffset += player->eye_offset_third; - - // Set head node transformation - m_headnode->setPosition(PlayerEyeOffset+v3f(0,cameratilt*-player->hurt_tilt_strength+fall_bobbing,0)); - m_headnode->setRotation(v3f(player->getPitch(), 0, cameratilt*player->hurt_tilt_strength)); - m_headnode->updateAbsolutePosition(); - - // Compute relative camera position and target - v3f rel_cam_pos = v3f(0,0,0); - v3f rel_cam_target = v3f(0,0,1); - v3f rel_cam_up = v3f(0,1,0); - - if (m_cache_view_bobbing_amount != 0.0f && m_view_bobbing_anim != 0.0f && - m_camera_mode < CAMERA_MODE_THIRD) { - f32 bobfrac = my_modf(m_view_bobbing_anim * 2); - f32 bobdir = (m_view_bobbing_anim < 0.5) ? 1.0 : -1.0; - - #if 1 - f32 bobknob = 1.2; - f32 bobtmp = sin(pow(bobfrac, bobknob) * M_PI); - //f32 bobtmp2 = cos(pow(bobfrac, bobknob) * M_PI); - - v3f bobvec = v3f( - 0.3 * bobdir * sin(bobfrac * M_PI), - -0.28 * bobtmp * bobtmp, - 0.); - - //rel_cam_pos += 0.2 * bobvec; - //rel_cam_target += 0.03 * bobvec; - //rel_cam_up.rotateXYBy(0.02 * bobdir * bobtmp * M_PI); - float f = 1.0; - f *= m_cache_view_bobbing_amount; - rel_cam_pos += bobvec * f; - //rel_cam_target += 0.995 * bobvec * f; - rel_cam_target += bobvec * f; - rel_cam_target.Z -= 0.005 * bobvec.Z * f; - //rel_cam_target.X -= 0.005 * bobvec.X * f; - //rel_cam_target.Y -= 0.005 * bobvec.Y * f; - rel_cam_up.rotateXYBy(-0.03 * bobdir * bobtmp * M_PI * f); - #else - f32 angle_deg = 1 * bobdir * sin(bobfrac * M_PI); - f32 angle_rad = angle_deg * M_PI / 180; - f32 r = 0.05; - v3f off = v3f( - r * sin(angle_rad), - r * (cos(angle_rad) - 1), - 0); - rel_cam_pos += off; - //rel_cam_target += off; - rel_cam_up.rotateXYBy(angle_deg); - #endif - - } - - // Compute absolute camera position and target - m_headnode->getAbsoluteTransformation().transformVect(m_camera_position, rel_cam_pos); - m_headnode->getAbsoluteTransformation().rotateVect(m_camera_direction, rel_cam_target - rel_cam_pos); - - v3f abs_cam_up; - m_headnode->getAbsoluteTransformation().rotateVect(abs_cam_up, rel_cam_up); - - // Seperate camera position for calculation - v3f my_cp = m_camera_position; - - // Reposition the camera for third person view - if (m_camera_mode > CAMERA_MODE_FIRST) - { - if (m_camera_mode == CAMERA_MODE_THIRD_FRONT) - m_camera_direction *= -1; - - my_cp.Y += 2; - - // Calculate new position - bool abort = false; - for (int i = BS; i <= BS * 2.75; i++) { - my_cp.X = m_camera_position.X + m_camera_direction.X * -i; - my_cp.Z = m_camera_position.Z + m_camera_direction.Z * -i; - if (i > 12) - my_cp.Y = m_camera_position.Y + (m_camera_direction.Y * -i); - - // Prevent camera positioned inside nodes - const NodeDefManager *nodemgr = m_client->ndef(); - MapNode n = m_client->getEnv().getClientMap() - .getNodeNoEx(floatToInt(my_cp, BS)); - - const ContentFeatures& features = nodemgr->get(n); - if (features.walkable) { - my_cp.X += m_camera_direction.X*-1*-BS/2; - my_cp.Z += m_camera_direction.Z*-1*-BS/2; - my_cp.Y += m_camera_direction.Y*-1*-BS/2; - abort = true; - break; - } - } - - // If node blocks camera position don't move y to heigh - if (abort && my_cp.Y > player_position.Y+BS*2) - my_cp.Y = player_position.Y+BS*2; - } - - // Update offset if too far away from the center of the map - m_camera_offset.X += CAMERA_OFFSET_STEP* - (((s16)(my_cp.X/BS) - m_camera_offset.X)/CAMERA_OFFSET_STEP); - m_camera_offset.Y += CAMERA_OFFSET_STEP* - (((s16)(my_cp.Y/BS) - m_camera_offset.Y)/CAMERA_OFFSET_STEP); - m_camera_offset.Z += CAMERA_OFFSET_STEP* - (((s16)(my_cp.Z/BS) - m_camera_offset.Z)/CAMERA_OFFSET_STEP); - - // Set camera node transformation - m_cameranode->setPosition(my_cp-intToFloat(m_camera_offset, BS)); - m_cameranode->setUpVector(abs_cam_up); - // *100.0 helps in large map coordinates - m_cameranode->setTarget(my_cp-intToFloat(m_camera_offset, BS) + 100 * m_camera_direction); - - // update the camera position in third-person mode to render blocks behind player - // and correctly apply liquid post FX. - if (m_camera_mode != CAMERA_MODE_FIRST) - m_camera_position = my_cp; - - // Get FOV - f32 fov_degrees; - // Disable zoom with zoom FOV = 0 - if (player->getPlayerControl().zoom && player->getZoomFOV() > 0.001f) { - fov_degrees = player->getZoomFOV(); - } else { - fov_degrees = m_cache_fov; - } - fov_degrees = rangelim(fov_degrees, 1.0f, 160.0f); - - // FOV and aspect ratio - const v2u32 &window_size = RenderingEngine::get_instance()->getWindowSize(); - m_aspect = (f32) window_size.X / (f32) window_size.Y; - m_fov_y = fov_degrees * M_PI / 180.0; - // Increase vertical FOV on lower aspect ratios (<16:10) - m_fov_y *= MYMAX(1.0, MYMIN(1.4, sqrt(16./10. / m_aspect))); - m_fov_x = 2 * atan(m_aspect * tan(0.5 * m_fov_y)); - m_cameranode->setAspectRatio(m_aspect); - m_cameranode->setFOV(m_fov_y); - - if (m_arm_inertia) - addArmInertia(player->getYaw()); - - // Position the wielded item - //v3f wield_position = v3f(45, -35, 65); - v3f wield_position = v3f(m_wieldmesh_offset.X, m_wieldmesh_offset.Y, 65); - //v3f wield_rotation = v3f(-100, 120, -100); - v3f wield_rotation = v3f(-100, 120, -100); - wield_position.Y += fabs(m_wield_change_timer)*320 - 40; - if(m_digging_anim < 0.05 || m_digging_anim > 0.5) - { - f32 frac = 1.0; - if(m_digging_anim > 0.5) - frac = 2.0 * (m_digging_anim - 0.5); - // This value starts from 1 and settles to 0 - f32 ratiothing = std::pow((1.0f - tool_reload_ratio), 0.5f); - //f32 ratiothing2 = pow(ratiothing, 0.5f); - f32 ratiothing2 = (easeCurve(ratiothing*0.5))*2.0; - wield_position.Y -= frac * 25.0 * pow(ratiothing2, 1.7f); - //wield_position.Z += frac * 5.0 * ratiothing2; - wield_position.X -= frac * 35.0 * pow(ratiothing2, 1.1f); - wield_rotation.Y += frac * 70.0 * pow(ratiothing2, 1.4f); - //wield_rotation.X -= frac * 15.0 * pow(ratiothing2, 1.4f); - //wield_rotation.Z += frac * 15.0 * pow(ratiothing2, 1.0f); - } - if (m_digging_button != -1) - { - f32 digfrac = m_digging_anim; - wield_position.X -= 50 * sin(pow(digfrac, 0.8f) * M_PI); - wield_position.Y += 24 * sin(digfrac * 1.8 * M_PI); - wield_position.Z += 25 * 0.5; - - // Euler angles are PURE EVIL, so why not use quaternions? - core::quaternion quat_begin(wield_rotation * core::DEGTORAD); - core::quaternion quat_end(v3f(80, 30, 100) * core::DEGTORAD); - core::quaternion quat_slerp; - quat_slerp.slerp(quat_begin, quat_end, sin(digfrac * M_PI)); - quat_slerp.toEuler(wield_rotation); - wield_rotation *= core::RADTODEG; - } else { - f32 bobfrac = my_modf(m_view_bobbing_anim); - wield_position.X -= sin(bobfrac*M_PI*2.0) * 3.0; - wield_position.Y += sin(my_modf(bobfrac*2.0)*M_PI) * 3.0; - } - m_wieldnode->setPosition(wield_position); - m_wieldnode->setRotation(wield_rotation); - - m_wieldnode->setColor(player->light_color); - - // Set render distance - updateViewingRange(); - - // If the player is walking, swimming, or climbing, - // view bobbing is enabled and free_move is off, - // start (or continue) the view bobbing animation. - const v3f &speed = player->getSpeed(); - const bool movement_XZ = hypot(speed.X, speed.Z) > BS; - const bool movement_Y = fabs(speed.Y) > BS; - - const bool walking = movement_XZ && player->touching_ground; - const bool swimming = (movement_XZ || player->swimming_vertical) && player->in_liquid; - const bool climbing = movement_Y && player->is_climbing; - if ((walking || swimming || climbing) && - (!g_settings->getBool("free_move") || !m_client->checkLocalPrivilege("fly"))) { - // Start animation - m_view_bobbing_state = 1; - m_view_bobbing_speed = MYMIN(speed.getLength(), 70); - } - else if (m_view_bobbing_state == 1) - { - // Stop animation - m_view_bobbing_state = 2; - m_view_bobbing_speed = 60; - } -} - -void Camera::updateViewingRange() -{ - f32 viewing_range = g_settings->getFloat("viewing_range"); - f32 near_plane = g_settings->getFloat("near_plane"); - - m_draw_control.wanted_range = std::fmin(adjustDist(viewing_range, getFovMax()), 4000); - m_cameranode->setNearValue(rangelim(near_plane, 0.0f, 0.5f) * BS); - if (m_draw_control.range_all) { - m_cameranode->setFarValue(100000.0); - return; - } - m_cameranode->setFarValue((viewing_range < 2000) ? 2000 * BS : viewing_range * BS); -} - -void Camera::setDigging(s32 button) -{ - if (m_digging_button == -1) - m_digging_button = button; -} - -void Camera::wield(const ItemStack &item) -{ - if (item.name != m_wield_item_next.name || - item.metadata != m_wield_item_next.metadata) { - m_wield_item_next = item; - if (m_wield_change_timer > 0) - m_wield_change_timer = -m_wield_change_timer; - else if (m_wield_change_timer == 0) - m_wield_change_timer = -0.001; - } -} - -void Camera::drawWieldedTool(irr::core::matrix4* translation) -{ - // Clear Z buffer so that the wielded tool stay in front of world geometry - m_wieldmgr->getVideoDriver()->clearZBuffer(); - - // Draw the wielded node (in a separate scene manager) - scene::ICameraSceneNode* cam = m_wieldmgr->getActiveCamera(); - cam->setAspectRatio(m_cameranode->getAspectRatio()); - cam->setFOV(72.0*M_PI/180.0); - cam->setNearValue(10); - cam->setFarValue(1000); - if (translation != NULL) - { - irr::core::matrix4 startMatrix = cam->getAbsoluteTransformation(); - irr::core::vector3df focusPoint = (cam->getTarget() - - cam->getAbsolutePosition()).setLength(1) - + cam->getAbsolutePosition(); - - irr::core::vector3df camera_pos = - (startMatrix * *translation).getTranslation(); - cam->setPosition(camera_pos); - cam->setTarget(focusPoint); - } - m_wieldmgr->drawAll(); -} - -void Camera::drawNametags() -{ - core::matrix4 trans = m_cameranode->getProjectionMatrix(); - trans *= m_cameranode->getViewMatrix(); - - for (std::list::const_iterator - i = m_nametags.begin(); - i != m_nametags.end(); ++i) { - Nametag *nametag = *i; - if (nametag->nametag_color.getAlpha() == 0) { - // Enforce hiding nametag, - // because if freetype is enabled, a grey - // shadow can remain. - continue; - } - v3f pos = nametag->parent_node->getAbsolutePosition() + nametag->nametag_pos * BS; - f32 transformed_pos[4] = { pos.X, pos.Y, pos.Z, 1.0f }; - trans.multiplyWith1x4Matrix(transformed_pos); - if (transformed_pos[3] > 0) { - std::wstring nametag_colorless = - unescape_translate(utf8_to_wide(nametag->nametag_text)); - core::dimension2d textsize = - g_fontengine->getFont()->getDimension( - nametag_colorless.c_str()); - f32 zDiv = transformed_pos[3] == 0.0f ? 1.0f : - core::reciprocal(transformed_pos[3]); - v2u32 screensize = RenderingEngine::get_video_driver()->getScreenSize(); - v2s32 screen_pos; - screen_pos.X = screensize.X * - (0.5 * transformed_pos[0] * zDiv + 0.5) - textsize.Width / 2; - screen_pos.Y = screensize.Y * - (0.5 - transformed_pos[1] * zDiv * 0.5) - textsize.Height / 2; - core::rect size(0, 0, textsize.Width, textsize.Height); - g_fontengine->getFont()->draw( - translate_string(utf8_to_wide(nametag->nametag_text)).c_str(), - size + screen_pos, nametag->nametag_color); - } - } -} - -Nametag *Camera::addNametag(scene::ISceneNode *parent_node, - const std::string &nametag_text, video::SColor nametag_color, - const v3f &pos) -{ - Nametag *nametag = new Nametag(parent_node, nametag_text, nametag_color, pos); - m_nametags.push_back(nametag); - return nametag; -} - -void Camera::removeNametag(Nametag *nametag) -{ - m_nametags.remove(nametag); - delete nametag; -} diff --git a/src/camera.h b/src/camera.h deleted file mode 100644 index 88de3570a..000000000 --- a/src/camera.h +++ /dev/null @@ -1,231 +0,0 @@ -/* -Minetest -Copyright (C) 2010-2013 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#pragma once - -#include "irrlichttypes_extrabloated.h" -#include "inventory.h" -#include "client/tile.h" -#include -#include -#include - -class LocalPlayer; -struct MapDrawControl; -class Client; -class WieldMeshSceneNode; - -struct Nametag { - Nametag(scene::ISceneNode *a_parent_node, - const std::string &a_nametag_text, - const video::SColor &a_nametag_color, - const v3f &a_nametag_pos): - parent_node(a_parent_node), - nametag_text(a_nametag_text), - nametag_color(a_nametag_color), - nametag_pos(a_nametag_pos) - { - } - scene::ISceneNode *parent_node; - std::string nametag_text; - video::SColor nametag_color; - v3f nametag_pos; -}; - -enum CameraMode {CAMERA_MODE_FIRST, CAMERA_MODE_THIRD, CAMERA_MODE_THIRD_FRONT}; - -/* - Client camera class, manages the player and camera scene nodes, the viewing distance - and performs view bobbing etc. It also displays the wielded tool in front of the - first-person camera. -*/ -class Camera -{ -public: - Camera(MapDrawControl &draw_control, Client *client); - ~Camera(); - - // Get camera scene node. - // It has the eye transformation, pitch and view bobbing applied. - inline scene::ICameraSceneNode* getCameraNode() const - { - return m_cameranode; - } - - // Get the camera position (in absolute scene coordinates). - // This has view bobbing applied. - inline v3f getPosition() const - { - return m_camera_position; - } - - // Get the camera direction (in absolute camera coordinates). - // This has view bobbing applied. - inline v3f getDirection() const - { - return m_camera_direction; - } - - // Get the camera offset - inline v3s16 getOffset() const - { - return m_camera_offset; - } - - // Horizontal field of view - inline f32 getFovX() const - { - return m_fov_x; - } - - // Vertical field of view - inline f32 getFovY() const - { - return m_fov_y; - } - - // Get maximum of getFovX() and getFovY() - inline f32 getFovMax() const - { - return MYMAX(m_fov_x, m_fov_y); - } - - // Checks if the constructor was able to create the scene nodes - bool successfullyCreated(std::string &error_message); - - // Step the camera: updates the viewing range and view bobbing. - void step(f32 dtime); - - // Update the camera from the local player's position. - // busytime is used to adjust the viewing range. - void update(LocalPlayer* player, f32 frametime, f32 busytime, - f32 tool_reload_ratio); - - // Update render distance - void updateViewingRange(); - - // Start digging animation - // Pass 0 for left click, 1 for right click - void setDigging(s32 button); - - // Replace the wielded item mesh - void wield(const ItemStack &item); - - // Draw the wielded tool. - // This has to happen *after* the main scene is drawn. - // Warning: This clears the Z buffer. - void drawWieldedTool(irr::core::matrix4* translation=NULL); - - // Toggle the current camera mode - void toggleCameraMode() { - if (m_camera_mode == CAMERA_MODE_FIRST) - m_camera_mode = CAMERA_MODE_THIRD; - else if (m_camera_mode == CAMERA_MODE_THIRD) - m_camera_mode = CAMERA_MODE_THIRD_FRONT; - else - m_camera_mode = CAMERA_MODE_FIRST; - } - - // Set the current camera mode - inline void setCameraMode(CameraMode mode) - { - m_camera_mode = mode; - } - - //read the current camera mode - inline CameraMode getCameraMode() - { - return m_camera_mode; - } - - Nametag *addNametag(scene::ISceneNode *parent_node, - const std::string &nametag_text, video::SColor nametag_color, - const v3f &pos); - - void removeNametag(Nametag *nametag); - - const std::list &getNametags() { return m_nametags; } - - void drawNametags(); - - inline void addArmInertia(f32 player_yaw); - -private: - // Nodes - scene::ISceneNode *m_playernode = nullptr; - scene::ISceneNode *m_headnode = nullptr; - scene::ICameraSceneNode *m_cameranode = nullptr; - - scene::ISceneManager *m_wieldmgr = nullptr; - WieldMeshSceneNode *m_wieldnode = nullptr; - - // draw control - MapDrawControl& m_draw_control; - - Client *m_client; - - // Absolute camera position - v3f m_camera_position; - // Absolute camera direction - v3f m_camera_direction; - // Camera offset - v3s16 m_camera_offset; - - v2f m_wieldmesh_offset = v2f(55.0f, -35.0f); - v2f m_arm_dir; - v2f m_cam_vel; - v2f m_cam_vel_old; - v2f m_last_cam_pos; - - // Field of view and aspect ratio stuff - f32 m_aspect = 1.0f; - f32 m_fov_x = 1.0f; - f32 m_fov_y = 1.0f; - - // View bobbing animation frame (0 <= m_view_bobbing_anim < 1) - f32 m_view_bobbing_anim = 0.0f; - // If 0, view bobbing is off (e.g. player is standing). - // If 1, view bobbing is on (player is walking). - // If 2, view bobbing is getting switched off. - s32 m_view_bobbing_state = 0; - // Speed of view bobbing animation - f32 m_view_bobbing_speed = 0.0f; - // Fall view bobbing - f32 m_view_bobbing_fall = 0.0f; - - // Digging animation frame (0 <= m_digging_anim < 1) - f32 m_digging_anim = 0.0f; - // If -1, no digging animation - // If 0, left-click digging animation - // If 1, right-click digging animation - s32 m_digging_button = -1; - - // Animation when changing wielded item - f32 m_wield_change_timer = 0.125f; - ItemStack m_wield_item_next; - - CameraMode m_camera_mode = CAMERA_MODE_FIRST; - - f32 m_cache_fall_bobbing_amount; - f32 m_cache_view_bobbing_amount; - f32 m_cache_fov; - bool m_arm_inertia; - - std::list m_nametags; -}; diff --git a/src/client.cpp b/src/client.cpp deleted file mode 100644 index 17ee3a10a..000000000 --- a/src/client.cpp +++ /dev/null @@ -1,1970 +0,0 @@ -/* -Minetest -Copyright (C) 2013 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include -#include -#include -#include -#include -#include "client.h" -#include "network/clientopcodes.h" -#include "network/connection.h" -#include "network/networkpacket.h" -#include "threading/mutex_auto_lock.h" -#include "client/clientevent.h" -#include "client/gameui.h" -#include "client/renderingengine.h" -#include "client/sound.h" -#include "client/tile.h" -#include "util/auth.h" -#include "util/directiontables.h" -#include "util/pointedthing.h" -#include "util/serialize.h" -#include "util/string.h" -#include "util/srp.h" -#include "filesys.h" -#include "mapblock_mesh.h" -#include "mapblock.h" -#include "minimap.h" -#include "modchannels.h" -#include "content/mods.h" -#include "profiler.h" -#include "shader.h" -#include "gettext.h" -#include "clientmap.h" -#include "clientmedia.h" -#include "version.h" -#include "database/database-sqlite3.h" -#include "serialization.h" -#include "guiscalingfilter.h" -#include "script/scripting_client.h" -#include "game.h" -#include "chatmessage.h" -#include "translation.h" - -extern gui::IGUIEnvironment* guienv; - -/* - Client -*/ - -Client::Client( - const char *playername, - const std::string &password, - const std::string &address_name, - MapDrawControl &control, - IWritableTextureSource *tsrc, - IWritableShaderSource *shsrc, - IWritableItemDefManager *itemdef, - NodeDefManager *nodedef, - ISoundManager *sound, - MtEventManager *event, - bool ipv6, - GameUI *game_ui -): - m_tsrc(tsrc), - m_shsrc(shsrc), - m_itemdef(itemdef), - m_nodedef(nodedef), - m_sound(sound), - m_event(event), - m_mesh_update_thread(this), - m_env( - new ClientMap(this, control, 666), - tsrc, this - ), - m_particle_manager(&m_env), - m_con(new con::Connection(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, ipv6, this)), - m_address_name(address_name), - m_server_ser_ver(SER_FMT_VER_INVALID), - m_last_chat_message_sent(time(NULL)), - m_password(password), - m_chosen_auth_mech(AUTH_MECHANISM_NONE), - m_media_downloader(new ClientMediaDownloader()), - m_state(LC_Created), - m_game_ui(game_ui), - m_modchannel_mgr(new ModChannelMgr()) -{ - // Add local player - m_env.setLocalPlayer(new LocalPlayer(this, playername)); - - if (g_settings->getBool("enable_minimap")) { - m_minimap = new Minimap(this); - } - m_cache_save_interval = g_settings->getU16("server_map_save_interval"); - - m_modding_enabled = g_settings->getBool("enable_client_modding"); - // Only create the client script environment if client modding is enabled - if (m_modding_enabled) { - m_script = new ClientScripting(this); - m_env.setScript(m_script); - m_script->setEnv(&m_env); - } -} - -void Client::loadMods() -{ - // Don't load mods twice - if (m_mods_loaded) { - return; - } - - // If client modding is not enabled, don't load client-provided CSM mods or - // builtin. - if (!m_modding_enabled) { - warningstream << "Client side mods are disabled by configuration." << std::endl; - return; - } - - // Load builtin - scanModIntoMemory(BUILTIN_MOD_NAME, getBuiltinLuaPath()); - m_script->loadModFromMemory(BUILTIN_MOD_NAME); - - // If the server has disabled client-provided CSM mod loading, don't load - // client-provided CSM mods. Builtin is loaded so needs verfying. - if (checkCSMRestrictionFlag(CSMRestrictionFlags::CSM_RF_LOAD_CLIENT_MODS)) { - warningstream << "Client side mods are disabled by server." << std::endl; - // If builtin integrity is wrong, disconnect user - if (!checkBuiltinIntegrity()) { - // @TODO disconnect user - } - return; - } - - ClientModConfiguration modconf(getClientModsLuaPath()); - m_mods = modconf.getMods(); - // complain about mods with unsatisfied dependencies - if (!modconf.isConsistent()) { - modconf.printUnsatisfiedModsError(); - } - - // Print mods - infostream << "Client Loading mods: "; - for (const ModSpec &mod : m_mods) - infostream << mod.name << " "; - infostream << std::endl; - - // Load and run "mod" scripts - for (const ModSpec &mod : m_mods) { - if (!string_allowed(mod.name, MODNAME_ALLOWED_CHARS)) { - throw ModError("Error loading mod \"" + mod.name + - "\": Mod name does not follow naming conventions: " - "Only characters [a-z0-9_] are allowed."); - } - scanModIntoMemory(mod.name, mod.path); - } - - // Load and run "mod" scripts - for (const ModSpec &mod : m_mods) - m_script->loadModFromMemory(mod.name); - - // Run a callback when mods are loaded - m_script->on_mods_loaded(); - m_mods_loaded = true; -} - -bool Client::checkBuiltinIntegrity() -{ - // @TODO - return true; -} - -void Client::scanModSubfolder(const std::string &mod_name, const std::string &mod_path, - std::string mod_subpath) -{ - std::string full_path = mod_path + DIR_DELIM + mod_subpath; - std::vector mod = fs::GetDirListing(full_path); - for (const fs::DirListNode &j : mod) { - std::string filename = j.name; - if (j.dir) { - scanModSubfolder(mod_name, mod_path, mod_subpath - + filename + DIR_DELIM); - continue; - } - std::replace( mod_subpath.begin(), mod_subpath.end(), DIR_DELIM_CHAR, '/'); - m_mod_files[mod_name + ":" + mod_subpath + filename] = full_path + filename; - } -} - -const std::string &Client::getBuiltinLuaPath() -{ - static const std::string builtin_dir = porting::path_share + DIR_DELIM + "builtin"; - return builtin_dir; -} - -const std::string &Client::getClientModsLuaPath() -{ - static const std::string clientmods_dir = porting::path_share + DIR_DELIM + "clientmods"; - return clientmods_dir; -} - -const std::vector& Client::getMods() const -{ - static std::vector client_modspec_temp; - return client_modspec_temp; -} - -const ModSpec* Client::getModSpec(const std::string &modname) const -{ - return NULL; -} - -void Client::Stop() -{ - m_shutdown = true; - if (m_modding_enabled) - m_script->on_shutdown(); - //request all client managed threads to stop - m_mesh_update_thread.stop(); - // Save local server map - if (m_localdb) { - infostream << "Local map saving ended." << std::endl; - m_localdb->endSave(); - } - - if (m_modding_enabled) - delete m_script; -} - -bool Client::isShutdown() -{ - return m_shutdown || !m_mesh_update_thread.isRunning(); -} - -Client::~Client() -{ - m_shutdown = true; - m_con->Disconnect(); - - deleteAuthData(); - - m_mesh_update_thread.stop(); - m_mesh_update_thread.wait(); - while (!m_mesh_update_thread.m_queue_out.empty()) { - MeshUpdateResult r = m_mesh_update_thread.m_queue_out.pop_frontNoEx(); - delete r.mesh; - } - - - delete m_inventory_from_server; - - // Delete detached inventories - for (auto &m_detached_inventorie : m_detached_inventories) { - delete m_detached_inventorie.second; - } - - // cleanup 3d model meshes on client shutdown - while (RenderingEngine::get_mesh_cache()->getMeshCount() != 0) { - scene::IAnimatedMesh *mesh = RenderingEngine::get_mesh_cache()->getMeshByIndex(0); - - if (mesh) - RenderingEngine::get_mesh_cache()->removeMesh(mesh); - } - - delete m_minimap; - delete m_media_downloader; -} - -void Client::connect(Address address, bool is_local_server) -{ - initLocalMapSaving(address, m_address_name, is_local_server); - - m_con->SetTimeoutMs(0); - m_con->Connect(address); -} - -void Client::step(float dtime) -{ - // Limit a bit - if (dtime > 2.0) - dtime = 2.0; - - m_animation_time += dtime; - if(m_animation_time > 60.0) - m_animation_time -= 60.0; - - m_time_of_day_update_timer += dtime; - - ReceiveAll(); - - /* - Packet counter - */ - { - float &counter = m_packetcounter_timer; - counter -= dtime; - if(counter <= 0.0) - { - counter = 20.0; - - infostream << "Client packetcounter (" << m_packetcounter_timer - << "):"<getName()); - } - - // Not connected, return - return; - } - - /* - Do stuff if connected - */ - - /* - Run Map's timers and unload unused data - */ - const float map_timer_and_unload_dtime = 5.25; - if(m_map_timer_and_unload_interval.step(dtime, map_timer_and_unload_dtime)) { - ScopeProfiler sp(g_profiler, "Client: map timer and unload"); - std::vector deleted_blocks; - m_env.getMap().timerUpdate(map_timer_and_unload_dtime, - g_settings->getFloat("client_unload_unused_data_timeout"), - g_settings->getS32("client_mapblock_limit"), - &deleted_blocks); - - /* - Send info to server - NOTE: This loop is intentionally iterated the way it is. - */ - - std::vector::iterator i = deleted_blocks.begin(); - std::vector sendlist; - for(;;) { - if(sendlist.size() == 255 || i == deleted_blocks.end()) { - if(sendlist.empty()) - break; - /* - [0] u16 command - [2] u8 count - [3] v3s16 pos_0 - [3+6] v3s16 pos_1 - ... - */ - - sendDeletedBlocks(sendlist); - - if(i == deleted_blocks.end()) - break; - - sendlist.clear(); - } - - sendlist.push_back(*i); - ++i; - } - } - - /* - Send pending messages on out chat queue - */ - if (!m_out_chat_queue.empty() && canSendChatMessage()) { - sendChatMessage(m_out_chat_queue.front()); - m_out_chat_queue.pop(); - } - - /* - Handle environment - */ - // Control local player (0ms) - LocalPlayer *player = m_env.getLocalPlayer(); - assert(player); - player->applyControl(dtime, &m_env); - - // Step environment - m_env.step(dtime); - m_sound->step(dtime); - - /* - Get events - */ - while (m_env.hasClientEnvEvents()) { - ClientEnvEvent envEvent = m_env.getClientEnvEvent(); - - if (envEvent.type == CEE_PLAYER_DAMAGE) { - u8 damage = envEvent.player_damage.amount; - - if (envEvent.player_damage.send_to_server) - sendDamage(damage); - - // Add to ClientEvent queue - ClientEvent *event = new ClientEvent(); - event->type = CE_PLAYER_DAMAGE; - event->player_damage.amount = damage; - m_client_event_queue.push(event); - } - } - - /* - Print some info - */ - float &counter = m_avg_rtt_timer; - counter += dtime; - if(counter >= 10) { - counter = 0.0; - // connectedAndInitialized() is true, peer exists. - float avg_rtt = getRTT(); - infostream << "Client: average rtt: " << avg_rtt << std::endl; - } - - /* - Send player position to server - */ - { - float &counter = m_playerpos_send_timer; - counter += dtime; - if((m_state == LC_Ready) && (counter >= m_recommended_send_interval)) - { - counter = 0.0; - sendPlayerPos(); - } - } - - /* - Replace updated meshes - */ - { - int num_processed_meshes = 0; - while (!m_mesh_update_thread.m_queue_out.empty()) - { - num_processed_meshes++; - - MinimapMapblock *minimap_mapblock = NULL; - bool do_mapper_update = true; - - MeshUpdateResult r = m_mesh_update_thread.m_queue_out.pop_frontNoEx(); - MapBlock *block = m_env.getMap().getBlockNoCreateNoEx(r.p); - if (block) { - // Delete the old mesh - delete block->mesh; - block->mesh = nullptr; - - if (r.mesh) { - minimap_mapblock = r.mesh->moveMinimapMapblock(); - if (minimap_mapblock == NULL) - do_mapper_update = false; - - bool is_empty = true; - for (int l = 0; l < MAX_TILE_LAYERS; l++) - if (r.mesh->getMesh(l)->getMeshBufferCount() != 0) - is_empty = false; - - if (is_empty) - delete r.mesh; - else - // Replace with the new mesh - block->mesh = r.mesh; - } - } else { - delete r.mesh; - } - - if (m_minimap && do_mapper_update) - m_minimap->addBlock(r.p, minimap_mapblock); - - if (r.ack_block_to_server) { - /* - Acknowledge block - [0] u8 count - [1] v3s16 pos_0 - */ - sendGotBlocks(r.p); - } - } - - if (num_processed_meshes > 0) - g_profiler->graphAdd("num_processed_meshes", num_processed_meshes); - } - - /* - Load fetched media - */ - if (m_media_downloader && m_media_downloader->isStarted()) { - m_media_downloader->step(this); - if (m_media_downloader->isDone()) { - delete m_media_downloader; - m_media_downloader = NULL; - } - } - - /* - If the server didn't update the inventory in a while, revert - the local inventory (so the player notices the lag problem - and knows something is wrong). - */ - if (m_inventory_from_server) { - float interval = 10.0f; - float count_before = std::floor(m_inventory_from_server_age / interval); - - m_inventory_from_server_age += dtime; - - float count_after = std::floor(m_inventory_from_server_age / interval); - - if (count_after != count_before) { - // Do this every seconds after TOCLIENT_INVENTORY - // Reset the locally changed inventory to the authoritative inventory - m_env.getLocalPlayer()->inventory = *m_inventory_from_server; - m_inventory_updated = true; - } - } - - /* - Update positions of sounds attached to objects - */ - { - for (auto &m_sounds_to_object : m_sounds_to_objects) { - int client_id = m_sounds_to_object.first; - u16 object_id = m_sounds_to_object.second; - ClientActiveObject *cao = m_env.getActiveObject(object_id); - if (!cao) - continue; - m_sound->updateSoundPosition(client_id, cao->getPosition()); - } - } - - /* - Handle removed remotely initiated sounds - */ - m_removed_sounds_check_timer += dtime; - if(m_removed_sounds_check_timer >= 2.32) { - m_removed_sounds_check_timer = 0; - // Find removed sounds and clear references to them - std::vector removed_server_ids; - for (std::unordered_map::iterator i = m_sounds_server_to_client.begin(); - i != m_sounds_server_to_client.end();) { - s32 server_id = i->first; - int client_id = i->second; - ++i; - if(!m_sound->soundExists(client_id)) { - m_sounds_server_to_client.erase(server_id); - m_sounds_client_to_server.erase(client_id); - m_sounds_to_objects.erase(client_id); - removed_server_ids.push_back(server_id); - } - } - - // Sync to server - if(!removed_server_ids.empty()) { - sendRemovedSounds(removed_server_ids); - } - } - - m_mod_storage_save_timer -= dtime; - if (m_mod_storage_save_timer <= 0.0f) { - verbosestream << "Saving registered mod storages." << std::endl; - m_mod_storage_save_timer = g_settings->getFloat("server_map_save_interval"); - for (std::unordered_map::const_iterator - it = m_mod_storages.begin(); it != m_mod_storages.end(); ++it) { - if (it->second->isModified()) { - it->second->save(getModStoragePath()); - } - } - } - - // Write server map - if (m_localdb && m_localdb_save_interval.step(dtime, - m_cache_save_interval)) { - m_localdb->endSave(); - m_localdb->beginSave(); - } -} - -bool Client::loadMedia(const std::string &data, const std::string &filename) -{ - // Silly irrlicht's const-incorrectness - Buffer data_rw(data.c_str(), data.size()); - - std::string name; - - const char *image_ext[] = { - ".png", ".jpg", ".bmp", ".tga", - ".pcx", ".ppm", ".psd", ".wal", ".rgb", - NULL - }; - name = removeStringEnd(filename, image_ext); - if (!name.empty()) { - verbosestream<<"Client: Attempting to load image " - <<"file \""<createMemoryReadFile( - *data_rw, data_rw.getSize(), "_tempreadfile"); - - FATAL_ERROR_IF(!rfile, "Could not create irrlicht memory file."); - - // Read image - video::IImage *img = vdrv->createImageFromFile(rfile); - if (!img) { - errorstream<<"Client: Cannot create image from data of " - <<"file \""<drop(); - return false; - } - - m_tsrc->insertSourceImage(filename, img); - img->drop(); - rfile->drop(); - return true; - } - - const char *sound_ext[] = { - ".0.ogg", ".1.ogg", ".2.ogg", ".3.ogg", ".4.ogg", - ".5.ogg", ".6.ogg", ".7.ogg", ".8.ogg", ".9.ogg", - ".ogg", NULL - }; - name = removeStringEnd(filename, sound_ext); - if (!name.empty()) { - verbosestream<<"Client: Attempting to load sound " - <<"file \""<loadSoundData(name, data); - return true; - } - - const char *model_ext[] = { - ".x", ".b3d", ".md2", ".obj", - NULL - }; - - name = removeStringEnd(filename, model_ext); - if (!name.empty()) { - verbosestream<<"Client: Storing model into memory: " - <<"\""<loadTranslation(data); - return true; - } - - errorstream << "Client: Don't know how to load file \"" - << filename << "\"" << std::endl; - return false; -} - -// Virtual methods from con::PeerHandler -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_reason = gettext("Connection timed out."); - } -} - -/* - u16 command - u16 number of files requested - for each file { - u16 length of name - string name - } -*/ -void Client::request_media(const std::vector &file_requests) -{ - std::ostringstream os(std::ios_base::binary); - writeU16(os, TOSERVER_REQUEST_MEDIA); - size_t file_requests_size = file_requests.size(); - - FATAL_ERROR_IF(file_requests_size > 0xFFFF, "Unsupported number of file requests"); - - // Packet dynamicly resized - NetworkPacket pkt(TOSERVER_REQUEST_MEDIA, 2 + 0); - - pkt << (u16) (file_requests_size & 0xFFFF); - - for (const std::string &file_request : file_requests) { - pkt << file_request; - } - - Send(&pkt); - - infostream << "Client: Sending media request list to server (" - << file_requests.size() << " files. packet size)" << std::endl; -} - -void Client::initLocalMapSaving(const Address &address, - const std::string &hostname, - bool is_local_server) -{ - if (!g_settings->getBool("enable_local_map_saving") || is_local_server) { - return; - } - - const std::string world_path = porting::path_user - + DIR_DELIM + "worlds" - + DIR_DELIM + "server_" - + hostname + "_" + std::to_string(address.getPort()); - - fs::CreateAllDirs(world_path); - - m_localdb = new MapDatabaseSQLite3(world_path); - m_localdb->beginSave(); - actionstream << "Local map saving started, map will be saved at '" << world_path << "'" << std::endl; -} - -void Client::ReceiveAll() -{ - u64 start_ms = porting::getTimeMs(); - for(;;) - { - // Limit time even if there would be huge amounts of data to - // process - if(porting::getTimeMs() > start_ms + 100) - break; - - try { - Receive(); - g_profiler->graphAdd("client_received_packets", 1); - } - catch(con::NoIncomingDataException &e) { - break; - } - catch(con::InvalidIncomingDataException &e) { - infostream<<"Client::ReceiveAll(): " - "InvalidIncomingDataException: what()=" - <Receive(&pkt); - ProcessData(&pkt); -} - -inline void Client::handleCommand(NetworkPacket* pkt) -{ - const ToClientCommandHandler& opHandle = toClientCommandTable[pkt->getCommand()]; - (this->*opHandle.handler)(pkt); -} - -/* - sender_peer_id given to this shall be quaranteed to be a valid peer -*/ -void Client::ProcessData(NetworkPacket *pkt) -{ - ToClientCommand command = (ToClientCommand) pkt->getCommand(); - u32 sender_peer_id = pkt->getPeerId(); - - //infostream<<"Client: received command="< &blocks) -{ - NetworkPacket pkt(TOSERVER_DELETEDBLOCKS, 1 + sizeof(v3s16) * blocks.size()); - - pkt << (u8) blocks.size(); - - for (const v3s16 &block : blocks) { - pkt << block; - } - - Send(&pkt); -} - -void Client::sendGotBlocks(v3s16 block) -{ - NetworkPacket pkt(TOSERVER_GOTBLOCKS, 1 + 6); - pkt << (u8) 1 << block; - Send(&pkt); -} - -void Client::sendRemovedSounds(std::vector &soundList) -{ - size_t server_ids = soundList.size(); - assert(server_ids <= 0xFFFF); - - NetworkPacket pkt(TOSERVER_REMOVED_SOUNDS, 2 + server_ids * 4); - - pkt << (u16) (server_ids & 0xFFFF); - - for (int sound_id : soundList) - pkt << sound_id; - - Send(&pkt); -} - -void Client::sendNodemetaFields(v3s16 p, const std::string &formname, - const StringMap &fields) -{ - size_t fields_size = fields.size(); - - FATAL_ERROR_IF(fields_size > 0xFFFF, "Unsupported number of nodemeta fields"); - - NetworkPacket pkt(TOSERVER_NODEMETA_FIELDS, 0); - - pkt << p << formname << (u16) (fields_size & 0xFFFF); - - StringMap::const_iterator it; - for (it = fields.begin(); it != fields.end(); ++it) { - const std::string &name = it->first; - const std::string &value = it->second; - pkt << name; - pkt.putLongString(value); - } - - Send(&pkt); -} - -void Client::sendInventoryFields(const std::string &formname, - const StringMap &fields) -{ - size_t fields_size = fields.size(); - FATAL_ERROR_IF(fields_size > 0xFFFF, "Unsupported number of inventory fields"); - - NetworkPacket pkt(TOSERVER_INVENTORY_FIELDS, 0); - pkt << formname << (u16) (fields_size & 0xFFFF); - - StringMap::const_iterator it; - for (it = fields.begin(); it != fields.end(); ++it) { - const std::string &name = it->first; - const std::string &value = it->second; - pkt << name; - pkt.putLongString(value); - } - - Send(&pkt); -} - -void Client::sendInventoryAction(InventoryAction *a) -{ - std::ostringstream os(std::ios_base::binary); - - a->serialize(os); - - // Make data buffer - std::string s = os.str(); - - NetworkPacket pkt(TOSERVER_INVENTORY_ACTION, s.size()); - pkt.putRawString(s.c_str(),s.size()); - - Send(&pkt); -} - -bool Client::canSendChatMessage() const -{ - u32 now = time(NULL); - float time_passed = now - m_last_chat_message_sent; - - float virt_chat_message_allowance = m_chat_message_allowance + time_passed * - (CLIENT_CHAT_MESSAGE_LIMIT_PER_10S / 8.0f); - - if (virt_chat_message_allowance < 1.0f) - return false; - - return true; -} - -void Client::sendChatMessage(const std::wstring &message) -{ - const s16 max_queue_size = g_settings->getS16("max_out_chat_queue_size"); - if (canSendChatMessage()) { - u32 now = time(NULL); - float time_passed = now - m_last_chat_message_sent; - m_last_chat_message_sent = time(NULL); - - m_chat_message_allowance += time_passed * (CLIENT_CHAT_MESSAGE_LIMIT_PER_10S / 8.0f); - if (m_chat_message_allowance > CLIENT_CHAT_MESSAGE_LIMIT_PER_10S) - m_chat_message_allowance = CLIENT_CHAT_MESSAGE_LIMIT_PER_10S; - - m_chat_message_allowance -= 1.0f; - - NetworkPacket pkt(TOSERVER_CHAT_MESSAGE, 2 + message.size() * sizeof(u16)); - - pkt << message; - - Send(&pkt); - } else if (m_out_chat_queue.size() < (u16) max_queue_size || max_queue_size == -1) { - m_out_chat_queue.push(message); - } else { - infostream << "Could not queue chat message because maximum out chat queue size (" - << max_queue_size << ") is reached." << std::endl; - } -} - -void Client::clearOutChatQueue() -{ - m_out_chat_queue = std::queue(); -} - -void Client::sendChangePassword(const std::string &oldpassword, - const std::string &newpassword) -{ - LocalPlayer *player = m_env.getLocalPlayer(); - if (player == NULL) - return; - - // get into sudo mode and then send new password to server - m_password = oldpassword; - m_new_password = newpassword; - startAuth(choseAuthMech(m_sudo_auth_methods)); -} - - -void Client::sendDamage(u8 damage) -{ - NetworkPacket pkt(TOSERVER_DAMAGE, sizeof(u8)); - pkt << damage; - Send(&pkt); -} - -void Client::sendRespawn() -{ - NetworkPacket pkt(TOSERVER_RESPAWN, 0); - Send(&pkt); -} - -void Client::sendReady() -{ - NetworkPacket pkt(TOSERVER_CLIENT_READY, - 1 + 1 + 1 + 1 + 2 + sizeof(char) * strlen(g_version_hash)); - - pkt << (u8) VERSION_MAJOR << (u8) VERSION_MINOR << (u8) VERSION_PATCH - << (u8) 0 << (u16) strlen(g_version_hash); - - pkt.putRawString(g_version_hash, (u16) strlen(g_version_hash)); - Send(&pkt); -} - -void Client::sendPlayerPos() -{ - LocalPlayer *myplayer = m_env.getLocalPlayer(); - if (!myplayer) - return; - - ClientMap &map = m_env.getClientMap(); - - u8 camera_fov = map.getCameraFov(); - u8 wanted_range = map.getControl().wanted_range; - - // Save bandwidth by only updating position when something changed - if(myplayer->last_position == myplayer->getPosition() && - myplayer->last_speed == myplayer->getSpeed() && - myplayer->last_pitch == myplayer->getPitch() && - myplayer->last_yaw == myplayer->getYaw() && - myplayer->last_keyPressed == myplayer->keyPressed && - myplayer->last_camera_fov == camera_fov && - myplayer->last_wanted_range == wanted_range) - return; - - myplayer->last_position = myplayer->getPosition(); - myplayer->last_speed = myplayer->getSpeed(); - myplayer->last_pitch = myplayer->getPitch(); - myplayer->last_yaw = myplayer->getYaw(); - myplayer->last_keyPressed = myplayer->keyPressed; - myplayer->last_camera_fov = camera_fov; - myplayer->last_wanted_range = wanted_range; - - NetworkPacket pkt(TOSERVER_PLAYERPOS, 12 + 12 + 4 + 4 + 4 + 1 + 1); - - writePlayerPos(myplayer, &map, &pkt); - - Send(&pkt); -} - -void Client::sendPlayerItem(u16 item) -{ - LocalPlayer *myplayer = m_env.getLocalPlayer(); - if (!myplayer) - return; - - NetworkPacket pkt(TOSERVER_PLAYERITEM, 2); - - pkt << item; - - Send(&pkt); -} - -void Client::removeNode(v3s16 p) -{ - std::map modified_blocks; - - try { - m_env.getMap().removeNodeAndUpdate(p, modified_blocks); - } - catch(InvalidPositionException &e) { - } - - for (const auto &modified_block : modified_blocks) { - addUpdateMeshTaskWithEdge(modified_block.first, false, true); - } -} - -/** - * Helper function for Client Side Modding - * CSM restrictions are applied there, this should not be used for core engine - * @param p - * @param is_valid_position - * @return - */ -MapNode Client::getNode(v3s16 p, bool *is_valid_position) -{ - if (checkCSMRestrictionFlag(CSMRestrictionFlags::CSM_RF_LOOKUP_NODES)) { - v3s16 ppos = floatToInt(m_env.getLocalPlayer()->getPosition(), BS); - if ((u32) ppos.getDistanceFrom(p) > m_csm_restriction_noderange) { - *is_valid_position = false; - return {}; - } - } - return m_env.getMap().getNodeNoEx(p, is_valid_position); -} - -void Client::addNode(v3s16 p, MapNode n, bool remove_metadata) -{ - //TimeTaker timer1("Client::addNode()"); - - std::map modified_blocks; - - try { - //TimeTaker timer3("Client::addNode(): addNodeAndUpdate"); - m_env.getMap().addNodeAndUpdate(p, n, modified_blocks, remove_metadata); - } - catch(InvalidPositionException &e) { - } - - for (const auto &modified_block : modified_blocks) { - addUpdateMeshTaskWithEdge(modified_block.first, false, true); - } -} - -void Client::setPlayerControl(PlayerControl &control) -{ - LocalPlayer *player = m_env.getLocalPlayer(); - assert(player); - player->control = control; -} - -void Client::selectPlayerItem(u16 item) -{ - m_playeritem = item; - m_inventory_updated = true; - sendPlayerItem(item); -} - -// Returns true if the inventory of the local player has been -// updated from the server. If it is true, it is set to false. -bool Client::getLocalInventoryUpdated() -{ - bool updated = m_inventory_updated; - m_inventory_updated = false; - return updated; -} - -// Copies the inventory of the local player to parameter -void Client::getLocalInventory(Inventory &dst) -{ - LocalPlayer *player = m_env.getLocalPlayer(); - assert(player); - dst = player->inventory; -} - -Inventory* Client::getInventory(const InventoryLocation &loc) -{ - switch(loc.type){ - case InventoryLocation::UNDEFINED: - {} - break; - case InventoryLocation::CURRENT_PLAYER: - { - LocalPlayer *player = m_env.getLocalPlayer(); - assert(player); - return &player->inventory; - } - break; - case InventoryLocation::PLAYER: - { - // Check if we are working with local player inventory - LocalPlayer *player = m_env.getLocalPlayer(); - if (!player || strcmp(player->getName(), loc.name.c_str()) != 0) - return NULL; - return &player->inventory; - } - break; - case InventoryLocation::NODEMETA: - { - NodeMetadata *meta = m_env.getMap().getNodeMetadata(loc.p); - if(!meta) - return NULL; - return meta->getInventory(); - } - break; - case InventoryLocation::DETACHED: - { - if (m_detached_inventories.count(loc.name) == 0) - return NULL; - return m_detached_inventories[loc.name]; - } - break; - default: - FATAL_ERROR("Invalid inventory location type."); - break; - } - return NULL; -} - -void Client::inventoryAction(InventoryAction *a) -{ - /* - Send it to the server - */ - sendInventoryAction(a); - - /* - Predict some local inventory changes - */ - a->clientApply(this, this); - - // Remove it - delete a; -} - -float Client::getAnimationTime() -{ - return m_animation_time; -} - -int Client::getCrackLevel() -{ - return m_crack_level; -} - -v3s16 Client::getCrackPos() -{ - return m_crack_pos; -} - -void Client::setCrack(int level, v3s16 pos) -{ - int old_crack_level = m_crack_level; - v3s16 old_crack_pos = m_crack_pos; - - m_crack_level = level; - m_crack_pos = pos; - - if(old_crack_level >= 0 && (level < 0 || pos != old_crack_pos)) - { - // remove old crack - addUpdateMeshTaskForNode(old_crack_pos, false, true); - } - if(level >= 0 && (old_crack_level < 0 || pos != old_crack_pos)) - { - // add new crack - addUpdateMeshTaskForNode(pos, false, true); - } -} - -u16 Client::getHP() -{ - LocalPlayer *player = m_env.getLocalPlayer(); - assert(player); - return player->hp; -} - -bool Client::getChatMessage(std::wstring &res) -{ - if (m_chat_queue.empty()) - return false; - - ChatMessage *chatMessage = m_chat_queue.front(); - m_chat_queue.pop(); - - res = L""; - - switch (chatMessage->type) { - case CHATMESSAGE_TYPE_RAW: - case CHATMESSAGE_TYPE_ANNOUNCE: - case CHATMESSAGE_TYPE_SYSTEM: - res = chatMessage->message; - break; - case CHATMESSAGE_TYPE_NORMAL: { - if (!chatMessage->sender.empty()) - res = L"<" + chatMessage->sender + L"> " + chatMessage->message; - else - res = chatMessage->message; - break; - } - default: - break; - } - - delete chatMessage; - return true; -} - -void Client::typeChatMessage(const std::wstring &message) -{ - // Discard empty line - if (message.empty()) - return; - - // If message was consumed by script API, don't send it to server - if (m_modding_enabled && m_script->on_sending_message(wide_to_utf8(message))) - return; - - // Send to others - sendChatMessage(message); - - // Show locally - if (message[0] != L'/') { - // compatibility code - if (m_proto_ver < 29) { - LocalPlayer *player = m_env.getLocalPlayer(); - assert(player); - std::wstring name = narrow_to_wide(player->getName()); - pushToChatQueue(new ChatMessage(CHATMESSAGE_TYPE_NORMAL, message, name)); - } - } -} - -void Client::addUpdateMeshTask(v3s16 p, bool ack_to_server, bool urgent) -{ - // Check if the block exists to begin with. In the case when a non-existing - // neighbor is automatically added, it may not. In that case we don't want - // to tell the mesh update thread about it. - MapBlock *b = m_env.getMap().getBlockNoCreateNoEx(p); - if (b == NULL) - return; - - m_mesh_update_thread.updateBlock(&m_env.getMap(), p, ack_to_server, urgent); -} - -void Client::addUpdateMeshTaskWithEdge(v3s16 blockpos, bool ack_to_server, bool urgent) -{ - try{ - addUpdateMeshTask(blockpos, ack_to_server, urgent); - } - catch(InvalidPositionException &e){} - - // Leading edge - for (int i=0;i<6;i++) - { - try{ - v3s16 p = blockpos + g_6dirs[i]; - addUpdateMeshTask(p, false, urgent); - } - catch(InvalidPositionException &e){} - } -} - -void Client::addUpdateMeshTaskForNode(v3s16 nodepos, bool ack_to_server, bool urgent) -{ - { - v3s16 p = nodepos; - infostream<<"Client::addUpdateMeshTaskForNode(): " - <<"("<Connected(); -} - -const Address Client::getServerAddress() -{ - return m_con->GetPeerAddress(PEER_ID_SERVER); -} - -float Client::mediaReceiveProgress() -{ - if (m_media_downloader) - return m_media_downloader->getProgress(); - - return 1.0; // downloader only exists when not yet done -} - -typedef struct TextureUpdateArgs { - gui::IGUIEnvironment *guienv; - u64 last_time_ms; - u16 last_percent; - const wchar_t* text_base; - ITextureSource *tsrc; -} TextureUpdateArgs; - -void texture_update_progress(void *args, u32 progress, u32 max_progress) -{ - TextureUpdateArgs* targs = (TextureUpdateArgs*) args; - u16 cur_percent = ceil(progress / (double) max_progress * 100.); - - // update the loading menu -- if neccessary - bool do_draw = false; - u64 time_ms = targs->last_time_ms; - if (cur_percent != targs->last_percent) { - targs->last_percent = cur_percent; - time_ms = porting::getTimeMs(); - // only draw when the user will notice something: - do_draw = (time_ms - targs->last_time_ms > 100); - } - - if (do_draw) { - targs->last_time_ms = time_ms; - std::basic_stringstream strm; - strm << targs->text_base << " " << targs->last_percent << "%..."; - RenderingEngine::draw_load_screen(strm.str(), targs->guienv, targs->tsrc, 0, - 72 + (u16) ((18. / 100.) * (double) targs->last_percent), true); - } -} - -void Client::afterContentReceived() -{ - infostream<<"Client::afterContentReceived() started"<rebuildImagesAndTextures(); - delete[] text; - - // Rebuild shaders - infostream<<"- Rebuilding shaders"<rebuildShaders(); - delete[] text; - - // Update node aliases - infostream<<"- Updating node aliases"<updateAliases(m_itemdef); - for (const auto &path : getTextureDirs()) - m_nodedef->applyTextureOverrides(path + DIR_DELIM + "override.txt"); - m_nodedef->setNodeRegistrationStatus(true); - m_nodedef->runNodeResolveCallbacks(); - delete[] text; - - // Update node textures and assign shaders to each tile - infostream<<"- Updating node textures"<updateTextures(this, texture_update_progress, &tu_args); - delete[] tu_args.text_base; - - // Start mesh update thread after setting up content definitions - infostream<<"- Starting mesh update thread"<getBool("enable_client_modding")) { - m_script->on_client_ready(m_env.getLocalPlayer()); - } - - text = wgettext("Done!"); - RenderingEngine::draw_load_screen(text, guienv, m_tsrc, 0, 100); - infostream<<"Client::afterContentReceived() done"<getPeerStat(PEER_ID_SERVER, con::AVG_RTT); - float time_from_last_rtt = - m_con->getPeerStat(PEER_ID_SERVER, con::TIMEOUT_COUNTER); - if (avg_rtt + 2.0f > time_from_last_rtt) - return avg_rtt; - return time_from_last_rtt; -} - -float Client::getCurRate() -{ - return (m_con->getLocalStat(con::CUR_INC_RATE) + - m_con->getLocalStat(con::CUR_DL_RATE)); -} - -void Client::makeScreenshot() -{ - irr::video::IVideoDriver *driver = RenderingEngine::get_video_driver(); - irr::video::IImage* const raw_image = driver->createScreenShot(); - - if (!raw_image) - return; - - time_t t = time(NULL); - struct tm *tm = localtime(&t); - - char timetstamp_c[64]; - strftime(timetstamp_c, sizeof(timetstamp_c), "%Y%m%d_%H%M%S", tm); - - std::string filename_base = g_settings->get("screenshot_path") - + DIR_DELIM - + std::string("screenshot_") - + std::string(timetstamp_c); - std::string filename_ext = "." + g_settings->get("screenshot_format"); - std::string filename; - - u32 quality = (u32)g_settings->getS32("screenshot_quality"); - quality = MYMIN(MYMAX(quality, 0), 100) / 100.0 * 255; - - // Try to find a unique filename - unsigned serial = 0; - - while (serial < SCREENSHOT_MAX_SERIAL_TRIES) { - filename = filename_base + (serial > 0 ? ("_" + itos(serial)) : "") + filename_ext; - std::ifstream tmp(filename.c_str()); - if (!tmp.good()) - break; // File did not apparently exist, we'll go with it - serial++; - } - - if (serial == SCREENSHOT_MAX_SERIAL_TRIES) { - infostream << "Could not find suitable filename for screenshot" << std::endl; - } else { - irr::video::IImage* const image = - driver->createImage(video::ECF_R8G8B8, raw_image->getDimension()); - - if (image) { - raw_image->copyTo(image); - - std::ostringstream sstr; - if (driver->writeImageToFile(image, filename.c_str(), quality)) { - sstr << "Saved screenshot to '" << filename << "'"; - } else { - sstr << "Failed to save screenshot '" << filename << "'"; - } - pushToChatQueue(new ChatMessage(CHATMESSAGE_TYPE_SYSTEM, - narrow_to_wide(sstr.str()))); - infostream << sstr.str() << std::endl; - image->drop(); - } - } - - raw_image->drop(); -} - -bool Client::shouldShowMinimap() const -{ - return !m_minimap_disabled_by_server; -} - -void Client::pushToEventQueue(ClientEvent *event) -{ - m_client_event_queue.push(event); -} - -void Client::showMinimap(const bool show) -{ - m_game_ui->showMinimap(show); -} - -// IGameDef interface -// Under envlock -IItemDefManager* Client::getItemDefManager() -{ - return m_itemdef; -} -const NodeDefManager* Client::getNodeDefManager() -{ - return m_nodedef; -} -ICraftDefManager* Client::getCraftDefManager() -{ - return NULL; - //return m_craftdef; -} -ITextureSource* Client::getTextureSource() -{ - return m_tsrc; -} -IShaderSource* Client::getShaderSource() -{ - return m_shsrc; -} - -u16 Client::allocateUnknownNodeId(const std::string &name) -{ - errorstream << "Client::allocateUnknownNodeId(): " - << "Client cannot allocate node IDs" << std::endl; - FATAL_ERROR("Client allocated unknown node"); - - return CONTENT_IGNORE; -} -ISoundManager* Client::getSoundManager() -{ - return m_sound; -} -MtEventManager* Client::getEventManager() -{ - return m_event; -} - -ParticleManager* Client::getParticleManager() -{ - return &m_particle_manager; -} - -scene::IAnimatedMesh* Client::getMesh(const std::string &filename, bool cache) -{ - StringMap::const_iterator it = m_mesh_data.find(filename); - if (it == m_mesh_data.end()) { - errorstream << "Client::getMesh(): Mesh not found: \"" << filename - << "\"" << std::endl; - return NULL; - } - const std::string &data = it->second; - - // Create the mesh, remove it from cache and return it - // This allows unique vertex colors and other properties for each instance - Buffer data_rw(data.c_str(), data.size()); // Const-incorrect Irrlicht - io::IReadFile *rfile = RenderingEngine::get_filesystem()->createMemoryReadFile( - *data_rw, data_rw.getSize(), filename.c_str()); - FATAL_ERROR_IF(!rfile, "Could not create/open RAM file"); - - scene::IAnimatedMesh *mesh = RenderingEngine::get_scene_manager()->getMesh(rfile); - rfile->drop(); - mesh->grab(); - if (!cache) - RenderingEngine::get_mesh_cache()->removeMesh(mesh); - return mesh; -} - -const std::string* Client::getModFile(const std::string &filename) -{ - StringMap::const_iterator it = m_mod_files.find(filename); - if (it == m_mod_files.end()) { - errorstream << "Client::getModFile(): File not found: \"" << filename - << "\"" << std::endl; - return NULL; - } - return &it->second; -} - -bool Client::registerModStorage(ModMetadata *storage) -{ - if (m_mod_storages.find(storage->getModName()) != m_mod_storages.end()) { - errorstream << "Unable to register same mod storage twice. Storage name: " - << storage->getModName() << std::endl; - return false; - } - - m_mod_storages[storage->getModName()] = storage; - return true; -} - -void Client::unregisterModStorage(const std::string &name) -{ - std::unordered_map::const_iterator it = - m_mod_storages.find(name); - if (it != m_mod_storages.end()) { - // Save unconditionaly on unregistration - it->second->save(getModStoragePath()); - m_mod_storages.erase(name); - } -} - -std::string Client::getModStoragePath() const -{ - return porting::path_user + DIR_DELIM + "client" + DIR_DELIM + "mod_storage"; -} - -/* - * Mod channels - */ - -bool Client::joinModChannel(const std::string &channel) -{ - if (m_modchannel_mgr->channelRegistered(channel)) - return false; - - NetworkPacket pkt(TOSERVER_MODCHANNEL_JOIN, 2 + channel.size()); - pkt << channel; - Send(&pkt); - - m_modchannel_mgr->joinChannel(channel, 0); - return true; -} - -bool Client::leaveModChannel(const std::string &channel) -{ - if (!m_modchannel_mgr->channelRegistered(channel)) - return false; - - NetworkPacket pkt(TOSERVER_MODCHANNEL_LEAVE, 2 + channel.size()); - pkt << channel; - Send(&pkt); - - m_modchannel_mgr->leaveChannel(channel, 0); - return true; -} - -bool Client::sendModChannelMessage(const std::string &channel, const std::string &message) -{ - if (!m_modchannel_mgr->canWriteOnChannel(channel)) - return false; - - if (message.size() > STRING_MAX_LEN) { - warningstream << "ModChannel message too long, dropping before sending " - << " (" << message.size() << " > " << STRING_MAX_LEN << ", channel: " - << channel << ")" << std::endl; - return false; - } - - // @TODO: do some client rate limiting - NetworkPacket pkt(TOSERVER_MODCHANNEL_MSG, 2 + channel.size() + 2 + message.size()); - pkt << channel << message; - Send(&pkt); - return true; -} - -ModChannel* Client::getModChannel(const std::string &channel) -{ - return m_modchannel_mgr->getModChannel(channel); -} diff --git a/src/client.h b/src/client.h deleted file mode 100644 index 68a832d8e..000000000 --- a/src/client.h +++ /dev/null @@ -1,608 +0,0 @@ -/* -Minetest -Copyright (C) 2013 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#pragma once - -#include "clientenvironment.h" -#include "irrlichttypes_extrabloated.h" -#include -#include -#include -#include -#include -#include "clientobject.h" -#include "gamedef.h" -#include "inventorymanager.h" -#include "localplayer.h" -#include "client/hud.h" -#include "particles.h" -#include "mapnode.h" -#include "tileanimation.h" -#include "mesh_generator_thread.h" -#include "network/address.h" -#include "network/peerhandler.h" -#include - -#define CLIENT_CHAT_MESSAGE_LIMIT_PER_10S 10.0f - -struct ClientEvent; -struct MeshMakeData; -struct ChatMessage; -class MapBlockMesh; -class IWritableTextureSource; -class IWritableShaderSource; -class IWritableItemDefManager; -class ISoundManager; -class NodeDefManager; -//class IWritableCraftDefManager; -class ClientMediaDownloader; -struct MapDrawControl; -class ModChannelMgr; -class MtEventManager; -struct PointedThing; -class MapDatabase; -class Minimap; -struct MinimapMapblock; -class Camera; -class NetworkPacket; -namespace con { -class Connection; -} - -enum LocalClientState { - LC_Created, - LC_Init, - LC_Ready -}; - -/* - Packet counter -*/ - -class PacketCounter -{ -public: - PacketCounter() = default; - - void add(u16 command) - { - std::map::iterator n = m_packets.find(command); - if(n == m_packets.end()) - { - m_packets[command] = 1; - } - else - { - n->second++; - } - } - - void clear() - { - for (auto &m_packet : m_packets) { - m_packet.second = 0; - } - } - - void print(std::ostream &o) - { - for (const auto &m_packet : m_packets) { - o << "cmd "<< m_packet.first <<" count "<< m_packet.second << std::endl; - } - } - -private: - // command, count - std::map m_packets; -}; - -class ClientScripting; -class GameUI; - -class Client : public con::PeerHandler, public InventoryManager, public IGameDef -{ -public: - /* - NOTE: Nothing is thread-safe here. - */ - - Client( - const char *playername, - const std::string &password, - const std::string &address_name, - MapDrawControl &control, - IWritableTextureSource *tsrc, - IWritableShaderSource *shsrc, - IWritableItemDefManager *itemdef, - NodeDefManager *nodedef, - ISoundManager *sound, - MtEventManager *event, - bool ipv6, - GameUI *game_ui - ); - - ~Client(); - DISABLE_CLASS_COPY(Client); - - // Load local mods into memory - void scanModSubfolder(const std::string &mod_name, const std::string &mod_path, - std::string mod_subpath); - inline void scanModIntoMemory(const std::string &mod_name, const std::string &mod_path) - { - scanModSubfolder(mod_name, mod_path, ""); - } - - /* - request all threads managed by client to be stopped - */ - void Stop(); - - - bool isShutdown(); - - /* - The name of the local player should already be set when - calling this, as it is sent in the initialization. - */ - void connect(Address address, bool is_local_server); - - /* - Stuff that references the environment is valid only as - long as this is not called. (eg. Players) - If this throws a PeerNotFoundException, the connection has - timed out. - */ - void step(float dtime); - - /* - * Command Handlers - */ - - void handleCommand(NetworkPacket* pkt); - - void handleCommand_Null(NetworkPacket* pkt) {}; - void handleCommand_Deprecated(NetworkPacket* pkt); - void handleCommand_Hello(NetworkPacket* pkt); - void handleCommand_AuthAccept(NetworkPacket* pkt); - void handleCommand_AcceptSudoMode(NetworkPacket* pkt); - void handleCommand_DenySudoMode(NetworkPacket* pkt); - void handleCommand_AccessDenied(NetworkPacket* pkt); - void handleCommand_RemoveNode(NetworkPacket* pkt); - void handleCommand_AddNode(NetworkPacket* pkt); - void handleCommand_BlockData(NetworkPacket* pkt); - void handleCommand_Inventory(NetworkPacket* pkt); - void handleCommand_TimeOfDay(NetworkPacket* pkt); - void handleCommand_ChatMessage(NetworkPacket *pkt); - void handleCommand_ActiveObjectRemoveAdd(NetworkPacket* pkt); - void handleCommand_ActiveObjectMessages(NetworkPacket* pkt); - void handleCommand_Movement(NetworkPacket* pkt); - void handleCommand_HP(NetworkPacket* pkt); - void handleCommand_Breath(NetworkPacket* pkt); - void handleCommand_MovePlayer(NetworkPacket* pkt); - void handleCommand_DeathScreen(NetworkPacket* pkt); - void handleCommand_AnnounceMedia(NetworkPacket* pkt); - void handleCommand_Media(NetworkPacket* pkt); - void handleCommand_NodeDef(NetworkPacket* pkt); - void handleCommand_ItemDef(NetworkPacket* pkt); - void handleCommand_PlaySound(NetworkPacket* pkt); - void handleCommand_StopSound(NetworkPacket* pkt); - void handleCommand_FadeSound(NetworkPacket *pkt); - void handleCommand_Privileges(NetworkPacket* pkt); - void handleCommand_InventoryFormSpec(NetworkPacket* pkt); - void handleCommand_DetachedInventory(NetworkPacket* pkt); - void handleCommand_ShowFormSpec(NetworkPacket* pkt); - void handleCommand_SpawnParticle(NetworkPacket* pkt); - void handleCommand_AddParticleSpawner(NetworkPacket* pkt); - void handleCommand_DeleteParticleSpawner(NetworkPacket* pkt); - void handleCommand_HudAdd(NetworkPacket* pkt); - void handleCommand_HudRemove(NetworkPacket* pkt); - void handleCommand_HudChange(NetworkPacket* pkt); - void handleCommand_HudSetFlags(NetworkPacket* pkt); - void handleCommand_HudSetParam(NetworkPacket* pkt); - void handleCommand_HudSetSky(NetworkPacket* pkt); - void handleCommand_CloudParams(NetworkPacket* pkt); - void handleCommand_OverrideDayNightRatio(NetworkPacket* pkt); - void handleCommand_LocalPlayerAnimations(NetworkPacket* pkt); - void handleCommand_EyeOffset(NetworkPacket* pkt); - void handleCommand_UpdatePlayerList(NetworkPacket* pkt); - void handleCommand_ModChannelMsg(NetworkPacket *pkt); - void handleCommand_ModChannelSignal(NetworkPacket *pkt); - void handleCommand_SrpBytesSandB(NetworkPacket *pkt); - void handleCommand_FormspecPrepend(NetworkPacket *pkt); - void handleCommand_CSMRestrictionFlags(NetworkPacket *pkt); - - void ProcessData(NetworkPacket *pkt); - - void Send(NetworkPacket* pkt); - - void interact(u8 action, const PointedThing& pointed); - - void sendNodemetaFields(v3s16 p, const std::string &formname, - const StringMap &fields); - void sendInventoryFields(const std::string &formname, - const StringMap &fields); - void sendInventoryAction(InventoryAction *a); - void sendChatMessage(const std::wstring &message); - void clearOutChatQueue(); - void sendChangePassword(const std::string &oldpassword, - const std::string &newpassword); - void sendDamage(u8 damage); - void sendRespawn(); - void sendReady(); - - ClientEnvironment& getEnv() { return m_env; } - ITextureSource *tsrc() { return getTextureSource(); } - ISoundManager *sound() { return getSoundManager(); } - static const std::string &getBuiltinLuaPath(); - static const std::string &getClientModsLuaPath(); - - const std::vector &getMods() const override; - const ModSpec* getModSpec(const std::string &modname) const override; - - // Causes urgent mesh updates (unlike Map::add/removeNodeWithEvent) - void removeNode(v3s16 p); - - /** - * Helper function for Client Side Modding - * CSM restrictions are applied there, this should not be used for core engine - * @param p - * @param is_valid_position - * @return - */ - MapNode getNode(v3s16 p, bool *is_valid_position); - void addNode(v3s16 p, MapNode n, bool remove_metadata = true); - - void setPlayerControl(PlayerControl &control); - - void selectPlayerItem(u16 item); - u16 getPlayerItem() const - { return m_playeritem; } - - // Returns true if the inventory of the local player has been - // updated from the server. If it is true, it is set to false. - bool getLocalInventoryUpdated(); - // Copies the inventory of the local player to parameter - void getLocalInventory(Inventory &dst); - - /* InventoryManager interface */ - Inventory* getInventory(const InventoryLocation &loc) override; - void inventoryAction(InventoryAction *a) override; - - const std::list &getConnectedPlayerNames() - { - return m_env.getPlayerNames(); - } - - float getAnimationTime(); - - int getCrackLevel(); - v3s16 getCrackPos(); - void setCrack(int level, v3s16 pos); - - u16 getHP(); - - bool checkPrivilege(const std::string &priv) const - { return (m_privileges.count(priv) != 0); } - - const std::unordered_set &getPrivilegeList() const - { return m_privileges; } - - bool getChatMessage(std::wstring &message); - void typeChatMessage(const std::wstring& message); - - u64 getMapSeed(){ return m_map_seed; } - - void addUpdateMeshTask(v3s16 blockpos, bool ack_to_server=false, bool urgent=false); - // Including blocks at appropriate edges - void addUpdateMeshTaskWithEdge(v3s16 blockpos, bool ack_to_server=false, bool urgent=false); - void addUpdateMeshTaskForNode(v3s16 nodepos, bool ack_to_server=false, bool urgent=false); - - void updateCameraOffset(v3s16 camera_offset) - { m_mesh_update_thread.m_camera_offset = camera_offset; } - - bool hasClientEvents() const { return !m_client_event_queue.empty(); } - // Get event from queue. If queue is empty, it triggers an assertion failure. - ClientEvent * getClientEvent(); - - bool accessDenied() const { return m_access_denied; } - - bool reconnectRequested() const { return m_access_denied_reconnect; } - - void setFatalError(const std::string &reason) - { - m_access_denied = true; - m_access_denied_reason = reason; - } - - // Renaming accessDeniedReason to better name could be good as it's used to - // disconnect client when CSM failed. - const std::string &accessDeniedReason() const { return m_access_denied_reason; } - - bool itemdefReceived() - { return m_itemdef_received; } - bool nodedefReceived() - { return m_nodedef_received; } - bool mediaReceived() - { return !m_media_downloader; } - - u8 getProtoVersion() - { return m_proto_ver; } - - bool connectedToServer(); - void confirmRegistration(); - bool m_is_registration_confirmation_state = false; - bool m_simple_singleplayer_mode; - - float mediaReceiveProgress(); - - void afterContentReceived(); - - float getRTT(); - float getCurRate(); - - Minimap* getMinimap() { return m_minimap; } - void setCamera(Camera* camera) { m_camera = camera; } - - Camera* getCamera () { return m_camera; } - - bool shouldShowMinimap() const; - - // IGameDef interface - IItemDefManager* getItemDefManager() override; - const NodeDefManager* getNodeDefManager() override; - ICraftDefManager* getCraftDefManager() override; - ITextureSource* getTextureSource(); - virtual IShaderSource* getShaderSource(); - u16 allocateUnknownNodeId(const std::string &name) override; - virtual ISoundManager* getSoundManager(); - MtEventManager* getEventManager(); - virtual ParticleManager* getParticleManager(); - bool checkLocalPrivilege(const std::string &priv) - { return checkPrivilege(priv); } - virtual scene::IAnimatedMesh* getMesh(const std::string &filename, bool cache = false); - const std::string* getModFile(const std::string &filename); - - std::string getModStoragePath() const override; - bool registerModStorage(ModMetadata *meta) override; - void unregisterModStorage(const std::string &name) override; - - // The following set of functions is used by ClientMediaDownloader - // Insert a media file appropriately into the appropriate manager - bool loadMedia(const std::string &data, const std::string &filename); - // Send a request for conventional media transfer - void request_media(const std::vector &file_requests); - - LocalClientState getState() { return m_state; } - - void makeScreenshot(); - - inline void pushToChatQueue(ChatMessage *cec) - { - m_chat_queue.push(cec); - } - - ClientScripting *getScript() { return m_script; } - const bool moddingEnabled() const { return m_modding_enabled; } - const bool modsLoaded() const { return m_mods_loaded; } - - void pushToEventQueue(ClientEvent *event); - - void showMinimap(bool show = true); - - const Address getServerAddress(); - - const std::string &getAddressName() const - { - return m_address_name; - } - - inline bool checkCSMRestrictionFlag(CSMRestrictionFlags flag) const - { - return m_csm_restriction_flags & flag; - } - - u32 getCSMNodeRangeLimit() const - { - return m_csm_restriction_noderange; - } - - inline std::unordered_map &getHUDTranslationMap() - { - return m_hud_server_to_client; - } - - bool joinModChannel(const std::string &channel) override; - bool leaveModChannel(const std::string &channel) override; - bool sendModChannelMessage(const std::string &channel, - const std::string &message) override; - ModChannel *getModChannel(const std::string &channel) override; - - const std::string &getFormspecPrepend() const - { - return m_env.getLocalPlayer()->formspec_prepend; - } -private: - void loadMods(); - bool checkBuiltinIntegrity(); - - // Virtual methods from con::PeerHandler - void peerAdded(con::Peer *peer) override; - void deletingPeer(con::Peer *peer, bool timeout) override; - - void initLocalMapSaving(const Address &address, - const std::string &hostname, - bool is_local_server); - - void ReceiveAll(); - void Receive(); - - void sendPlayerPos(); - // Send the item number 'item' as player item to the server - void sendPlayerItem(u16 item); - - void deleteAuthData(); - // helper method shared with clientpackethandler - 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 &blocks); - void sendGotBlocks(v3s16 block); - void sendRemovedSounds(std::vector &soundList); - - // Helper function - inline std::string getPlayerName() - { return m_env.getLocalPlayer()->getName(); } - - bool canSendChatMessage() const; - - float m_packetcounter_timer = 0.0f; - float m_connection_reinit_timer = 0.1f; - float m_avg_rtt_timer = 0.0f; - float m_playerpos_send_timer = 0.0f; - IntervalLimiter m_map_timer_and_unload_interval; - - IWritableTextureSource *m_tsrc; - IWritableShaderSource *m_shsrc; - IWritableItemDefManager *m_itemdef; - NodeDefManager *m_nodedef; - ISoundManager *m_sound; - MtEventManager *m_event; - - - MeshUpdateThread m_mesh_update_thread; - ClientEnvironment m_env; - ParticleManager m_particle_manager; - std::unique_ptr m_con; - std::string m_address_name; - Camera *m_camera = nullptr; - Minimap *m_minimap = nullptr; - bool m_minimap_disabled_by_server = false; - // Server serialization version - u8 m_server_ser_ver; - - // Used version of the protocol with server - // Values smaller than 25 only mean they are smaller than 25, - // and aren't accurate. We simply just don't know, because - // the server didn't send the version back then. - // If 0, server init hasn't been received yet. - u8 m_proto_ver = 0; - - u16 m_playeritem = 0; - bool m_inventory_updated = false; - Inventory *m_inventory_from_server = nullptr; - float m_inventory_from_server_age = 0.0f; - PacketCounter m_packetcounter; - // Block mesh animation parameters - float m_animation_time = 0.0f; - int m_crack_level = -1; - v3s16 m_crack_pos; - // 0 <= m_daynight_i < DAYNIGHT_CACHE_COUNT - //s32 m_daynight_i; - //u32 m_daynight_ratio; - std::queue m_out_chat_queue; - u32 m_last_chat_message_sent; - float m_chat_message_allowance = 5.0f; - std::queue m_chat_queue; - - // The authentication methods we can use to enter sudo mode (=change password) - u32 m_sudo_auth_methods; - - // The seed returned by the server in TOCLIENT_INIT is stored here - u64 m_map_seed = 0; - - // Auth data - std::string m_playername; - std::string m_password; - // If set, this will be sent (and cleared) upon a TOCLIENT_ACCEPT_SUDO_MODE - std::string m_new_password; - // Usable by auth mechanisms. - AuthMechanism m_chosen_auth_mech; - void *m_auth_data = nullptr; - - - bool m_access_denied = false; - bool m_access_denied_reconnect = false; - std::string m_access_denied_reason = ""; - std::queue m_client_event_queue; - bool m_itemdef_received = false; - bool m_nodedef_received = false; - bool m_mods_loaded = false; - ClientMediaDownloader *m_media_downloader; - - // time_of_day speed approximation for old protocol - bool m_time_of_day_set = false; - float m_last_time_of_day_f = -1.0f; - float m_time_of_day_update_timer = 0.0f; - - // An interval for generally sending object positions and stuff - float m_recommended_send_interval = 0.1f; - - // Sounds - float m_removed_sounds_check_timer = 0.0f; - // Mapping from server sound ids to our sound ids - std::unordered_map m_sounds_server_to_client; - // And the other way! - std::unordered_map m_sounds_client_to_server; - // And relations to objects - std::unordered_map m_sounds_to_objects; - - // CSM/client IDs to SSM/server IDs Mapping - // Map server particle spawner IDs to client IDs - std::unordered_map m_particles_server_to_client; - // Map server hud ids to client hud ids - std::unordered_map m_hud_server_to_client; - - // Privileges - std::unordered_set m_privileges; - - // Detached inventories - // key = name - std::unordered_map m_detached_inventories; - - // Storage for mesh data for creating multiple instances of the same mesh - StringMap m_mesh_data; - - StringMap m_mod_files; - - // own state - LocalClientState m_state; - - GameUI *m_game_ui; - - // Used for saving server map to disk client-side - MapDatabase *m_localdb = nullptr; - IntervalLimiter m_localdb_save_interval; - u16 m_cache_save_interval; - - ClientScripting *m_script = nullptr; - bool m_modding_enabled; - std::unordered_map m_mod_storages; - float m_mod_storage_save_timer = 10.0f; - std::vector m_mods; - - bool m_shutdown = false; - - // CSM restrictions byteflag - u64 m_csm_restriction_flags = CSMRestrictionFlags::CSM_RF_NONE; - u32 m_csm_restriction_noderange = 8; - - std::unique_ptr m_modchannel_mgr; -}; diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt index 1cabe1b11..e24b73e80 100644 --- a/src/client/CMakeLists.txt +++ b/src/client/CMakeLists.txt @@ -25,12 +25,37 @@ set(client_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/render/plain.cpp ${CMAKE_CURRENT_SOURCE_DIR}/render/sidebyside.cpp ${CMAKE_CURRENT_SOURCE_DIR}/render/stereo.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/renderingengine.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/camera.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/client.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/clientenvironment.cpp ${CMAKE_CURRENT_SOURCE_DIR}/clientlauncher.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/clientmap.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/clientmedia.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/clientobject.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/clouds.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/content_cao.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/content_cso.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/content_mapblock.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/filecache.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/fontengine.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/game.cpp ${CMAKE_CURRENT_SOURCE_DIR}/gameui.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/guiscalingfilter.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/hud.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/imagefilters.cpp ${CMAKE_CURRENT_SOURCE_DIR}/inputhandler.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/tile.cpp ${CMAKE_CURRENT_SOURCE_DIR}/joystick_controller.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/hud.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/keycode.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/localplayer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mapblock_mesh.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mesh.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mesh_generator_thread.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/minimap.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/particles.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/renderingengine.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/shader.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/sky.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tile.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/wieldmesh.cpp PARENT_SCOPE ) diff --git a/src/client/camera.cpp b/src/client/camera.cpp new file mode 100644 index 000000000..1bbdb56ea --- /dev/null +++ b/src/client/camera.cpp @@ -0,0 +1,658 @@ +/* +Minetest +Copyright (C) 2010-2013 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "camera.h" +#include "debug.h" +#include "client.h" +#include "map.h" +#include "clientmap.h" // MapDrawControl +#include "player.h" +#include +#include "client/renderingengine.h" +#include "settings.h" +#include "wieldmesh.h" +#include "noise.h" // easeCurve +#include "sound.h" +#include "event.h" +#include "nodedef.h" +#include "util/numeric.h" +#include "constants.h" +#include "fontengine.h" +#include "script/scripting_client.h" + +#define CAMERA_OFFSET_STEP 200 +#define WIELDMESH_OFFSET_X 55.0f +#define WIELDMESH_OFFSET_Y -35.0f + +Camera::Camera(MapDrawControl &draw_control, Client *client): + m_draw_control(draw_control), + m_client(client) +{ + scene::ISceneManager *smgr = RenderingEngine::get_scene_manager(); + // note: making the camera node a child of the player node + // would lead to unexpected behaviour, so we don't do that. + m_playernode = smgr->addEmptySceneNode(smgr->getRootSceneNode()); + m_headnode = smgr->addEmptySceneNode(m_playernode); + m_cameranode = smgr->addCameraSceneNode(smgr->getRootSceneNode()); + m_cameranode->bindTargetAndRotation(true); + + // This needs to be in its own scene manager. It is drawn after + // all other 3D scene nodes and before the GUI. + m_wieldmgr = smgr->createNewSceneManager(); + m_wieldmgr->addCameraSceneNode(); + m_wieldnode = new WieldMeshSceneNode(m_wieldmgr, -1, false); + m_wieldnode->setItem(ItemStack(), m_client); + m_wieldnode->drop(); // m_wieldmgr grabbed it + + /* TODO: Add a callback function so these can be updated when a setting + * changes. At this point in time it doesn't matter (e.g. /set + * is documented to change server settings only) + * + * TODO: Local caching of settings is not optimal and should at some stage + * be updated to use a global settings object for getting thse values + * (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"); + // 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_arm_inertia = g_settings->getBool("arm_inertia"); + m_nametags.clear(); +} + +Camera::~Camera() +{ + m_wieldmgr->drop(); +} + +bool Camera::successfullyCreated(std::string &error_message) +{ + if (!m_playernode) { + error_message = "Failed to create the player scene node"; + } else if (!m_headnode) { + error_message = "Failed to create the head scene node"; + } else if (!m_cameranode) { + error_message = "Failed to create the camera scene node"; + } else if (!m_wieldmgr) { + error_message = "Failed to create the wielded item scene manager"; + } else if (!m_wieldnode) { + error_message = "Failed to create the wielded item scene node"; + } else { + error_message.clear(); + } + + if (g_settings->getBool("enable_client_modding")) { + m_client->getScript()->on_camera_ready(this); + } + return error_message.empty(); +} + +// Returns the fractional part of x +inline f32 my_modf(f32 x) +{ + double dummy; + return modf(x, &dummy); +} + +void Camera::step(f32 dtime) +{ + if(m_view_bobbing_fall > 0) + { + m_view_bobbing_fall -= 3 * dtime; + if(m_view_bobbing_fall <= 0) + m_view_bobbing_fall = -1; // Mark the effect as finished + } + + 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) + m_wieldnode->setItem(m_wield_item_next, m_client); + + if (m_view_bobbing_state != 0) + { + //f32 offset = dtime * m_view_bobbing_speed * 0.035; + f32 offset = dtime * m_view_bobbing_speed * 0.030; + if (m_view_bobbing_state == 2) { + // Animation is getting turned off + if (m_view_bobbing_anim < 0.25) { + m_view_bobbing_anim -= offset; + } else if (m_view_bobbing_anim > 0.75) { + m_view_bobbing_anim += offset; + } + + if (m_view_bobbing_anim < 0.5) { + m_view_bobbing_anim += offset; + if (m_view_bobbing_anim > 0.5) + m_view_bobbing_anim = 0.5; + } else { + m_view_bobbing_anim -= offset; + if (m_view_bobbing_anim < 0.5) + m_view_bobbing_anim = 0.5; + } + + if (m_view_bobbing_anim <= 0 || m_view_bobbing_anim >= 1 || + fabs(m_view_bobbing_anim - 0.5) < 0.01) { + m_view_bobbing_anim = 0; + m_view_bobbing_state = 0; + } + } + else { + float was = m_view_bobbing_anim; + m_view_bobbing_anim = my_modf(m_view_bobbing_anim + offset); + bool step = (was == 0 || + (was < 0.5f && m_view_bobbing_anim >= 0.5f) || + (was > 0.5f && m_view_bobbing_anim <= 0.5f)); + if(step) { + m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::VIEW_BOBBING_STEP)); + } + } + } + + if (m_digging_button != -1) { + f32 offset = dtime * 3.5f; + float m_digging_anim_was = m_digging_anim; + m_digging_anim += offset; + if (m_digging_anim >= 1) + { + m_digging_anim = 0; + m_digging_button = -1; + } + float lim = 0.15; + if(m_digging_anim_was < lim && m_digging_anim >= lim) + { + if (m_digging_button == 0) { + m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::CAMERA_PUNCH_LEFT)); + } else if(m_digging_button == 1) { + m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::CAMERA_PUNCH_RIGHT)); + } + } + } +} + +static inline v2f dir(const v2f &pos_dist) +{ + f32 x = pos_dist.X - WIELDMESH_OFFSET_X; + f32 y = pos_dist.Y - WIELDMESH_OFFSET_Y; + + f32 x_abs = std::fabs(x); + f32 y_abs = std::fabs(y); + + if (x_abs >= y_abs) { + y *= (1.0f / x_abs); + x /= x_abs; + } + + if (y_abs >= x_abs) { + x *= (1.0f / y_abs); + y /= y_abs; + } + + return v2f(std::fabs(x), std::fabs(y)); +} + +void Camera::addArmInertia(f32 player_yaw) +{ + m_cam_vel.X = std::fabs(rangelim(m_last_cam_pos.X - player_yaw, + -100.0f, 100.0f) / 0.016f) * 0.01f; + m_cam_vel.Y = std::fabs((m_last_cam_pos.Y - m_camera_direction.Y) / 0.016f); + f32 gap_X = std::fabs(WIELDMESH_OFFSET_X - m_wieldmesh_offset.X); + f32 gap_Y = std::fabs(WIELDMESH_OFFSET_Y - m_wieldmesh_offset.Y); + + if (m_cam_vel.X > 1.0f || m_cam_vel.Y > 1.0f) { + /* + The arm moves relative to the camera speed, + with an acceleration factor. + */ + + if (m_cam_vel.X > 1.0f) { + if (m_cam_vel.X > m_cam_vel_old.X) + m_cam_vel_old.X = m_cam_vel.X; + + f32 acc_X = 0.12f * (m_cam_vel.X - (gap_X * 0.1f)); + m_wieldmesh_offset.X += m_last_cam_pos.X < player_yaw ? acc_X : -acc_X; + + if (m_last_cam_pos.X != player_yaw) + m_last_cam_pos.X = player_yaw; + + m_wieldmesh_offset.X = rangelim(m_wieldmesh_offset.X, + WIELDMESH_OFFSET_X - 7.0f, WIELDMESH_OFFSET_X + 7.0f); + } + + if (m_cam_vel.Y > 1.0f) { + if (m_cam_vel.Y > m_cam_vel_old.Y) + m_cam_vel_old.Y = m_cam_vel.Y; + + f32 acc_Y = 0.12f * (m_cam_vel.Y - (gap_Y * 0.1f)); + m_wieldmesh_offset.Y += + m_last_cam_pos.Y > m_camera_direction.Y ? acc_Y : -acc_Y; + + if (m_last_cam_pos.Y != m_camera_direction.Y) + m_last_cam_pos.Y = m_camera_direction.Y; + + m_wieldmesh_offset.Y = rangelim(m_wieldmesh_offset.Y, + WIELDMESH_OFFSET_Y - 10.0f, WIELDMESH_OFFSET_Y + 5.0f); + } + + m_arm_dir = dir(m_wieldmesh_offset); + } else { + /* + Now the arm gets back to its default position when the camera stops, + following a vector, with a smooth deceleration factor. + */ + + f32 dec_X = 0.12f * (m_cam_vel_old.X * (1.0f + + (1.0f - m_arm_dir.X))) * (gap_X / 20.0f); + + f32 dec_Y = 0.06f * (m_cam_vel_old.Y * (1.0f + + (1.0f - m_arm_dir.Y))) * (gap_Y / 15.0f); + + if (gap_X < 0.1f) + m_cam_vel_old.X = 0.0f; + + m_wieldmesh_offset.X -= + m_wieldmesh_offset.X > WIELDMESH_OFFSET_X ? dec_X : -dec_X; + + if (gap_Y < 0.1f) + m_cam_vel_old.Y = 0.0f; + + m_wieldmesh_offset.Y -= + m_wieldmesh_offset.Y > WIELDMESH_OFFSET_Y ? dec_Y : -dec_Y; + } +} + +void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_reload_ratio) +{ + // Get player position + // Smooth the movement when walking up stairs + v3f old_player_position = m_playernode->getPosition(); + v3f player_position = player->getPosition(); + if (player->isAttached && player->parent) + player_position = player->parent->getPosition(); + //if(player->touching_ground && player_position.Y > old_player_position.Y) + if(player->touching_ground && + player_position.Y > old_player_position.Y) + { + f32 oldy = old_player_position.Y; + f32 newy = player_position.Y; + f32 t = std::exp(-23 * frametime); + player_position.Y = oldy * t + newy * (1-t); + } + + // Set player node transformation + m_playernode->setPosition(player_position); + m_playernode->setRotation(v3f(0, -1 * player->getYaw(), 0)); + m_playernode->updateAbsolutePosition(); + + // Get camera tilt timer (hurt animation) + float cameratilt = fabs(fabs(player->hurt_tilt_timer-0.75)-0.75); + + // Fall bobbing animation + float fall_bobbing = 0; + if(player->camera_impact >= 1 && m_camera_mode < CAMERA_MODE_THIRD) + { + if(m_view_bobbing_fall == -1) // Effect took place and has finished + player->camera_impact = m_view_bobbing_fall = 0; + else if(m_view_bobbing_fall == 0) // Initialize effect + m_view_bobbing_fall = 1; + + // Convert 0 -> 1 to 0 -> 1 -> 0 + fall_bobbing = m_view_bobbing_fall < 0.5 ? m_view_bobbing_fall * 2 : -(m_view_bobbing_fall - 0.5) * 2 + 1; + // Smoothen and invert the above + fall_bobbing = sin(fall_bobbing * 0.5 * M_PI) * -1; + // Amplify according to the intensity of the impact + fall_bobbing *= (1 - rangelim(50 / player->camera_impact, 0, 1)) * 5; + + fall_bobbing *= m_cache_fall_bobbing_amount; + } + + // Calculate players eye offset for different camera modes + v3f PlayerEyeOffset = player->getEyeOffset(); + if (m_camera_mode == CAMERA_MODE_FIRST) + PlayerEyeOffset += player->eye_offset_first; + else + PlayerEyeOffset += player->eye_offset_third; + + // Set head node transformation + m_headnode->setPosition(PlayerEyeOffset+v3f(0,cameratilt*-player->hurt_tilt_strength+fall_bobbing,0)); + m_headnode->setRotation(v3f(player->getPitch(), 0, cameratilt*player->hurt_tilt_strength)); + m_headnode->updateAbsolutePosition(); + + // Compute relative camera position and target + v3f rel_cam_pos = v3f(0,0,0); + v3f rel_cam_target = v3f(0,0,1); + v3f rel_cam_up = v3f(0,1,0); + + if (m_cache_view_bobbing_amount != 0.0f && m_view_bobbing_anim != 0.0f && + m_camera_mode < CAMERA_MODE_THIRD) { + f32 bobfrac = my_modf(m_view_bobbing_anim * 2); + f32 bobdir = (m_view_bobbing_anim < 0.5) ? 1.0 : -1.0; + + #if 1 + f32 bobknob = 1.2; + f32 bobtmp = sin(pow(bobfrac, bobknob) * M_PI); + //f32 bobtmp2 = cos(pow(bobfrac, bobknob) * M_PI); + + v3f bobvec = v3f( + 0.3 * bobdir * sin(bobfrac * M_PI), + -0.28 * bobtmp * bobtmp, + 0.); + + //rel_cam_pos += 0.2 * bobvec; + //rel_cam_target += 0.03 * bobvec; + //rel_cam_up.rotateXYBy(0.02 * bobdir * bobtmp * M_PI); + float f = 1.0; + f *= m_cache_view_bobbing_amount; + rel_cam_pos += bobvec * f; + //rel_cam_target += 0.995 * bobvec * f; + rel_cam_target += bobvec * f; + rel_cam_target.Z -= 0.005 * bobvec.Z * f; + //rel_cam_target.X -= 0.005 * bobvec.X * f; + //rel_cam_target.Y -= 0.005 * bobvec.Y * f; + rel_cam_up.rotateXYBy(-0.03 * bobdir * bobtmp * M_PI * f); + #else + f32 angle_deg = 1 * bobdir * sin(bobfrac * M_PI); + f32 angle_rad = angle_deg * M_PI / 180; + f32 r = 0.05; + v3f off = v3f( + r * sin(angle_rad), + r * (cos(angle_rad) - 1), + 0); + rel_cam_pos += off; + //rel_cam_target += off; + rel_cam_up.rotateXYBy(angle_deg); + #endif + + } + + // Compute absolute camera position and target + m_headnode->getAbsoluteTransformation().transformVect(m_camera_position, rel_cam_pos); + m_headnode->getAbsoluteTransformation().rotateVect(m_camera_direction, rel_cam_target - rel_cam_pos); + + v3f abs_cam_up; + m_headnode->getAbsoluteTransformation().rotateVect(abs_cam_up, rel_cam_up); + + // Seperate camera position for calculation + v3f my_cp = m_camera_position; + + // Reposition the camera for third person view + if (m_camera_mode > CAMERA_MODE_FIRST) + { + if (m_camera_mode == CAMERA_MODE_THIRD_FRONT) + m_camera_direction *= -1; + + my_cp.Y += 2; + + // Calculate new position + bool abort = false; + for (int i = BS; i <= BS * 2.75; i++) { + my_cp.X = m_camera_position.X + m_camera_direction.X * -i; + my_cp.Z = m_camera_position.Z + m_camera_direction.Z * -i; + if (i > 12) + my_cp.Y = m_camera_position.Y + (m_camera_direction.Y * -i); + + // Prevent camera positioned inside nodes + const NodeDefManager *nodemgr = m_client->ndef(); + MapNode n = m_client->getEnv().getClientMap() + .getNodeNoEx(floatToInt(my_cp, BS)); + + const ContentFeatures& features = nodemgr->get(n); + if (features.walkable) { + my_cp.X += m_camera_direction.X*-1*-BS/2; + my_cp.Z += m_camera_direction.Z*-1*-BS/2; + my_cp.Y += m_camera_direction.Y*-1*-BS/2; + abort = true; + break; + } + } + + // If node blocks camera position don't move y to heigh + if (abort && my_cp.Y > player_position.Y+BS*2) + my_cp.Y = player_position.Y+BS*2; + } + + // Update offset if too far away from the center of the map + m_camera_offset.X += CAMERA_OFFSET_STEP* + (((s16)(my_cp.X/BS) - m_camera_offset.X)/CAMERA_OFFSET_STEP); + m_camera_offset.Y += CAMERA_OFFSET_STEP* + (((s16)(my_cp.Y/BS) - m_camera_offset.Y)/CAMERA_OFFSET_STEP); + m_camera_offset.Z += CAMERA_OFFSET_STEP* + (((s16)(my_cp.Z/BS) - m_camera_offset.Z)/CAMERA_OFFSET_STEP); + + // Set camera node transformation + m_cameranode->setPosition(my_cp-intToFloat(m_camera_offset, BS)); + m_cameranode->setUpVector(abs_cam_up); + // *100.0 helps in large map coordinates + m_cameranode->setTarget(my_cp-intToFloat(m_camera_offset, BS) + 100 * m_camera_direction); + + // update the camera position in third-person mode to render blocks behind player + // and correctly apply liquid post FX. + if (m_camera_mode != CAMERA_MODE_FIRST) + m_camera_position = my_cp; + + // Get FOV + f32 fov_degrees; + // Disable zoom with zoom FOV = 0 + if (player->getPlayerControl().zoom && player->getZoomFOV() > 0.001f) { + fov_degrees = player->getZoomFOV(); + } else { + fov_degrees = m_cache_fov; + } + fov_degrees = rangelim(fov_degrees, 1.0f, 160.0f); + + // FOV and aspect ratio + const v2u32 &window_size = RenderingEngine::get_instance()->getWindowSize(); + m_aspect = (f32) window_size.X / (f32) window_size.Y; + m_fov_y = fov_degrees * M_PI / 180.0; + // Increase vertical FOV on lower aspect ratios (<16:10) + m_fov_y *= MYMAX(1.0, MYMIN(1.4, sqrt(16./10. / m_aspect))); + m_fov_x = 2 * atan(m_aspect * tan(0.5 * m_fov_y)); + m_cameranode->setAspectRatio(m_aspect); + m_cameranode->setFOV(m_fov_y); + + if (m_arm_inertia) + addArmInertia(player->getYaw()); + + // Position the wielded item + //v3f wield_position = v3f(45, -35, 65); + v3f wield_position = v3f(m_wieldmesh_offset.X, m_wieldmesh_offset.Y, 65); + //v3f wield_rotation = v3f(-100, 120, -100); + v3f wield_rotation = v3f(-100, 120, -100); + wield_position.Y += fabs(m_wield_change_timer)*320 - 40; + if(m_digging_anim < 0.05 || m_digging_anim > 0.5) + { + f32 frac = 1.0; + if(m_digging_anim > 0.5) + frac = 2.0 * (m_digging_anim - 0.5); + // This value starts from 1 and settles to 0 + f32 ratiothing = std::pow((1.0f - tool_reload_ratio), 0.5f); + //f32 ratiothing2 = pow(ratiothing, 0.5f); + f32 ratiothing2 = (easeCurve(ratiothing*0.5))*2.0; + wield_position.Y -= frac * 25.0 * pow(ratiothing2, 1.7f); + //wield_position.Z += frac * 5.0 * ratiothing2; + wield_position.X -= frac * 35.0 * pow(ratiothing2, 1.1f); + wield_rotation.Y += frac * 70.0 * pow(ratiothing2, 1.4f); + //wield_rotation.X -= frac * 15.0 * pow(ratiothing2, 1.4f); + //wield_rotation.Z += frac * 15.0 * pow(ratiothing2, 1.0f); + } + if (m_digging_button != -1) + { + f32 digfrac = m_digging_anim; + wield_position.X -= 50 * sin(pow(digfrac, 0.8f) * M_PI); + wield_position.Y += 24 * sin(digfrac * 1.8 * M_PI); + wield_position.Z += 25 * 0.5; + + // Euler angles are PURE EVIL, so why not use quaternions? + core::quaternion quat_begin(wield_rotation * core::DEGTORAD); + core::quaternion quat_end(v3f(80, 30, 100) * core::DEGTORAD); + core::quaternion quat_slerp; + quat_slerp.slerp(quat_begin, quat_end, sin(digfrac * M_PI)); + quat_slerp.toEuler(wield_rotation); + wield_rotation *= core::RADTODEG; + } else { + f32 bobfrac = my_modf(m_view_bobbing_anim); + wield_position.X -= sin(bobfrac*M_PI*2.0) * 3.0; + wield_position.Y += sin(my_modf(bobfrac*2.0)*M_PI) * 3.0; + } + m_wieldnode->setPosition(wield_position); + m_wieldnode->setRotation(wield_rotation); + + m_wieldnode->setColor(player->light_color); + + // Set render distance + updateViewingRange(); + + // If the player is walking, swimming, or climbing, + // view bobbing is enabled and free_move is off, + // start (or continue) the view bobbing animation. + const v3f &speed = player->getSpeed(); + const bool movement_XZ = hypot(speed.X, speed.Z) > BS; + const bool movement_Y = fabs(speed.Y) > BS; + + const bool walking = movement_XZ && player->touching_ground; + const bool swimming = (movement_XZ || player->swimming_vertical) && player->in_liquid; + const bool climbing = movement_Y && player->is_climbing; + if ((walking || swimming || climbing) && + (!g_settings->getBool("free_move") || !m_client->checkLocalPrivilege("fly"))) { + // Start animation + m_view_bobbing_state = 1; + m_view_bobbing_speed = MYMIN(speed.getLength(), 70); + } + else if (m_view_bobbing_state == 1) + { + // Stop animation + m_view_bobbing_state = 2; + m_view_bobbing_speed = 60; + } +} + +void Camera::updateViewingRange() +{ + f32 viewing_range = g_settings->getFloat("viewing_range"); + f32 near_plane = g_settings->getFloat("near_plane"); + + m_draw_control.wanted_range = std::fmin(adjustDist(viewing_range, getFovMax()), 4000); + m_cameranode->setNearValue(rangelim(near_plane, 0.0f, 0.5f) * BS); + if (m_draw_control.range_all) { + m_cameranode->setFarValue(100000.0); + return; + } + m_cameranode->setFarValue((viewing_range < 2000) ? 2000 * BS : viewing_range * BS); +} + +void Camera::setDigging(s32 button) +{ + if (m_digging_button == -1) + m_digging_button = button; +} + +void Camera::wield(const ItemStack &item) +{ + if (item.name != m_wield_item_next.name || + item.metadata != m_wield_item_next.metadata) { + m_wield_item_next = item; + if (m_wield_change_timer > 0) + m_wield_change_timer = -m_wield_change_timer; + else if (m_wield_change_timer == 0) + m_wield_change_timer = -0.001; + } +} + +void Camera::drawWieldedTool(irr::core::matrix4* translation) +{ + // Clear Z buffer so that the wielded tool stay in front of world geometry + m_wieldmgr->getVideoDriver()->clearZBuffer(); + + // Draw the wielded node (in a separate scene manager) + scene::ICameraSceneNode* cam = m_wieldmgr->getActiveCamera(); + cam->setAspectRatio(m_cameranode->getAspectRatio()); + cam->setFOV(72.0*M_PI/180.0); + cam->setNearValue(10); + cam->setFarValue(1000); + if (translation != NULL) + { + irr::core::matrix4 startMatrix = cam->getAbsoluteTransformation(); + irr::core::vector3df focusPoint = (cam->getTarget() + - cam->getAbsolutePosition()).setLength(1) + + cam->getAbsolutePosition(); + + irr::core::vector3df camera_pos = + (startMatrix * *translation).getTranslation(); + cam->setPosition(camera_pos); + cam->setTarget(focusPoint); + } + m_wieldmgr->drawAll(); +} + +void Camera::drawNametags() +{ + core::matrix4 trans = m_cameranode->getProjectionMatrix(); + trans *= m_cameranode->getViewMatrix(); + + for (std::list::const_iterator + i = m_nametags.begin(); + i != m_nametags.end(); ++i) { + Nametag *nametag = *i; + if (nametag->nametag_color.getAlpha() == 0) { + // Enforce hiding nametag, + // because if freetype is enabled, a grey + // shadow can remain. + continue; + } + v3f pos = nametag->parent_node->getAbsolutePosition() + nametag->nametag_pos * BS; + f32 transformed_pos[4] = { pos.X, pos.Y, pos.Z, 1.0f }; + trans.multiplyWith1x4Matrix(transformed_pos); + if (transformed_pos[3] > 0) { + std::wstring nametag_colorless = + unescape_translate(utf8_to_wide(nametag->nametag_text)); + core::dimension2d textsize = + g_fontengine->getFont()->getDimension( + nametag_colorless.c_str()); + f32 zDiv = transformed_pos[3] == 0.0f ? 1.0f : + core::reciprocal(transformed_pos[3]); + v2u32 screensize = RenderingEngine::get_video_driver()->getScreenSize(); + v2s32 screen_pos; + screen_pos.X = screensize.X * + (0.5 * transformed_pos[0] * zDiv + 0.5) - textsize.Width / 2; + screen_pos.Y = screensize.Y * + (0.5 - transformed_pos[1] * zDiv * 0.5) - textsize.Height / 2; + core::rect size(0, 0, textsize.Width, textsize.Height); + g_fontengine->getFont()->draw( + translate_string(utf8_to_wide(nametag->nametag_text)).c_str(), + size + screen_pos, nametag->nametag_color); + } + } +} + +Nametag *Camera::addNametag(scene::ISceneNode *parent_node, + const std::string &nametag_text, video::SColor nametag_color, + const v3f &pos) +{ + Nametag *nametag = new Nametag(parent_node, nametag_text, nametag_color, pos); + m_nametags.push_back(nametag); + return nametag; +} + +void Camera::removeNametag(Nametag *nametag) +{ + m_nametags.remove(nametag); + delete nametag; +} diff --git a/src/client/camera.h b/src/client/camera.h new file mode 100644 index 000000000..88de3570a --- /dev/null +++ b/src/client/camera.h @@ -0,0 +1,231 @@ +/* +Minetest +Copyright (C) 2010-2013 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include "irrlichttypes_extrabloated.h" +#include "inventory.h" +#include "client/tile.h" +#include +#include +#include + +class LocalPlayer; +struct MapDrawControl; +class Client; +class WieldMeshSceneNode; + +struct Nametag { + Nametag(scene::ISceneNode *a_parent_node, + const std::string &a_nametag_text, + const video::SColor &a_nametag_color, + const v3f &a_nametag_pos): + parent_node(a_parent_node), + nametag_text(a_nametag_text), + nametag_color(a_nametag_color), + nametag_pos(a_nametag_pos) + { + } + scene::ISceneNode *parent_node; + std::string nametag_text; + video::SColor nametag_color; + v3f nametag_pos; +}; + +enum CameraMode {CAMERA_MODE_FIRST, CAMERA_MODE_THIRD, CAMERA_MODE_THIRD_FRONT}; + +/* + Client camera class, manages the player and camera scene nodes, the viewing distance + and performs view bobbing etc. It also displays the wielded tool in front of the + first-person camera. +*/ +class Camera +{ +public: + Camera(MapDrawControl &draw_control, Client *client); + ~Camera(); + + // Get camera scene node. + // It has the eye transformation, pitch and view bobbing applied. + inline scene::ICameraSceneNode* getCameraNode() const + { + return m_cameranode; + } + + // Get the camera position (in absolute scene coordinates). + // This has view bobbing applied. + inline v3f getPosition() const + { + return m_camera_position; + } + + // Get the camera direction (in absolute camera coordinates). + // This has view bobbing applied. + inline v3f getDirection() const + { + return m_camera_direction; + } + + // Get the camera offset + inline v3s16 getOffset() const + { + return m_camera_offset; + } + + // Horizontal field of view + inline f32 getFovX() const + { + return m_fov_x; + } + + // Vertical field of view + inline f32 getFovY() const + { + return m_fov_y; + } + + // Get maximum of getFovX() and getFovY() + inline f32 getFovMax() const + { + return MYMAX(m_fov_x, m_fov_y); + } + + // Checks if the constructor was able to create the scene nodes + bool successfullyCreated(std::string &error_message); + + // Step the camera: updates the viewing range and view bobbing. + void step(f32 dtime); + + // Update the camera from the local player's position. + // busytime is used to adjust the viewing range. + void update(LocalPlayer* player, f32 frametime, f32 busytime, + f32 tool_reload_ratio); + + // Update render distance + void updateViewingRange(); + + // Start digging animation + // Pass 0 for left click, 1 for right click + void setDigging(s32 button); + + // Replace the wielded item mesh + void wield(const ItemStack &item); + + // Draw the wielded tool. + // This has to happen *after* the main scene is drawn. + // Warning: This clears the Z buffer. + void drawWieldedTool(irr::core::matrix4* translation=NULL); + + // Toggle the current camera mode + void toggleCameraMode() { + if (m_camera_mode == CAMERA_MODE_FIRST) + m_camera_mode = CAMERA_MODE_THIRD; + else if (m_camera_mode == CAMERA_MODE_THIRD) + m_camera_mode = CAMERA_MODE_THIRD_FRONT; + else + m_camera_mode = CAMERA_MODE_FIRST; + } + + // Set the current camera mode + inline void setCameraMode(CameraMode mode) + { + m_camera_mode = mode; + } + + //read the current camera mode + inline CameraMode getCameraMode() + { + return m_camera_mode; + } + + Nametag *addNametag(scene::ISceneNode *parent_node, + const std::string &nametag_text, video::SColor nametag_color, + const v3f &pos); + + void removeNametag(Nametag *nametag); + + const std::list &getNametags() { return m_nametags; } + + void drawNametags(); + + inline void addArmInertia(f32 player_yaw); + +private: + // Nodes + scene::ISceneNode *m_playernode = nullptr; + scene::ISceneNode *m_headnode = nullptr; + scene::ICameraSceneNode *m_cameranode = nullptr; + + scene::ISceneManager *m_wieldmgr = nullptr; + WieldMeshSceneNode *m_wieldnode = nullptr; + + // draw control + MapDrawControl& m_draw_control; + + Client *m_client; + + // Absolute camera position + v3f m_camera_position; + // Absolute camera direction + v3f m_camera_direction; + // Camera offset + v3s16 m_camera_offset; + + v2f m_wieldmesh_offset = v2f(55.0f, -35.0f); + v2f m_arm_dir; + v2f m_cam_vel; + v2f m_cam_vel_old; + v2f m_last_cam_pos; + + // Field of view and aspect ratio stuff + f32 m_aspect = 1.0f; + f32 m_fov_x = 1.0f; + f32 m_fov_y = 1.0f; + + // View bobbing animation frame (0 <= m_view_bobbing_anim < 1) + f32 m_view_bobbing_anim = 0.0f; + // If 0, view bobbing is off (e.g. player is standing). + // If 1, view bobbing is on (player is walking). + // If 2, view bobbing is getting switched off. + s32 m_view_bobbing_state = 0; + // Speed of view bobbing animation + f32 m_view_bobbing_speed = 0.0f; + // Fall view bobbing + f32 m_view_bobbing_fall = 0.0f; + + // Digging animation frame (0 <= m_digging_anim < 1) + f32 m_digging_anim = 0.0f; + // If -1, no digging animation + // If 0, left-click digging animation + // If 1, right-click digging animation + s32 m_digging_button = -1; + + // Animation when changing wielded item + f32 m_wield_change_timer = 0.125f; + ItemStack m_wield_item_next; + + CameraMode m_camera_mode = CAMERA_MODE_FIRST; + + f32 m_cache_fall_bobbing_amount; + f32 m_cache_view_bobbing_amount; + f32 m_cache_fov; + bool m_arm_inertia; + + std::list m_nametags; +}; diff --git a/src/client/client.cpp b/src/client/client.cpp new file mode 100644 index 000000000..17ee3a10a --- /dev/null +++ b/src/client/client.cpp @@ -0,0 +1,1970 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include +#include +#include +#include +#include +#include "client.h" +#include "network/clientopcodes.h" +#include "network/connection.h" +#include "network/networkpacket.h" +#include "threading/mutex_auto_lock.h" +#include "client/clientevent.h" +#include "client/gameui.h" +#include "client/renderingengine.h" +#include "client/sound.h" +#include "client/tile.h" +#include "util/auth.h" +#include "util/directiontables.h" +#include "util/pointedthing.h" +#include "util/serialize.h" +#include "util/string.h" +#include "util/srp.h" +#include "filesys.h" +#include "mapblock_mesh.h" +#include "mapblock.h" +#include "minimap.h" +#include "modchannels.h" +#include "content/mods.h" +#include "profiler.h" +#include "shader.h" +#include "gettext.h" +#include "clientmap.h" +#include "clientmedia.h" +#include "version.h" +#include "database/database-sqlite3.h" +#include "serialization.h" +#include "guiscalingfilter.h" +#include "script/scripting_client.h" +#include "game.h" +#include "chatmessage.h" +#include "translation.h" + +extern gui::IGUIEnvironment* guienv; + +/* + Client +*/ + +Client::Client( + const char *playername, + const std::string &password, + const std::string &address_name, + MapDrawControl &control, + IWritableTextureSource *tsrc, + IWritableShaderSource *shsrc, + IWritableItemDefManager *itemdef, + NodeDefManager *nodedef, + ISoundManager *sound, + MtEventManager *event, + bool ipv6, + GameUI *game_ui +): + m_tsrc(tsrc), + m_shsrc(shsrc), + m_itemdef(itemdef), + m_nodedef(nodedef), + m_sound(sound), + m_event(event), + m_mesh_update_thread(this), + m_env( + new ClientMap(this, control, 666), + tsrc, this + ), + m_particle_manager(&m_env), + m_con(new con::Connection(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, ipv6, this)), + m_address_name(address_name), + m_server_ser_ver(SER_FMT_VER_INVALID), + m_last_chat_message_sent(time(NULL)), + m_password(password), + m_chosen_auth_mech(AUTH_MECHANISM_NONE), + m_media_downloader(new ClientMediaDownloader()), + m_state(LC_Created), + m_game_ui(game_ui), + m_modchannel_mgr(new ModChannelMgr()) +{ + // Add local player + m_env.setLocalPlayer(new LocalPlayer(this, playername)); + + if (g_settings->getBool("enable_minimap")) { + m_minimap = new Minimap(this); + } + m_cache_save_interval = g_settings->getU16("server_map_save_interval"); + + m_modding_enabled = g_settings->getBool("enable_client_modding"); + // Only create the client script environment if client modding is enabled + if (m_modding_enabled) { + m_script = new ClientScripting(this); + m_env.setScript(m_script); + m_script->setEnv(&m_env); + } +} + +void Client::loadMods() +{ + // Don't load mods twice + if (m_mods_loaded) { + return; + } + + // If client modding is not enabled, don't load client-provided CSM mods or + // builtin. + if (!m_modding_enabled) { + warningstream << "Client side mods are disabled by configuration." << std::endl; + return; + } + + // Load builtin + scanModIntoMemory(BUILTIN_MOD_NAME, getBuiltinLuaPath()); + m_script->loadModFromMemory(BUILTIN_MOD_NAME); + + // If the server has disabled client-provided CSM mod loading, don't load + // client-provided CSM mods. Builtin is loaded so needs verfying. + if (checkCSMRestrictionFlag(CSMRestrictionFlags::CSM_RF_LOAD_CLIENT_MODS)) { + warningstream << "Client side mods are disabled by server." << std::endl; + // If builtin integrity is wrong, disconnect user + if (!checkBuiltinIntegrity()) { + // @TODO disconnect user + } + return; + } + + ClientModConfiguration modconf(getClientModsLuaPath()); + m_mods = modconf.getMods(); + // complain about mods with unsatisfied dependencies + if (!modconf.isConsistent()) { + modconf.printUnsatisfiedModsError(); + } + + // Print mods + infostream << "Client Loading mods: "; + for (const ModSpec &mod : m_mods) + infostream << mod.name << " "; + infostream << std::endl; + + // Load and run "mod" scripts + for (const ModSpec &mod : m_mods) { + if (!string_allowed(mod.name, MODNAME_ALLOWED_CHARS)) { + throw ModError("Error loading mod \"" + mod.name + + "\": Mod name does not follow naming conventions: " + "Only characters [a-z0-9_] are allowed."); + } + scanModIntoMemory(mod.name, mod.path); + } + + // Load and run "mod" scripts + for (const ModSpec &mod : m_mods) + m_script->loadModFromMemory(mod.name); + + // Run a callback when mods are loaded + m_script->on_mods_loaded(); + m_mods_loaded = true; +} + +bool Client::checkBuiltinIntegrity() +{ + // @TODO + return true; +} + +void Client::scanModSubfolder(const std::string &mod_name, const std::string &mod_path, + std::string mod_subpath) +{ + std::string full_path = mod_path + DIR_DELIM + mod_subpath; + std::vector mod = fs::GetDirListing(full_path); + for (const fs::DirListNode &j : mod) { + std::string filename = j.name; + if (j.dir) { + scanModSubfolder(mod_name, mod_path, mod_subpath + + filename + DIR_DELIM); + continue; + } + std::replace( mod_subpath.begin(), mod_subpath.end(), DIR_DELIM_CHAR, '/'); + m_mod_files[mod_name + ":" + mod_subpath + filename] = full_path + filename; + } +} + +const std::string &Client::getBuiltinLuaPath() +{ + static const std::string builtin_dir = porting::path_share + DIR_DELIM + "builtin"; + return builtin_dir; +} + +const std::string &Client::getClientModsLuaPath() +{ + static const std::string clientmods_dir = porting::path_share + DIR_DELIM + "clientmods"; + return clientmods_dir; +} + +const std::vector& Client::getMods() const +{ + static std::vector client_modspec_temp; + return client_modspec_temp; +} + +const ModSpec* Client::getModSpec(const std::string &modname) const +{ + return NULL; +} + +void Client::Stop() +{ + m_shutdown = true; + if (m_modding_enabled) + m_script->on_shutdown(); + //request all client managed threads to stop + m_mesh_update_thread.stop(); + // Save local server map + if (m_localdb) { + infostream << "Local map saving ended." << std::endl; + m_localdb->endSave(); + } + + if (m_modding_enabled) + delete m_script; +} + +bool Client::isShutdown() +{ + return m_shutdown || !m_mesh_update_thread.isRunning(); +} + +Client::~Client() +{ + m_shutdown = true; + m_con->Disconnect(); + + deleteAuthData(); + + m_mesh_update_thread.stop(); + m_mesh_update_thread.wait(); + while (!m_mesh_update_thread.m_queue_out.empty()) { + MeshUpdateResult r = m_mesh_update_thread.m_queue_out.pop_frontNoEx(); + delete r.mesh; + } + + + delete m_inventory_from_server; + + // Delete detached inventories + for (auto &m_detached_inventorie : m_detached_inventories) { + delete m_detached_inventorie.second; + } + + // cleanup 3d model meshes on client shutdown + while (RenderingEngine::get_mesh_cache()->getMeshCount() != 0) { + scene::IAnimatedMesh *mesh = RenderingEngine::get_mesh_cache()->getMeshByIndex(0); + + if (mesh) + RenderingEngine::get_mesh_cache()->removeMesh(mesh); + } + + delete m_minimap; + delete m_media_downloader; +} + +void Client::connect(Address address, bool is_local_server) +{ + initLocalMapSaving(address, m_address_name, is_local_server); + + m_con->SetTimeoutMs(0); + m_con->Connect(address); +} + +void Client::step(float dtime) +{ + // Limit a bit + if (dtime > 2.0) + dtime = 2.0; + + m_animation_time += dtime; + if(m_animation_time > 60.0) + m_animation_time -= 60.0; + + m_time_of_day_update_timer += dtime; + + ReceiveAll(); + + /* + Packet counter + */ + { + float &counter = m_packetcounter_timer; + counter -= dtime; + if(counter <= 0.0) + { + counter = 20.0; + + infostream << "Client packetcounter (" << m_packetcounter_timer + << "):"<getName()); + } + + // Not connected, return + return; + } + + /* + Do stuff if connected + */ + + /* + Run Map's timers and unload unused data + */ + const float map_timer_and_unload_dtime = 5.25; + if(m_map_timer_and_unload_interval.step(dtime, map_timer_and_unload_dtime)) { + ScopeProfiler sp(g_profiler, "Client: map timer and unload"); + std::vector deleted_blocks; + m_env.getMap().timerUpdate(map_timer_and_unload_dtime, + g_settings->getFloat("client_unload_unused_data_timeout"), + g_settings->getS32("client_mapblock_limit"), + &deleted_blocks); + + /* + Send info to server + NOTE: This loop is intentionally iterated the way it is. + */ + + std::vector::iterator i = deleted_blocks.begin(); + std::vector sendlist; + for(;;) { + if(sendlist.size() == 255 || i == deleted_blocks.end()) { + if(sendlist.empty()) + break; + /* + [0] u16 command + [2] u8 count + [3] v3s16 pos_0 + [3+6] v3s16 pos_1 + ... + */ + + sendDeletedBlocks(sendlist); + + if(i == deleted_blocks.end()) + break; + + sendlist.clear(); + } + + sendlist.push_back(*i); + ++i; + } + } + + /* + Send pending messages on out chat queue + */ + if (!m_out_chat_queue.empty() && canSendChatMessage()) { + sendChatMessage(m_out_chat_queue.front()); + m_out_chat_queue.pop(); + } + + /* + Handle environment + */ + // Control local player (0ms) + LocalPlayer *player = m_env.getLocalPlayer(); + assert(player); + player->applyControl(dtime, &m_env); + + // Step environment + m_env.step(dtime); + m_sound->step(dtime); + + /* + Get events + */ + while (m_env.hasClientEnvEvents()) { + ClientEnvEvent envEvent = m_env.getClientEnvEvent(); + + if (envEvent.type == CEE_PLAYER_DAMAGE) { + u8 damage = envEvent.player_damage.amount; + + if (envEvent.player_damage.send_to_server) + sendDamage(damage); + + // Add to ClientEvent queue + ClientEvent *event = new ClientEvent(); + event->type = CE_PLAYER_DAMAGE; + event->player_damage.amount = damage; + m_client_event_queue.push(event); + } + } + + /* + Print some info + */ + float &counter = m_avg_rtt_timer; + counter += dtime; + if(counter >= 10) { + counter = 0.0; + // connectedAndInitialized() is true, peer exists. + float avg_rtt = getRTT(); + infostream << "Client: average rtt: " << avg_rtt << std::endl; + } + + /* + Send player position to server + */ + { + float &counter = m_playerpos_send_timer; + counter += dtime; + if((m_state == LC_Ready) && (counter >= m_recommended_send_interval)) + { + counter = 0.0; + sendPlayerPos(); + } + } + + /* + Replace updated meshes + */ + { + int num_processed_meshes = 0; + while (!m_mesh_update_thread.m_queue_out.empty()) + { + num_processed_meshes++; + + MinimapMapblock *minimap_mapblock = NULL; + bool do_mapper_update = true; + + MeshUpdateResult r = m_mesh_update_thread.m_queue_out.pop_frontNoEx(); + MapBlock *block = m_env.getMap().getBlockNoCreateNoEx(r.p); + if (block) { + // Delete the old mesh + delete block->mesh; + block->mesh = nullptr; + + if (r.mesh) { + minimap_mapblock = r.mesh->moveMinimapMapblock(); + if (minimap_mapblock == NULL) + do_mapper_update = false; + + bool is_empty = true; + for (int l = 0; l < MAX_TILE_LAYERS; l++) + if (r.mesh->getMesh(l)->getMeshBufferCount() != 0) + is_empty = false; + + if (is_empty) + delete r.mesh; + else + // Replace with the new mesh + block->mesh = r.mesh; + } + } else { + delete r.mesh; + } + + if (m_minimap && do_mapper_update) + m_minimap->addBlock(r.p, minimap_mapblock); + + if (r.ack_block_to_server) { + /* + Acknowledge block + [0] u8 count + [1] v3s16 pos_0 + */ + sendGotBlocks(r.p); + } + } + + if (num_processed_meshes > 0) + g_profiler->graphAdd("num_processed_meshes", num_processed_meshes); + } + + /* + Load fetched media + */ + if (m_media_downloader && m_media_downloader->isStarted()) { + m_media_downloader->step(this); + if (m_media_downloader->isDone()) { + delete m_media_downloader; + m_media_downloader = NULL; + } + } + + /* + If the server didn't update the inventory in a while, revert + the local inventory (so the player notices the lag problem + and knows something is wrong). + */ + if (m_inventory_from_server) { + float interval = 10.0f; + float count_before = std::floor(m_inventory_from_server_age / interval); + + m_inventory_from_server_age += dtime; + + float count_after = std::floor(m_inventory_from_server_age / interval); + + if (count_after != count_before) { + // Do this every seconds after TOCLIENT_INVENTORY + // Reset the locally changed inventory to the authoritative inventory + m_env.getLocalPlayer()->inventory = *m_inventory_from_server; + m_inventory_updated = true; + } + } + + /* + Update positions of sounds attached to objects + */ + { + for (auto &m_sounds_to_object : m_sounds_to_objects) { + int client_id = m_sounds_to_object.first; + u16 object_id = m_sounds_to_object.second; + ClientActiveObject *cao = m_env.getActiveObject(object_id); + if (!cao) + continue; + m_sound->updateSoundPosition(client_id, cao->getPosition()); + } + } + + /* + Handle removed remotely initiated sounds + */ + m_removed_sounds_check_timer += dtime; + if(m_removed_sounds_check_timer >= 2.32) { + m_removed_sounds_check_timer = 0; + // Find removed sounds and clear references to them + std::vector removed_server_ids; + for (std::unordered_map::iterator i = m_sounds_server_to_client.begin(); + i != m_sounds_server_to_client.end();) { + s32 server_id = i->first; + int client_id = i->second; + ++i; + if(!m_sound->soundExists(client_id)) { + m_sounds_server_to_client.erase(server_id); + m_sounds_client_to_server.erase(client_id); + m_sounds_to_objects.erase(client_id); + removed_server_ids.push_back(server_id); + } + } + + // Sync to server + if(!removed_server_ids.empty()) { + sendRemovedSounds(removed_server_ids); + } + } + + m_mod_storage_save_timer -= dtime; + if (m_mod_storage_save_timer <= 0.0f) { + verbosestream << "Saving registered mod storages." << std::endl; + m_mod_storage_save_timer = g_settings->getFloat("server_map_save_interval"); + for (std::unordered_map::const_iterator + it = m_mod_storages.begin(); it != m_mod_storages.end(); ++it) { + if (it->second->isModified()) { + it->second->save(getModStoragePath()); + } + } + } + + // Write server map + if (m_localdb && m_localdb_save_interval.step(dtime, + m_cache_save_interval)) { + m_localdb->endSave(); + m_localdb->beginSave(); + } +} + +bool Client::loadMedia(const std::string &data, const std::string &filename) +{ + // Silly irrlicht's const-incorrectness + Buffer data_rw(data.c_str(), data.size()); + + std::string name; + + const char *image_ext[] = { + ".png", ".jpg", ".bmp", ".tga", + ".pcx", ".ppm", ".psd", ".wal", ".rgb", + NULL + }; + name = removeStringEnd(filename, image_ext); + if (!name.empty()) { + verbosestream<<"Client: Attempting to load image " + <<"file \""<createMemoryReadFile( + *data_rw, data_rw.getSize(), "_tempreadfile"); + + FATAL_ERROR_IF(!rfile, "Could not create irrlicht memory file."); + + // Read image + video::IImage *img = vdrv->createImageFromFile(rfile); + if (!img) { + errorstream<<"Client: Cannot create image from data of " + <<"file \""<drop(); + return false; + } + + m_tsrc->insertSourceImage(filename, img); + img->drop(); + rfile->drop(); + return true; + } + + const char *sound_ext[] = { + ".0.ogg", ".1.ogg", ".2.ogg", ".3.ogg", ".4.ogg", + ".5.ogg", ".6.ogg", ".7.ogg", ".8.ogg", ".9.ogg", + ".ogg", NULL + }; + name = removeStringEnd(filename, sound_ext); + if (!name.empty()) { + verbosestream<<"Client: Attempting to load sound " + <<"file \""<loadSoundData(name, data); + return true; + } + + const char *model_ext[] = { + ".x", ".b3d", ".md2", ".obj", + NULL + }; + + name = removeStringEnd(filename, model_ext); + if (!name.empty()) { + verbosestream<<"Client: Storing model into memory: " + <<"\""<loadTranslation(data); + return true; + } + + errorstream << "Client: Don't know how to load file \"" + << filename << "\"" << std::endl; + return false; +} + +// Virtual methods from con::PeerHandler +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_reason = gettext("Connection timed out."); + } +} + +/* + u16 command + u16 number of files requested + for each file { + u16 length of name + string name + } +*/ +void Client::request_media(const std::vector &file_requests) +{ + std::ostringstream os(std::ios_base::binary); + writeU16(os, TOSERVER_REQUEST_MEDIA); + size_t file_requests_size = file_requests.size(); + + FATAL_ERROR_IF(file_requests_size > 0xFFFF, "Unsupported number of file requests"); + + // Packet dynamicly resized + NetworkPacket pkt(TOSERVER_REQUEST_MEDIA, 2 + 0); + + pkt << (u16) (file_requests_size & 0xFFFF); + + for (const std::string &file_request : file_requests) { + pkt << file_request; + } + + Send(&pkt); + + infostream << "Client: Sending media request list to server (" + << file_requests.size() << " files. packet size)" << std::endl; +} + +void Client::initLocalMapSaving(const Address &address, + const std::string &hostname, + bool is_local_server) +{ + if (!g_settings->getBool("enable_local_map_saving") || is_local_server) { + return; + } + + const std::string world_path = porting::path_user + + DIR_DELIM + "worlds" + + DIR_DELIM + "server_" + + hostname + "_" + std::to_string(address.getPort()); + + fs::CreateAllDirs(world_path); + + m_localdb = new MapDatabaseSQLite3(world_path); + m_localdb->beginSave(); + actionstream << "Local map saving started, map will be saved at '" << world_path << "'" << std::endl; +} + +void Client::ReceiveAll() +{ + u64 start_ms = porting::getTimeMs(); + for(;;) + { + // Limit time even if there would be huge amounts of data to + // process + if(porting::getTimeMs() > start_ms + 100) + break; + + try { + Receive(); + g_profiler->graphAdd("client_received_packets", 1); + } + catch(con::NoIncomingDataException &e) { + break; + } + catch(con::InvalidIncomingDataException &e) { + infostream<<"Client::ReceiveAll(): " + "InvalidIncomingDataException: what()=" + <Receive(&pkt); + ProcessData(&pkt); +} + +inline void Client::handleCommand(NetworkPacket* pkt) +{ + const ToClientCommandHandler& opHandle = toClientCommandTable[pkt->getCommand()]; + (this->*opHandle.handler)(pkt); +} + +/* + sender_peer_id given to this shall be quaranteed to be a valid peer +*/ +void Client::ProcessData(NetworkPacket *pkt) +{ + ToClientCommand command = (ToClientCommand) pkt->getCommand(); + u32 sender_peer_id = pkt->getPeerId(); + + //infostream<<"Client: received command="< &blocks) +{ + NetworkPacket pkt(TOSERVER_DELETEDBLOCKS, 1 + sizeof(v3s16) * blocks.size()); + + pkt << (u8) blocks.size(); + + for (const v3s16 &block : blocks) { + pkt << block; + } + + Send(&pkt); +} + +void Client::sendGotBlocks(v3s16 block) +{ + NetworkPacket pkt(TOSERVER_GOTBLOCKS, 1 + 6); + pkt << (u8) 1 << block; + Send(&pkt); +} + +void Client::sendRemovedSounds(std::vector &soundList) +{ + size_t server_ids = soundList.size(); + assert(server_ids <= 0xFFFF); + + NetworkPacket pkt(TOSERVER_REMOVED_SOUNDS, 2 + server_ids * 4); + + pkt << (u16) (server_ids & 0xFFFF); + + for (int sound_id : soundList) + pkt << sound_id; + + Send(&pkt); +} + +void Client::sendNodemetaFields(v3s16 p, const std::string &formname, + const StringMap &fields) +{ + size_t fields_size = fields.size(); + + FATAL_ERROR_IF(fields_size > 0xFFFF, "Unsupported number of nodemeta fields"); + + NetworkPacket pkt(TOSERVER_NODEMETA_FIELDS, 0); + + pkt << p << formname << (u16) (fields_size & 0xFFFF); + + StringMap::const_iterator it; + for (it = fields.begin(); it != fields.end(); ++it) { + const std::string &name = it->first; + const std::string &value = it->second; + pkt << name; + pkt.putLongString(value); + } + + Send(&pkt); +} + +void Client::sendInventoryFields(const std::string &formname, + const StringMap &fields) +{ + size_t fields_size = fields.size(); + FATAL_ERROR_IF(fields_size > 0xFFFF, "Unsupported number of inventory fields"); + + NetworkPacket pkt(TOSERVER_INVENTORY_FIELDS, 0); + pkt << formname << (u16) (fields_size & 0xFFFF); + + StringMap::const_iterator it; + for (it = fields.begin(); it != fields.end(); ++it) { + const std::string &name = it->first; + const std::string &value = it->second; + pkt << name; + pkt.putLongString(value); + } + + Send(&pkt); +} + +void Client::sendInventoryAction(InventoryAction *a) +{ + std::ostringstream os(std::ios_base::binary); + + a->serialize(os); + + // Make data buffer + std::string s = os.str(); + + NetworkPacket pkt(TOSERVER_INVENTORY_ACTION, s.size()); + pkt.putRawString(s.c_str(),s.size()); + + Send(&pkt); +} + +bool Client::canSendChatMessage() const +{ + u32 now = time(NULL); + float time_passed = now - m_last_chat_message_sent; + + float virt_chat_message_allowance = m_chat_message_allowance + time_passed * + (CLIENT_CHAT_MESSAGE_LIMIT_PER_10S / 8.0f); + + if (virt_chat_message_allowance < 1.0f) + return false; + + return true; +} + +void Client::sendChatMessage(const std::wstring &message) +{ + const s16 max_queue_size = g_settings->getS16("max_out_chat_queue_size"); + if (canSendChatMessage()) { + u32 now = time(NULL); + float time_passed = now - m_last_chat_message_sent; + m_last_chat_message_sent = time(NULL); + + m_chat_message_allowance += time_passed * (CLIENT_CHAT_MESSAGE_LIMIT_PER_10S / 8.0f); + if (m_chat_message_allowance > CLIENT_CHAT_MESSAGE_LIMIT_PER_10S) + m_chat_message_allowance = CLIENT_CHAT_MESSAGE_LIMIT_PER_10S; + + m_chat_message_allowance -= 1.0f; + + NetworkPacket pkt(TOSERVER_CHAT_MESSAGE, 2 + message.size() * sizeof(u16)); + + pkt << message; + + Send(&pkt); + } else if (m_out_chat_queue.size() < (u16) max_queue_size || max_queue_size == -1) { + m_out_chat_queue.push(message); + } else { + infostream << "Could not queue chat message because maximum out chat queue size (" + << max_queue_size << ") is reached." << std::endl; + } +} + +void Client::clearOutChatQueue() +{ + m_out_chat_queue = std::queue(); +} + +void Client::sendChangePassword(const std::string &oldpassword, + const std::string &newpassword) +{ + LocalPlayer *player = m_env.getLocalPlayer(); + if (player == NULL) + return; + + // get into sudo mode and then send new password to server + m_password = oldpassword; + m_new_password = newpassword; + startAuth(choseAuthMech(m_sudo_auth_methods)); +} + + +void Client::sendDamage(u8 damage) +{ + NetworkPacket pkt(TOSERVER_DAMAGE, sizeof(u8)); + pkt << damage; + Send(&pkt); +} + +void Client::sendRespawn() +{ + NetworkPacket pkt(TOSERVER_RESPAWN, 0); + Send(&pkt); +} + +void Client::sendReady() +{ + NetworkPacket pkt(TOSERVER_CLIENT_READY, + 1 + 1 + 1 + 1 + 2 + sizeof(char) * strlen(g_version_hash)); + + pkt << (u8) VERSION_MAJOR << (u8) VERSION_MINOR << (u8) VERSION_PATCH + << (u8) 0 << (u16) strlen(g_version_hash); + + pkt.putRawString(g_version_hash, (u16) strlen(g_version_hash)); + Send(&pkt); +} + +void Client::sendPlayerPos() +{ + LocalPlayer *myplayer = m_env.getLocalPlayer(); + if (!myplayer) + return; + + ClientMap &map = m_env.getClientMap(); + + u8 camera_fov = map.getCameraFov(); + u8 wanted_range = map.getControl().wanted_range; + + // Save bandwidth by only updating position when something changed + if(myplayer->last_position == myplayer->getPosition() && + myplayer->last_speed == myplayer->getSpeed() && + myplayer->last_pitch == myplayer->getPitch() && + myplayer->last_yaw == myplayer->getYaw() && + myplayer->last_keyPressed == myplayer->keyPressed && + myplayer->last_camera_fov == camera_fov && + myplayer->last_wanted_range == wanted_range) + return; + + myplayer->last_position = myplayer->getPosition(); + myplayer->last_speed = myplayer->getSpeed(); + myplayer->last_pitch = myplayer->getPitch(); + myplayer->last_yaw = myplayer->getYaw(); + myplayer->last_keyPressed = myplayer->keyPressed; + myplayer->last_camera_fov = camera_fov; + myplayer->last_wanted_range = wanted_range; + + NetworkPacket pkt(TOSERVER_PLAYERPOS, 12 + 12 + 4 + 4 + 4 + 1 + 1); + + writePlayerPos(myplayer, &map, &pkt); + + Send(&pkt); +} + +void Client::sendPlayerItem(u16 item) +{ + LocalPlayer *myplayer = m_env.getLocalPlayer(); + if (!myplayer) + return; + + NetworkPacket pkt(TOSERVER_PLAYERITEM, 2); + + pkt << item; + + Send(&pkt); +} + +void Client::removeNode(v3s16 p) +{ + std::map modified_blocks; + + try { + m_env.getMap().removeNodeAndUpdate(p, modified_blocks); + } + catch(InvalidPositionException &e) { + } + + for (const auto &modified_block : modified_blocks) { + addUpdateMeshTaskWithEdge(modified_block.first, false, true); + } +} + +/** + * Helper function for Client Side Modding + * CSM restrictions are applied there, this should not be used for core engine + * @param p + * @param is_valid_position + * @return + */ +MapNode Client::getNode(v3s16 p, bool *is_valid_position) +{ + if (checkCSMRestrictionFlag(CSMRestrictionFlags::CSM_RF_LOOKUP_NODES)) { + v3s16 ppos = floatToInt(m_env.getLocalPlayer()->getPosition(), BS); + if ((u32) ppos.getDistanceFrom(p) > m_csm_restriction_noderange) { + *is_valid_position = false; + return {}; + } + } + return m_env.getMap().getNodeNoEx(p, is_valid_position); +} + +void Client::addNode(v3s16 p, MapNode n, bool remove_metadata) +{ + //TimeTaker timer1("Client::addNode()"); + + std::map modified_blocks; + + try { + //TimeTaker timer3("Client::addNode(): addNodeAndUpdate"); + m_env.getMap().addNodeAndUpdate(p, n, modified_blocks, remove_metadata); + } + catch(InvalidPositionException &e) { + } + + for (const auto &modified_block : modified_blocks) { + addUpdateMeshTaskWithEdge(modified_block.first, false, true); + } +} + +void Client::setPlayerControl(PlayerControl &control) +{ + LocalPlayer *player = m_env.getLocalPlayer(); + assert(player); + player->control = control; +} + +void Client::selectPlayerItem(u16 item) +{ + m_playeritem = item; + m_inventory_updated = true; + sendPlayerItem(item); +} + +// Returns true if the inventory of the local player has been +// updated from the server. If it is true, it is set to false. +bool Client::getLocalInventoryUpdated() +{ + bool updated = m_inventory_updated; + m_inventory_updated = false; + return updated; +} + +// Copies the inventory of the local player to parameter +void Client::getLocalInventory(Inventory &dst) +{ + LocalPlayer *player = m_env.getLocalPlayer(); + assert(player); + dst = player->inventory; +} + +Inventory* Client::getInventory(const InventoryLocation &loc) +{ + switch(loc.type){ + case InventoryLocation::UNDEFINED: + {} + break; + case InventoryLocation::CURRENT_PLAYER: + { + LocalPlayer *player = m_env.getLocalPlayer(); + assert(player); + return &player->inventory; + } + break; + case InventoryLocation::PLAYER: + { + // Check if we are working with local player inventory + LocalPlayer *player = m_env.getLocalPlayer(); + if (!player || strcmp(player->getName(), loc.name.c_str()) != 0) + return NULL; + return &player->inventory; + } + break; + case InventoryLocation::NODEMETA: + { + NodeMetadata *meta = m_env.getMap().getNodeMetadata(loc.p); + if(!meta) + return NULL; + return meta->getInventory(); + } + break; + case InventoryLocation::DETACHED: + { + if (m_detached_inventories.count(loc.name) == 0) + return NULL; + return m_detached_inventories[loc.name]; + } + break; + default: + FATAL_ERROR("Invalid inventory location type."); + break; + } + return NULL; +} + +void Client::inventoryAction(InventoryAction *a) +{ + /* + Send it to the server + */ + sendInventoryAction(a); + + /* + Predict some local inventory changes + */ + a->clientApply(this, this); + + // Remove it + delete a; +} + +float Client::getAnimationTime() +{ + return m_animation_time; +} + +int Client::getCrackLevel() +{ + return m_crack_level; +} + +v3s16 Client::getCrackPos() +{ + return m_crack_pos; +} + +void Client::setCrack(int level, v3s16 pos) +{ + int old_crack_level = m_crack_level; + v3s16 old_crack_pos = m_crack_pos; + + m_crack_level = level; + m_crack_pos = pos; + + if(old_crack_level >= 0 && (level < 0 || pos != old_crack_pos)) + { + // remove old crack + addUpdateMeshTaskForNode(old_crack_pos, false, true); + } + if(level >= 0 && (old_crack_level < 0 || pos != old_crack_pos)) + { + // add new crack + addUpdateMeshTaskForNode(pos, false, true); + } +} + +u16 Client::getHP() +{ + LocalPlayer *player = m_env.getLocalPlayer(); + assert(player); + return player->hp; +} + +bool Client::getChatMessage(std::wstring &res) +{ + if (m_chat_queue.empty()) + return false; + + ChatMessage *chatMessage = m_chat_queue.front(); + m_chat_queue.pop(); + + res = L""; + + switch (chatMessage->type) { + case CHATMESSAGE_TYPE_RAW: + case CHATMESSAGE_TYPE_ANNOUNCE: + case CHATMESSAGE_TYPE_SYSTEM: + res = chatMessage->message; + break; + case CHATMESSAGE_TYPE_NORMAL: { + if (!chatMessage->sender.empty()) + res = L"<" + chatMessage->sender + L"> " + chatMessage->message; + else + res = chatMessage->message; + break; + } + default: + break; + } + + delete chatMessage; + return true; +} + +void Client::typeChatMessage(const std::wstring &message) +{ + // Discard empty line + if (message.empty()) + return; + + // If message was consumed by script API, don't send it to server + if (m_modding_enabled && m_script->on_sending_message(wide_to_utf8(message))) + return; + + // Send to others + sendChatMessage(message); + + // Show locally + if (message[0] != L'/') { + // compatibility code + if (m_proto_ver < 29) { + LocalPlayer *player = m_env.getLocalPlayer(); + assert(player); + std::wstring name = narrow_to_wide(player->getName()); + pushToChatQueue(new ChatMessage(CHATMESSAGE_TYPE_NORMAL, message, name)); + } + } +} + +void Client::addUpdateMeshTask(v3s16 p, bool ack_to_server, bool urgent) +{ + // Check if the block exists to begin with. In the case when a non-existing + // neighbor is automatically added, it may not. In that case we don't want + // to tell the mesh update thread about it. + MapBlock *b = m_env.getMap().getBlockNoCreateNoEx(p); + if (b == NULL) + return; + + m_mesh_update_thread.updateBlock(&m_env.getMap(), p, ack_to_server, urgent); +} + +void Client::addUpdateMeshTaskWithEdge(v3s16 blockpos, bool ack_to_server, bool urgent) +{ + try{ + addUpdateMeshTask(blockpos, ack_to_server, urgent); + } + catch(InvalidPositionException &e){} + + // Leading edge + for (int i=0;i<6;i++) + { + try{ + v3s16 p = blockpos + g_6dirs[i]; + addUpdateMeshTask(p, false, urgent); + } + catch(InvalidPositionException &e){} + } +} + +void Client::addUpdateMeshTaskForNode(v3s16 nodepos, bool ack_to_server, bool urgent) +{ + { + v3s16 p = nodepos; + infostream<<"Client::addUpdateMeshTaskForNode(): " + <<"("<Connected(); +} + +const Address Client::getServerAddress() +{ + return m_con->GetPeerAddress(PEER_ID_SERVER); +} + +float Client::mediaReceiveProgress() +{ + if (m_media_downloader) + return m_media_downloader->getProgress(); + + return 1.0; // downloader only exists when not yet done +} + +typedef struct TextureUpdateArgs { + gui::IGUIEnvironment *guienv; + u64 last_time_ms; + u16 last_percent; + const wchar_t* text_base; + ITextureSource *tsrc; +} TextureUpdateArgs; + +void texture_update_progress(void *args, u32 progress, u32 max_progress) +{ + TextureUpdateArgs* targs = (TextureUpdateArgs*) args; + u16 cur_percent = ceil(progress / (double) max_progress * 100.); + + // update the loading menu -- if neccessary + bool do_draw = false; + u64 time_ms = targs->last_time_ms; + if (cur_percent != targs->last_percent) { + targs->last_percent = cur_percent; + time_ms = porting::getTimeMs(); + // only draw when the user will notice something: + do_draw = (time_ms - targs->last_time_ms > 100); + } + + if (do_draw) { + targs->last_time_ms = time_ms; + std::basic_stringstream strm; + strm << targs->text_base << " " << targs->last_percent << "%..."; + RenderingEngine::draw_load_screen(strm.str(), targs->guienv, targs->tsrc, 0, + 72 + (u16) ((18. / 100.) * (double) targs->last_percent), true); + } +} + +void Client::afterContentReceived() +{ + infostream<<"Client::afterContentReceived() started"<rebuildImagesAndTextures(); + delete[] text; + + // Rebuild shaders + infostream<<"- Rebuilding shaders"<rebuildShaders(); + delete[] text; + + // Update node aliases + infostream<<"- Updating node aliases"<updateAliases(m_itemdef); + for (const auto &path : getTextureDirs()) + m_nodedef->applyTextureOverrides(path + DIR_DELIM + "override.txt"); + m_nodedef->setNodeRegistrationStatus(true); + m_nodedef->runNodeResolveCallbacks(); + delete[] text; + + // Update node textures and assign shaders to each tile + infostream<<"- Updating node textures"<updateTextures(this, texture_update_progress, &tu_args); + delete[] tu_args.text_base; + + // Start mesh update thread after setting up content definitions + infostream<<"- Starting mesh update thread"<getBool("enable_client_modding")) { + m_script->on_client_ready(m_env.getLocalPlayer()); + } + + text = wgettext("Done!"); + RenderingEngine::draw_load_screen(text, guienv, m_tsrc, 0, 100); + infostream<<"Client::afterContentReceived() done"<getPeerStat(PEER_ID_SERVER, con::AVG_RTT); + float time_from_last_rtt = + m_con->getPeerStat(PEER_ID_SERVER, con::TIMEOUT_COUNTER); + if (avg_rtt + 2.0f > time_from_last_rtt) + return avg_rtt; + return time_from_last_rtt; +} + +float Client::getCurRate() +{ + return (m_con->getLocalStat(con::CUR_INC_RATE) + + m_con->getLocalStat(con::CUR_DL_RATE)); +} + +void Client::makeScreenshot() +{ + irr::video::IVideoDriver *driver = RenderingEngine::get_video_driver(); + irr::video::IImage* const raw_image = driver->createScreenShot(); + + if (!raw_image) + return; + + time_t t = time(NULL); + struct tm *tm = localtime(&t); + + char timetstamp_c[64]; + strftime(timetstamp_c, sizeof(timetstamp_c), "%Y%m%d_%H%M%S", tm); + + std::string filename_base = g_settings->get("screenshot_path") + + DIR_DELIM + + std::string("screenshot_") + + std::string(timetstamp_c); + std::string filename_ext = "." + g_settings->get("screenshot_format"); + std::string filename; + + u32 quality = (u32)g_settings->getS32("screenshot_quality"); + quality = MYMIN(MYMAX(quality, 0), 100) / 100.0 * 255; + + // Try to find a unique filename + unsigned serial = 0; + + while (serial < SCREENSHOT_MAX_SERIAL_TRIES) { + filename = filename_base + (serial > 0 ? ("_" + itos(serial)) : "") + filename_ext; + std::ifstream tmp(filename.c_str()); + if (!tmp.good()) + break; // File did not apparently exist, we'll go with it + serial++; + } + + if (serial == SCREENSHOT_MAX_SERIAL_TRIES) { + infostream << "Could not find suitable filename for screenshot" << std::endl; + } else { + irr::video::IImage* const image = + driver->createImage(video::ECF_R8G8B8, raw_image->getDimension()); + + if (image) { + raw_image->copyTo(image); + + std::ostringstream sstr; + if (driver->writeImageToFile(image, filename.c_str(), quality)) { + sstr << "Saved screenshot to '" << filename << "'"; + } else { + sstr << "Failed to save screenshot '" << filename << "'"; + } + pushToChatQueue(new ChatMessage(CHATMESSAGE_TYPE_SYSTEM, + narrow_to_wide(sstr.str()))); + infostream << sstr.str() << std::endl; + image->drop(); + } + } + + raw_image->drop(); +} + +bool Client::shouldShowMinimap() const +{ + return !m_minimap_disabled_by_server; +} + +void Client::pushToEventQueue(ClientEvent *event) +{ + m_client_event_queue.push(event); +} + +void Client::showMinimap(const bool show) +{ + m_game_ui->showMinimap(show); +} + +// IGameDef interface +// Under envlock +IItemDefManager* Client::getItemDefManager() +{ + return m_itemdef; +} +const NodeDefManager* Client::getNodeDefManager() +{ + return m_nodedef; +} +ICraftDefManager* Client::getCraftDefManager() +{ + return NULL; + //return m_craftdef; +} +ITextureSource* Client::getTextureSource() +{ + return m_tsrc; +} +IShaderSource* Client::getShaderSource() +{ + return m_shsrc; +} + +u16 Client::allocateUnknownNodeId(const std::string &name) +{ + errorstream << "Client::allocateUnknownNodeId(): " + << "Client cannot allocate node IDs" << std::endl; + FATAL_ERROR("Client allocated unknown node"); + + return CONTENT_IGNORE; +} +ISoundManager* Client::getSoundManager() +{ + return m_sound; +} +MtEventManager* Client::getEventManager() +{ + return m_event; +} + +ParticleManager* Client::getParticleManager() +{ + return &m_particle_manager; +} + +scene::IAnimatedMesh* Client::getMesh(const std::string &filename, bool cache) +{ + StringMap::const_iterator it = m_mesh_data.find(filename); + if (it == m_mesh_data.end()) { + errorstream << "Client::getMesh(): Mesh not found: \"" << filename + << "\"" << std::endl; + return NULL; + } + const std::string &data = it->second; + + // Create the mesh, remove it from cache and return it + // This allows unique vertex colors and other properties for each instance + Buffer data_rw(data.c_str(), data.size()); // Const-incorrect Irrlicht + io::IReadFile *rfile = RenderingEngine::get_filesystem()->createMemoryReadFile( + *data_rw, data_rw.getSize(), filename.c_str()); + FATAL_ERROR_IF(!rfile, "Could not create/open RAM file"); + + scene::IAnimatedMesh *mesh = RenderingEngine::get_scene_manager()->getMesh(rfile); + rfile->drop(); + mesh->grab(); + if (!cache) + RenderingEngine::get_mesh_cache()->removeMesh(mesh); + return mesh; +} + +const std::string* Client::getModFile(const std::string &filename) +{ + StringMap::const_iterator it = m_mod_files.find(filename); + if (it == m_mod_files.end()) { + errorstream << "Client::getModFile(): File not found: \"" << filename + << "\"" << std::endl; + return NULL; + } + return &it->second; +} + +bool Client::registerModStorage(ModMetadata *storage) +{ + if (m_mod_storages.find(storage->getModName()) != m_mod_storages.end()) { + errorstream << "Unable to register same mod storage twice. Storage name: " + << storage->getModName() << std::endl; + return false; + } + + m_mod_storages[storage->getModName()] = storage; + return true; +} + +void Client::unregisterModStorage(const std::string &name) +{ + std::unordered_map::const_iterator it = + m_mod_storages.find(name); + if (it != m_mod_storages.end()) { + // Save unconditionaly on unregistration + it->second->save(getModStoragePath()); + m_mod_storages.erase(name); + } +} + +std::string Client::getModStoragePath() const +{ + return porting::path_user + DIR_DELIM + "client" + DIR_DELIM + "mod_storage"; +} + +/* + * Mod channels + */ + +bool Client::joinModChannel(const std::string &channel) +{ + if (m_modchannel_mgr->channelRegistered(channel)) + return false; + + NetworkPacket pkt(TOSERVER_MODCHANNEL_JOIN, 2 + channel.size()); + pkt << channel; + Send(&pkt); + + m_modchannel_mgr->joinChannel(channel, 0); + return true; +} + +bool Client::leaveModChannel(const std::string &channel) +{ + if (!m_modchannel_mgr->channelRegistered(channel)) + return false; + + NetworkPacket pkt(TOSERVER_MODCHANNEL_LEAVE, 2 + channel.size()); + pkt << channel; + Send(&pkt); + + m_modchannel_mgr->leaveChannel(channel, 0); + return true; +} + +bool Client::sendModChannelMessage(const std::string &channel, const std::string &message) +{ + if (!m_modchannel_mgr->canWriteOnChannel(channel)) + return false; + + if (message.size() > STRING_MAX_LEN) { + warningstream << "ModChannel message too long, dropping before sending " + << " (" << message.size() << " > " << STRING_MAX_LEN << ", channel: " + << channel << ")" << std::endl; + return false; + } + + // @TODO: do some client rate limiting + NetworkPacket pkt(TOSERVER_MODCHANNEL_MSG, 2 + channel.size() + 2 + message.size()); + pkt << channel << message; + Send(&pkt); + return true; +} + +ModChannel* Client::getModChannel(const std::string &channel) +{ + return m_modchannel_mgr->getModChannel(channel); +} diff --git a/src/client/client.h b/src/client/client.h new file mode 100644 index 000000000..68a832d8e --- /dev/null +++ b/src/client/client.h @@ -0,0 +1,608 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include "clientenvironment.h" +#include "irrlichttypes_extrabloated.h" +#include +#include +#include +#include +#include +#include "clientobject.h" +#include "gamedef.h" +#include "inventorymanager.h" +#include "localplayer.h" +#include "client/hud.h" +#include "particles.h" +#include "mapnode.h" +#include "tileanimation.h" +#include "mesh_generator_thread.h" +#include "network/address.h" +#include "network/peerhandler.h" +#include + +#define CLIENT_CHAT_MESSAGE_LIMIT_PER_10S 10.0f + +struct ClientEvent; +struct MeshMakeData; +struct ChatMessage; +class MapBlockMesh; +class IWritableTextureSource; +class IWritableShaderSource; +class IWritableItemDefManager; +class ISoundManager; +class NodeDefManager; +//class IWritableCraftDefManager; +class ClientMediaDownloader; +struct MapDrawControl; +class ModChannelMgr; +class MtEventManager; +struct PointedThing; +class MapDatabase; +class Minimap; +struct MinimapMapblock; +class Camera; +class NetworkPacket; +namespace con { +class Connection; +} + +enum LocalClientState { + LC_Created, + LC_Init, + LC_Ready +}; + +/* + Packet counter +*/ + +class PacketCounter +{ +public: + PacketCounter() = default; + + void add(u16 command) + { + std::map::iterator n = m_packets.find(command); + if(n == m_packets.end()) + { + m_packets[command] = 1; + } + else + { + n->second++; + } + } + + void clear() + { + for (auto &m_packet : m_packets) { + m_packet.second = 0; + } + } + + void print(std::ostream &o) + { + for (const auto &m_packet : m_packets) { + o << "cmd "<< m_packet.first <<" count "<< m_packet.second << std::endl; + } + } + +private: + // command, count + std::map m_packets; +}; + +class ClientScripting; +class GameUI; + +class Client : public con::PeerHandler, public InventoryManager, public IGameDef +{ +public: + /* + NOTE: Nothing is thread-safe here. + */ + + Client( + const char *playername, + const std::string &password, + const std::string &address_name, + MapDrawControl &control, + IWritableTextureSource *tsrc, + IWritableShaderSource *shsrc, + IWritableItemDefManager *itemdef, + NodeDefManager *nodedef, + ISoundManager *sound, + MtEventManager *event, + bool ipv6, + GameUI *game_ui + ); + + ~Client(); + DISABLE_CLASS_COPY(Client); + + // Load local mods into memory + void scanModSubfolder(const std::string &mod_name, const std::string &mod_path, + std::string mod_subpath); + inline void scanModIntoMemory(const std::string &mod_name, const std::string &mod_path) + { + scanModSubfolder(mod_name, mod_path, ""); + } + + /* + request all threads managed by client to be stopped + */ + void Stop(); + + + bool isShutdown(); + + /* + The name of the local player should already be set when + calling this, as it is sent in the initialization. + */ + void connect(Address address, bool is_local_server); + + /* + Stuff that references the environment is valid only as + long as this is not called. (eg. Players) + If this throws a PeerNotFoundException, the connection has + timed out. + */ + void step(float dtime); + + /* + * Command Handlers + */ + + void handleCommand(NetworkPacket* pkt); + + void handleCommand_Null(NetworkPacket* pkt) {}; + void handleCommand_Deprecated(NetworkPacket* pkt); + void handleCommand_Hello(NetworkPacket* pkt); + void handleCommand_AuthAccept(NetworkPacket* pkt); + void handleCommand_AcceptSudoMode(NetworkPacket* pkt); + void handleCommand_DenySudoMode(NetworkPacket* pkt); + void handleCommand_AccessDenied(NetworkPacket* pkt); + void handleCommand_RemoveNode(NetworkPacket* pkt); + void handleCommand_AddNode(NetworkPacket* pkt); + void handleCommand_BlockData(NetworkPacket* pkt); + void handleCommand_Inventory(NetworkPacket* pkt); + void handleCommand_TimeOfDay(NetworkPacket* pkt); + void handleCommand_ChatMessage(NetworkPacket *pkt); + void handleCommand_ActiveObjectRemoveAdd(NetworkPacket* pkt); + void handleCommand_ActiveObjectMessages(NetworkPacket* pkt); + void handleCommand_Movement(NetworkPacket* pkt); + void handleCommand_HP(NetworkPacket* pkt); + void handleCommand_Breath(NetworkPacket* pkt); + void handleCommand_MovePlayer(NetworkPacket* pkt); + void handleCommand_DeathScreen(NetworkPacket* pkt); + void handleCommand_AnnounceMedia(NetworkPacket* pkt); + void handleCommand_Media(NetworkPacket* pkt); + void handleCommand_NodeDef(NetworkPacket* pkt); + void handleCommand_ItemDef(NetworkPacket* pkt); + void handleCommand_PlaySound(NetworkPacket* pkt); + void handleCommand_StopSound(NetworkPacket* pkt); + void handleCommand_FadeSound(NetworkPacket *pkt); + void handleCommand_Privileges(NetworkPacket* pkt); + void handleCommand_InventoryFormSpec(NetworkPacket* pkt); + void handleCommand_DetachedInventory(NetworkPacket* pkt); + void handleCommand_ShowFormSpec(NetworkPacket* pkt); + void handleCommand_SpawnParticle(NetworkPacket* pkt); + void handleCommand_AddParticleSpawner(NetworkPacket* pkt); + void handleCommand_DeleteParticleSpawner(NetworkPacket* pkt); + void handleCommand_HudAdd(NetworkPacket* pkt); + void handleCommand_HudRemove(NetworkPacket* pkt); + void handleCommand_HudChange(NetworkPacket* pkt); + void handleCommand_HudSetFlags(NetworkPacket* pkt); + void handleCommand_HudSetParam(NetworkPacket* pkt); + void handleCommand_HudSetSky(NetworkPacket* pkt); + void handleCommand_CloudParams(NetworkPacket* pkt); + void handleCommand_OverrideDayNightRatio(NetworkPacket* pkt); + void handleCommand_LocalPlayerAnimations(NetworkPacket* pkt); + void handleCommand_EyeOffset(NetworkPacket* pkt); + void handleCommand_UpdatePlayerList(NetworkPacket* pkt); + void handleCommand_ModChannelMsg(NetworkPacket *pkt); + void handleCommand_ModChannelSignal(NetworkPacket *pkt); + void handleCommand_SrpBytesSandB(NetworkPacket *pkt); + void handleCommand_FormspecPrepend(NetworkPacket *pkt); + void handleCommand_CSMRestrictionFlags(NetworkPacket *pkt); + + void ProcessData(NetworkPacket *pkt); + + void Send(NetworkPacket* pkt); + + void interact(u8 action, const PointedThing& pointed); + + void sendNodemetaFields(v3s16 p, const std::string &formname, + const StringMap &fields); + void sendInventoryFields(const std::string &formname, + const StringMap &fields); + void sendInventoryAction(InventoryAction *a); + void sendChatMessage(const std::wstring &message); + void clearOutChatQueue(); + void sendChangePassword(const std::string &oldpassword, + const std::string &newpassword); + void sendDamage(u8 damage); + void sendRespawn(); + void sendReady(); + + ClientEnvironment& getEnv() { return m_env; } + ITextureSource *tsrc() { return getTextureSource(); } + ISoundManager *sound() { return getSoundManager(); } + static const std::string &getBuiltinLuaPath(); + static const std::string &getClientModsLuaPath(); + + const std::vector &getMods() const override; + const ModSpec* getModSpec(const std::string &modname) const override; + + // Causes urgent mesh updates (unlike Map::add/removeNodeWithEvent) + void removeNode(v3s16 p); + + /** + * Helper function for Client Side Modding + * CSM restrictions are applied there, this should not be used for core engine + * @param p + * @param is_valid_position + * @return + */ + MapNode getNode(v3s16 p, bool *is_valid_position); + void addNode(v3s16 p, MapNode n, bool remove_metadata = true); + + void setPlayerControl(PlayerControl &control); + + void selectPlayerItem(u16 item); + u16 getPlayerItem() const + { return m_playeritem; } + + // Returns true if the inventory of the local player has been + // updated from the server. If it is true, it is set to false. + bool getLocalInventoryUpdated(); + // Copies the inventory of the local player to parameter + void getLocalInventory(Inventory &dst); + + /* InventoryManager interface */ + Inventory* getInventory(const InventoryLocation &loc) override; + void inventoryAction(InventoryAction *a) override; + + const std::list &getConnectedPlayerNames() + { + return m_env.getPlayerNames(); + } + + float getAnimationTime(); + + int getCrackLevel(); + v3s16 getCrackPos(); + void setCrack(int level, v3s16 pos); + + u16 getHP(); + + bool checkPrivilege(const std::string &priv) const + { return (m_privileges.count(priv) != 0); } + + const std::unordered_set &getPrivilegeList() const + { return m_privileges; } + + bool getChatMessage(std::wstring &message); + void typeChatMessage(const std::wstring& message); + + u64 getMapSeed(){ return m_map_seed; } + + void addUpdateMeshTask(v3s16 blockpos, bool ack_to_server=false, bool urgent=false); + // Including blocks at appropriate edges + void addUpdateMeshTaskWithEdge(v3s16 blockpos, bool ack_to_server=false, bool urgent=false); + void addUpdateMeshTaskForNode(v3s16 nodepos, bool ack_to_server=false, bool urgent=false); + + void updateCameraOffset(v3s16 camera_offset) + { m_mesh_update_thread.m_camera_offset = camera_offset; } + + bool hasClientEvents() const { return !m_client_event_queue.empty(); } + // Get event from queue. If queue is empty, it triggers an assertion failure. + ClientEvent * getClientEvent(); + + bool accessDenied() const { return m_access_denied; } + + bool reconnectRequested() const { return m_access_denied_reconnect; } + + void setFatalError(const std::string &reason) + { + m_access_denied = true; + m_access_denied_reason = reason; + } + + // Renaming accessDeniedReason to better name could be good as it's used to + // disconnect client when CSM failed. + const std::string &accessDeniedReason() const { return m_access_denied_reason; } + + bool itemdefReceived() + { return m_itemdef_received; } + bool nodedefReceived() + { return m_nodedef_received; } + bool mediaReceived() + { return !m_media_downloader; } + + u8 getProtoVersion() + { return m_proto_ver; } + + bool connectedToServer(); + void confirmRegistration(); + bool m_is_registration_confirmation_state = false; + bool m_simple_singleplayer_mode; + + float mediaReceiveProgress(); + + void afterContentReceived(); + + float getRTT(); + float getCurRate(); + + Minimap* getMinimap() { return m_minimap; } + void setCamera(Camera* camera) { m_camera = camera; } + + Camera* getCamera () { return m_camera; } + + bool shouldShowMinimap() const; + + // IGameDef interface + IItemDefManager* getItemDefManager() override; + const NodeDefManager* getNodeDefManager() override; + ICraftDefManager* getCraftDefManager() override; + ITextureSource* getTextureSource(); + virtual IShaderSource* getShaderSource(); + u16 allocateUnknownNodeId(const std::string &name) override; + virtual ISoundManager* getSoundManager(); + MtEventManager* getEventManager(); + virtual ParticleManager* getParticleManager(); + bool checkLocalPrivilege(const std::string &priv) + { return checkPrivilege(priv); } + virtual scene::IAnimatedMesh* getMesh(const std::string &filename, bool cache = false); + const std::string* getModFile(const std::string &filename); + + std::string getModStoragePath() const override; + bool registerModStorage(ModMetadata *meta) override; + void unregisterModStorage(const std::string &name) override; + + // The following set of functions is used by ClientMediaDownloader + // Insert a media file appropriately into the appropriate manager + bool loadMedia(const std::string &data, const std::string &filename); + // Send a request for conventional media transfer + void request_media(const std::vector &file_requests); + + LocalClientState getState() { return m_state; } + + void makeScreenshot(); + + inline void pushToChatQueue(ChatMessage *cec) + { + m_chat_queue.push(cec); + } + + ClientScripting *getScript() { return m_script; } + const bool moddingEnabled() const { return m_modding_enabled; } + const bool modsLoaded() const { return m_mods_loaded; } + + void pushToEventQueue(ClientEvent *event); + + void showMinimap(bool show = true); + + const Address getServerAddress(); + + const std::string &getAddressName() const + { + return m_address_name; + } + + inline bool checkCSMRestrictionFlag(CSMRestrictionFlags flag) const + { + return m_csm_restriction_flags & flag; + } + + u32 getCSMNodeRangeLimit() const + { + return m_csm_restriction_noderange; + } + + inline std::unordered_map &getHUDTranslationMap() + { + return m_hud_server_to_client; + } + + bool joinModChannel(const std::string &channel) override; + bool leaveModChannel(const std::string &channel) override; + bool sendModChannelMessage(const std::string &channel, + const std::string &message) override; + ModChannel *getModChannel(const std::string &channel) override; + + const std::string &getFormspecPrepend() const + { + return m_env.getLocalPlayer()->formspec_prepend; + } +private: + void loadMods(); + bool checkBuiltinIntegrity(); + + // Virtual methods from con::PeerHandler + void peerAdded(con::Peer *peer) override; + void deletingPeer(con::Peer *peer, bool timeout) override; + + void initLocalMapSaving(const Address &address, + const std::string &hostname, + bool is_local_server); + + void ReceiveAll(); + void Receive(); + + void sendPlayerPos(); + // Send the item number 'item' as player item to the server + void sendPlayerItem(u16 item); + + void deleteAuthData(); + // helper method shared with clientpackethandler + 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 &blocks); + void sendGotBlocks(v3s16 block); + void sendRemovedSounds(std::vector &soundList); + + // Helper function + inline std::string getPlayerName() + { return m_env.getLocalPlayer()->getName(); } + + bool canSendChatMessage() const; + + float m_packetcounter_timer = 0.0f; + float m_connection_reinit_timer = 0.1f; + float m_avg_rtt_timer = 0.0f; + float m_playerpos_send_timer = 0.0f; + IntervalLimiter m_map_timer_and_unload_interval; + + IWritableTextureSource *m_tsrc; + IWritableShaderSource *m_shsrc; + IWritableItemDefManager *m_itemdef; + NodeDefManager *m_nodedef; + ISoundManager *m_sound; + MtEventManager *m_event; + + + MeshUpdateThread m_mesh_update_thread; + ClientEnvironment m_env; + ParticleManager m_particle_manager; + std::unique_ptr m_con; + std::string m_address_name; + Camera *m_camera = nullptr; + Minimap *m_minimap = nullptr; + bool m_minimap_disabled_by_server = false; + // Server serialization version + u8 m_server_ser_ver; + + // Used version of the protocol with server + // Values smaller than 25 only mean they are smaller than 25, + // and aren't accurate. We simply just don't know, because + // the server didn't send the version back then. + // If 0, server init hasn't been received yet. + u8 m_proto_ver = 0; + + u16 m_playeritem = 0; + bool m_inventory_updated = false; + Inventory *m_inventory_from_server = nullptr; + float m_inventory_from_server_age = 0.0f; + PacketCounter m_packetcounter; + // Block mesh animation parameters + float m_animation_time = 0.0f; + int m_crack_level = -1; + v3s16 m_crack_pos; + // 0 <= m_daynight_i < DAYNIGHT_CACHE_COUNT + //s32 m_daynight_i; + //u32 m_daynight_ratio; + std::queue m_out_chat_queue; + u32 m_last_chat_message_sent; + float m_chat_message_allowance = 5.0f; + std::queue m_chat_queue; + + // The authentication methods we can use to enter sudo mode (=change password) + u32 m_sudo_auth_methods; + + // The seed returned by the server in TOCLIENT_INIT is stored here + u64 m_map_seed = 0; + + // Auth data + std::string m_playername; + std::string m_password; + // If set, this will be sent (and cleared) upon a TOCLIENT_ACCEPT_SUDO_MODE + std::string m_new_password; + // Usable by auth mechanisms. + AuthMechanism m_chosen_auth_mech; + void *m_auth_data = nullptr; + + + bool m_access_denied = false; + bool m_access_denied_reconnect = false; + std::string m_access_denied_reason = ""; + std::queue m_client_event_queue; + bool m_itemdef_received = false; + bool m_nodedef_received = false; + bool m_mods_loaded = false; + ClientMediaDownloader *m_media_downloader; + + // time_of_day speed approximation for old protocol + bool m_time_of_day_set = false; + float m_last_time_of_day_f = -1.0f; + float m_time_of_day_update_timer = 0.0f; + + // An interval for generally sending object positions and stuff + float m_recommended_send_interval = 0.1f; + + // Sounds + float m_removed_sounds_check_timer = 0.0f; + // Mapping from server sound ids to our sound ids + std::unordered_map m_sounds_server_to_client; + // And the other way! + std::unordered_map m_sounds_client_to_server; + // And relations to objects + std::unordered_map m_sounds_to_objects; + + // CSM/client IDs to SSM/server IDs Mapping + // Map server particle spawner IDs to client IDs + std::unordered_map m_particles_server_to_client; + // Map server hud ids to client hud ids + std::unordered_map m_hud_server_to_client; + + // Privileges + std::unordered_set m_privileges; + + // Detached inventories + // key = name + std::unordered_map m_detached_inventories; + + // Storage for mesh data for creating multiple instances of the same mesh + StringMap m_mesh_data; + + StringMap m_mod_files; + + // own state + LocalClientState m_state; + + GameUI *m_game_ui; + + // Used for saving server map to disk client-side + MapDatabase *m_localdb = nullptr; + IntervalLimiter m_localdb_save_interval; + u16 m_cache_save_interval; + + ClientScripting *m_script = nullptr; + bool m_modding_enabled; + std::unordered_map m_mod_storages; + float m_mod_storage_save_timer = 10.0f; + std::vector m_mods; + + bool m_shutdown = false; + + // CSM restrictions byteflag + u64 m_csm_restriction_flags = CSMRestrictionFlags::CSM_RF_NONE; + u32 m_csm_restriction_noderange = 8; + + std::unique_ptr m_modchannel_mgr; +}; diff --git a/src/client/clientenvironment.cpp b/src/client/clientenvironment.cpp new file mode 100644 index 000000000..e2f24aaa3 --- /dev/null +++ b/src/client/clientenvironment.cpp @@ -0,0 +1,540 @@ +/* +Minetest +Copyright (C) 2010-2017 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "util/serialize.h" +#include "util/pointedthing.h" +#include "client.h" +#include "clientenvironment.h" +#include "clientsimpleobject.h" +#include "clientmap.h" +#include "scripting_client.h" +#include "mapblock_mesh.h" +#include "event.h" +#include "collision.h" +#include "nodedef.h" +#include "profiler.h" +#include "raycast.h" +#include "voxelalgorithms.h" +#include "settings.h" +#include "content_cao.h" +#include +#include "client/renderingengine.h" + +/* + ClientEnvironment +*/ + +ClientEnvironment::ClientEnvironment(ClientMap *map, + ITextureSource *texturesource, Client *client): + Environment(client), + m_map(map), + m_texturesource(texturesource), + m_client(client) +{ + char zero = 0; + memset(attachement_parent_ids, zero, sizeof(attachement_parent_ids)); +} + +ClientEnvironment::~ClientEnvironment() +{ + // delete active objects + for (auto &active_object : m_active_objects) { + delete active_object.second; + } + + for (auto &simple_object : m_simple_objects) { + delete simple_object; + } + + // Drop/delete map + m_map->drop(); + + delete m_local_player; +} + +Map & ClientEnvironment::getMap() +{ + return *m_map; +} + +ClientMap & ClientEnvironment::getClientMap() +{ + return *m_map; +} + +void ClientEnvironment::setLocalPlayer(LocalPlayer *player) +{ + /* + It is a failure if already is a local player + */ + FATAL_ERROR_IF(m_local_player != NULL, + "Local player already allocated"); + + m_local_player = player; +} + +void ClientEnvironment::step(float dtime) +{ + /* Step time of day */ + stepTimeOfDay(dtime); + + // Get some settings + bool fly_allowed = m_client->checkLocalPrivilege("fly"); + bool free_move = fly_allowed && g_settings->getBool("free_move"); + + // Get local player + LocalPlayer *lplayer = getLocalPlayer(); + assert(lplayer); + // collision info queue + std::vector player_collisions; + + /* + Get the speed the player is going + */ + bool is_climbing = lplayer->is_climbing; + + f32 player_speed = lplayer->getSpeed().getLength(); + + /* + Maximum position increment + */ + //f32 position_max_increment = 0.05*BS; + f32 position_max_increment = 0.1*BS; + + // Maximum time increment (for collision detection etc) + // time = distance / speed + f32 dtime_max_increment = 1; + if(player_speed > 0.001) + dtime_max_increment = position_max_increment / player_speed; + + // Maximum time increment is 10ms or lower + if(dtime_max_increment > 0.01) + dtime_max_increment = 0.01; + + // Don't allow overly huge dtime + if(dtime > 0.5) + dtime = 0.5; + + f32 dtime_downcount = dtime; + + /* + Stuff that has a maximum time increment + */ + + u32 loopcount = 0; + do + { + loopcount++; + + f32 dtime_part; + if(dtime_downcount > dtime_max_increment) + { + dtime_part = dtime_max_increment; + dtime_downcount -= dtime_part; + } + else + { + dtime_part = dtime_downcount; + /* + Setting this to 0 (no -=dtime_part) disables an infinite loop + when dtime_part is so small that dtime_downcount -= dtime_part + does nothing + */ + dtime_downcount = 0; + } + + /* + Handle local player + */ + + { + // Apply physics + if (!free_move && !is_climbing) { + // Gravity + v3f speed = lplayer->getSpeed(); + if (!lplayer->in_liquid) + speed.Y -= lplayer->movement_gravity * + lplayer->physics_override_gravity * dtime_part * 2.0f; + + // Liquid floating / sinking + if (lplayer->in_liquid && !lplayer->swimming_vertical) + speed.Y -= lplayer->movement_liquid_sink * dtime_part * 2.0f; + + // Liquid resistance + if (lplayer->in_liquid_stable || lplayer->in_liquid) { + // How much the node's viscosity blocks movement, ranges + // between 0 and 1. Should match the scale at which viscosity + // increase affects other liquid attributes. + static const f32 viscosity_factor = 0.3f; + + v3f d_wanted = -speed / lplayer->movement_liquid_fluidity; + f32 dl = d_wanted.getLength(); + if (dl > lplayer->movement_liquid_fluidity_smooth) + dl = lplayer->movement_liquid_fluidity_smooth; + + dl *= (lplayer->liquid_viscosity * viscosity_factor) + + (1 - viscosity_factor); + v3f d = d_wanted.normalize() * (dl * dtime_part * 100.0f); + speed += d; + } + + lplayer->setSpeed(speed); + } + + /* + Move the lplayer. + This also does collision detection. + */ + lplayer->move(dtime_part, this, position_max_increment, + &player_collisions); + } + } while (dtime_downcount > 0.001); + + bool player_immortal = lplayer->getCAO() && lplayer->getCAO()->isImmortal(); + + for (const CollisionInfo &info : player_collisions) { + v3f speed_diff = info.new_speed - info.old_speed;; + // Handle only fall damage + // (because otherwise walking against something in fast_move kills you) + if (speed_diff.Y < 0 || info.old_speed.Y >= 0) + continue; + // Get rid of other components + speed_diff.X = 0; + speed_diff.Z = 0; + f32 pre_factor = 1; // 1 hp per node/s + f32 tolerance = BS*14; // 5 without damage + f32 post_factor = 1; // 1 hp per node/s + if (info.type == COLLISION_NODE) { + const ContentFeatures &f = m_client->ndef()-> + get(m_map->getNodeNoEx(info.node_p)); + // Determine fall damage multiplier + int addp = itemgroup_get(f.groups, "fall_damage_add_percent"); + pre_factor = 1.0f + (float)addp / 100.0f; + } + float speed = pre_factor * speed_diff.getLength(); + if (speed > tolerance && !player_immortal) { + f32 damage_f = (speed - tolerance) / BS * post_factor; + u8 damage = (u8)MYMIN(damage_f + 0.5, 255); + if (damage != 0) { + damageLocalPlayer(damage, true); + m_client->getEventManager()->put( + new SimpleTriggerEvent(MtEvent::PLAYER_FALLING_DAMAGE)); + } + } + } + + if (m_client->modsLoaded()) + m_script->environment_step(dtime); + + // Update lighting on local player (used for wield item) + u32 day_night_ratio = getDayNightRatio(); + { + // Get node at head + + // On InvalidPositionException, use this as default + // (day: LIGHT_SUN, night: 0) + MapNode node_at_lplayer(CONTENT_AIR, 0x0f, 0); + + v3s16 p = lplayer->getLightPosition(); + node_at_lplayer = m_map->getNodeNoEx(p); + + u16 light = getInteriorLight(node_at_lplayer, 0, m_client->ndef()); + final_color_blend(&lplayer->light_color, light, day_night_ratio); + } + + /* + Step active objects and update lighting of them + */ + + g_profiler->avg("CEnv: num of objects", m_active_objects.size()); + bool update_lighting = m_active_object_light_update_interval.step(dtime, 0.21); + for (auto &ao_it : m_active_objects) { + ClientActiveObject* obj = ao_it.second; + // Step object + obj->step(dtime, this); + + if (update_lighting) { + // Update lighting + u8 light = 0; + bool pos_ok; + + // Get node at head + v3s16 p = obj->getLightPosition(); + MapNode n = m_map->getNodeNoEx(p, &pos_ok); + if (pos_ok) + light = n.getLightBlend(day_night_ratio, m_client->ndef()); + else + light = blend_light(day_night_ratio, LIGHT_SUN, 0); + + obj->updateLight(light); + } + } + + /* + Step and handle simple objects + */ + g_profiler->avg("CEnv: num of simple objects", m_simple_objects.size()); + for (auto i = m_simple_objects.begin(); i != m_simple_objects.end();) { + auto cur = i; + ClientSimpleObject *simple = *cur; + + simple->step(dtime); + if(simple->m_to_be_removed) { + delete simple; + i = m_simple_objects.erase(cur); + } + else { + ++i; + } + } +} + +void ClientEnvironment::addSimpleObject(ClientSimpleObject *simple) +{ + m_simple_objects.push_back(simple); +} + +GenericCAO* ClientEnvironment::getGenericCAO(u16 id) +{ + ClientActiveObject *obj = getActiveObject(id); + if (obj && obj->getType() == ACTIVEOBJECT_TYPE_GENERIC) + return (GenericCAO*) obj; + + return NULL; +} + +ClientActiveObject* ClientEnvironment::getActiveObject(u16 id) +{ + auto n = m_active_objects.find(id); + if (n == m_active_objects.end()) + return NULL; + return n->second; +} + +bool isFreeClientActiveObjectId(const u16 id, + ClientActiveObjectMap &objects) +{ + return id != 0 && objects.find(id) == objects.end(); + +} + +u16 getFreeClientActiveObjectId(ClientActiveObjectMap &objects) +{ + //try to reuse id's as late as possible + static u16 last_used_id = 0; + u16 startid = last_used_id; + for(;;) { + last_used_id ++; + if (isFreeClientActiveObjectId(last_used_id, objects)) + return last_used_id; + + if (last_used_id == startid) + return 0; + } +} + +u16 ClientEnvironment::addActiveObject(ClientActiveObject *object) +{ + assert(object); // Pre-condition + if(object->getId() == 0) + { + u16 new_id = getFreeClientActiveObjectId(m_active_objects); + if(new_id == 0) + { + infostream<<"ClientEnvironment::addActiveObject(): " + <<"no free ids available"<setId(new_id); + } + if (!isFreeClientActiveObjectId(object->getId(), m_active_objects)) { + infostream<<"ClientEnvironment::addActiveObject(): " + <<"id is not free ("<getId()<<")"<getId()] = object; + object->addToScene(m_texturesource); + { // Update lighting immediately + u8 light = 0; + bool pos_ok; + + // Get node at head + v3s16 p = object->getLightPosition(); + MapNode n = m_map->getNodeNoEx(p, &pos_ok); + if (pos_ok) + light = n.getLightBlend(getDayNightRatio(), m_client->ndef()); + else + light = blend_light(getDayNightRatio(), LIGHT_SUN, 0); + + object->updateLight(light); + } + return object->getId(); +} + +void ClientEnvironment::addActiveObject(u16 id, u8 type, + const std::string &init_data) +{ + ClientActiveObject* obj = + ClientActiveObject::create((ActiveObjectType) type, m_client, this); + if(obj == NULL) + { + infostream<<"ClientEnvironment::addActiveObject(): " + <<"id="<setId(id); + + try + { + obj->initialize(init_data); + } + catch(SerializationError &e) + { + errorstream<<"ClientEnvironment::addActiveObject():" + <<" id="<removeFromScene(true); + delete obj; + m_active_objects.erase(id); +} + +void ClientEnvironment::processActiveObjectMessage(u16 id, const std::string &data) +{ + ClientActiveObject *obj = getActiveObject(id); + if (obj == NULL) { + infostream << "ClientEnvironment::processActiveObjectMessage():" + << " got message for id=" << id << ", which doesn't exist." + << std::endl; + return; + } + + try { + obj->processMessage(data); + } catch (SerializationError &e) { + errorstream<<"ClientEnvironment::processActiveObjectMessage():" + << " id=" << id << " type=" << obj->getType() + << " SerializationError in processMessage(): " << e.what() + << std::endl; + } +} + +/* + Callbacks for activeobjects +*/ + +void ClientEnvironment::damageLocalPlayer(u8 damage, bool handle_hp) +{ + LocalPlayer *lplayer = getLocalPlayer(); + assert(lplayer); + + if (handle_hp) { + if (lplayer->hp > damage) + lplayer->hp -= damage; + else + lplayer->hp = 0; + } + + ClientEnvEvent event; + event.type = CEE_PLAYER_DAMAGE; + event.player_damage.amount = damage; + event.player_damage.send_to_server = handle_hp; + m_client_event_queue.push(event); +} + +/* + Client likes to call these +*/ + +void ClientEnvironment::getActiveObjects(v3f origin, f32 max_d, + std::vector &dest) +{ + for (auto &ao_it : m_active_objects) { + ClientActiveObject* obj = ao_it.second; + + f32 d = (obj->getPosition() - origin).getLength(); + + if (d > max_d) + continue; + + dest.emplace_back(obj, d); + } +} + +ClientEnvEvent ClientEnvironment::getClientEnvEvent() +{ + FATAL_ERROR_IF(m_client_event_queue.empty(), + "ClientEnvironment::getClientEnvEvent(): queue is empty"); + + ClientEnvEvent event = m_client_event_queue.front(); + m_client_event_queue.pop(); + return event; +} + +void ClientEnvironment::getSelectedActiveObjects( + const core::line3d &shootline_on_map, + std::vector &objects) +{ + std::vector allObjects; + getActiveObjects(shootline_on_map.start, + shootline_on_map.getLength() + 10.0f, allObjects); + const v3f line_vector = shootline_on_map.getVector(); + + for (const auto &allObject : allObjects) { + ClientActiveObject *obj = allObject.obj; + aabb3f selection_box; + if (!obj->getSelectionBox(&selection_box)) + continue; + + const v3f &pos = obj->getPosition(); + aabb3f offsetted_box(selection_box.MinEdge + pos, + selection_box.MaxEdge + pos); + + v3f current_intersection; + v3s16 current_normal; + if (boxLineCollision(offsetted_box, shootline_on_map.start, line_vector, + ¤t_intersection, ¤t_normal)) { + objects.emplace_back((s16) obj->getId(), current_intersection, current_normal, + (current_intersection - shootline_on_map.start).getLengthSQ()); + } + } +} diff --git a/src/client/clientenvironment.h b/src/client/clientenvironment.h new file mode 100644 index 000000000..606070e3a --- /dev/null +++ b/src/client/clientenvironment.h @@ -0,0 +1,151 @@ +/* +Minetest +Copyright (C) 2010-2017 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include "environment.h" +#include +#include "clientobject.h" +#include "util/numeric.h" + +class ClientSimpleObject; +class ClientMap; +class ClientScripting; +class ClientActiveObject; +class GenericCAO; +class LocalPlayer; + +/* + The client-side environment. + + This is not thread-safe. + Must be called from main (irrlicht) thread (uses the SceneManager) + Client uses an environment mutex. +*/ + +enum ClientEnvEventType +{ + CEE_NONE, + CEE_PLAYER_DAMAGE +}; + +struct ClientEnvEvent +{ + ClientEnvEventType type; + union { + //struct{ + //} none; + struct{ + u8 amount; + bool send_to_server; + } player_damage; + }; +}; + +typedef std::unordered_map ClientActiveObjectMap; +class ClientEnvironment : public Environment +{ +public: + ClientEnvironment(ClientMap *map, ITextureSource *texturesource, Client *client); + ~ClientEnvironment(); + + Map & getMap(); + ClientMap & getClientMap(); + + Client *getGameDef() { return m_client; } + void setScript(ClientScripting *script) { m_script = script; } + + void step(f32 dtime); + + virtual void setLocalPlayer(LocalPlayer *player); + LocalPlayer *getLocalPlayer() const { return m_local_player; } + + /* + ClientSimpleObjects + */ + + void addSimpleObject(ClientSimpleObject *simple); + + /* + ActiveObjects + */ + + GenericCAO* getGenericCAO(u16 id); + ClientActiveObject* getActiveObject(u16 id); + + /* + Adds an active object to the environment. + Environment handles deletion of object. + Object may be deleted by environment immediately. + If id of object is 0, assigns a free id to it. + Returns the id of the object. + Returns 0 if not added and thus deleted. + */ + u16 addActiveObject(ClientActiveObject *object); + + void addActiveObject(u16 id, u8 type, const std::string &init_data); + void removeActiveObject(u16 id); + + void processActiveObjectMessage(u16 id, const std::string &data); + + /* + Callbacks for activeobjects + */ + + void damageLocalPlayer(u8 damage, bool handle_hp=true); + + /* + Client likes to call these + */ + + // Get all nearby objects + void getActiveObjects(v3f origin, f32 max_d, + std::vector &dest); + + bool hasClientEnvEvents() const { return !m_client_event_queue.empty(); } + + // Get event from queue. If queue is empty, it triggers an assertion failure. + ClientEnvEvent getClientEnvEvent(); + + virtual void getSelectedActiveObjects( + const core::line3d &shootline_on_map, + std::vector &objects + ); + + u16 attachement_parent_ids[USHRT_MAX + 1]; + + const std::list &getPlayerNames() { return m_player_names; } + void addPlayerName(const std::string &name) { m_player_names.push_back(name); } + void removePlayerName(const std::string &name) { m_player_names.remove(name); } + void updateCameraOffset(const v3s16 &camera_offset) + { m_camera_offset = camera_offset; } + v3s16 getCameraOffset() const { return m_camera_offset; } +private: + ClientMap *m_map; + LocalPlayer *m_local_player = nullptr; + ITextureSource *m_texturesource; + Client *m_client; + ClientScripting *m_script = nullptr; + ClientActiveObjectMap m_active_objects; + std::vector m_simple_objects; + std::queue m_client_event_queue; + IntervalLimiter m_active_object_light_update_interval; + std::list m_player_names; + v3s16 m_camera_offset; +}; diff --git a/src/client/clientmap.cpp b/src/client/clientmap.cpp new file mode 100644 index 000000000..969c55539 --- /dev/null +++ b/src/client/clientmap.cpp @@ -0,0 +1,671 @@ +/* +Minetest +Copyright (C) 2010-2013 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "clientmap.h" +#include "client.h" +#include "mapblock_mesh.h" +#include +#include +#include "mapsector.h" +#include "mapblock.h" +#include "profiler.h" +#include "settings.h" +#include "camera.h" // CameraModes +#include "util/basic_macros.h" +#include +#include "client/renderingengine.h" + +ClientMap::ClientMap( + Client *client, + MapDrawControl &control, + s32 id +): + Map(dout_client, client), + scene::ISceneNode(RenderingEngine::get_scene_manager()->getRootSceneNode(), + RenderingEngine::get_scene_manager(), id), + m_client(client), + m_control(control) +{ + m_box = aabb3f(-BS*1000000,-BS*1000000,-BS*1000000, + BS*1000000,BS*1000000,BS*1000000); + + /* TODO: Add a callback function so these can be updated when a setting + * changes. At this point in time it doesn't matter (e.g. /set + * is documented to change server settings only) + * + * TODO: Local caching of settings is not optimal and should at some stage + * be updated to use a global settings object for getting thse values + * (as opposed to the this local caching). This can be addressed in + * a later release. + */ + 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"); + +} + +MapSector * ClientMap::emergeSector(v2s16 p2d) +{ + // Check that it doesn't exist already + try { + return getSectorNoGenerate(p2d); + } catch(InvalidPositionException &e) { + } + + // Create a sector + MapSector *sector = new MapSector(this, p2d, m_gamedef); + m_sectors[p2d] = sector; + + return sector; +} + +void ClientMap::OnRegisterSceneNode() +{ + if(IsVisible) + { + SceneManager->registerNodeForRendering(this, scene::ESNRP_SOLID); + SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT); + } + + ISceneNode::OnRegisterSceneNode(); +} + +void ClientMap::getBlocksInViewRange(v3s16 cam_pos_nodes, + v3s16 *p_blocks_min, v3s16 *p_blocks_max) +{ + v3s16 box_nodes_d = m_control.wanted_range * v3s16(1, 1, 1); + // Define p_nodes_min/max as v3s32 because 'cam_pos_nodes -/+ box_nodes_d' + // can exceed the range of v3s16 when a large view range is used near the + // world edges. + v3s32 p_nodes_min( + cam_pos_nodes.X - box_nodes_d.X, + cam_pos_nodes.Y - box_nodes_d.Y, + cam_pos_nodes.Z - box_nodes_d.Z); + v3s32 p_nodes_max( + cam_pos_nodes.X + box_nodes_d.X, + cam_pos_nodes.Y + box_nodes_d.Y, + cam_pos_nodes.Z + box_nodes_d.Z); + // Take a fair amount as we will be dropping more out later + // Umm... these additions are a bit strange but they are needed. + *p_blocks_min = v3s16( + p_nodes_min.X / MAP_BLOCKSIZE - 3, + p_nodes_min.Y / MAP_BLOCKSIZE - 3, + p_nodes_min.Z / MAP_BLOCKSIZE - 3); + *p_blocks_max = v3s16( + p_nodes_max.X / MAP_BLOCKSIZE + 1, + p_nodes_max.Y / MAP_BLOCKSIZE + 1, + p_nodes_max.Z / MAP_BLOCKSIZE + 1); +} + +void ClientMap::updateDrawList() +{ + ScopeProfiler sp(g_profiler, "CM::updateDrawList()", SPT_AVG); + g_profiler->add("CM::updateDrawList() count", 1); + + for (auto &i : m_drawlist) { + MapBlock *block = i.second; + block->refDrop(); + } + m_drawlist.clear(); + + v3f camera_position = m_camera_position; + v3f camera_direction = m_camera_direction; + f32 camera_fov = m_camera_fov; + + // Use a higher fov to accomodate faster camera movements. + // Blocks are cropped better when they are drawn. + // Or maybe they aren't? Well whatever. + camera_fov *= 1.2; + + v3s16 cam_pos_nodes = floatToInt(camera_position, BS); + v3s16 p_blocks_min; + v3s16 p_blocks_max; + getBlocksInViewRange(cam_pos_nodes, &p_blocks_min, &p_blocks_max); + + // Number of blocks in rendering range + u32 blocks_in_range = 0; + // Number of blocks occlusion culled + u32 blocks_occlusion_culled = 0; + // Number of blocks in rendering range but don't have a mesh + u32 blocks_in_range_without_mesh = 0; + // Blocks that had mesh that would have been drawn according to + // rendering range (if max blocks limit didn't kick in) + u32 blocks_would_have_drawn = 0; + // Blocks that were drawn and had a mesh + u32 blocks_drawn = 0; + // Blocks which had a corresponding meshbuffer for this pass + //u32 blocks_had_pass_meshbuf = 0; + // Blocks from which stuff was actually drawn + //u32 blocks_without_stuff = 0; + // Distance to farthest drawn block + float farthest_drawn = 0; + + // No occlusion culling when free_move is on and camera is + // inside ground + bool occlusion_culling_enabled = true; + if (g_settings->getBool("free_move")) { + MapNode n = getNodeNoEx(cam_pos_nodes); + if (n.getContent() == CONTENT_IGNORE || + m_nodedef->get(n).solidness == 2) + occlusion_culling_enabled = false; + } + + for (const auto §or_it : m_sectors) { + MapSector *sector = sector_it.second; + v2s16 sp = sector->getPos(); + + if (!m_control.range_all) { + if (sp.X < p_blocks_min.X || sp.X > p_blocks_max.X || + sp.Y < p_blocks_min.Z || sp.Y > p_blocks_max.Z) + continue; + } + + MapBlockVect sectorblocks; + sector->getBlocks(sectorblocks); + + /* + Loop through blocks in sector + */ + + u32 sector_blocks_drawn = 0; + + for (auto block : sectorblocks) { + /* + Compare block position to camera position, skip + if not seen on display + */ + + if (block->mesh) + block->mesh->updateCameraOffset(m_camera_offset); + + float range = 100000 * BS; + if (!m_control.range_all) + range = m_control.wanted_range * BS; + + float d = 0.0; + if (!isBlockInSight(block->getPos(), camera_position, + camera_direction, camera_fov, range, &d)) + continue; + + blocks_in_range++; + + /* + Ignore if mesh doesn't exist + */ + if (!block->mesh) { + blocks_in_range_without_mesh++; + continue; + } + + /* + Occlusion culling + */ + if (occlusion_culling_enabled && isBlockOccluded(block, cam_pos_nodes)) { + blocks_occlusion_culled++; + continue; + } + + // This block is in range. Reset usage timer. + block->resetUsageTimer(); + + // Limit block count in case of a sudden increase + blocks_would_have_drawn++; + if (blocks_drawn >= m_control.wanted_max_blocks && + !m_control.range_all && + d > m_control.wanted_range * BS) + continue; + + // Add to set + block->refGrab(); + m_drawlist[block->getPos()] = block; + + sector_blocks_drawn++; + blocks_drawn++; + if (d / BS > farthest_drawn) + farthest_drawn = d / BS; + + } // foreach sectorblocks + + if (sector_blocks_drawn != 0) + m_last_drawn_sectors.insert(sp); + } + + g_profiler->avg("CM: blocks in range", blocks_in_range); + g_profiler->avg("CM: blocks occlusion culled", blocks_occlusion_culled); + if (blocks_in_range != 0) + g_profiler->avg("CM: blocks in range without mesh (frac)", + (float)blocks_in_range_without_mesh / blocks_in_range); + g_profiler->avg("CM: blocks drawn", blocks_drawn); + g_profiler->avg("CM: farthest drawn", farthest_drawn); + g_profiler->avg("CM: wanted max blocks", m_control.wanted_max_blocks); +} + +struct MeshBufList +{ + video::SMaterial m; + std::vector bufs; +}; + +struct MeshBufListList +{ + /*! + * Stores the mesh buffers of the world. + * The array index is the material's layer. + * The vector part groups vertices by material. + */ + std::vector lists[MAX_TILE_LAYERS]; + + void clear() + { + for (auto &list : lists) + list.clear(); + } + + void add(scene::IMeshBuffer *buf, u8 layer) + { + // Append to the correct layer + std::vector &list = lists[layer]; + const video::SMaterial &m = buf->getMaterial(); + for (MeshBufList &l : list) { + // comparing a full material is quite expensive so we don't do it if + // not even first texture is equal + if (l.m.TextureLayer[0].Texture != m.TextureLayer[0].Texture) + continue; + + if (l.m == m) { + l.bufs.push_back(buf); + return; + } + } + MeshBufList l; + l.m = m; + l.bufs.push_back(buf); + list.push_back(l); + } +}; + +void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) +{ + bool is_transparent_pass = pass == scene::ESNRP_TRANSPARENT; + + std::string prefix; + if (pass == scene::ESNRP_SOLID) + prefix = "CM: solid: "; + else + prefix = "CM: transparent: "; + + /* + This is called two times per frame, reset on the non-transparent one + */ + if (pass == scene::ESNRP_SOLID) + m_last_drawn_sectors.clear(); + + /* + Get time for measuring timeout. + + Measuring time is very useful for long delays when the + machine is swapping a lot. + */ + std::time_t time1 = time(0); + + /* + Get animation parameters + */ + float animation_time = m_client->getAnimationTime(); + int crack = m_client->getCrackLevel(); + u32 daynight_ratio = m_client->getEnv().getDayNightRatio(); + + v3f camera_position = m_camera_position; + v3f camera_direction = m_camera_direction; + f32 camera_fov = m_camera_fov; + + /* + Get all blocks and draw all visible ones + */ + + u32 vertex_count = 0; + u32 meshbuffer_count = 0; + + // For limiting number of mesh animations per frame + u32 mesh_animate_count = 0; + u32 mesh_animate_count_far = 0; + + // Blocks that were drawn and had a mesh + u32 blocks_drawn = 0; + // Blocks which had a corresponding meshbuffer for this pass + u32 blocks_had_pass_meshbuf = 0; + // Blocks from which stuff was actually drawn + u32 blocks_without_stuff = 0; + + /* + Draw the selected MapBlocks + */ + + { + ScopeProfiler sp(g_profiler, prefix + "drawing blocks", SPT_AVG); + + MeshBufListList drawbufs; + + for (auto &i : m_drawlist) { + MapBlock *block = i.second; + + // If the mesh of the block happened to get deleted, ignore it + if (!block->mesh) + continue; + + float d = 0.0; + if (!isBlockInSight(block->getPos(), camera_position, + camera_direction, camera_fov, 100000 * BS, &d)) + continue; + + // Mesh animation + if (pass == scene::ESNRP_SOLID) { + //MutexAutoLock lock(block->mesh_mutex); + MapBlockMesh *mapBlockMesh = block->mesh; + assert(mapBlockMesh); + // Pretty random but this should work somewhat nicely + bool faraway = d >= BS * 50; + //bool faraway = d >= m_control.wanted_range * BS; + if (mapBlockMesh->isAnimationForced() || !faraway || + mesh_animate_count_far < (m_control.range_all ? 200 : 50)) { + bool animated = mapBlockMesh->animate(faraway, animation_time, + crack, daynight_ratio); + if (animated) + mesh_animate_count++; + if (animated && faraway) + mesh_animate_count_far++; + } else { + mapBlockMesh->decreaseAnimationForceTimer(); + } + } + + /* + Get the meshbuffers of the block + */ + { + //MutexAutoLock lock(block->mesh_mutex); + + MapBlockMesh *mapBlockMesh = block->mesh; + assert(mapBlockMesh); + + for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) { + scene::IMesh *mesh = mapBlockMesh->getMesh(layer); + assert(mesh); + + u32 c = mesh->getMeshBufferCount(); + for (u32 i = 0; i < c; i++) { + scene::IMeshBuffer *buf = mesh->getMeshBuffer(i); + + video::SMaterial& material = buf->getMaterial(); + video::IMaterialRenderer* rnd = + driver->getMaterialRenderer(material.MaterialType); + bool transparent = (rnd && rnd->isTransparent()); + if (transparent == is_transparent_pass) { + if (buf->getVertexCount() == 0) + errorstream << "Block [" << analyze_block(block) + << "] contains an empty meshbuf" << std::endl; + + material.setFlag(video::EMF_TRILINEAR_FILTER, + m_cache_trilinear_filter); + material.setFlag(video::EMF_BILINEAR_FILTER, + m_cache_bilinear_filter); + material.setFlag(video::EMF_ANISOTROPIC_FILTER, + m_cache_anistropic_filter); + material.setFlag(video::EMF_WIREFRAME, + m_control.show_wireframe); + + drawbufs.add(buf, layer); + } + } + } + } + } + + // Render all layers in order + for (auto &lists : drawbufs.lists) { + int timecheck_counter = 0; + for (MeshBufList &list : lists) { + timecheck_counter++; + if (timecheck_counter > 50) { + timecheck_counter = 0; + std::time_t time2 = time(0); + if (time2 > time1 + 4) { + infostream << "ClientMap::renderMap(): " + "Rendering takes ages, returning." + << std::endl; + return; + } + } + + driver->setMaterial(list.m); + + for (scene::IMeshBuffer *buf : list.bufs) { + driver->drawMeshBuffer(buf); + vertex_count += buf->getVertexCount(); + meshbuffer_count++; + } + } + } + } // ScopeProfiler + + // Log only on solid pass because values are the same + if (pass == scene::ESNRP_SOLID) { + g_profiler->avg("CM: animated meshes", mesh_animate_count); + g_profiler->avg("CM: animated meshes (far)", mesh_animate_count_far); + } + + g_profiler->avg(prefix + "vertices drawn", vertex_count); + if (blocks_had_pass_meshbuf != 0) + g_profiler->avg(prefix + "meshbuffers per block", + (float)meshbuffer_count / (float)blocks_had_pass_meshbuf); + if (blocks_drawn != 0) + g_profiler->avg(prefix + "empty blocks (frac)", + (float)blocks_without_stuff / blocks_drawn); +} + +static bool getVisibleBrightness(Map *map, const v3f &p0, v3f dir, float step, + float step_multiplier, float start_distance, float end_distance, + const NodeDefManager *ndef, u32 daylight_factor, float sunlight_min_d, + int *result, bool *sunlight_seen) +{ + int brightness_sum = 0; + int brightness_count = 0; + float distance = start_distance; + dir.normalize(); + v3f pf = p0; + pf += dir * distance; + int noncount = 0; + bool nonlight_seen = false; + bool allow_allowing_non_sunlight_propagates = false; + bool allow_non_sunlight_propagates = false; + // Check content nearly at camera position + { + v3s16 p = floatToInt(p0 /*+ dir * 3*BS*/, BS); + MapNode n = map->getNodeNoEx(p); + if(ndef->get(n).param_type == CPT_LIGHT && + !ndef->get(n).sunlight_propagates) + allow_allowing_non_sunlight_propagates = true; + } + // If would start at CONTENT_IGNORE, start closer + { + v3s16 p = floatToInt(pf, BS); + MapNode n = map->getNodeNoEx(p); + if(n.getContent() == CONTENT_IGNORE){ + float newd = 2*BS; + pf = p0 + dir * 2*newd; + distance = newd; + sunlight_min_d = 0; + } + } + for (int i=0; distance < end_distance; i++) { + pf += dir * step; + distance += step; + step *= step_multiplier; + + v3s16 p = floatToInt(pf, BS); + MapNode n = map->getNodeNoEx(p); + if (allow_allowing_non_sunlight_propagates && i == 0 && + ndef->get(n).param_type == CPT_LIGHT && + !ndef->get(n).sunlight_propagates) { + allow_non_sunlight_propagates = true; + } + + if (ndef->get(n).param_type != CPT_LIGHT || + (!ndef->get(n).sunlight_propagates && + !allow_non_sunlight_propagates)){ + nonlight_seen = true; + noncount++; + if(noncount >= 4) + break; + continue; + } + + if (distance >= sunlight_min_d && !*sunlight_seen && !nonlight_seen) + if (n.getLight(LIGHTBANK_DAY, ndef) == LIGHT_SUN) + *sunlight_seen = true; + noncount = 0; + brightness_sum += decode_light(n.getLightBlend(daylight_factor, ndef)); + brightness_count++; + } + *result = 0; + if(brightness_count == 0) + return false; + *result = brightness_sum / brightness_count; + /*std::cerr<<"Sampled "<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) + { + post_effect_color = video::SColor(255, 0, 0, 0); + } + if (post_effect_color.getAlpha() != 0) + { + // Draw a full-screen rectangle + video::IVideoDriver* driver = SceneManager->getVideoDriver(); + v2u32 ss = driver->getScreenSize(); + core::rect rect(0,0, ss.X, ss.Y); + driver->draw2DRectangle(post_effect_color, rect); + } +} + +void ClientMap::PrintInfo(std::ostream &out) +{ + out<<"ClientMap: "; +} + + diff --git a/src/client/clientmap.h b/src/client/clientmap.h new file mode 100644 index 000000000..8402bb00d --- /dev/null +++ b/src/client/clientmap.h @@ -0,0 +1,138 @@ +/* +Minetest +Copyright (C) 2010-2013 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include "irrlichttypes_extrabloated.h" +#include "map.h" +#include "camera.h" +#include +#include + +struct MapDrawControl +{ + // Overrides limits by drawing everything + bool range_all = false; + // Wanted drawing range + float wanted_range = 0.0f; + // Maximum number of blocks to draw + u32 wanted_max_blocks = 0; + // show a wire frame for debugging + bool show_wireframe = false; +}; + +class Client; +class ITextureSource; + +/* + ClientMap + + This is the only map class that is able to render itself on screen. +*/ + +class ClientMap : public Map, public scene::ISceneNode +{ +public: + ClientMap( + Client *client, + MapDrawControl &control, + s32 id + ); + + virtual ~ClientMap() = default; + + s32 mapType() const + { + return MAPTYPE_CLIENT; + } + + void drop() + { + ISceneNode::drop(); + } + + void updateCamera(const v3f &pos, const v3f &dir, f32 fov, const v3s16 &offset) + { + m_camera_position = pos; + m_camera_direction = dir; + m_camera_fov = fov; + m_camera_offset = offset; + } + + /* + Forcefully get a sector from somewhere + */ + MapSector * emergeSector(v2s16 p); + + //void deSerializeSector(v2s16 p2d, std::istream &is); + + /* + ISceneNode methods + */ + + virtual void OnRegisterSceneNode(); + + virtual void render() + { + video::IVideoDriver* driver = SceneManager->getVideoDriver(); + driver->setTransform(video::ETS_WORLD, AbsoluteTransformation); + renderMap(driver, SceneManager->getSceneNodeRenderPass()); + } + + virtual const aabb3f &getBoundingBox() const + { + return m_box; + } + + void getBlocksInViewRange(v3s16 cam_pos_nodes, + v3s16 *p_blocks_min, v3s16 *p_blocks_max); + void updateDrawList(); + void renderMap(video::IVideoDriver* driver, s32 pass); + + int getBackgroundBrightness(float max_d, u32 daylight_factor, + int oldvalue, bool *sunlight_seen_result); + + void renderPostFx(CameraMode cam_mode); + + // For debug printing + virtual void PrintInfo(std::ostream &out); + + const MapDrawControl & getControl() const { return m_control; } + f32 getCameraFov() const { return m_camera_fov; } +private: + Client *m_client; + + aabb3f m_box = aabb3f(-BS * 1000000, -BS * 1000000, -BS * 1000000, + BS * 1000000, BS * 1000000, BS * 1000000); + + MapDrawControl &m_control; + + v3f m_camera_position = v3f(0,0,0); + v3f m_camera_direction = v3f(0,0,1); + f32 m_camera_fov = M_PI; + v3s16 m_camera_offset; + + std::map m_drawlist; + + std::set m_last_drawn_sectors; + + bool m_cache_trilinear_filter; + bool m_cache_bilinear_filter; + bool m_cache_anistropic_filter; +}; diff --git a/src/client/clientmedia.cpp b/src/client/clientmedia.cpp new file mode 100644 index 000000000..97931ee68 --- /dev/null +++ b/src/client/clientmedia.cpp @@ -0,0 +1,639 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "clientmedia.h" +#include "httpfetch.h" +#include "client.h" +#include "filecache.h" +#include "filesys.h" +#include "log.h" +#include "porting.h" +#include "settings.h" +#include "util/hex.h" +#include "util/serialize.h" +#include "util/sha1.h" +#include "util/string.h" + +static std::string getMediaCacheDir() +{ + return porting::path_cache + DIR_DELIM + "media"; +} + +/* + ClientMediaDownloader +*/ + +ClientMediaDownloader::ClientMediaDownloader(): + m_media_cache(getMediaCacheDir()), + m_httpfetch_caller(HTTPFETCH_DISCARD) +{ +} + +ClientMediaDownloader::~ClientMediaDownloader() +{ + if (m_httpfetch_caller != HTTPFETCH_DISCARD) + httpfetch_caller_free(m_httpfetch_caller); + + for (auto &file_it : m_files) + delete file_it.second; + + for (auto &remote : m_remotes) + delete remote; +} + +void ClientMediaDownloader::addFile(const std::string &name, const std::string &sha1) +{ + assert(!m_initial_step_done); // pre-condition + + // if name was already announced, ignore the new announcement + if (m_files.count(name) != 0) { + errorstream << "Client: ignoring duplicate media announcement " + << "sent by server: \"" << name << "\"" + << std::endl; + return; + } + + // if name is empty or contains illegal characters, ignore the file + if (name.empty() || !string_allowed(name, TEXTURENAME_ALLOWED_CHARS)) { + errorstream << "Client: ignoring illegal file name " + << "sent by server: \"" << name << "\"" + << std::endl; + return; + } + + // length of sha1 must be exactly 20 (160 bits), else ignore the file + if (sha1.size() != 20) { + errorstream << "Client: ignoring illegal SHA1 sent by server: " + << hex_encode(sha1) << " \"" << name << "\"" + << std::endl; + return; + } + + FileStatus *filestatus = new FileStatus(); + filestatus->received = false; + filestatus->sha1 = sha1; + filestatus->current_remote = -1; + m_files.insert(std::make_pair(name, filestatus)); +} + +void ClientMediaDownloader::addRemoteServer(const std::string &baseurl) +{ + assert(!m_initial_step_done); // pre-condition + + #ifdef USE_CURL + + if (g_settings->getBool("enable_remote_media_server")) { + infostream << "Client: Adding remote server \"" + << baseurl << "\" for media download" << std::endl; + + RemoteServerStatus *remote = new RemoteServerStatus(); + remote->baseurl = baseurl; + remote->active_count = 0; + remote->request_by_filename = false; + m_remotes.push_back(remote); + } + + #else + + infostream << "Client: Ignoring remote server \"" + << baseurl << "\" because cURL support is not compiled in" + << std::endl; + + #endif +} + +void ClientMediaDownloader::step(Client *client) +{ + if (!m_initial_step_done) { + initialStep(client); + m_initial_step_done = true; + } + + // Remote media: check for completion of fetches + if (m_httpfetch_active) { + bool fetched_something = false; + HTTPFetchResult fetch_result; + + while (httpfetch_async_get(m_httpfetch_caller, fetch_result)) { + m_httpfetch_active--; + fetched_something = true; + + // Is this a hashset (index.mth) or a media file? + if (fetch_result.request_id < m_remotes.size()) + remoteHashSetReceived(fetch_result); + else + remoteMediaReceived(fetch_result, client); + } + + if (fetched_something) + startRemoteMediaTransfers(); + + // Did all remote transfers end and no new ones can be started? + // If so, request still missing files from the minetest server + // (Or report that we have all files.) + if (m_httpfetch_active == 0) { + if (m_uncached_received_count < m_uncached_count) { + infostream << "Client: Failed to remote-fetch " + << (m_uncached_count-m_uncached_received_count) + << " files. Requesting them" + << " the usual way." << std::endl; + } + startConventionalTransfers(client); + } + } +} + +void ClientMediaDownloader::initialStep(Client *client) +{ + // Check media cache + m_uncached_count = m_files.size(); + for (auto &file_it : m_files) { + std::string name = file_it.first; + FileStatus *filestatus = file_it.second; + const std::string &sha1 = filestatus->sha1; + + std::ostringstream tmp_os(std::ios_base::binary); + bool found_in_cache = m_media_cache.load(hex_encode(sha1), tmp_os); + + // If found in cache, try to load it from there + if (found_in_cache) { + bool success = checkAndLoad(name, sha1, + tmp_os.str(), true, client); + if (success) { + filestatus->received = true; + m_uncached_count--; + } + } + } + + assert(m_uncached_received_count == 0); + + // Create the media cache dir if we are likely to write to it + if (m_uncached_count != 0) { + bool did = fs::CreateAllDirs(getMediaCacheDir()); + if (!did) { + errorstream << "Client: " + << "Could not create media cache directory: " + << getMediaCacheDir() + << std::endl; + } + } + + // If we found all files in the cache, report this fact to the server. + // If the server reported no remote servers, immediately start + // conventional transfers. Note: if cURL support is not compiled in, + // m_remotes is always empty, so "!USE_CURL" is redundant but may + // reduce the size of the compiled code + if (!USE_CURL || m_uncached_count == 0 || m_remotes.empty()) { + startConventionalTransfers(client); + } + else { + // Otherwise start off by requesting each server's sha1 set + + // This is the first time we use httpfetch, so alloc a caller ID + m_httpfetch_caller = httpfetch_caller_alloc(); + m_httpfetch_timeout = g_settings->getS32("curl_timeout"); + + // Set the active fetch limit to curl_parallel_limit or 84, + // whichever is greater. This gives us some leeway so that + // inefficiencies in communicating with the httpfetch thread + // don't slow down fetches too much. (We still want some limit + // so that when the first remote server returns its hash set, + // not all files are requested from that server immediately.) + // One such inefficiency is that ClientMediaDownloader::step() + // is only called a couple times per second, while httpfetch + // might return responses much faster than that. + // Note that httpfetch strictly enforces curl_parallel_limit + // but at no inter-thread communication cost. This however + // doesn't help with the aforementioned inefficiencies. + // The signifance of 84 is that it is 2*6*9 in base 13. + m_httpfetch_active_limit = g_settings->getS32("curl_parallel_limit"); + m_httpfetch_active_limit = MYMAX(m_httpfetch_active_limit, 84); + + // Write a list of hashes that we need. This will be POSTed + // to the server using Content-Type: application/octet-stream + std::string required_hash_set = serializeRequiredHashSet(); + + // minor fixme: this loop ignores m_httpfetch_active_limit + + // another minor fixme, unlikely to matter in normal usage: + // these index.mth fetches do (however) count against + // m_httpfetch_active_limit when starting actual media file + // requests, so if there are lots of remote servers that are + // not responding, those will stall new media file transfers. + + for (u32 i = 0; i < m_remotes.size(); ++i) { + assert(m_httpfetch_next_id == i); + + RemoteServerStatus *remote = m_remotes[i]; + actionstream << "Client: Contacting remote server \"" + << remote->baseurl << "\"" << std::endl; + + HTTPFetchRequest fetch_request; + fetch_request.url = + remote->baseurl + MTHASHSET_FILE_NAME; + fetch_request.caller = m_httpfetch_caller; + fetch_request.request_id = m_httpfetch_next_id; // == i + fetch_request.timeout = m_httpfetch_timeout; + fetch_request.connect_timeout = m_httpfetch_timeout; + fetch_request.post_data = required_hash_set; + fetch_request.extra_headers.emplace_back( + "Content-Type: application/octet-stream"); + httpfetch_async(fetch_request); + + m_httpfetch_active++; + m_httpfetch_next_id++; + m_outstanding_hash_sets++; + } + } +} + +void ClientMediaDownloader::remoteHashSetReceived( + const HTTPFetchResult &fetch_result) +{ + u32 remote_id = fetch_result.request_id; + assert(remote_id < m_remotes.size()); + RemoteServerStatus *remote = m_remotes[remote_id]; + + m_outstanding_hash_sets--; + + if (fetch_result.succeeded) { + try { + // Server sent a list of file hashes that are + // available on it, try to parse the list + + std::set sha1_set; + deSerializeHashSet(fetch_result.data, sha1_set); + + // Parsing succeeded: For every file that is + // available on this server, add this server + // to the available_remotes array + + for(std::map::iterator + it = m_files.upper_bound(m_name_bound); + it != m_files.end(); ++it) { + FileStatus *f = it->second; + if (!f->received && sha1_set.count(f->sha1)) + f->available_remotes.push_back(remote_id); + } + } + catch (SerializationError &e) { + infostream << "Client: Remote server \"" + << remote->baseurl << "\" sent invalid hash set: " + << e.what() << std::endl; + } + } + + // For compatibility: If index.mth is not found, assume that the + // server contains files named like the original files (not their sha1) + + // Do NOT check for any particular response code (e.g. 404) here, + // because different servers respond differently + + if (!fetch_result.succeeded && !fetch_result.timeout) { + infostream << "Client: Enabling compatibility mode for remote " + << "server \"" << remote->baseurl << "\"" << std::endl; + remote->request_by_filename = true; + + // Assume every file is available on this server + + for(std::map::iterator + it = m_files.upper_bound(m_name_bound); + it != m_files.end(); ++it) { + FileStatus *f = it->second; + if (!f->received) + f->available_remotes.push_back(remote_id); + } + } +} + +void ClientMediaDownloader::remoteMediaReceived( + const HTTPFetchResult &fetch_result, + Client *client) +{ + // Some remote server sent us a file. + // -> decrement number of active fetches + // -> mark file as received if fetch succeeded + // -> try to load media + + std::string name; + { + std::unordered_map::iterator it = + m_remote_file_transfers.find(fetch_result.request_id); + assert(it != m_remote_file_transfers.end()); + name = it->second; + m_remote_file_transfers.erase(it); + } + + sanity_check(m_files.count(name) != 0); + + FileStatus *filestatus = m_files[name]; + sanity_check(!filestatus->received); + sanity_check(filestatus->current_remote >= 0); + + RemoteServerStatus *remote = m_remotes[filestatus->current_remote]; + + filestatus->current_remote = -1; + remote->active_count--; + + // If fetch succeeded, try to load media file + + if (fetch_result.succeeded) { + bool success = checkAndLoad(name, filestatus->sha1, + fetch_result.data, false, client); + if (success) { + filestatus->received = true; + assert(m_uncached_received_count < m_uncached_count); + m_uncached_received_count++; + } + } +} + +s32 ClientMediaDownloader::selectRemoteServer(FileStatus *filestatus) +{ + // Pre-conditions + assert(filestatus != NULL); + assert(!filestatus->received); + assert(filestatus->current_remote < 0); + + if (filestatus->available_remotes.empty()) + return -1; + + // Of all servers that claim to provide the file (and haven't + // been unsuccessfully tried before), find the one with the + // smallest number of currently active transfers + + s32 best = 0; + s32 best_remote_id = filestatus->available_remotes[best]; + s32 best_active_count = m_remotes[best_remote_id]->active_count; + + for (u32 i = 1; i < filestatus->available_remotes.size(); ++i) { + s32 remote_id = filestatus->available_remotes[i]; + s32 active_count = m_remotes[remote_id]->active_count; + if (active_count < best_active_count) { + best = i; + best_remote_id = remote_id; + best_active_count = active_count; + } + } + + filestatus->available_remotes.erase( + filestatus->available_remotes.begin() + best); + + return best_remote_id; + +} + +void ClientMediaDownloader::startRemoteMediaTransfers() +{ + bool changing_name_bound = true; + + for (std::map::iterator + files_iter = m_files.upper_bound(m_name_bound); + files_iter != m_files.end(); ++files_iter) { + + // Abort if active fetch limit is exceeded + if (m_httpfetch_active >= m_httpfetch_active_limit) + break; + + const std::string &name = files_iter->first; + FileStatus *filestatus = files_iter->second; + + if (!filestatus->received && filestatus->current_remote < 0) { + // File has not been received yet and is not currently + // being transferred. Choose a server for it. + s32 remote_id = selectRemoteServer(filestatus); + if (remote_id >= 0) { + // Found a server, so start fetching + RemoteServerStatus *remote = + m_remotes[remote_id]; + + std::string url = remote->baseurl + + (remote->request_by_filename ? name : + hex_encode(filestatus->sha1)); + verbosestream << "Client: " + << "Requesting remote media file " + << "\"" << name << "\" " + << "\"" << url << "\"" << std::endl; + + HTTPFetchRequest fetch_request; + fetch_request.url = url; + fetch_request.caller = m_httpfetch_caller; + fetch_request.request_id = m_httpfetch_next_id; + fetch_request.timeout = 0; // no data timeout! + fetch_request.connect_timeout = + m_httpfetch_timeout; + httpfetch_async(fetch_request); + + m_remote_file_transfers.insert(std::make_pair( + m_httpfetch_next_id, + name)); + + filestatus->current_remote = remote_id; + remote->active_count++; + m_httpfetch_active++; + m_httpfetch_next_id++; + } + } + + if (filestatus->received || + (filestatus->current_remote < 0 && + !m_outstanding_hash_sets)) { + // If we arrive here, we conclusively know that we + // won't fetch this file from a remote server in the + // future. So update the name bound if possible. + if (changing_name_bound) + m_name_bound = name; + } + else + changing_name_bound = false; + } + +} + +void ClientMediaDownloader::startConventionalTransfers(Client *client) +{ + assert(m_httpfetch_active == 0); // pre-condition + + if (m_uncached_received_count != m_uncached_count) { + // Some media files have not been received yet, use the + // conventional slow method (minetest protocol) to get them + std::vector file_requests; + for (auto &file : m_files) { + if (!file.second->received) + file_requests.push_back(file.first); + } + assert((s32) file_requests.size() == + m_uncached_count - m_uncached_received_count); + client->request_media(file_requests); + } +} + +void ClientMediaDownloader::conventionalTransferDone( + const std::string &name, + const std::string &data, + Client *client) +{ + // Check that file was announced + std::map::iterator + file_iter = m_files.find(name); + if (file_iter == m_files.end()) { + errorstream << "Client: server sent media file that was" + << "not announced, ignoring it: \"" << name << "\"" + << std::endl; + return; + } + FileStatus *filestatus = file_iter->second; + assert(filestatus != NULL); + + // Check that file hasn't already been received + if (filestatus->received) { + errorstream << "Client: server sent media file that we already" + << "received, ignoring it: \"" << name << "\"" + << std::endl; + return; + } + + // Mark file as received, regardless of whether loading it works and + // whether the checksum matches (because at this point there is no + // other server that could send a replacement) + filestatus->received = true; + assert(m_uncached_received_count < m_uncached_count); + m_uncached_received_count++; + + // Check that received file matches announced checksum + // If so, load it + checkAndLoad(name, filestatus->sha1, data, false, client); +} + +bool ClientMediaDownloader::checkAndLoad( + const std::string &name, const std::string &sha1, + const std::string &data, bool is_from_cache, Client *client) +{ + const char *cached_or_received = is_from_cache ? "cached" : "received"; + const char *cached_or_received_uc = is_from_cache ? "Cached" : "Received"; + std::string sha1_hex = hex_encode(sha1); + + // Compute actual checksum of data + std::string data_sha1; + { + SHA1 data_sha1_calculator; + data_sha1_calculator.addBytes(data.c_str(), data.size()); + unsigned char *data_tmpdigest = data_sha1_calculator.getDigest(); + data_sha1.assign((char*) data_tmpdigest, 20); + free(data_tmpdigest); + } + + // Check that received file matches announced checksum + if (data_sha1 != sha1) { + std::string data_sha1_hex = hex_encode(data_sha1); + infostream << "Client: " + << cached_or_received_uc << " media file " + << sha1_hex << " \"" << name << "\" " + << "mismatches actual checksum " << data_sha1_hex + << std::endl; + return false; + } + + // Checksum is ok, try loading the file + bool success = client->loadMedia(data, name); + if (!success) { + infostream << "Client: " + << "Failed to load " << cached_or_received << " media: " + << sha1_hex << " \"" << name << "\"" + << std::endl; + return false; + } + + verbosestream << "Client: " + << "Loaded " << cached_or_received << " media: " + << sha1_hex << " \"" << name << "\"" + << std::endl; + + // Update cache (unless we just loaded the file from the cache) + if (!is_from_cache) + m_media_cache.update(sha1_hex, data); + + return true; +} + + +/* + Minetest Hashset File Format + + All values are stored in big-endian byte order. + [u32] signature: 'MTHS' + [u16] version: 1 + For each hash in set: + [u8*20] SHA1 hash + + Version changes: + 1 - Initial version +*/ + +std::string ClientMediaDownloader::serializeRequiredHashSet() +{ + std::ostringstream os(std::ios::binary); + + writeU32(os, MTHASHSET_FILE_SIGNATURE); // signature + writeU16(os, 1); // version + + // Write list of hashes of files that have not been + // received (found in cache) yet + for (std::map::iterator + it = m_files.begin(); + it != m_files.end(); ++it) { + if (!it->second->received) { + FATAL_ERROR_IF(it->second->sha1.size() != 20, "Invalid SHA1 size"); + os << it->second->sha1; + } + } + + return os.str(); +} + +void ClientMediaDownloader::deSerializeHashSet(const std::string &data, + std::set &result) +{ + if (data.size() < 6 || data.size() % 20 != 6) { + throw SerializationError( + "ClientMediaDownloader::deSerializeHashSet: " + "invalid hash set file size"); + } + + const u8 *data_cstr = (const u8*) data.c_str(); + + u32 signature = readU32(&data_cstr[0]); + if (signature != MTHASHSET_FILE_SIGNATURE) { + throw SerializationError( + "ClientMediaDownloader::deSerializeHashSet: " + "invalid hash set file signature"); + } + + u16 version = readU16(&data_cstr[4]); + if (version != 1) { + throw SerializationError( + "ClientMediaDownloader::deSerializeHashSet: " + "unsupported hash set file version"); + } + + for (u32 pos = 6; pos < data.size(); pos += 20) { + result.insert(data.substr(pos, 20)); + } +} diff --git a/src/client/clientmedia.h b/src/client/clientmedia.h new file mode 100644 index 000000000..b08b83e4d --- /dev/null +++ b/src/client/clientmedia.h @@ -0,0 +1,148 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include "irrlichttypes.h" +#include "filecache.h" +#include +#include +#include +#include +#include + +class Client; +struct HTTPFetchResult; + +#define MTHASHSET_FILE_SIGNATURE 0x4d544853 // 'MTHS' +#define MTHASHSET_FILE_NAME "index.mth" + +class ClientMediaDownloader +{ +public: + ClientMediaDownloader(); + ~ClientMediaDownloader(); + + float getProgress() const { + if (m_uncached_count >= 1) + return 1.0f * m_uncached_received_count / + m_uncached_count; + + return 0.0f; + } + + bool isStarted() const { + return m_initial_step_done; + } + + // If this returns true, the downloader is done and can be deleted + bool isDone() const { + return m_initial_step_done && + m_uncached_received_count == m_uncached_count; + } + + // Add a file to the list of required file (but don't fetch it yet) + void addFile(const std::string &name, const std::string &sha1); + + // Add a remote server to the list; ignored if not built with cURL + void addRemoteServer(const std::string &baseurl); + + // Steps the media downloader: + // - May load media into client by calling client->loadMedia() + // - May check media cache for files + // - May add files to media cache + // - May start remote transfers by calling httpfetch_async + // - May check for completion of current remote transfers + // - May start conventional transfers by calling client->request_media() + // - May inform server that all media has been loaded + // by calling client->received_media() + // After step has been called once, don't call addFile/addRemoteServer. + void step(Client *client); + + // Must be called for each file received through TOCLIENT_MEDIA + void conventionalTransferDone( + const std::string &name, + const std::string &data, + Client *client); + +private: + struct FileStatus { + bool received; + std::string sha1; + s32 current_remote; + std::vector available_remotes; + }; + + struct RemoteServerStatus { + std::string baseurl; + s32 active_count; + bool request_by_filename; + }; + + void initialStep(Client *client); + void remoteHashSetReceived(const HTTPFetchResult &fetch_result); + void remoteMediaReceived(const HTTPFetchResult &fetch_result, + Client *client); + s32 selectRemoteServer(FileStatus *filestatus); + void startRemoteMediaTransfers(); + void startConventionalTransfers(Client *client); + + bool checkAndLoad(const std::string &name, const std::string &sha1, + const std::string &data, bool is_from_cache, + Client *client); + + std::string serializeRequiredHashSet(); + static void deSerializeHashSet(const std::string &data, + std::set &result); + + // Maps filename to file status + std::map m_files; + + // Array of remote media servers + std::vector m_remotes; + + // Filesystem-based media cache + FileCache m_media_cache; + + // Has an attempt been made to load media files from the file cache? + // Have hash sets been requested from remote servers? + bool m_initial_step_done = false; + + // Total number of media files to load + s32 m_uncached_count = 0; + + // Number of media files that have been received + s32 m_uncached_received_count = 0; + + // Status of remote transfers + unsigned long m_httpfetch_caller; + unsigned long m_httpfetch_next_id = 0; + long m_httpfetch_timeout = 0; + s32 m_httpfetch_active = 0; + s32 m_httpfetch_active_limit = 0; + s32 m_outstanding_hash_sets = 0; + std::unordered_map m_remote_file_transfers; + + // All files up to this name have either been received from a + // remote server or failed on all remote servers, so those files + // don't need to be looked at again + // (use m_files.upper_bound(m_name_bound) to get an iterator) + std::string m_name_bound = ""; + +}; diff --git a/src/client/clientobject.cpp b/src/client/clientobject.cpp new file mode 100644 index 000000000..f4b69201b --- /dev/null +++ b/src/client/clientobject.cpp @@ -0,0 +1,66 @@ +/* +Minetest +Copyright (C) 2010-2013 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "clientobject.h" +#include "debug.h" +#include "porting.h" + +/* + ClientActiveObject +*/ + +ClientActiveObject::ClientActiveObject(u16 id, Client *client, + ClientEnvironment *env): + ActiveObject(id), + m_client(client), + m_env(env) +{ +} + +ClientActiveObject::~ClientActiveObject() +{ + removeFromScene(true); +} + +ClientActiveObject* ClientActiveObject::create(ActiveObjectType type, + Client *client, ClientEnvironment *env) +{ + // Find factory function + auto n = m_types.find(type); + if (n == m_types.end()) { + // If factory is not found, just return. + warningstream << "ClientActiveObject: No factory for type=" + << (int)type << std::endl; + return NULL; + } + + Factory f = n->second; + ClientActiveObject *object = (*f)(client, env); + return object; +} + +void ClientActiveObject::registerType(u16 type, Factory f) +{ + auto n = m_types.find(type); + if (n != m_types.end()) + return; + m_types[type] = f; +} + + diff --git a/src/client/clientobject.h b/src/client/clientobject.h new file mode 100644 index 000000000..9377d1e67 --- /dev/null +++ b/src/client/clientobject.h @@ -0,0 +1,108 @@ +/* +Minetest +Copyright (C) 2010-2013 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include "irrlichttypes_extrabloated.h" +#include "activeobject.h" +#include + +class ClientEnvironment; +class ITextureSource; +class Client; +class IGameDef; +class LocalPlayer; +struct ItemStack; +class WieldMeshSceneNode; + +class ClientActiveObject : public ActiveObject +{ +public: + ClientActiveObject(u16 id, Client *client, ClientEnvironment *env); + virtual ~ClientActiveObject(); + + virtual void addToScene(ITextureSource *tsrc) {}; + virtual void removeFromScene(bool permanent) {} + // 0 <= light_at_pos <= LIGHT_SUN + virtual void updateLight(u8 light_at_pos){} + virtual void updateLightNoCheck(u8 light_at_pos){} + virtual v3s16 getLightPosition(){return v3s16(0,0,0);} + virtual bool getCollisionBox(aabb3f *toset) const { return false; } + virtual bool getSelectionBox(aabb3f *toset) const { return false; } + virtual bool collideWithObjects() const { return false; } + virtual v3f getPosition(){ return v3f(0,0,0); } + virtual float getYaw() const { return 0; } + virtual scene::ISceneNode *getSceneNode() { return NULL; } + virtual scene::IAnimatedMeshSceneNode *getAnimatedMeshSceneNode() { return NULL; } + virtual bool isLocalPlayer() const {return false;} + virtual ClientActiveObject *getParent() const { return nullptr; }; + virtual void setAttachments() {} + virtual bool doShowSelectionBox(){return true;} + + // Step object in time + virtual void step(float dtime, ClientEnvironment *env){} + + // Process a message sent by the server side object + virtual void processMessage(const std::string &data){} + + virtual std::string infoText() {return "";} + virtual std::string debugInfoText() {return "";} + + /* + This takes the return value of + ServerActiveObject::getClientInitializationData + */ + virtual void initialize(const std::string &data){} + + // Create a certain type of ClientActiveObject + static ClientActiveObject* create(ActiveObjectType type, Client *client, + ClientEnvironment *env); + + // If returns true, punch will not be sent to the server + virtual bool directReportPunch(v3f dir, const ItemStack *punchitem=NULL, + float time_from_last_punch=1000000) + { return false; } + +protected: + // Used for creating objects based on type + typedef ClientActiveObject* (*Factory)(Client *client, ClientEnvironment *env); + static void registerType(u16 type, Factory f); + Client *m_client; + ClientEnvironment *m_env; +private: + // Used for creating objects based on type + static std::unordered_map m_types; +}; + +struct DistanceSortedActiveObject +{ + ClientActiveObject *obj; + f32 d; + + DistanceSortedActiveObject(ClientActiveObject *a_obj, f32 a_d) + { + obj = a_obj; + d = a_d; + } + + bool operator < (const DistanceSortedActiveObject &other) const + { + return d < other.d; + } +}; diff --git a/src/client/clouds.cpp b/src/client/clouds.cpp new file mode 100644 index 000000000..13051f32c --- /dev/null +++ b/src/client/clouds.cpp @@ -0,0 +1,386 @@ +/* +Minetest +Copyright (C) 2010-2013 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "client/renderingengine.h" +#include "clouds.h" +#include "noise.h" +#include "constants.h" +#include "debug.h" +#include "profiler.h" +#include "settings.h" +#include + + +// Menu clouds are created later +class Clouds; +Clouds *g_menuclouds = NULL; +irr::scene::ISceneManager *g_menucloudsmgr = NULL; + +// Constant for now +static constexpr const float cloud_size = BS * 64.0f; + +static void cloud_3d_setting_changed(const std::string &settingname, void *data) +{ + ((Clouds *)data)->readSettings(); +} + +Clouds::Clouds(scene::ISceneManager* mgr, + s32 id, + u32 seed +): + scene::ISceneNode(mgr->getRootSceneNode(), mgr, id), + m_seed(seed) +{ + m_material.setFlag(video::EMF_LIGHTING, false); + //m_material.setFlag(video::EMF_BACK_FACE_CULLING, false); + m_material.setFlag(video::EMF_BACK_FACE_CULLING, true); + m_material.setFlag(video::EMF_BILINEAR_FILTER, false); + m_material.setFlag(video::EMF_FOG_ENABLE, true); + m_material.setFlag(video::EMF_ANTI_ALIASING, true); + //m_material.MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA; + m_material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; + + m_params.height = 120; + m_params.density = 0.4f; + m_params.thickness = 16.0f; + m_params.color_bright = video::SColor(229, 240, 240, 255); + m_params.color_ambient = video::SColor(255, 0, 0, 0); + m_params.speed = v2f(0.0f, -2.0f); + + readSettings(); + g_settings->registerChangedCallback("enable_3d_clouds", + &cloud_3d_setting_changed, this); + + updateBox(); +} + +Clouds::~Clouds() +{ + g_settings->deregisterChangedCallback("enable_3d_clouds", + &cloud_3d_setting_changed, this); +} + +void Clouds::OnRegisterSceneNode() +{ + if(IsVisible) + { + SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT); + //SceneManager->registerNodeForRendering(this, scene::ESNRP_SOLID); + } + + ISceneNode::OnRegisterSceneNode(); +} + +void Clouds::render() +{ + + if (m_params.density <= 0.0f) + return; // no need to do anything + + video::IVideoDriver* driver = SceneManager->getVideoDriver(); + + if(SceneManager->getSceneNodeRenderPass() != scene::ESNRP_TRANSPARENT) + //if(SceneManager->getSceneNodeRenderPass() != scene::ESNRP_SOLID) + return; + + ScopeProfiler sp(g_profiler, "Rendering of clouds, avg", SPT_AVG); + + int num_faces_to_draw = m_enable_3d ? 6 : 1; + + m_material.setFlag(video::EMF_BACK_FACE_CULLING, m_enable_3d); + + driver->setTransform(video::ETS_WORLD, AbsoluteTransformation); + driver->setMaterial(m_material); + + /* + Clouds move from Z+ towards Z- + */ + + const float cloud_full_radius = cloud_size * m_cloud_radius_i; + + v2f camera_pos_2d(m_camera_pos.X, m_camera_pos.Z); + // Position of cloud noise origin from the camera + v2f cloud_origin_from_camera_f = m_origin - camera_pos_2d; + // The center point of drawing in the noise + v2f center_of_drawing_in_noise_f = -cloud_origin_from_camera_f; + // The integer center point of drawing in the noise + v2s16 center_of_drawing_in_noise_i( + std::floor(center_of_drawing_in_noise_f.X / cloud_size), + std::floor(center_of_drawing_in_noise_f.Y / cloud_size) + ); + + // The world position of the integer center point of drawing in the noise + v2f world_center_of_drawing_in_noise_f = v2f( + center_of_drawing_in_noise_i.X * cloud_size, + center_of_drawing_in_noise_i.Y * cloud_size + ) + m_origin; + + /*video::SColor c_top(128,b*240,b*240,b*255); + video::SColor c_side_1(128,b*230,b*230,b*255); + video::SColor c_side_2(128,b*220,b*220,b*245); + video::SColor c_bottom(128,b*205,b*205,b*230);*/ + video::SColorf c_top_f(m_color); + video::SColorf c_side_1_f(m_color); + video::SColorf c_side_2_f(m_color); + video::SColorf c_bottom_f(m_color); + c_side_1_f.r *= 0.95; + c_side_1_f.g *= 0.95; + c_side_1_f.b *= 0.95; + c_side_2_f.r *= 0.90; + c_side_2_f.g *= 0.90; + c_side_2_f.b *= 0.90; + c_bottom_f.r *= 0.80; + c_bottom_f.g *= 0.80; + c_bottom_f.b *= 0.80; + video::SColor c_top = c_top_f.toSColor(); + video::SColor c_side_1 = c_side_1_f.toSColor(); + video::SColor c_side_2 = c_side_2_f.toSColor(); + video::SColor c_bottom = c_bottom_f.toSColor(); + + // Get fog parameters for setting them back later + video::SColor fog_color(0,0,0,0); + video::E_FOG_TYPE fog_type = video::EFT_FOG_LINEAR; + f32 fog_start = 0; + f32 fog_end = 0; + f32 fog_density = 0; + bool fog_pixelfog = false; + bool fog_rangefog = false; + driver->getFog(fog_color, fog_type, fog_start, fog_end, fog_density, + fog_pixelfog, fog_rangefog); + + // Set our own fog + driver->setFog(fog_color, fog_type, cloud_full_radius * 0.5, + cloud_full_radius*1.2, fog_density, fog_pixelfog, fog_rangefog); + + // Read noise + + bool *grid = new bool[m_cloud_radius_i * 2 * m_cloud_radius_i * 2]; + + + for(s16 zi = -m_cloud_radius_i; zi < m_cloud_radius_i; zi++) { + u32 si = (zi + m_cloud_radius_i) * m_cloud_radius_i * 2 + m_cloud_radius_i; + + for (s16 xi = -m_cloud_radius_i; xi < m_cloud_radius_i; xi++) { + u32 i = si + xi; + + grid[i] = gridFilled( + xi + center_of_drawing_in_noise_i.X, + zi + center_of_drawing_in_noise_i.Y + ); + } + } + +#define GETINDEX(x, z, radius) (((z)+(radius))*(radius)*2 + (x)+(radius)) +#define INAREA(x, z, radius) \ + ((x) >= -(radius) && (x) < (radius) && (z) >= -(radius) && (z) < (radius)) + + for (s16 zi0= -m_cloud_radius_i; zi0 < m_cloud_radius_i; zi0++) + for (s16 xi0= -m_cloud_radius_i; xi0 < m_cloud_radius_i; xi0++) + { + s16 zi = zi0; + s16 xi = xi0; + // Draw from front to back (needed for transparency) + /*if(zi <= 0) + zi = -m_cloud_radius_i - zi; + if(xi <= 0) + xi = -m_cloud_radius_i - xi;*/ + // Draw from back to front + if(zi >= 0) + zi = m_cloud_radius_i - zi - 1; + if(xi >= 0) + xi = m_cloud_radius_i - xi - 1; + + u32 i = GETINDEX(xi, zi, m_cloud_radius_i); + + if (!grid[i]) + continue; + + v2f p0 = v2f(xi,zi)*cloud_size + world_center_of_drawing_in_noise_f; + + video::S3DVertex v[4] = { + video::S3DVertex(0,0,0, 0,0,0, c_top, 0, 1), + video::S3DVertex(0,0,0, 0,0,0, c_top, 1, 1), + video::S3DVertex(0,0,0, 0,0,0, c_top, 1, 0), + video::S3DVertex(0,0,0, 0,0,0, c_top, 0, 0) + }; + + /*if(zi <= 0 && xi <= 0){ + v[0].Color.setBlue(255); + v[1].Color.setBlue(255); + v[2].Color.setBlue(255); + v[3].Color.setBlue(255); + }*/ + + f32 rx = cloud_size / 2.0f; + // if clouds are flat, the top layer should be at the given height + f32 ry = m_enable_3d ? m_params.thickness * BS : 0.0f; + f32 rz = cloud_size / 2; + + for(int i=0; idrawVertexPrimitiveList(v, 4, indices, 2, + video::EVT_STANDARD, scene::EPT_TRIANGLES, video::EIT_16BIT); + } + } + + delete[] grid; + + // Restore fog settings + driver->setFog(fog_color, fog_type, fog_start, fog_end, fog_density, + fog_pixelfog, fog_rangefog); +} + +void Clouds::step(float dtime) +{ + m_origin = m_origin + dtime * BS * m_params.speed; +} + +void Clouds::update(const v3f &camera_p, const video::SColorf &color_diffuse) +{ + m_camera_pos = camera_p; + m_color.r = MYMIN(MYMAX(color_diffuse.r * m_params.color_bright.getRed(), + m_params.color_ambient.getRed()), 255) / 255.0f; + m_color.g = MYMIN(MYMAX(color_diffuse.g * m_params.color_bright.getGreen(), + m_params.color_ambient.getGreen()), 255) / 255.0f; + m_color.b = MYMIN(MYMAX(color_diffuse.b * m_params.color_bright.getBlue(), + m_params.color_ambient.getBlue()), 255) / 255.0f; + m_color.a = m_params.color_bright.getAlpha() / 255.0f; + + // is the camera inside the cloud mesh? + m_camera_inside_cloud = false; // default + if (m_enable_3d) { + float camera_height = camera_p.Y; + if (camera_height >= m_box.MinEdge.Y && + camera_height <= m_box.MaxEdge.Y) { + v2f camera_in_noise; + camera_in_noise.X = floor((camera_p.X - m_origin.X) / cloud_size + 0.5); + camera_in_noise.Y = floor((camera_p.Z - m_origin.Y) / cloud_size + 0.5); + bool filled = gridFilled(camera_in_noise.X, camera_in_noise.Y); + m_camera_inside_cloud = filled; + } + } +} + +void Clouds::readSettings() +{ + m_cloud_radius_i = g_settings->getU16("cloud_radius"); + m_enable_3d = g_settings->getBool("enable_3d_clouds"); +} + +bool Clouds::gridFilled(int x, int y) const +{ + float cloud_size_noise = cloud_size / (BS * 200.f); + float noise = noise2d_perlin( + (float)x * cloud_size_noise, + (float)y * cloud_size_noise, + m_seed, 3, 0.5); + // normalize to 0..1 (given 3 octaves) + static constexpr const float noise_bound = 1.0f + 0.5f + 0.25f; + float density = noise / noise_bound * 0.5f + 0.5f; + return (density < m_params.density); +} diff --git a/src/client/clouds.h b/src/client/clouds.h new file mode 100644 index 000000000..a4d810faa --- /dev/null +++ b/src/client/clouds.h @@ -0,0 +1,144 @@ +/* +Minetest +Copyright (C) 2010-2013 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include "irrlichttypes_extrabloated.h" +#include +#include "constants.h" +#include "cloudparams.h" + +// Menu clouds +class Clouds; +extern Clouds *g_menuclouds; + +// Scene manager used for menu clouds +namespace irr{namespace scene{class ISceneManager;}} +extern irr::scene::ISceneManager *g_menucloudsmgr; + +class Clouds : public scene::ISceneNode +{ +public: + Clouds(scene::ISceneManager* mgr, + s32 id, + u32 seed + ); + + ~Clouds(); + + /* + ISceneNode methods + */ + + virtual void OnRegisterSceneNode(); + + virtual void render(); + + virtual const aabb3f &getBoundingBox() const + { + return m_box; + } + + virtual u32 getMaterialCount() const + { + return 1; + } + + virtual video::SMaterial& getMaterial(u32 i) + { + return m_material; + } + + /* + Other stuff + */ + + void step(float dtime); + + void update(const v3f &camera_p, const video::SColorf &color); + + void updateCameraOffset(const v3s16 &camera_offset) + { + m_camera_offset = camera_offset; + updateBox(); + } + + void readSettings(); + + void setDensity(float density) + { + m_params.density = density; + // currently does not need bounding + } + + void setColorBright(const video::SColor &color_bright) + { + m_params.color_bright = color_bright; + } + + void setColorAmbient(const video::SColor &color_ambient) + { + m_params.color_ambient = color_ambient; + } + + void setHeight(float height) + { + m_params.height = height; // add bounding when necessary + updateBox(); + } + + void setSpeed(v2f speed) + { + m_params.speed = speed; + } + + void setThickness(float thickness) + { + m_params.thickness = thickness; + updateBox(); + } + + bool isCameraInsideCloud() const { return m_camera_inside_cloud; } + + const video::SColor getColor() const { return m_color.toSColor(); } + +private: + void updateBox() + { + float height_bs = m_params.height * BS; + float thickness_bs = m_params.thickness * BS; + m_box = aabb3f(-BS * 1000000.0f, height_bs - BS * m_camera_offset.Y, -BS * 1000000.0f, + BS * 1000000.0f, height_bs + thickness_bs - BS * m_camera_offset.Y, BS * 1000000.0f); + } + + bool gridFilled(int x, int y) const; + + video::SMaterial m_material; + aabb3f m_box; + u16 m_cloud_radius_i; + bool m_enable_3d; + u32 m_seed; + v3f m_camera_pos; + v2f m_origin; + v3s16 m_camera_offset; + video::SColorf m_color = video::SColorf(1.0f, 1.0f, 1.0f, 1.0f); + CloudParams m_params; + bool m_camera_inside_cloud = false; + +}; diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp new file mode 100644 index 000000000..db59ae5c5 --- /dev/null +++ b/src/client/content_cao.cpp @@ -0,0 +1,1611 @@ +/* +Minetest +Copyright (C) 2010-2013 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include +#include +#include +#include +#include +#include "content_cao.h" +#include "util/numeric.h" // For IntervalLimiter +#include "util/serialize.h" +#include "util/basic_macros.h" +#include "client/sound.h" +#include "client/tile.h" +#include "environment.h" +#include "collision.h" +#include "settings.h" +#include "serialization.h" // For decompressZlib +#include "clientobject.h" +#include "mesh.h" +#include "itemdef.h" +#include "tool.h" +#include "content_cso.h" +#include "sound.h" +#include "nodedef.h" +#include "localplayer.h" +#include "map.h" +#include "camera.h" // CameraModes +#include "client.h" +#include "wieldmesh.h" +#include +#include +#include "client/renderingengine.h" + +class Settings; +struct ToolCapabilities; + +std::unordered_map ClientActiveObject::m_types; + +template +void SmoothTranslator::init(T current) +{ + val_old = current; + val_current = current; + val_target = current; + anim_time = 0; + anim_time_counter = 0; + aim_is_end = true; +} + +template +void SmoothTranslator::update(T new_target, bool is_end_position, float update_interval) +{ + aim_is_end = is_end_position; + val_old = val_current; + val_target = new_target; + if (update_interval > 0) { + anim_time = update_interval; + } else { + if (anim_time < 0.001 || anim_time > 1.0) + anim_time = anim_time_counter; + else + anim_time = anim_time * 0.9 + anim_time_counter * 0.1; + } + anim_time_counter = 0; +} + +template +void SmoothTranslator::translate(f32 dtime) +{ + anim_time_counter = anim_time_counter + dtime; + T val_diff = val_target - val_old; + f32 moveratio = 1.0; + if (anim_time > 0.001) + moveratio = anim_time_counter / anim_time; + f32 move_end = aim_is_end ? 1.0 : 1.5; + + // Move a bit less than should, to avoid oscillation + moveratio = std::min(moveratio * 0.8f, move_end); + val_current = val_old + val_diff * moveratio; +} + +void SmoothTranslatorWrapped::translate(f32 dtime) +{ + anim_time_counter = anim_time_counter + dtime; + f32 val_diff = std::abs(val_target - val_old); + if (val_diff > 180.f) + val_diff = 360.f - val_diff; + + f32 moveratio = 1.0; + if (anim_time > 0.001) + moveratio = anim_time_counter / anim_time; + f32 move_end = aim_is_end ? 1.0 : 1.5; + + // Move a bit less than should, to avoid oscillation + moveratio = std::min(moveratio * 0.8f, move_end); + wrappedApproachShortest(val_current, val_target, + val_diff * moveratio, 360.f); +} + +void SmoothTranslatorWrappedv3f::translate(f32 dtime) +{ + anim_time_counter = anim_time_counter + dtime; + + v3f val_diff_v3f; + val_diff_v3f.X = std::abs(val_target.X - val_old.X); + val_diff_v3f.Y = std::abs(val_target.Y - val_old.Y); + val_diff_v3f.Z = std::abs(val_target.Z - val_old.Z); + + if (val_diff_v3f.X > 180.f) + val_diff_v3f.X = 360.f - val_diff_v3f.X; + + if (val_diff_v3f.Y > 180.f) + val_diff_v3f.Y = 360.f - val_diff_v3f.Y; + + if (val_diff_v3f.Z > 180.f) + val_diff_v3f.Z = 360.f - val_diff_v3f.Z; + + f32 moveratio = 1.0; + if (anim_time > 0.001) + moveratio = anim_time_counter / anim_time; + f32 move_end = aim_is_end ? 1.0 : 1.5; + + // Move a bit less than should, to avoid oscillation + moveratio = std::min(moveratio * 0.8f, move_end); + wrappedApproachShortest(val_current.X, val_target.X, + val_diff_v3f.X * moveratio, 360.f); + + wrappedApproachShortest(val_current.Y, val_target.Y, + val_diff_v3f.Y * moveratio, 360.f); + + wrappedApproachShortest(val_current.Z, val_target.Z, + val_diff_v3f.Z * moveratio, 360.f); +} + +/* + Other stuff +*/ + +static void setBillboardTextureMatrix(scene::IBillboardSceneNode *bill, + float txs, float tys, int col, int row) +{ + video::SMaterial& material = bill->getMaterial(0); + core::matrix4& matrix = material.getTextureMatrix(0); + matrix.setTextureTranslate(txs*col, tys*row); + matrix.setTextureScale(txs, tys); +} + +/* + TestCAO +*/ + +class TestCAO : public ClientActiveObject +{ +public: + TestCAO(Client *client, ClientEnvironment *env); + virtual ~TestCAO() = default; + + ActiveObjectType getType() const + { + return ACTIVEOBJECT_TYPE_TEST; + } + + static ClientActiveObject* create(Client *client, ClientEnvironment *env); + + void addToScene(ITextureSource *tsrc); + void removeFromScene(bool permanent); + void updateLight(u8 light_at_pos); + v3s16 getLightPosition(); + void updateNodePos(); + + void step(float dtime, ClientEnvironment *env); + + void processMessage(const std::string &data); + + bool getCollisionBox(aabb3f *toset) const { return false; } +private: + scene::IMeshSceneNode *m_node; + v3f m_position; +}; + +// Prototype +TestCAO proto_TestCAO(NULL, NULL); + +TestCAO::TestCAO(Client *client, ClientEnvironment *env): + ClientActiveObject(0, client, env), + m_node(NULL), + m_position(v3f(0,10*BS,0)) +{ + ClientActiveObject::registerType(getType(), create); +} + +ClientActiveObject* TestCAO::create(Client *client, ClientEnvironment *env) +{ + return new TestCAO(client, env); +} + +void TestCAO::addToScene(ITextureSource *tsrc) +{ + if(m_node != NULL) + return; + + //video::IVideoDriver* driver = smgr->getVideoDriver(); + + scene::SMesh *mesh = new scene::SMesh(); + scene::IMeshBuffer *buf = new scene::SMeshBuffer(); + video::SColor c(255,255,255,255); + video::S3DVertex vertices[4] = + { + video::S3DVertex(-BS/2,-BS/4,0, 0,0,0, c, 0,1), + video::S3DVertex(BS/2,-BS/4,0, 0,0,0, c, 1,1), + video::S3DVertex(BS/2,BS/4,0, 0,0,0, c, 1,0), + video::S3DVertex(-BS/2,BS/4,0, 0,0,0, c, 0,0), + }; + u16 indices[] = {0,1,2,2,3,0}; + buf->append(vertices, 4, indices, 6); + // Set material + buf->getMaterial().setFlag(video::EMF_LIGHTING, false); + buf->getMaterial().setFlag(video::EMF_BACK_FACE_CULLING, false); + buf->getMaterial().setTexture(0, tsrc->getTextureForMesh("rat.png")); + buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false); + buf->getMaterial().setFlag(video::EMF_FOG_ENABLE, true); + buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; + // Add to mesh + mesh->addMeshBuffer(buf); + buf->drop(); + m_node = RenderingEngine::get_scene_manager()->addMeshSceneNode(mesh, NULL); + mesh->drop(); + updateNodePos(); +} + +void TestCAO::removeFromScene(bool permanent) +{ + if (!m_node) + return; + + m_node->remove(); + m_node = NULL; +} + +void TestCAO::updateLight(u8 light_at_pos) +{ +} + +v3s16 TestCAO::getLightPosition() +{ + return floatToInt(m_position, BS); +} + +void TestCAO::updateNodePos() +{ + if (!m_node) + return; + + m_node->setPosition(m_position); + //m_node->setRotation(v3f(0, 45, 0)); +} + +void TestCAO::step(float dtime, ClientEnvironment *env) +{ + if(m_node) + { + v3f rot = m_node->getRotation(); + //infostream<<"dtime="<>cmd; + if(cmd == 0) + { + v3f newpos; + is>>newpos.X; + is>>newpos.Y; + is>>newpos.Z; + m_position = newpos; + updateNodePos(); + } +} + +/* + GenericCAO +*/ + +#include "genericobject.h" + +GenericCAO::GenericCAO(Client *client, ClientEnvironment *env): + ClientActiveObject(0, client, env) +{ + if (client == NULL) { + ClientActiveObject::registerType(getType(), create); + } else { + m_client = client; + } +} + +bool GenericCAO::getCollisionBox(aabb3f *toset) const +{ + if (m_prop.physical) + { + //update collision box + toset->MinEdge = m_prop.collisionbox.MinEdge * BS; + toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS; + + toset->MinEdge += m_position; + toset->MaxEdge += m_position; + + return true; + } + + return false; +} + +bool GenericCAO::collideWithObjects() const +{ + return m_prop.collideWithObjects; +} + +void GenericCAO::initialize(const std::string &data) +{ + infostream<<"GenericCAO: Got init data"<getLocalPlayer(); + if (player && strcmp(player->getName(), m_name.c_str()) == 0) { + m_is_local_player = true; + m_is_visible = false; + player->setCAO(this); + } + } +} + +void GenericCAO::processInitData(const std::string &data) +{ + std::istringstream is(data, std::ios::binary); + int num_messages = 0; + // version + u8 version = readU8(is); + // check version + if (version == 1) { // In PROTOCOL_VERSION 14 + m_name = deSerializeString(is); + m_is_player = readU8(is); + m_id = readU16(is); + m_position = readV3F1000(is); + m_rotation = readV3F1000(is); + m_hp = readS16(is); + num_messages = readU8(is); + } else { + errorstream<<"GenericCAO: Unsupported init data version" + <getAbsolutePosition(); + + return m_position; + } + return pos_translator.val_current; +} + +const bool GenericCAO::isImmortal() +{ + return itemgroup_get(getGroups(), "immortal"); +} + +scene::ISceneNode* GenericCAO::getSceneNode() +{ + if (m_meshnode) { + return m_meshnode; + } + + if (m_animated_meshnode) { + return m_animated_meshnode; + } + + if (m_wield_meshnode) { + return m_wield_meshnode; + } + + if (m_spritenode) { + return m_spritenode; + } + return NULL; +} + +scene::IAnimatedMeshSceneNode* GenericCAO::getAnimatedMeshSceneNode() +{ + return m_animated_meshnode; +} + +void GenericCAO::setChildrenVisible(bool toset) +{ + for (u16 cao_id : m_children) { + GenericCAO *obj = m_env->getGenericCAO(cao_id); + if (obj) { + obj->setVisible(toset); + } + } +} + +void GenericCAO::setAttachments() +{ + updateAttachments(); +} + +ClientActiveObject* GenericCAO::getParent() const +{ + ClientActiveObject *obj = NULL; + + u16 attached_id = m_env->attachement_parent_ids[getId()]; + + if ((attached_id != 0) && + (attached_id != getId())) { + obj = m_env->getActiveObject(attached_id); + } + return obj; +} + +void GenericCAO::removeFromScene(bool permanent) +{ + // Should be true when removing the object permanently and false when refreshing (eg: updating visuals) + if((m_env != NULL) && (permanent)) + { + for (u16 ci : m_children) { + if (m_env->attachement_parent_ids[ci] == getId()) { + m_env->attachement_parent_ids[ci] = 0; + } + } + m_children.clear(); + + m_env->attachement_parent_ids[getId()] = 0; + + LocalPlayer* player = m_env->getLocalPlayer(); + if (this == player->parent) { + player->parent = NULL; + player->isAttached = false; + } + } + + if (m_meshnode) { + m_meshnode->remove(); + m_meshnode->drop(); + m_meshnode = NULL; + } else if (m_animated_meshnode) { + m_animated_meshnode->remove(); + m_animated_meshnode->drop(); + m_animated_meshnode = NULL; + } else if (m_wield_meshnode) { + m_wield_meshnode->remove(); + m_wield_meshnode->drop(); + m_wield_meshnode = NULL; + } else if (m_spritenode) { + m_spritenode->remove(); + m_spritenode->drop(); + m_spritenode = NULL; + } + + if (m_nametag) { + m_client->getCamera()->removeNametag(m_nametag); + m_nametag = NULL; + } +} + +void GenericCAO::addToScene(ITextureSource *tsrc) +{ + m_smgr = RenderingEngine::get_scene_manager(); + + if (getSceneNode() != NULL) { + return; + } + + m_visuals_expired = false; + + if (!m_prop.is_visible) { + return; + } + + video::E_MATERIAL_TYPE material_type = (m_prop.use_texture_alpha) ? + video::EMT_TRANSPARENT_ALPHA_CHANNEL : video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; + + if (m_prop.visual == "sprite") { + infostream<<"GenericCAO::addToScene(): single_sprite"<addBillboardSceneNode( + NULL, v2f(1, 1), v3f(0,0,0), -1); + m_spritenode->grab(); + m_spritenode->setMaterialTexture(0, + tsrc->getTextureForMesh("unknown_node.png")); + m_spritenode->setMaterialFlag(video::EMF_LIGHTING, false); + m_spritenode->setMaterialFlag(video::EMF_BILINEAR_FILTER, false); + m_spritenode->setMaterialType(material_type); + m_spritenode->setMaterialFlag(video::EMF_FOG_ENABLE, true); + u8 li = m_last_light; + m_spritenode->setColor(video::SColor(255,li,li,li)); + m_spritenode->setSize(m_prop.visual_size*BS); + { + const float txs = 1.0 / 1; + const float tys = 1.0 / 1; + setBillboardTextureMatrix(m_spritenode, + txs, tys, 0, 0); + } + } else if (m_prop.visual == "upright_sprite") { + scene::SMesh *mesh = new scene::SMesh(); + double dx = BS * m_prop.visual_size.X / 2; + double dy = BS * m_prop.visual_size.Y / 2; + u8 li = m_last_light; + video::SColor c(255, li, li, li); + + { // Front + scene::IMeshBuffer *buf = new scene::SMeshBuffer(); + video::S3DVertex vertices[4] = { + video::S3DVertex(-dx, -dy, 0, 0,0,0, c, 1,1), + video::S3DVertex( dx, -dy, 0, 0,0,0, c, 0,1), + video::S3DVertex( dx, dy, 0, 0,0,0, c, 0,0), + video::S3DVertex(-dx, dy, 0, 0,0,0, c, 1,0), + }; + if (m_is_player) { + // Move minimal Y position to 0 (feet position) + for (video::S3DVertex &vertex : vertices) + vertex.Pos.Y += dy; + } + u16 indices[] = {0,1,2,2,3,0}; + buf->append(vertices, 4, indices, 6); + // Set material + buf->getMaterial().setFlag(video::EMF_LIGHTING, false); + buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false); + buf->getMaterial().setFlag(video::EMF_FOG_ENABLE, true); + buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; + // Add to mesh + mesh->addMeshBuffer(buf); + buf->drop(); + } + { // Back + scene::IMeshBuffer *buf = new scene::SMeshBuffer(); + video::S3DVertex vertices[4] = { + video::S3DVertex( dx,-dy, 0, 0,0,0, c, 1,1), + video::S3DVertex(-dx,-dy, 0, 0,0,0, c, 0,1), + video::S3DVertex(-dx, dy, 0, 0,0,0, c, 0,0), + video::S3DVertex( dx, dy, 0, 0,0,0, c, 1,0), + }; + if (m_is_player) { + // Move minimal Y position to 0 (feet position) + for (video::S3DVertex &vertex : vertices) + vertex.Pos.Y += dy; + } + u16 indices[] = {0,1,2,2,3,0}; + buf->append(vertices, 4, indices, 6); + // Set material + buf->getMaterial().setFlag(video::EMF_LIGHTING, false); + buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false); + buf->getMaterial().setFlag(video::EMF_FOG_ENABLE, true); + buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; + // Add to mesh + mesh->addMeshBuffer(buf); + buf->drop(); + } + m_meshnode = RenderingEngine::get_scene_manager()->addMeshSceneNode(mesh, NULL); + 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") { + infostream<<"GenericCAO::addToScene(): cube"<addMeshSceneNode(mesh, NULL); + m_meshnode->grab(); + mesh->drop(); + + m_meshnode->setScale(v3f(m_prop.visual_size.X, + m_prop.visual_size.Y, + m_prop.visual_size.X)); + u8 li = m_last_light; + setMeshColor(m_meshnode->getMesh(), video::SColor(255,li,li,li)); + + m_meshnode->setMaterialFlag(video::EMF_LIGHTING, false); + m_meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, false); + m_meshnode->setMaterialType(material_type); + m_meshnode->setMaterialFlag(video::EMF_FOG_ENABLE, true); + } + else if(m_prop.visual == "mesh") { + infostream<<"GenericCAO::addToScene(): mesh"<getMesh(m_prop.mesh, true); + if(mesh) + { + m_animated_meshnode = RenderingEngine::get_scene_manager()-> + addAnimatedMeshSceneNode(mesh, NULL); + m_animated_meshnode->grab(); + mesh->drop(); // The scene node took hold of it + m_animated_meshnode->animateJoints(); // Needed for some animations + m_animated_meshnode->setScale(v3f(m_prop.visual_size.X, + m_prop.visual_size.Y, + m_prop.visual_size.X)); + u8 li = m_last_light; + + // set vertex colors to ensure alpha is set + setMeshColor(m_animated_meshnode->getMesh(), video::SColor(255,li,li,li)); + + setAnimatedMeshColor(m_animated_meshnode, video::SColor(255,li,li,li)); + + m_animated_meshnode->setMaterialFlag(video::EMF_LIGHTING, true); + m_animated_meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, false); + m_animated_meshnode->setMaterialType(material_type); + m_animated_meshnode->setMaterialFlag(video::EMF_FOG_ENABLE, true); + m_animated_meshnode->setMaterialFlag(video::EMF_BACK_FACE_CULLING, + m_prop.backface_culling); + } + else + errorstream<<"GenericCAO::addToScene(): Could not load mesh "<idef(); + item = ItemStack(m_prop.textures[0], 1, 0, idef); + } + } else { + infostream << "serialized form: " << m_prop.wield_item << std::endl; + item.deSerialize(m_prop.wield_item, m_client->idef()); + } + m_wield_meshnode = new WieldMeshSceneNode( + RenderingEngine::get_scene_manager(), -1); + m_wield_meshnode->setItem(item, m_client); + + m_wield_meshnode->setScale( + v3f(m_prop.visual_size.X / 2, m_prop.visual_size.Y / 2, + m_prop.visual_size.X / 2)); + u8 li = m_last_light; + m_wield_meshnode->setColor(video::SColor(255, li, li, li)); + } else { + infostream<<"GenericCAO::addToScene(): \""<getCamera()->addNametag(node, + m_prop.nametag, m_prop.nametag_color, + pos); + } + + updateNodePos(); + updateAnimation(); + updateBonePosition(); + updateAttachments(); +} + +void GenericCAO::updateLight(u8 light_at_pos) +{ + // Don't update light of attached one + if (getParent() != NULL) { + return; + } + + updateLightNoCheck(light_at_pos); + + // Update light of all children + for (u16 i : m_children) { + ClientActiveObject *obj = m_env->getActiveObject(i); + if (obj) { + obj->updateLightNoCheck(light_at_pos); + } + } +} + +void GenericCAO::updateLightNoCheck(u8 light_at_pos) +{ + if (m_glow < 0) + return; + + u8 li = decode_light(light_at_pos + m_glow); + if (li != m_last_light) { + m_last_light = li; + video::SColor color(255,li,li,li); + if (m_meshnode) { + setMeshColor(m_meshnode->getMesh(), color); + } else if (m_animated_meshnode) { + setAnimatedMeshColor(m_animated_meshnode, color); + } else if (m_wield_meshnode) { + m_wield_meshnode->setColor(color); + } else if (m_spritenode) { + m_spritenode->setColor(color); + } + } +} + +v3s16 GenericCAO::getLightPosition() +{ + if (m_is_player) + return floatToInt(m_position + v3f(0, 0.5 * BS, 0), BS); + + return floatToInt(m_position, BS); +} + +void GenericCAO::updateNodePos() +{ + if (getParent() != NULL) + return; + + scene::ISceneNode *node = getSceneNode(); + + if (node) { + v3s16 camera_offset = m_env->getCameraOffset(); + node->setPosition(pos_translator.val_current - intToFloat(camera_offset, BS)); + if (node != m_spritenode) { // rotate if not a sprite + v3f rot = m_is_local_player ? -m_rotation : -rot_translator.val_current; + node->setRotation(rot); + } + } +} + +void GenericCAO::step(float dtime, ClientEnvironment *env) +{ + // Handel model of local player instantly to prevent lags + if (m_is_local_player) { + LocalPlayer *player = m_env->getLocalPlayer(); + if (m_is_visible) { + int old_anim = player->last_animation; + float old_anim_speed = player->last_animation_speed; + m_position = player->getPosition(); + m_rotation.Y = wrapDegrees_0_360(player->getYaw()); + m_velocity = v3f(0,0,0); + m_acceleration = v3f(0,0,0); + pos_translator.val_current = m_position; + rot_translator.val_current = m_rotation; + const PlayerControl &controls = player->getPlayerControl(); + + bool walking = false; + if (controls.up || controls.down || controls.left || controls.right || + controls.forw_move_joystick_axis != 0.f || + controls.sidew_move_joystick_axis != 0.f) + walking = true; + + f32 new_speed = player->local_animation_speed; + v2s32 new_anim = v2s32(0,0); + bool allow_update = false; + + // increase speed if using fast or flying fast + if((g_settings->getBool("fast_move") && + m_client->checkLocalPrivilege("fast")) && + (controls.aux1 || + (!player->touching_ground && + g_settings->getBool("free_move") && + m_client->checkLocalPrivilege("fly")))) + new_speed *= 1.5; + // slowdown speed if sneeking + if (controls.sneak && walking) + new_speed /= 2; + + if (walking && (controls.LMB || controls.RMB)) { + new_anim = player->local_animations[3]; + player->last_animation = WD_ANIM; + } else if(walking) { + new_anim = player->local_animations[1]; + player->last_animation = WALK_ANIM; + } else if(controls.LMB || controls.RMB) { + new_anim = player->local_animations[2]; + player->last_animation = DIG_ANIM; + } + + // Apply animations if input detected and not attached + // or set idle animation + if ((new_anim.X + new_anim.Y) > 0 && !player->isAttached) { + allow_update = true; + m_animation_range = new_anim; + m_animation_speed = new_speed; + player->last_animation_speed = m_animation_speed; + } else { + player->last_animation = NO_ANIM; + + if (old_anim != NO_ANIM) { + m_animation_range = player->local_animations[0]; + updateAnimation(); + } + } + + // Update local player animations + if ((player->last_animation != old_anim || + m_animation_speed != old_anim_speed) && + player->last_animation != NO_ANIM && allow_update) + updateAnimation(); + + } + } + + if (m_visuals_expired && m_smgr) { + m_visuals_expired = false; + + // Attachments, part 1: All attached objects must be unparented first, + // or Irrlicht causes a segmentation fault + for (auto ci = m_children.begin(); ci != m_children.end();) { + if (m_env->attachement_parent_ids[*ci] != getId()) { + ci = m_children.erase(ci); + continue; + } + ClientActiveObject *obj = m_env->getActiveObject(*ci); + if (obj) { + scene::ISceneNode *child_node = obj->getSceneNode(); + if (child_node) + child_node->setParent(m_smgr->getRootSceneNode()); + } + ++ci; + } + + removeFromScene(false); + addToScene(m_client->tsrc()); + + // Attachments, part 2: Now that the parent has been refreshed, put its attachments back + for (u16 cao_id : m_children) { + // Get the object of the child + ClientActiveObject *obj = m_env->getActiveObject(cao_id); + if (obj) + obj->setAttachments(); + } + } + + // Make sure m_is_visible is always applied + scene::ISceneNode *node = getSceneNode(); + if (node) + node->setVisible(m_is_visible); + + if(getParent() != NULL) // Attachments should be glued to their parent by Irrlicht + { + // Set these for later + m_position = getPosition(); + m_velocity = v3f(0,0,0); + m_acceleration = v3f(0,0,0); + pos_translator.val_current = m_position; + + if(m_is_local_player) // Update local player attachment position + { + LocalPlayer *player = m_env->getLocalPlayer(); + player->overridePosition = getParent()->getPosition(); + m_env->getLocalPlayer()->parent = getParent(); + } + } else { + rot_translator.translate(dtime); + v3f lastpos = pos_translator.val_current; + + if(m_prop.physical) + { + aabb3f box = m_prop.collisionbox; + box.MinEdge *= BS; + box.MaxEdge *= BS; + collisionMoveResult moveresult; + f32 pos_max_d = BS*0.125; // Distance per iteration + v3f p_pos = m_position; + v3f p_velocity = m_velocity; + moveresult = collisionMoveSimple(env,env->getGameDef(), + pos_max_d, box, m_prop.stepheight, dtime, + &p_pos, &p_velocity, m_acceleration, + this, m_prop.collideWithObjects); + // Apply results + m_position = p_pos; + m_velocity = p_velocity; + + bool is_end_position = moveresult.collides; + pos_translator.update(m_position, is_end_position, dtime); + pos_translator.translate(dtime); + updateNodePos(); + } else { + m_position += dtime * m_velocity + 0.5 * dtime * dtime * m_acceleration; + m_velocity += dtime * m_acceleration; + pos_translator.update(m_position, pos_translator.aim_is_end, + pos_translator.anim_time); + pos_translator.translate(dtime); + updateNodePos(); + } + + float moved = lastpos.getDistanceFrom(pos_translator.val_current); + m_step_distance_counter += moved; + if (m_step_distance_counter > 1.5f * BS) { + m_step_distance_counter = 0.0f; + if (!m_is_local_player && m_prop.makes_footstep_sound) { + const NodeDefManager *ndef = m_client->ndef(); + v3s16 p = floatToInt(getPosition() + + v3f(0.0f, (m_prop.collisionbox.MinEdge.Y - 0.5f) * BS, 0.0f), BS); + MapNode n = m_env->getMap().getNodeNoEx(p); + SimpleSoundSpec spec = ndef->get(n).sound_footstep; + // Reduce footstep gain, as non-local-player footsteps are + // somehow louder. + spec.gain *= 0.6f; + m_client->sound()->playSoundAt(spec, false, getPosition()); + } + } + } + + m_anim_timer += dtime; + if(m_anim_timer >= m_anim_framelength) + { + m_anim_timer -= m_anim_framelength; + m_anim_frame++; + if(m_anim_frame >= m_anim_num_frames) + m_anim_frame = 0; + } + + updateTexturePos(); + + if(m_reset_textures_timer >= 0) + { + m_reset_textures_timer -= dtime; + if(m_reset_textures_timer <= 0) { + m_reset_textures_timer = -1; + updateTextures(m_previous_texture_modifier); + } + } + if (!getParent() && std::fabs(m_prop.automatic_rotate) > 0.001) { + m_rotation.Y += dtime * m_prop.automatic_rotate * 180 / M_PI; + rot_translator.val_current = m_rotation; + updateNodePos(); + } + + if (!getParent() && m_prop.automatic_face_movement_dir && + (fabs(m_velocity.Z) > 0.001 || fabs(m_velocity.X) > 0.001)) { + + float target_yaw = atan2(m_velocity.Z, m_velocity.X) * 180 / M_PI + + m_prop.automatic_face_movement_dir_offset; + float max_rotation_delta = + dtime * m_prop.automatic_face_movement_max_rotation_per_sec; + + wrappedApproachShortest(m_rotation.Y, target_yaw, max_rotation_delta, 360.f); + rot_translator.val_current = m_rotation; + + updateNodePos(); + } +} + +void GenericCAO::updateTexturePos() +{ + if(m_spritenode) + { + scene::ICameraSceneNode* camera = + m_spritenode->getSceneManager()->getActiveCamera(); + if(!camera) + return; + v3f cam_to_entity = m_spritenode->getAbsolutePosition() + - camera->getAbsolutePosition(); + cam_to_entity.normalize(); + + int row = m_tx_basepos.Y; + int col = m_tx_basepos.X; + + if (m_tx_select_horiz_by_yawpitch) { + if (cam_to_entity.Y > 0.75) + col += 5; + else if (cam_to_entity.Y < -0.75) + col += 4; + else { + float mob_dir = + atan2(cam_to_entity.Z, cam_to_entity.X) / M_PI * 180.; + float dir = mob_dir - m_rotation.Y; + dir = wrapDegrees_180(dir); + if (std::fabs(wrapDegrees_180(dir - 0)) <= 45.1f) + col += 2; + else if(std::fabs(wrapDegrees_180(dir - 90)) <= 45.1f) + col += 3; + else if(std::fabs(wrapDegrees_180(dir - 180)) <= 45.1f) + col += 0; + else if(std::fabs(wrapDegrees_180(dir + 90)) <= 45.1f) + col += 1; + else + col += 4; + } + } + + // Animation goes downwards + row += m_anim_frame; + + float txs = m_tx_size.X; + float tys = m_tx_size.Y; + setBillboardTextureMatrix(m_spritenode, txs, tys, col, row); + } +} + +void GenericCAO::updateTextures(std::string mod) +{ + ITextureSource *tsrc = m_client->tsrc(); + + bool use_trilinear_filter = g_settings->getBool("trilinear_filter"); + bool use_bilinear_filter = g_settings->getBool("bilinear_filter"); + bool use_anisotropic_filter = g_settings->getBool("anisotropic_filter"); + + m_previous_texture_modifier = m_current_texture_modifier; + m_current_texture_modifier = mod; + m_glow = m_prop.glow; + + video::E_MATERIAL_TYPE material_type = (m_prop.use_texture_alpha) ? + video::EMT_TRANSPARENT_ALPHA_CHANNEL : video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; + + if (m_spritenode) { + if (m_prop.visual == "sprite") { + std::string texturestring = "unknown_node.png"; + if (!m_prop.textures.empty()) + texturestring = m_prop.textures[0]; + texturestring += mod; + m_spritenode->getMaterial(0).MaterialType = material_type; + m_spritenode->setMaterialTexture(0, + tsrc->getTextureForMesh(texturestring)); + + // 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()) { + m_spritenode->getMaterial(0).AmbientColor = m_prop.colors[0]; + m_spritenode->getMaterial(0).DiffuseColor = m_prop.colors[0]; + m_spritenode->getMaterial(0).SpecularColor = m_prop.colors[0]; + } + + m_spritenode->getMaterial(0).setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter); + m_spritenode->getMaterial(0).setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter); + m_spritenode->getMaterial(0).setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter); + } + } + + if (m_animated_meshnode) { + if (m_prop.visual == "mesh") { + for (u32 i = 0; i < m_prop.textures.size() && + i < m_animated_meshnode->getMaterialCount(); ++i) { + std::string texturestring = m_prop.textures[i]; + if (texturestring.empty()) + continue; // Empty texture string means don't modify that material + texturestring += mod; + video::ITexture* texture = tsrc->getTextureForMesh(texturestring); + if (!texture) { + errorstream<<"GenericCAO::updateTextures(): Could not load texture "<getMaterial(i); + material.MaterialType = material_type; + material.TextureLayer[0].Texture = texture; + material.setFlag(video::EMF_LIGHTING, true); + material.setFlag(video::EMF_BILINEAR_FILTER, false); + material.setFlag(video::EMF_BACK_FACE_CULLING, m_prop.backface_culling); + + // don't filter low-res textures, makes them look blurry + // player models have a res of 64 + const core::dimension2d &size = texture->getOriginalSize(); + const u32 res = std::min(size.Height, size.Width); + use_trilinear_filter &= res > 64; + use_bilinear_filter &= res > 64; + + m_animated_meshnode->getMaterial(i) + .setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter); + m_animated_meshnode->getMaterial(i) + .setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter); + m_animated_meshnode->getMaterial(i) + .setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter); + } + for (u32 i = 0; i < m_prop.colors.size() && + i < m_animated_meshnode->getMaterialCount(); ++i) + { + // 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. + m_animated_meshnode->getMaterial(i).AmbientColor = m_prop.colors[i]; + m_animated_meshnode->getMaterial(i).DiffuseColor = m_prop.colors[i]; + m_animated_meshnode->getMaterial(i).SpecularColor = m_prop.colors[i]; + } + } + } + if(m_meshnode) + { + if(m_prop.visual == "cube") + { + for (u32 i = 0; i < 6; ++i) + { + std::string texturestring = "unknown_node.png"; + if(m_prop.textures.size() > i) + texturestring = m_prop.textures[i]; + texturestring += mod; + + + // Set material flags and texture + video::SMaterial& material = m_meshnode->getMaterial(i); + material.MaterialType = material_type; + material.setFlag(video::EMF_LIGHTING, false); + material.setFlag(video::EMF_BILINEAR_FILTER, false); + material.setTexture(0, + tsrc->getTextureForMesh(texturestring)); + material.getTextureMatrix(0).makeIdentity(); + + // 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() > i) + { + m_meshnode->getMaterial(i).AmbientColor = m_prop.colors[i]; + m_meshnode->getMaterial(i).DiffuseColor = m_prop.colors[i]; + m_meshnode->getMaterial(i).SpecularColor = m_prop.colors[i]; + } + + m_meshnode->getMaterial(i).setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter); + m_meshnode->getMaterial(i).setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter); + m_meshnode->getMaterial(i).setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter); + } + } else if (m_prop.visual == "upright_sprite") { + scene::IMesh *mesh = m_meshnode->getMesh(); + { + std::string tname = "unknown_object.png"; + if (!m_prop.textures.empty()) + tname = m_prop.textures[0]; + tname += mod; + scene::IMeshBuffer *buf = mesh->getMeshBuffer(0); + buf->getMaterial().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]; + } + + 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); + } + { + std::string tname = "unknown_object.png"; + if (m_prop.textures.size() >= 2) + tname = m_prop.textures[1]; + else if (!m_prop.textures.empty()) + tname = m_prop.textures[0]; + tname += mod; + scene::IMeshBuffer *buf = mesh->getMeshBuffer(1); + buf->getMaterial().setTexture(0, + 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]; + setMeshColor(mesh, 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]; + setMeshColor(mesh, 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); + } + } + } +} + +void GenericCAO::updateAnimation() +{ + if (!m_animated_meshnode) + return; + + if (m_animated_meshnode->getStartFrame() != m_animation_range.X || + m_animated_meshnode->getEndFrame() != m_animation_range.Y) + m_animated_meshnode->setFrameLoop(m_animation_range.X, m_animation_range.Y); + if (m_animated_meshnode->getAnimationSpeed() != m_animation_speed) + m_animated_meshnode->setAnimationSpeed(m_animation_speed); + m_animated_meshnode->setTransitionTime(m_animation_blend); +// Requires Irrlicht 1.8 or greater +#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR >= 8) || IRRLICHT_VERSION_MAJOR > 1 + if (m_animated_meshnode->getLoopMode() != m_animation_loop) + m_animated_meshnode->setLoopMode(m_animation_loop); +#endif +} + +void GenericCAO::updateAnimationSpeed() +{ + if (!m_animated_meshnode) + return; + + m_animated_meshnode->setAnimationSpeed(m_animation_speed); +} + +void GenericCAO::updateBonePosition() +{ + if (m_bone_position.empty() || !m_animated_meshnode) + return; + + m_animated_meshnode->setJointMode(irr::scene::EJUOR_CONTROL); // To write positions to the mesh on render + for(std::unordered_map>::const_iterator + ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) { + std::string bone_name = (*ii).first; + v3f bone_pos = (*ii).second.X; + v3f bone_rot = (*ii).second.Y; + irr::scene::IBoneSceneNode* bone = m_animated_meshnode->getJointNode(bone_name.c_str()); + if(bone) + { + bone->setPosition(bone_pos); + bone->setRotation(bone_rot); + } + } +} + +void GenericCAO::updateAttachments() +{ + + if (!getParent()) { // Detach or don't attach + scene::ISceneNode *node = getSceneNode(); + if (node) { + v3f old_position = node->getAbsolutePosition(); + v3f old_rotation = node->getRotation(); + node->setParent(m_smgr->getRootSceneNode()); + node->setPosition(old_position); + node->setRotation(old_rotation); + node->updateAbsolutePosition(); + } + if (m_is_local_player) { + LocalPlayer *player = m_env->getLocalPlayer(); + player->isAttached = false; + } + } + else // Attach + { + scene::ISceneNode *my_node = getSceneNode(); + + scene::ISceneNode *parent_node = getParent()->getSceneNode(); + scene::IAnimatedMeshSceneNode *parent_animated_mesh_node = + getParent()->getAnimatedMeshSceneNode(); + if (parent_animated_mesh_node && !m_attachment_bone.empty()) { + parent_node = parent_animated_mesh_node->getJointNode(m_attachment_bone.c_str()); + } + + if (my_node && parent_node) { + my_node->setParent(parent_node); + my_node->setPosition(m_attachment_position); + my_node->setRotation(m_attachment_rotation); + my_node->updateAbsolutePosition(); + } + if (m_is_local_player) { + LocalPlayer *player = m_env->getLocalPlayer(); + player->isAttached = true; + } + } +} + +void GenericCAO::processMessage(const std::string &data) +{ + //infostream<<"GenericCAO: Got message"<getLocalPlayer(); + player->makes_footstep_sound = m_prop.makes_footstep_sound; + aabb3f collision_box = m_prop.collisionbox; + collision_box.MinEdge *= BS; + collision_box.MaxEdge *= BS; + player->setCollisionbox(collision_box); + player->setEyeHeight(m_prop.eye_height); + player->setZoomFOV(m_prop.zoom_fov); + } + + if ((m_is_player && !m_is_local_player) && m_prop.nametag.empty()) + m_prop.nametag = m_name; + + expireVisuals(); + } else if (cmd == GENERIC_CMD_UPDATE_POSITION) { + // Not sent by the server if this object is an attachment. + // We might however get here if the server notices the object being detached before the client. + m_position = readV3F1000(is); + m_velocity = readV3F1000(is); + m_acceleration = readV3F1000(is); + + if (std::fabs(m_prop.automatic_rotate) < 0.001f) + m_rotation = readV3F1000(is); + else + readV3F1000(is); + + m_rotation = wrapDegrees_0_360_v3f(m_rotation); + bool do_interpolate = readU8(is); + bool is_end_position = readU8(is); + float update_interval = readF1000(is); + + // Place us a bit higher if we're physical, to not sink into + // the ground due to sucky collision detection... + if(m_prop.physical) + m_position += v3f(0,0.002,0); + + if(getParent() != NULL) // Just in case + return; + + if(do_interpolate) + { + if(!m_prop.physical) + pos_translator.update(m_position, is_end_position, update_interval); + } else { + pos_translator.init(m_position); + } + rot_translator.update(m_rotation, false, update_interval); + updateNodePos(); + } else if (cmd == GENERIC_CMD_SET_TEXTURE_MOD) { + std::string mod = deSerializeString(is); + + // immediatly reset a engine issued texture modifier if a mod sends a different one + if (m_reset_textures_timer > 0) { + m_reset_textures_timer = -1; + updateTextures(m_previous_texture_modifier); + } + updateTextures(mod); + } else if (cmd == GENERIC_CMD_SET_SPRITE) { + v2s16 p = readV2S16(is); + int num_frames = readU16(is); + float framelength = readF1000(is); + bool select_horiz_by_yawpitch = readU8(is); + + m_tx_basepos = p; + m_anim_num_frames = num_frames; + m_anim_framelength = framelength; + m_tx_select_horiz_by_yawpitch = select_horiz_by_yawpitch; + + updateTexturePos(); + } else if (cmd == GENERIC_CMD_SET_PHYSICS_OVERRIDE) { + float override_speed = readF1000(is); + float override_jump = readF1000(is); + float override_gravity = readF1000(is); + // these are sent inverted so we get true when the server sends nothing + bool sneak = !readU8(is); + bool sneak_glitch = !readU8(is); + bool new_move = !readU8(is); + + + if(m_is_local_player) + { + LocalPlayer *player = m_env->getLocalPlayer(); + player->physics_override_speed = override_speed; + player->physics_override_jump = override_jump; + player->physics_override_gravity = override_gravity; + player->physics_override_sneak = sneak; + player->physics_override_sneak_glitch = sneak_glitch; + player->physics_override_new_move = new_move; + } + } else if (cmd == GENERIC_CMD_SET_ANIMATION) { + // TODO: change frames send as v2s32 value + v2f range = readV2F1000(is); + if (!m_is_local_player) { + m_animation_range = v2s32((s32)range.X, (s32)range.Y); + m_animation_speed = readF1000(is); + m_animation_blend = readF1000(is); + // these are sent inverted so we get true when the server sends nothing + m_animation_loop = !readU8(is); + updateAnimation(); + } else { + LocalPlayer *player = m_env->getLocalPlayer(); + if(player->last_animation == NO_ANIM) + { + m_animation_range = v2s32((s32)range.X, (s32)range.Y); + m_animation_speed = readF1000(is); + m_animation_blend = readF1000(is); + // these are sent inverted so we get true when the server sends nothing + m_animation_loop = !readU8(is); + } + // update animation only if local animations present + // and received animation is unknown (except idle animation) + bool is_known = false; + for (int i = 1;i<4;i++) + { + if(m_animation_range.Y == player->local_animations[i].Y) + is_known = true; + } + if(!is_known || + (player->local_animations[1].Y + player->local_animations[2].Y < 1)) + { + updateAnimation(); + } + } + } else if (cmd == GENERIC_CMD_SET_ANIMATION_SPEED) { + m_animation_speed = readF1000(is); + updateAnimationSpeed(); + } else if (cmd == GENERIC_CMD_SET_BONE_POSITION) { + std::string bone = deSerializeString(is); + v3f position = readV3F1000(is); + v3f rotation = readV3F1000(is); + m_bone_position[bone] = core::vector2d(position, rotation); + + updateBonePosition(); + } else if (cmd == GENERIC_CMD_ATTACH_TO) { + u16 parent_id = readS16(is); + u16 &old_parent_id = m_env->attachement_parent_ids[getId()]; + if (parent_id != old_parent_id) { + if (GenericCAO *old_parent = m_env->getGenericCAO(old_parent_id)) { + old_parent->m_children.erase(std::remove( + m_children.begin(), m_children.end(), + getId()), m_children.end()); + } + if (GenericCAO *new_parent = m_env->getGenericCAO(parent_id)) + new_parent->m_children.push_back(getId()); + + old_parent_id = parent_id; + } + + m_attachment_bone = deSerializeString(is); + m_attachment_position = readV3F1000(is); + m_attachment_rotation = readV3F1000(is); + + // localplayer itself can't be attached to localplayer + if (!m_is_local_player) { + m_attached_to_local = getParent() != NULL && getParent()->isLocalPlayer(); + // Objects attached to the local player should be hidden by default + m_is_visible = !m_attached_to_local; + } + + updateAttachments(); + } else if (cmd == GENERIC_CMD_PUNCHED) { + /*s16 damage =*/ readS16(is); + s16 result_hp = readS16(is); + + // Use this instead of the send damage to not interfere with prediction + s16 damage = m_hp - result_hp; + + m_hp = result_hp; + + if (damage > 0) + { + if (m_hp <= 0) + { + // TODO: Execute defined fast response + // As there is no definition, make a smoke puff + ClientSimpleObject *simple = createSmokePuff( + m_smgr, m_env, m_position, + m_prop.visual_size * BS); + m_env->addSimpleObject(simple); + } else if (m_reset_textures_timer < 0) { + // TODO: Execute defined fast response + // Flashing shall suffice as there is no definition + m_reset_textures_timer = 0.05; + if(damage >= 2) + m_reset_textures_timer += 0.05 * damage; + updateTextures(m_current_texture_modifier + "^[brighten"); + } + } + } else if (cmd == GENERIC_CMD_UPDATE_ARMOR_GROUPS) { + m_armor_groups.clear(); + int armor_groups_size = readU16(is); + for(int i=0; inametag_color = m_prop.nametag_color; + v3f pos; + pos.Y = m_prop.collisionbox.MaxEdge.Y + 0.3f; + m_nametag->nametag_pos = pos; + } + } else if (cmd == GENERIC_CMD_SPAWN_INFANT) { + u16 child_id = readU16(is); + u8 type = readU8(is); + + if (GenericCAO *childobj = m_env->getGenericCAO(child_id)) { + childobj->processInitData(deSerializeLongString(is)); + } else { + m_env->addActiveObject(child_id, type, deSerializeLongString(is)); + } + } else { + warningstream << FUNCTION_NAME + << ": unknown command or outdated client \"" + << +cmd << "\"" << std::endl; + } +} + +/* \pre punchitem != NULL + */ +bool GenericCAO::directReportPunch(v3f dir, const ItemStack *punchitem, + float time_from_last_punch) +{ + assert(punchitem); // pre-condition + const ToolCapabilities *toolcap = + &punchitem->getToolCapabilities(m_client->idef()); + PunchDamageResult result = getPunchDamage( + m_armor_groups, + toolcap, + punchitem, + time_from_last_punch); + + if(result.did_punch && result.damage != 0) + { + if(result.damage < m_hp) + { + m_hp -= result.damage; + } else { + m_hp = 0; + // TODO: Execute defined fast response + // As there is no definition, make a smoke puff + ClientSimpleObject *simple = createSmokePuff( + m_smgr, m_env, m_position, + m_prop.visual_size * BS); + m_env->addSimpleObject(simple); + } + // TODO: Execute defined fast response + // Flashing shall suffice as there is no definition + if (m_reset_textures_timer < 0) { + m_reset_textures_timer = 0.05; + if (result.damage >= 2) + m_reset_textures_timer += 0.05 * result.damage; + updateTextures(m_current_texture_modifier + "^[brighten"); + } + } + + return false; +} + +std::string GenericCAO::debugInfoText() +{ + std::ostringstream os(std::ios::binary); + os<<"GenericCAO hp="<first<<"="<second<<", "; + } + os<<"}"; + return os.str(); +} + +// Prototype +GenericCAO proto_GenericCAO(NULL, NULL); diff --git a/src/client/content_cao.h b/src/client/content_cao.h new file mode 100644 index 000000000..98932137e --- /dev/null +++ b/src/client/content_cao.h @@ -0,0 +1,236 @@ +/* +Minetest +Copyright (C) 2010-2013 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include +#include "irrlichttypes_extrabloated.h" +#include "clientobject.h" +#include "object_properties.h" +#include "itemgroup.h" +#include "constants.h" + +class Camera; +class Client; +struct Nametag; + +/* + SmoothTranslator +*/ + +template +struct SmoothTranslator +{ + T val_old; + T val_current; + T val_target; + f32 anim_time = 0; + f32 anim_time_counter = 0; + bool aim_is_end = true; + + SmoothTranslator() = default; + + void init(T current); + + void update(T new_target, bool is_end_position = false, + float update_interval = -1); + + void translate(f32 dtime); +}; + +struct SmoothTranslatorWrapped : SmoothTranslator +{ + void translate(f32 dtime); +}; + +struct SmoothTranslatorWrappedv3f : SmoothTranslator +{ + void translate(f32 dtime); +}; + +class GenericCAO : public ClientActiveObject +{ +private: + // Only set at initialization + std::string m_name = ""; + bool m_is_player = false; + bool m_is_local_player = false; + // Property-ish things + ObjectProperties m_prop; + // + scene::ISceneManager *m_smgr = nullptr; + Client *m_client = nullptr; + aabb3f m_selection_box = aabb3f(-BS/3.,-BS/3.,-BS/3., BS/3.,BS/3.,BS/3.); + scene::IMeshSceneNode *m_meshnode = nullptr; + scene::IAnimatedMeshSceneNode *m_animated_meshnode = nullptr; + WieldMeshSceneNode *m_wield_meshnode = nullptr; + scene::IBillboardSceneNode *m_spritenode = nullptr; + Nametag *m_nametag = nullptr; + v3f m_position = v3f(0.0f, 10.0f * BS, 0); + v3f m_velocity; + v3f m_acceleration; + v3f m_rotation; + s16 m_hp = 1; + SmoothTranslator pos_translator; + SmoothTranslatorWrappedv3f rot_translator; + // Spritesheet/animation stuff + v2f m_tx_size = v2f(1,1); + v2s16 m_tx_basepos; + bool m_initial_tx_basepos_set = false; + bool m_tx_select_horiz_by_yawpitch = false; + v2s32 m_animation_range; + float m_animation_speed = 15.0f; + float m_animation_blend = 0.0f; + bool m_animation_loop = true; + // stores position and rotation for each bone name + std::unordered_map> m_bone_position; + std::string m_attachment_bone = ""; + v3f m_attachment_position; + v3f m_attachment_rotation; + bool m_attached_to_local = false; + int m_anim_frame = 0; + int m_anim_num_frames = 1; + float m_anim_framelength = 0.2f; + float m_anim_timer = 0.0f; + ItemGroupList m_armor_groups; + float m_reset_textures_timer = -1.0f; + // stores texture modifier before punch update + std::string m_previous_texture_modifier = ""; + // last applied texture modifier + std::string m_current_texture_modifier = ""; + bool m_visuals_expired = false; + float m_step_distance_counter = 0.0f; + u8 m_last_light = 255; + bool m_is_visible = false; + s8 m_glow = 0; + + std::vector m_children; + +public: + GenericCAO(Client *client, ClientEnvironment *env); + + ~GenericCAO(); + + static ClientActiveObject* create(Client *client, ClientEnvironment *env) + { + return new GenericCAO(client, env); + } + + inline ActiveObjectType getType() const + { + return ACTIVEOBJECT_TYPE_GENERIC; + } + inline const ItemGroupList &getGroups() const + { + return m_armor_groups; + } + void initialize(const std::string &data); + + void processInitData(const std::string &data); + + bool getCollisionBox(aabb3f *toset) const; + + bool collideWithObjects() const; + + virtual bool getSelectionBox(aabb3f *toset) const; + + v3f getPosition(); + + inline const v3f &getRotation() + { + return m_rotation; + } + + const bool isImmortal(); + + scene::ISceneNode *getSceneNode(); + + scene::IAnimatedMeshSceneNode *getAnimatedMeshSceneNode(); + + inline f32 getStepHeight() const + { + return m_prop.stepheight; + } + + inline bool isLocalPlayer() const + { + return m_is_local_player; + } + + inline bool isVisible() const + { + return m_is_visible; + } + + inline void setVisible(bool toset) + { + m_is_visible = toset; + } + + void setChildrenVisible(bool toset); + + ClientActiveObject *getParent() const; + + void setAttachments(); + + void removeFromScene(bool permanent); + + void addToScene(ITextureSource *tsrc); + + inline void expireVisuals() + { + m_visuals_expired = true; + } + + void updateLight(u8 light_at_pos); + + void updateLightNoCheck(u8 light_at_pos); + + v3s16 getLightPosition(); + + void updateNodePos(); + + void step(float dtime, ClientEnvironment *env); + + void updateTexturePos(); + + // std::string copy is mandatory as mod can be a class member and there is a swap + // on those class members... do NOT pass by reference + void updateTextures(std::string mod); + + void updateAnimation(); + + void updateAnimationSpeed(); + + void updateBonePosition(); + + void updateAttachments(); + + void processMessage(const std::string &data); + + bool directReportPunch(v3f dir, const ItemStack *punchitem=NULL, + float time_from_last_punch=1000000); + + std::string debugInfoText(); + + std::string infoText() + { + return m_prop.infotext; + } +}; diff --git a/src/client/content_cso.cpp b/src/client/content_cso.cpp new file mode 100644 index 000000000..04c503f44 --- /dev/null +++ b/src/client/content_cso.cpp @@ -0,0 +1,77 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "content_cso.h" +#include +#include "client/tile.h" +#include "clientenvironment.h" +#include "client.h" +#include "map.h" + +class SmokePuffCSO: public ClientSimpleObject +{ + float m_age = 0.0f; + scene::IBillboardSceneNode *m_spritenode = nullptr; +public: + SmokePuffCSO(scene::ISceneManager *smgr, + ClientEnvironment *env, const v3f &pos, const v2f &size) + { + infostream<<"SmokePuffCSO: constructing"<addBillboardSceneNode( + NULL, v2f(1,1), pos, -1); + m_spritenode->setMaterialTexture(0, + env->getGameDef()->tsrc()->getTextureForMesh("smoke_puff.png")); + m_spritenode->setMaterialFlag(video::EMF_LIGHTING, false); + m_spritenode->setMaterialFlag(video::EMF_BILINEAR_FILTER, false); + //m_spritenode->setMaterialType(video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF); + m_spritenode->setMaterialType(video::EMT_TRANSPARENT_ALPHA_CHANNEL); + m_spritenode->setMaterialFlag(video::EMF_FOG_ENABLE, true); + m_spritenode->setColor(video::SColor(255,0,0,0)); + m_spritenode->setVisible(true); + m_spritenode->setSize(size); + /* Update brightness */ + u8 light; + bool pos_ok; + MapNode n = env->getMap().getNodeNoEx(floatToInt(pos, BS), &pos_ok); + light = pos_ok ? decode_light(n.getLightBlend(env->getDayNightRatio(), + env->getGameDef()->ndef())) + : 64; + video::SColor color(255,light,light,light); + m_spritenode->setColor(color); + } + virtual ~SmokePuffCSO() + { + infostream<<"SmokePuffCSO: destructing"<remove(); + } + void step(float dtime) + { + m_age += dtime; + if(m_age > 1.0){ + m_to_be_removed = true; + } + } +}; + +ClientSimpleObject* createSmokePuff(scene::ISceneManager *smgr, + ClientEnvironment *env, v3f pos, v2f size) +{ + return new SmokePuffCSO(smgr, env, pos, size); +} + diff --git a/src/client/content_cso.h b/src/client/content_cso.h new file mode 100644 index 000000000..cc9213175 --- /dev/null +++ b/src/client/content_cso.h @@ -0,0 +1,26 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include "irrlichttypes_extrabloated.h" +#include "clientsimpleobject.h" + +ClientSimpleObject* createSmokePuff(scene::ISceneManager *smgr, + ClientEnvironment *env, v3f pos, v2f size); diff --git a/src/client/content_mapblock.cpp b/src/client/content_mapblock.cpp new file mode 100644 index 000000000..4a0df6171 --- /dev/null +++ b/src/client/content_mapblock.cpp @@ -0,0 +1,1430 @@ +/* +Minetest +Copyright (C) 2010-2013 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "content_mapblock.h" +#include "util/numeric.h" +#include "util/directiontables.h" +#include "mapblock_mesh.h" +#include "settings.h" +#include "nodedef.h" +#include "client/tile.h" +#include "mesh.h" +#include +#include "client/meshgen/collector.h" +#include "client/renderingengine.h" +#include "client.h" +#include "noise.h" + +// Distance of light extrapolation (for oversized nodes) +// After this distance, it gives up and considers light level constant +#define SMOOTH_LIGHTING_OVERSIZE 1.0 + +// Node edge count (for glasslike-framed) +#define FRAMED_EDGE_COUNT 12 + +// Node neighbor count, including edge-connected, but not vertex-connected +// (for glasslike-framed) +// Corresponding offsets are listed in g_27dirs +#define FRAMED_NEIGHBOR_COUNT 18 + +static const v3s16 light_dirs[8] = { + v3s16(-1, -1, -1), + v3s16(-1, -1, 1), + v3s16(-1, 1, -1), + v3s16(-1, 1, 1), + v3s16( 1, -1, -1), + v3s16( 1, -1, 1), + v3s16( 1, 1, -1), + v3s16( 1, 1, 1), +}; + +// Standard index set to make a quad on 4 vertices +static constexpr u16 quad_indices[] = {0, 1, 2, 2, 3, 0}; + +const std::string MapblockMeshGenerator::raillike_groupname = "connect_to_raillike"; + +MapblockMeshGenerator::MapblockMeshGenerator(MeshMakeData *input, MeshCollector *output) +{ + data = input; + collector = output; + + nodedef = data->m_client->ndef(); + meshmanip = RenderingEngine::get_scene_manager()->getMeshManipulator(); + + enable_mesh_cache = g_settings->getBool("enable_mesh_cache") && + !data->m_smooth_lighting; // Mesh cache is not supported with smooth lighting + + blockpos_nodes = data->m_blockpos * MAP_BLOCKSIZE; +} + +void MapblockMeshGenerator::useTile(int index, u8 set_flags, u8 reset_flags, bool special) +{ + if (special) + getSpecialTile(index, &tile, p == data->m_crack_pos_relative); + else + getTile(index, &tile); + if (!data->m_smooth_lighting) + color = encode_light(light, f->light_source); + + for (auto &layer : tile.layers) { + layer.material_flags |= set_flags; + layer.material_flags &= ~reset_flags; + } +} + +// Returns a tile, ready for use, non-rotated. +void MapblockMeshGenerator::getTile(int index, TileSpec *tile) +{ + getNodeTileN(n, p, index, data, *tile); +} + +// Returns a tile, ready for use, rotated according to the node facedir. +void MapblockMeshGenerator::getTile(v3s16 direction, TileSpec *tile) +{ + getNodeTile(n, p, direction, data, *tile); +} + +// Returns a special tile, ready for use, non-rotated. +void MapblockMeshGenerator::getSpecialTile(int index, TileSpec *tile, bool apply_crack) +{ + *tile = f->special_tiles[index]; + TileLayer *top_layer = nullptr; + + for (auto &layernum : tile->layers) { + TileLayer *layer = &layernum; + if (layer->texture_id == 0) + continue; + top_layer = layer; + if (!layer->has_color) + n.getColor(*f, &layer->color); + } + + if (apply_crack) + top_layer->material_flags |= MATERIAL_FLAG_CRACK; +} + +void MapblockMeshGenerator::drawQuad(v3f *coords, const v3s16 &normal, + float vertical_tiling) +{ + const v2f tcoords[4] = {v2f(0.0, 0.0), v2f(1.0, 0.0), + v2f(1.0, vertical_tiling), v2f(0.0, vertical_tiling)}; + video::S3DVertex vertices[4]; + bool shade_face = !f->light_source && (normal != v3s16(0, 0, 0)); + v3f normal2(normal.X, normal.Y, normal.Z); + for (int j = 0; j < 4; j++) { + vertices[j].Pos = coords[j] + origin; + vertices[j].Normal = normal2; + if (data->m_smooth_lighting) + vertices[j].Color = blendLightColor(coords[j]); + else + vertices[j].Color = color; + if (shade_face) + applyFacesShading(vertices[j].Color, normal2); + vertices[j].TCoords = tcoords[j]; + } + collector->append(tile, vertices, 4, quad_indices, 6); +} + +// Create a cuboid. +// tiles - the tiles (materials) to use (for all 6 faces) +// tilecount - number of entries in tiles, 1<=tilecount<=6 +// lights - vertex light levels. The order is the same as in light_dirs. +// NULL may be passed if smooth lighting is disabled. +// txc - texture coordinates - this is a list of texture coordinates +// for the opposite corners of each face - therefore, there +// 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). +void MapblockMeshGenerator::drawCuboid(const aabb3f &box, + TileSpec *tiles, int tilecount, const LightInfo *lights, const f32 *txc) +{ + assert(tilecount >= 1 && tilecount <= 6); // pre-condition + + v3f min = box.MinEdge; + v3f max = box.MaxEdge; + + video::SColor colors[6]; + if (!data->m_smooth_lighting) { + for (int face = 0; face != 6; ++face) { + colors[face] = encode_light(light, f->light_source); + } + if (!f->light_source) { + applyFacesShading(colors[0], v3f(0, 1, 0)); + applyFacesShading(colors[1], v3f(0, -1, 0)); + applyFacesShading(colors[2], v3f(1, 0, 0)); + applyFacesShading(colors[3], v3f(-1, 0, 0)); + applyFacesShading(colors[4], v3f(0, 0, 1)); + applyFacesShading(colors[5], v3f(0, 0, -1)); + } + } + + video::S3DVertex vertices[24] = { + // top + video::S3DVertex(min.X, max.Y, max.Z, 0, 1, 0, colors[0], txc[0], txc[1]), + video::S3DVertex(max.X, max.Y, max.Z, 0, 1, 0, colors[0], txc[2], txc[1]), + video::S3DVertex(max.X, max.Y, min.Z, 0, 1, 0, colors[0], txc[2], txc[3]), + video::S3DVertex(min.X, max.Y, min.Z, 0, 1, 0, colors[0], txc[0], txc[3]), + // bottom + video::S3DVertex(min.X, min.Y, min.Z, 0, -1, 0, colors[1], txc[4], txc[5]), + video::S3DVertex(max.X, min.Y, min.Z, 0, -1, 0, colors[1], txc[6], txc[5]), + video::S3DVertex(max.X, min.Y, max.Z, 0, -1, 0, colors[1], txc[6], txc[7]), + video::S3DVertex(min.X, min.Y, max.Z, 0, -1, 0, colors[1], txc[4], txc[7]), + // right + video::S3DVertex(max.X, max.Y, min.Z, 1, 0, 0, colors[2], txc[ 8], txc[9]), + video::S3DVertex(max.X, max.Y, max.Z, 1, 0, 0, colors[2], txc[10], txc[9]), + video::S3DVertex(max.X, min.Y, max.Z, 1, 0, 0, colors[2], txc[10], txc[11]), + video::S3DVertex(max.X, min.Y, min.Z, 1, 0, 0, colors[2], txc[ 8], txc[11]), + // left + video::S3DVertex(min.X, max.Y, max.Z, -1, 0, 0, colors[3], txc[12], txc[13]), + video::S3DVertex(min.X, max.Y, min.Z, -1, 0, 0, colors[3], txc[14], txc[13]), + video::S3DVertex(min.X, min.Y, min.Z, -1, 0, 0, colors[3], txc[14], txc[15]), + video::S3DVertex(min.X, min.Y, max.Z, -1, 0, 0, colors[3], txc[12], txc[15]), + // back + video::S3DVertex(max.X, max.Y, max.Z, 0, 0, 1, colors[4], txc[16], txc[17]), + video::S3DVertex(min.X, max.Y, max.Z, 0, 0, 1, colors[4], txc[18], txc[17]), + video::S3DVertex(min.X, min.Y, max.Z, 0, 0, 1, colors[4], txc[18], txc[19]), + video::S3DVertex(max.X, min.Y, max.Z, 0, 0, 1, colors[4], txc[16], txc[19]), + // front + video::S3DVertex(min.X, max.Y, min.Z, 0, 0, -1, colors[5], txc[20], txc[21]), + video::S3DVertex(max.X, max.Y, min.Z, 0, 0, -1, colors[5], txc[22], txc[21]), + video::S3DVertex(max.X, min.Y, min.Z, 0, 0, -1, colors[5], txc[22], txc[23]), + video::S3DVertex(min.X, min.Y, min.Z, 0, 0, -1, colors[5], txc[20], txc[23]), + }; + + static const u8 light_indices[24] = { + 3, 7, 6, 2, + 0, 4, 5, 1, + 6, 7, 5, 4, + 3, 2, 0, 1, + 7, 3, 1, 5, + 2, 6, 4, 0 + }; + + for (int face = 0; face < 6; face++) { + int tileindex = MYMIN(face, tilecount - 1); + const TileSpec &tile = tiles[tileindex]; + for (int j = 0; j < 4; j++) { + video::S3DVertex &vertex = vertices[face * 4 + j]; + v2f &tcoords = vertex.TCoords; + switch (tile.rotation) { + case 0: + break; + case 1: // R90 + tcoords.rotateBy(90, irr::core::vector2df(0, 0)); + break; + case 2: // R180 + tcoords.rotateBy(180, irr::core::vector2df(0, 0)); + break; + case 3: // R270 + tcoords.rotateBy(270, irr::core::vector2df(0, 0)); + break; + case 4: // FXR90 + tcoords.X = 1.0 - tcoords.X; + tcoords.rotateBy(90, irr::core::vector2df(0, 0)); + break; + case 5: // FXR270 + tcoords.X = 1.0 - tcoords.X; + tcoords.rotateBy(270, irr::core::vector2df(0, 0)); + break; + case 6: // FYR90 + tcoords.Y = 1.0 - tcoords.Y; + tcoords.rotateBy(90, irr::core::vector2df(0, 0)); + break; + case 7: // FYR270 + tcoords.Y = 1.0 - tcoords.Y; + tcoords.rotateBy(270, irr::core::vector2df(0, 0)); + break; + case 8: // FX + tcoords.X = 1.0 - tcoords.X; + break; + case 9: // FY + tcoords.Y = 1.0 - tcoords.Y; + break; + default: + break; + } + } + } + + if (data->m_smooth_lighting) { + for (int j = 0; j < 24; ++j) { + video::S3DVertex &vertex = vertices[j]; + vertex.Color = encode_light( + lights[light_indices[j]].getPair(MYMAX(0.0f, vertex.Normal.Y)), + f->light_source); + if (!f->light_source) + applyFacesShading(vertex.Color, vertex.Normal); + } + } + + // Add to mesh collector + for (int k = 0; k < 6; ++k) { + int tileindex = MYMIN(k, tilecount - 1); + collector->append(tiles[tileindex], vertices + 4 * k, 4, quad_indices, 6); + } +} + +// Gets the base lighting values for a node +void MapblockMeshGenerator::getSmoothLightFrame() +{ + for (int k = 0; k < 8; ++k) + frame.sunlight[k] = false; + for (int k = 0; k < 8; ++k) { + LightPair light(getSmoothLightTransparent(blockpos_nodes + p, light_dirs[k], data)); + frame.lightsDay[k] = light.lightDay; + frame.lightsNight[k] = light.lightNight; + // If there is direct sunlight and no ambient occlusion at some corner, + // mark the vertical edge (top and bottom corners) containing it. + if (light.lightDay == 255) { + frame.sunlight[k] = true; + frame.sunlight[k ^ 2] = true; + } + } +} + +// Calculates vertex light level +// vertex_pos - vertex position in the node (coordinates are clamped to [0.0, 1.0] or so) +LightInfo MapblockMeshGenerator::blendLight(const v3f &vertex_pos) +{ + // Light levels at (logical) node corners are known. Here, + // trilinear interpolation is used to calculate light level + // at a given point in the node. + f32 x = core::clamp(vertex_pos.X / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, 1.0 + SMOOTH_LIGHTING_OVERSIZE); + f32 y = core::clamp(vertex_pos.Y / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, 1.0 + SMOOTH_LIGHTING_OVERSIZE); + f32 z = core::clamp(vertex_pos.Z / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, 1.0 + SMOOTH_LIGHTING_OVERSIZE); + f32 lightDay = 0.0; // daylight + f32 lightNight = 0.0; + f32 lightBoosted = 0.0; // daylight + direct sunlight, if any + for (int k = 0; k < 8; ++k) { + f32 dx = (k & 4) ? x : 1 - x; + f32 dy = (k & 2) ? y : 1 - y; + f32 dz = (k & 1) ? z : 1 - z; + // Use direct sunlight (255), if any; use daylight otherwise. + f32 light_boosted = frame.sunlight[k] ? 255 : frame.lightsDay[k]; + lightDay += dx * dy * dz * frame.lightsDay[k]; + lightNight += dx * dy * dz * frame.lightsNight[k]; + lightBoosted += dx * dy * dz * light_boosted; + } + return LightInfo{lightDay, lightNight, lightBoosted}; +} + +// Calculates vertex color to be used in mapblock mesh +// vertex_pos - vertex position in the node (coordinates are clamped to [0.0, 1.0] or so) +// tile_color - node's tile color +video::SColor MapblockMeshGenerator::blendLightColor(const v3f &vertex_pos) +{ + LightInfo light = blendLight(vertex_pos); + return encode_light(light.getPair(), f->light_source); +} + +video::SColor MapblockMeshGenerator::blendLightColor(const v3f &vertex_pos, + const v3f &vertex_normal) +{ + LightInfo light = blendLight(vertex_pos); + video::SColor color = encode_light(light.getPair(MYMAX(0.0f, vertex_normal.Y)), f->light_source); + if (!f->light_source) + applyFacesShading(color, vertex_normal); + return color; +} + +void MapblockMeshGenerator::generateCuboidTextureCoords(const aabb3f &box, f32 *coords) +{ + f32 tx1 = (box.MinEdge.X / BS) + 0.5; + f32 ty1 = (box.MinEdge.Y / BS) + 0.5; + f32 tz1 = (box.MinEdge.Z / BS) + 0.5; + f32 tx2 = (box.MaxEdge.X / BS) + 0.5; + f32 ty2 = (box.MaxEdge.Y / BS) + 0.5; + f32 tz2 = (box.MaxEdge.Z / BS) + 0.5; + f32 txc[24] = { + tx1, 1 - tz2, tx2, 1 - tz1, // up + tx1, tz1, tx2, tz2, // down + tz1, 1 - ty2, tz2, 1 - ty1, // right + 1 - tz2, 1 - ty2, 1 - tz1, 1 - ty1, // left + 1 - tx2, 1 - ty2, 1 - tx1, 1 - ty1, // back + tx1, 1 - ty2, tx2, 1 - ty1, // front + }; + for (int i = 0; i != 24; ++i) + coords[i] = txc[i]; +} + +void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, const f32 *txc, + TileSpec *tiles, int tile_count) +{ + f32 texture_coord_buf[24]; + f32 dx1 = box.MinEdge.X; + f32 dy1 = box.MinEdge.Y; + f32 dz1 = box.MinEdge.Z; + f32 dx2 = box.MaxEdge.X; + f32 dy2 = box.MaxEdge.Y; + f32 dz2 = box.MaxEdge.Z; + box.MinEdge += origin; + box.MaxEdge += origin; + if (!txc) { + generateCuboidTextureCoords(box, texture_coord_buf); + txc = texture_coord_buf; + } + if (!tiles) { + tiles = &tile; + tile_count = 1; + } + if (data->m_smooth_lighting) { + LightInfo lights[8]; + for (int j = 0; j < 8; ++j) { + v3f d; + d.X = (j & 4) ? dx2 : dx1; + d.Y = (j & 2) ? dy2 : dy1; + d.Z = (j & 1) ? dz2 : dz1; + lights[j] = blendLight(d); + } + drawCuboid(box, tiles, tile_count, lights, txc); + } else { + drawCuboid(box, tiles, tile_count, nullptr, txc); + } +} + +void MapblockMeshGenerator::prepareLiquidNodeDrawing() +{ + getSpecialTile(0, &tile_liquid_top); + getSpecialTile(1, &tile_liquid); + + MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(p.X, p.Y + 1, p.Z)); + MapNode nbottom = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(p.X, p.Y - 1, p.Z)); + c_flowing = nodedef->getId(f->liquid_alternative_flowing); + c_source = nodedef->getId(f->liquid_alternative_source); + top_is_same_liquid = (ntop.getContent() == c_flowing) || (ntop.getContent() == c_source); + draw_liquid_bottom = (nbottom.getContent() != c_flowing) && (nbottom.getContent() != c_source); + if (draw_liquid_bottom) { + const ContentFeatures &f2 = nodedef->get(nbottom.getContent()); + if (f2.solidness > 1) + draw_liquid_bottom = false; + } + + if (data->m_smooth_lighting) + return; // don't need to pre-compute anything in this case + + if (f->light_source != 0) { + // If this liquid emits light and doesn't contain light, draw + // it at what it emits, for an increased effect + u8 e = decode_light(f->light_source); + light = LightPair(std::max(e, light.lightDay), std::max(e, light.lightNight)); + } else if (nodedef->get(ntop).param_type == CPT_LIGHT) { + // Otherwise, use the light of the node on top if possible + light = LightPair(getInteriorLight(ntop, 0, nodedef)); + } + + color_liquid_top = encode_light(light, f->light_source); + color = encode_light(light, f->light_source); +} + +void MapblockMeshGenerator::getLiquidNeighborhood() +{ + u8 range = rangelim(nodedef->get(c_flowing).liquid_range, 1, 8); + + for (int w = -1; w <= 1; w++) + for (int u = -1; u <= 1; u++) { + NeighborData &neighbor = liquid_neighbors[w + 1][u + 1]; + v3s16 p2 = p + v3s16(u, 0, w); + MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2); + neighbor.content = n2.getContent(); + neighbor.level = -0.5 * BS; + neighbor.is_same_liquid = false; + neighbor.top_is_same_liquid = false; + + if (neighbor.content == CONTENT_IGNORE) + continue; + + if (neighbor.content == c_source) { + neighbor.is_same_liquid = true; + neighbor.level = 0.5 * BS; + } else if (neighbor.content == c_flowing) { + neighbor.is_same_liquid = true; + u8 liquid_level = (n2.param2 & LIQUID_LEVEL_MASK); + if (liquid_level <= LIQUID_LEVEL_MAX + 1 - range) + liquid_level = 0; + else + liquid_level -= (LIQUID_LEVEL_MAX + 1 - range); + neighbor.level = (-0.5 + (liquid_level + 0.5) / range) * BS; + } + + // Check node above neighbor. + // NOTE: This doesn't get executed if neighbor + // doesn't exist + p2.Y++; + n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2); + if (n2.getContent() == c_source || n2.getContent() == c_flowing) + neighbor.top_is_same_liquid = true; + } +} + +void MapblockMeshGenerator::calculateCornerLevels() +{ + for (int k = 0; k < 2; k++) + for (int i = 0; i < 2; i++) + corner_levels[k][i] = getCornerLevel(i, k); +} + +f32 MapblockMeshGenerator::getCornerLevel(int i, int k) +{ + float sum = 0; + int count = 0; + int air_count = 0; + for (int dk = 0; dk < 2; dk++) + for (int di = 0; di < 2; di++) { + NeighborData &neighbor_data = liquid_neighbors[k + dk][i + di]; + content_t content = neighbor_data.content; + + // If top is liquid, draw starting from top of node + if (neighbor_data.top_is_same_liquid) + return 0.5 * BS; + + // Source always has the full height + if (content == c_source) + return 0.5 * BS; + + // Flowing liquid has level information + if (content == c_flowing) { + sum += neighbor_data.level; + count++; + } else if (content == CONTENT_AIR) { + air_count++; + if (air_count >= 2) + return -0.5 * BS + 0.2; + } + } + if (count > 0) + return sum / count; + return 0; +} + +void MapblockMeshGenerator::drawLiquidSides() +{ + struct LiquidFaceDesc { + v3s16 dir; // XZ + v3s16 p[2]; // XZ only; 1 means +, 0 means - + }; + struct UV { + int u, v; + }; + static const LiquidFaceDesc base_faces[4] = { + {v3s16( 1, 0, 0), {v3s16(1, 0, 1), v3s16(1, 0, 0)}}, + {v3s16(-1, 0, 0), {v3s16(0, 0, 0), v3s16(0, 0, 1)}}, + {v3s16( 0, 0, 1), {v3s16(0, 0, 1), v3s16(1, 0, 1)}}, + {v3s16( 0, 0, -1), {v3s16(1, 0, 0), v3s16(0, 0, 0)}}, + }; + static const UV base_vertices[4] = { + {0, 1}, + {1, 1}, + {1, 0}, + {0, 0} + }; + + for (const auto &face : base_faces) { + const NeighborData &neighbor = liquid_neighbors[face.dir.Z + 1][face.dir.X + 1]; + + // No face between nodes of the same liquid, unless there is node + // at the top to which it should be connected. Again, unless the face + // there would be inside the liquid + if (neighbor.is_same_liquid) { + if (!top_is_same_liquid) + continue; + if (neighbor.top_is_same_liquid) + continue; + } + + const ContentFeatures &neighbor_features = nodedef->get(neighbor.content); + // Don't draw face if neighbor is blocking the view + if (neighbor_features.solidness == 2) + continue; + + video::S3DVertex vertices[4]; + for (int j = 0; j < 4; j++) { + const UV &vertex = base_vertices[j]; + const v3s16 &base = face.p[vertex.u]; + v3f pos; + pos.X = (base.X - 0.5) * BS; + pos.Z = (base.Z - 0.5) * BS; + if (vertex.v) + pos.Y = neighbor.is_same_liquid ? corner_levels[base.Z][base.X] : -0.5 * BS; + else + pos.Y = !top_is_same_liquid ? corner_levels[base.Z][base.X] : 0.5 * BS; + if (data->m_smooth_lighting) + color = blendLightColor(pos); + pos += origin; + vertices[j] = video::S3DVertex(pos.X, pos.Y, pos.Z, 0, 0, 0, color, vertex.u, vertex.v); + }; + collector->append(tile_liquid, vertices, 4, quad_indices, 6); + } +} + +void MapblockMeshGenerator::drawLiquidTop() +{ + // To get backface culling right, the vertices need to go + // clockwise around the front of the face. And we happened to + // calculate corner levels in exact reverse order. + static const int corner_resolve[4][2] = {{0, 1}, {1, 1}, {1, 0}, {0, 0}}; + + video::S3DVertex vertices[4] = { + video::S3DVertex(-BS / 2, 0, BS / 2, 0, 0, 0, color_liquid_top, 0, 1), + video::S3DVertex( BS / 2, 0, BS / 2, 0, 0, 0, color_liquid_top, 1, 1), + video::S3DVertex( BS / 2, 0, -BS / 2, 0, 0, 0, color_liquid_top, 1, 0), + video::S3DVertex(-BS / 2, 0, -BS / 2, 0, 0, 0, color_liquid_top, 0, 0), + }; + + for (int i = 0; i < 4; i++) { + int u = corner_resolve[i][0]; + int w = corner_resolve[i][1]; + vertices[i].Pos.Y += corner_levels[w][u]; + if (data->m_smooth_lighting) + vertices[i].Color = blendLightColor(vertices[i].Pos); + vertices[i].Pos += origin; + } + + // Default downwards-flowing texture animation goes from + // -Z towards +Z, thus the direction is +Z. + // Rotate texture to make animation go in flow direction + // Positive if liquid moves towards +Z + f32 dz = (corner_levels[0][0] + corner_levels[0][1]) - + (corner_levels[1][0] + corner_levels[1][1]); + // Positive if liquid moves towards +X + f32 dx = (corner_levels[0][0] + corner_levels[1][0]) - + (corner_levels[0][1] + corner_levels[1][1]); + f32 tcoord_angle = atan2(dz, dx) * core::RADTODEG; + v2f tcoord_center(0.5, 0.5); + v2f tcoord_translate(blockpos_nodes.Z + p.Z, blockpos_nodes.X + p.X); + tcoord_translate.rotateBy(tcoord_angle); + tcoord_translate.X -= floor(tcoord_translate.X); + tcoord_translate.Y -= floor(tcoord_translate.Y); + + for (video::S3DVertex &vertex : vertices) { + vertex.TCoords.rotateBy(tcoord_angle, tcoord_center); + vertex.TCoords += tcoord_translate; + } + + std::swap(vertices[0].TCoords, vertices[2].TCoords); + + collector->append(tile_liquid_top, vertices, 4, quad_indices, 6); +} + +void MapblockMeshGenerator::drawLiquidBottom() +{ + video::S3DVertex vertices[4] = { + video::S3DVertex(-BS / 2, -BS / 2, -BS / 2, 0, 0, 0, color_liquid_top, 0, 0), + video::S3DVertex( BS / 2, -BS / 2, -BS / 2, 0, 0, 0, color_liquid_top, 1, 0), + video::S3DVertex( BS / 2, -BS / 2, BS / 2, 0, 0, 0, color_liquid_top, 1, 1), + video::S3DVertex(-BS / 2, -BS / 2, BS / 2, 0, 0, 0, color_liquid_top, 0, 1), + }; + + for (int i = 0; i < 4; i++) { + if (data->m_smooth_lighting) + vertices[i].Color = blendLightColor(vertices[i].Pos); + vertices[i].Pos += origin; + } + + collector->append(tile_liquid_top, vertices, 4, quad_indices, 6); +} + +void MapblockMeshGenerator::drawLiquidNode() +{ + prepareLiquidNodeDrawing(); + getLiquidNeighborhood(); + calculateCornerLevels(); + drawLiquidSides(); + if (!top_is_same_liquid) + drawLiquidTop(); + if (draw_liquid_bottom) + drawLiquidBottom(); +} + +void MapblockMeshGenerator::drawGlasslikeNode() +{ + useTile(0, 0, 0); + + for (int face = 0; face < 6; face++) { + // Check this neighbor + v3s16 dir = g_6dirs[face]; + v3s16 neighbor_pos = blockpos_nodes + p + dir; + MapNode neighbor = data->m_vmanip.getNodeNoExNoEmerge(neighbor_pos); + // Don't make face if neighbor is of same type + if (neighbor.getContent() == n.getContent()) + continue; + // Face at Z- + v3f vertices[4] = { + v3f(-BS / 2, BS / 2, -BS / 2), + v3f( BS / 2, BS / 2, -BS / 2), + v3f( BS / 2, -BS / 2, -BS / 2), + v3f(-BS / 2, -BS / 2, -BS / 2), + }; + + for (v3f &vertex : vertices) { + switch (face) { + case D6D_ZP: + vertex.rotateXZBy(180); break; + case D6D_YP: + vertex.rotateYZBy( 90); break; + case D6D_XP: + vertex.rotateXZBy( 90); break; + case D6D_ZN: + vertex.rotateXZBy( 0); break; + case D6D_YN: + vertex.rotateYZBy(-90); break; + case D6D_XN: + vertex.rotateXZBy(-90); break; + } + } + drawQuad(vertices, dir); + } +} + +void MapblockMeshGenerator::drawGlasslikeFramedNode() +{ + TileSpec tiles[6]; + for (int face = 0; face < 6; face++) + getTile(g_6dirs[face], &tiles[face]); + + if (!data->m_smooth_lighting) + color = encode_light(light, f->light_source); + + TileSpec glass_tiles[6]; + for (auto &glass_tile : glass_tiles) + glass_tile = tiles[4]; + + u8 param2 = n.getParam2(); + bool H_merge = !(param2 & 128); + bool V_merge = !(param2 & 64); + param2 &= 63; + + static const float a = BS / 2.0f; + static const float g = a - 0.03f; + static const float b = 0.876f * (BS / 2.0f); + + static const aabb3f frame_edges[FRAMED_EDGE_COUNT] = { + aabb3f( b, b, -a, a, a, a), // y+ + aabb3f(-a, b, -a, -b, a, a), // y+ + aabb3f( b, -a, -a, a, -b, a), // y- + aabb3f(-a, -a, -a, -b, -b, a), // y- + aabb3f( b, -a, b, a, a, a), // x+ + aabb3f( b, -a, -a, a, a, -b), // x+ + aabb3f(-a, -a, b, -b, a, a), // x- + aabb3f(-a, -a, -a, -b, a, -b), // x- + aabb3f(-a, b, b, a, a, a), // z+ + aabb3f(-a, -a, b, a, -b, a), // z+ + aabb3f(-a, -a, -a, a, -b, -b), // z- + aabb3f(-a, b, -a, a, a, -b), // z- + }; + + // tables of neighbour (connect if same type and merge allowed), + // checked with g_26dirs + + // 1 = connect, 0 = face visible + bool nb[FRAMED_NEIGHBOR_COUNT] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + + // 1 = check + static const bool check_nb_vertical [FRAMED_NEIGHBOR_COUNT] = + {0,1,0,0,1,0, 0,0,0,0, 0,0,0,0, 0,0,0,0}; + static const bool check_nb_horizontal [FRAMED_NEIGHBOR_COUNT] = + {1,0,1,1,0,1, 0,0,0,0, 1,1,1,1, 0,0,0,0}; + static const bool check_nb_all [FRAMED_NEIGHBOR_COUNT] = + {1,1,1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1}; + const bool *check_nb = check_nb_all; + + // neighbours checks for frames visibility + if (H_merge || V_merge) { + if (!H_merge) + check_nb = check_nb_vertical; // vertical-only merge + if (!V_merge) + check_nb = check_nb_horizontal; // horizontal-only merge + content_t current = n.getContent(); + for (int i = 0; i < FRAMED_NEIGHBOR_COUNT; i++) { + if (!check_nb[i]) + continue; + v3s16 n2p = blockpos_nodes + p + g_26dirs[i]; + MapNode n2 = data->m_vmanip.getNodeNoEx(n2p); + content_t n2c = n2.getContent(); + if (n2c == current) + nb[i] = 1; + } + } + + // edge visibility + + static const u8 nb_triplet[FRAMED_EDGE_COUNT][3] = { + {1, 2, 7}, {1, 5, 6}, {4, 2, 15}, {4, 5, 14}, + {2, 0, 11}, {2, 3, 13}, {5, 0, 10}, {5, 3, 12}, + {0, 1, 8}, {0, 4, 16}, {3, 4, 17}, {3, 1, 9}, + }; + + tile = tiles[1]; + for (int edge = 0; edge < FRAMED_EDGE_COUNT; edge++) { + bool edge_invisible; + if (nb[nb_triplet[edge][2]]) + edge_invisible = nb[nb_triplet[edge][0]] & nb[nb_triplet[edge][1]]; + else + edge_invisible = nb[nb_triplet[edge][0]] ^ nb[nb_triplet[edge][1]]; + if (edge_invisible) + continue; + drawAutoLightedCuboid(frame_edges[edge]); + } + + for (int face = 0; face < 6; face++) { + if (nb[face]) + continue; + + tile = glass_tiles[face]; + // Face at Z- + v3f vertices[4] = { + v3f(-a, a, -g), + v3f( a, a, -g), + v3f( a, -a, -g), + v3f(-a, -a, -g), + }; + + for (v3f &vertex : vertices) { + switch (face) { + case D6D_ZP: + vertex.rotateXZBy(180); break; + case D6D_YP: + vertex.rotateYZBy( 90); break; + case D6D_XP: + vertex.rotateXZBy( 90); break; + case D6D_ZN: + vertex.rotateXZBy( 0); break; + case D6D_YN: + vertex.rotateYZBy(-90); break; + case D6D_XN: + vertex.rotateXZBy(-90); break; + } + } + v3s16 dir = g_6dirs[face]; + drawQuad(vertices, dir); + } + + // Optionally render internal liquid level defined by param2 + // Liquid is textured with 1 tile defined in nodedef 'special_tiles' + if (param2 > 0 && f->param_type_2 == CPT2_GLASSLIKE_LIQUID_LEVEL && + f->special_tiles[0].layers[0].texture) { + // Internal liquid level has param2 range 0 .. 63, + // convert it to -0.5 .. 0.5 + float vlev = (param2 / 63.0f) * 2.0f - 1.0f; + getSpecialTile(0, &tile); + drawAutoLightedCuboid(aabb3f(-(nb[5] ? g : b), + -(nb[4] ? g : b), + -(nb[3] ? g : b), + (nb[2] ? g : b), + (nb[1] ? g : b) * vlev, + (nb[0] ? g : b))); + } +} + +void MapblockMeshGenerator::drawAllfacesNode() +{ + static const aabb3f box(-BS / 2, -BS / 2, -BS / 2, BS / 2, BS / 2, BS / 2); + useTile(0, 0, 0); + drawAutoLightedCuboid(box); +} + +void MapblockMeshGenerator::drawTorchlikeNode() +{ + u8 wall = n.getWallMounted(nodedef); + u8 tileindex = 0; + switch (wall) { + case DWM_YP: tileindex = 1; break; // ceiling + case DWM_YN: tileindex = 0; break; // floor + default: tileindex = 2; // side (or invalid—should we care?) + } + useTile(tileindex, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING); + + float size = BS / 2 * f->visual_scale; + v3f vertices[4] = { + v3f(-size, size, 0), + v3f( size, size, 0), + v3f( size, -size, 0), + v3f(-size, -size, 0), + }; + + for (v3f &vertex : vertices) { + switch (wall) { + case DWM_YP: + vertex.rotateXZBy(-45); break; + case DWM_YN: + vertex.rotateXZBy( 45); break; + case DWM_XP: + vertex.rotateXZBy( 0); break; + case DWM_XN: + vertex.rotateXZBy(180); break; + case DWM_ZP: + vertex.rotateXZBy( 90); break; + case DWM_ZN: + vertex.rotateXZBy(-90); break; + } + } + drawQuad(vertices); +} + +void MapblockMeshGenerator::drawSignlikeNode() +{ + u8 wall = n.getWallMounted(nodedef); + useTile(0, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING); + static const float offset = BS / 16; + float size = BS / 2 * f->visual_scale; + // Wall at X+ of node + v3f vertices[4] = { + v3f(BS / 2 - offset, size, size), + v3f(BS / 2 - offset, size, -size), + v3f(BS / 2 - offset, -size, -size), + v3f(BS / 2 - offset, -size, size), + }; + + for (v3f &vertex : vertices) { + switch (wall) { + case DWM_YP: + vertex.rotateXYBy( 90); break; + case DWM_YN: + vertex.rotateXYBy(-90); break; + case DWM_XP: + vertex.rotateXZBy( 0); break; + case DWM_XN: + vertex.rotateXZBy(180); break; + case DWM_ZP: + vertex.rotateXZBy( 90); break; + case DWM_ZN: + vertex.rotateXZBy(-90); break; + } + } + drawQuad(vertices); +} + +void MapblockMeshGenerator::drawPlantlikeQuad(float rotation, float quad_offset, + bool offset_top_only) +{ + v3f vertices[4] = { + v3f(-scale, -BS / 2 + 2.0 * scale * plant_height, 0), + v3f( scale, -BS / 2 + 2.0 * scale * plant_height, 0), + v3f( scale, -BS / 2, 0), + v3f(-scale, -BS / 2, 0), + }; + if (random_offset_Y) { + PseudoRandom yrng(face_num++ | p.X << 16 | p.Z << 8 | p.Y << 24); + offset.Y = -BS * ((yrng.next() % 16 / 16.0) * 0.125); + } + int offset_count = offset_top_only ? 2 : 4; + for (int i = 0; i < offset_count; i++) + vertices[i].Z += quad_offset; + + for (v3f &vertex : vertices) { + vertex.rotateXZBy(rotation + rotate_degree); + vertex += offset; + } + drawQuad(vertices, v3s16(0, 0, 0), plant_height); +} + +void MapblockMeshGenerator::drawPlantlike() +{ + draw_style = PLANT_STYLE_CROSS; + scale = BS / 2 * f->visual_scale; + offset = v3f(0, 0, 0); + rotate_degree = 0; + random_offset_Y = false; + face_num = 0; + plant_height = 1.0; + + switch (f->param_type_2) { + case CPT2_MESHOPTIONS: + draw_style = PlantlikeStyle(n.param2 & MO_MASK_STYLE); + if (n.param2 & MO_BIT_SCALE_SQRT2) + scale *= 1.41421; + if (n.param2 & MO_BIT_RANDOM_OFFSET) { + PseudoRandom rng(p.X << 8 | p.Z | p.Y << 16); + offset.X = BS * ((rng.next() % 16 / 16.0) * 0.29 - 0.145); + offset.Z = BS * ((rng.next() % 16 / 16.0) * 0.29 - 0.145); + } + if (n.param2 & MO_BIT_RANDOM_OFFSET_Y) + random_offset_Y = true; + break; + + case CPT2_DEGROTATE: + rotate_degree = n.param2 * 2; + break; + + case CPT2_LEVELED: + plant_height = n.param2 / 16.0; + break; + + default: + break; + } + + switch (draw_style) { + case PLANT_STYLE_CROSS: + drawPlantlikeQuad(46); + drawPlantlikeQuad(-44); + break; + + case PLANT_STYLE_CROSS2: + drawPlantlikeQuad(91); + drawPlantlikeQuad(1); + break; + + case PLANT_STYLE_STAR: + drawPlantlikeQuad(121); + drawPlantlikeQuad(241); + drawPlantlikeQuad(1); + break; + + case PLANT_STYLE_HASH: + drawPlantlikeQuad( 1, BS / 4); + drawPlantlikeQuad( 91, BS / 4); + drawPlantlikeQuad(181, BS / 4); + drawPlantlikeQuad(271, BS / 4); + break; + + case PLANT_STYLE_HASH2: + drawPlantlikeQuad( 1, -BS / 2, true); + drawPlantlikeQuad( 91, -BS / 2, true); + drawPlantlikeQuad(181, -BS / 2, true); + drawPlantlikeQuad(271, -BS / 2, true); + break; + } +} + +void MapblockMeshGenerator::drawPlantlikeNode() +{ + useTile(); + drawPlantlike(); +} + +void MapblockMeshGenerator::drawPlantlikeRootedNode() +{ + useTile(0, MATERIAL_FLAG_CRACK_OVERLAY, 0, true); + origin += v3f(0.0, BS, 0.0); + p.Y++; + if (data->m_smooth_lighting) { + getSmoothLightFrame(); + } else { + MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + p); + light = LightPair(getInteriorLight(ntop, 1, nodedef)); + } + drawPlantlike(); + p.Y--; +} + +void MapblockMeshGenerator::drawFirelikeQuad(float rotation, float opening_angle, + float offset_h, float offset_v) +{ + v3f vertices[4] = { + v3f(-scale, -BS / 2 + scale * 2, 0), + v3f( scale, -BS / 2 + scale * 2, 0), + v3f( scale, -BS / 2, 0), + v3f(-scale, -BS / 2, 0), + }; + + for (v3f &vertex : vertices) { + vertex.rotateYZBy(opening_angle); + vertex.Z += offset_h; + vertex.rotateXZBy(rotation); + vertex.Y += offset_v; + } + drawQuad(vertices); +} + +void MapblockMeshGenerator::drawFirelikeNode() +{ + useTile(); + scale = BS / 2 * f->visual_scale; + + // Check for adjacent nodes + bool neighbors = false; + bool neighbor[6] = {0, 0, 0, 0, 0, 0}; + content_t current = n.getContent(); + for (int i = 0; i < 6; i++) { + v3s16 n2p = blockpos_nodes + p + g_6dirs[i]; + MapNode n2 = data->m_vmanip.getNodeNoEx(n2p); + content_t n2c = n2.getContent(); + if (n2c != CONTENT_IGNORE && n2c != CONTENT_AIR && n2c != current) { + neighbor[i] = true; + neighbors = true; + } + } + bool drawBasicFire = neighbor[D6D_YN] || !neighbors; + bool drawBottomFire = neighbor[D6D_YP]; + + if (drawBasicFire || neighbor[D6D_ZP]) + drawFirelikeQuad(0, -10, 0.4 * BS); + else if (drawBottomFire) + drawFirelikeQuad(0, 70, 0.47 * BS, 0.484 * BS); + + if (drawBasicFire || neighbor[D6D_XN]) + drawFirelikeQuad(90, -10, 0.4 * BS); + else if (drawBottomFire) + drawFirelikeQuad(90, 70, 0.47 * BS, 0.484 * BS); + + if (drawBasicFire || neighbor[D6D_ZN]) + drawFirelikeQuad(180, -10, 0.4 * BS); + else if (drawBottomFire) + drawFirelikeQuad(180, 70, 0.47 * BS, 0.484 * BS); + + if (drawBasicFire || neighbor[D6D_XP]) + drawFirelikeQuad(270, -10, 0.4 * BS); + else if (drawBottomFire) + drawFirelikeQuad(270, 70, 0.47 * BS, 0.484 * BS); + + if (drawBasicFire) { + drawFirelikeQuad(45, 0, 0.0); + drawFirelikeQuad(-45, 0, 0.0); + } +} + +void MapblockMeshGenerator::drawFencelikeNode() +{ + useTile(0, 0, 0); + TileSpec tile_nocrack = tile; + + for (auto &layer : tile_nocrack.layers) + layer.material_flags &= ~MATERIAL_FLAG_CRACK; + + // Put wood the right way around in the posts + TileSpec tile_rot = tile; + tile_rot.rotation = 1; + + static const f32 post_rad = BS / 8; + static const f32 bar_rad = BS / 16; + static const f32 bar_len = BS / 2 - post_rad; + + // The post - always present + static const aabb3f post(-post_rad, -BS / 2, -post_rad, + post_rad, BS / 2, post_rad); + static const f32 postuv[24] = { + 0.375, 0.375, 0.625, 0.625, + 0.375, 0.375, 0.625, 0.625, + 0.000, 0.000, 0.250, 1.000, + 0.250, 0.000, 0.500, 1.000, + 0.500, 0.000, 0.750, 1.000, + 0.750, 0.000, 1.000, 1.000, + }; + tile = tile_rot; + drawAutoLightedCuboid(post, postuv); + + tile = tile_nocrack; + + // Now a section of fence, +X, if there's a post there + v3s16 p2 = p; + p2.X++; + MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2); + const ContentFeatures *f2 = &nodedef->get(n2); + if (f2->drawtype == NDT_FENCELIKE) { + static const aabb3f bar_x1(BS / 2 - bar_len, BS / 4 - bar_rad, -bar_rad, + BS / 2 + bar_len, BS / 4 + bar_rad, bar_rad); + static const aabb3f bar_x2(BS / 2 - bar_len, -BS / 4 - bar_rad, -bar_rad, + BS / 2 + bar_len, -BS / 4 + bar_rad, bar_rad); + static const f32 xrailuv[24] = { + 0.000, 0.125, 1.000, 0.250, + 0.000, 0.250, 1.000, 0.375, + 0.375, 0.375, 0.500, 0.500, + 0.625, 0.625, 0.750, 0.750, + 0.000, 0.500, 1.000, 0.625, + 0.000, 0.875, 1.000, 1.000, + }; + drawAutoLightedCuboid(bar_x1, xrailuv); + drawAutoLightedCuboid(bar_x2, xrailuv); + } + + // Now a section of fence, +Z, if there's a post there + p2 = p; + p2.Z++; + n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2); + f2 = &nodedef->get(n2); + if (f2->drawtype == NDT_FENCELIKE) { + static const aabb3f bar_z1(-bar_rad, BS / 4 - bar_rad, BS / 2 - bar_len, + bar_rad, BS / 4 + bar_rad, BS / 2 + bar_len); + static const aabb3f bar_z2(-bar_rad, -BS / 4 - bar_rad, BS / 2 - bar_len, + bar_rad, -BS / 4 + bar_rad, BS / 2 + bar_len); + static const f32 zrailuv[24] = { + 0.1875, 0.0625, 0.3125, 0.3125, // cannot rotate; stretch + 0.2500, 0.0625, 0.3750, 0.3125, // for wood texture instead + 0.0000, 0.5625, 1.0000, 0.6875, + 0.0000, 0.3750, 1.0000, 0.5000, + 0.3750, 0.3750, 0.5000, 0.5000, + 0.6250, 0.6250, 0.7500, 0.7500, + }; + drawAutoLightedCuboid(bar_z1, zrailuv); + drawAutoLightedCuboid(bar_z2, zrailuv); + } +} + +bool MapblockMeshGenerator::isSameRail(v3s16 dir) +{ + MapNode node2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p + dir); + if (node2.getContent() == n.getContent()) + return true; + const ContentFeatures &def2 = nodedef->get(node2); + return ((def2.drawtype == NDT_RAILLIKE) && + (def2.getGroup(raillike_groupname) == raillike_group)); +} + +void MapblockMeshGenerator::drawRaillikeNode() +{ + static const v3s16 direction[4] = { + v3s16( 0, 0, 1), + v3s16( 0, 0, -1), + v3s16(-1, 0, 0), + v3s16( 1, 0, 0), + }; + static const int slope_angle[4] = {0, 180, 90, -90}; + + enum RailTile { + straight, + curved, + junction, + cross, + }; + struct RailDesc { + int tile_index; + int angle; + }; + static const RailDesc rail_kinds[16] = { + // +x -x -z +z + //------------- + {straight, 0}, // . . . . + {straight, 0}, // . . . +Z + {straight, 0}, // . . -Z . + {straight, 0}, // . . -Z +Z + {straight, 90}, // . -X . . + { curved, 180}, // . -X . +Z + { curved, 270}, // . -X -Z . + {junction, 180}, // . -X -Z +Z + {straight, 90}, // +X . . . + { curved, 90}, // +X . . +Z + { curved, 0}, // +X . -Z . + {junction, 0}, // +X . -Z +Z + {straight, 90}, // +X -X . . + {junction, 90}, // +X -X . +Z + {junction, 270}, // +X -X -Z . + { cross, 0}, // +X -X -Z +Z + }; + + raillike_group = nodedef->get(n).getGroup(raillike_groupname); + + int code = 0; + int angle; + int tile_index; + bool sloped = false; + for (int dir = 0; dir < 4; dir++) { + bool rail_above = isSameRail(direction[dir] + v3s16(0, 1, 0)); + if (rail_above) { + sloped = true; + angle = slope_angle[dir]; + } + if (rail_above || + isSameRail(direction[dir]) || + isSameRail(direction[dir] + v3s16(0, -1, 0))) + code |= 1 << dir; + } + + if (sloped) { + tile_index = straight; + } else { + tile_index = rail_kinds[code].tile_index; + angle = rail_kinds[code].angle; + } + + useTile(tile_index, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING); + + static const float offset = BS / 64; + static const float size = BS / 2; + float y2 = sloped ? size : -size; + v3f vertices[4] = { + v3f(-size, y2 + offset, size), + v3f( size, y2 + offset, size), + v3f( size, -size + offset, -size), + v3f(-size, -size + offset, -size), + }; + if (angle) + for (v3f &vertex : vertices) + vertex.rotateXZBy(angle); + drawQuad(vertices); +} + +void MapblockMeshGenerator::drawNodeboxNode() +{ + static const v3s16 tile_dirs[6] = { + v3s16(0, 1, 0), + v3s16(0, -1, 0), + v3s16(1, 0, 0), + v3s16(-1, 0, 0), + v3s16(0, 0, 1), + v3s16(0, 0, -1) + }; + + // we have this order for some reason... + static const v3s16 connection_dirs[6] = { + v3s16( 0, 1, 0), // top + v3s16( 0, -1, 0), // bottom + v3s16( 0, 0, -1), // front + v3s16(-1, 0, 0), // left + v3s16( 0, 0, 1), // back + v3s16( 1, 0, 0), // right + }; + + TileSpec tiles[6]; + for (int face = 0; face < 6; face++) { + // Handles facedir rotation for textures + getTile(tile_dirs[face], &tiles[face]); + } + + // locate possible neighboring nodes to connect to + int neighbors_set = 0; + if (f->node_box.type == NODEBOX_CONNECTED) { + for (int dir = 0; dir != 6; dir++) { + int flag = 1 << dir; + v3s16 p2 = blockpos_nodes + p + connection_dirs[dir]; + MapNode n2 = data->m_vmanip.getNodeNoEx(p2); + if (nodedef->nodeboxConnects(n, n2, flag)) + neighbors_set |= flag; + } + } + + std::vector boxes; + n.getNodeBoxes(nodedef, &boxes, neighbors_set); + for (const auto &box : boxes) + drawAutoLightedCuboid(box, nullptr, tiles, 6); +} + +void MapblockMeshGenerator::drawMeshNode() +{ + u8 facedir = 0; + scene::IMesh* mesh; + bool private_mesh; // as a grab/drop pair is not thread-safe + + if (f->param_type_2 == CPT2_FACEDIR || + f->param_type_2 == CPT2_COLORED_FACEDIR) { + facedir = n.getFaceDir(nodedef); + } else if (f->param_type_2 == CPT2_WALLMOUNTED || + f->param_type_2 == CPT2_COLORED_WALLMOUNTED) { + // Convert wallmounted to 6dfacedir. + // When cache enabled, it is already converted. + facedir = n.getWallMounted(nodedef); + if (!enable_mesh_cache) + facedir = wallmounted_to_facedir[facedir]; + } + + if (!data->m_smooth_lighting && f->mesh_ptr[facedir]) { + // use cached meshes + private_mesh = false; + mesh = f->mesh_ptr[facedir]; + } else if (f->mesh_ptr[0]) { + // no cache, clone and rotate mesh + private_mesh = true; + mesh = cloneMesh(f->mesh_ptr[0]); + rotateMeshBy6dFacedir(mesh, facedir); + recalculateBoundingBox(mesh); + meshmanip->recalculateNormals(mesh, true, false); + } else + return; + + int mesh_buffer_count = mesh->getMeshBufferCount(); + for (int j = 0; j < mesh_buffer_count; j++) { + useTile(j); + scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); + video::S3DVertex *vertices = (video::S3DVertex *)buf->getVertices(); + int vertex_count = buf->getVertexCount(); + + if (data->m_smooth_lighting) { + // Mesh is always private here. So the lighting is applied to each + // vertex right here. + for (int k = 0; k < vertex_count; k++) { + video::S3DVertex &vertex = vertices[k]; + vertex.Color = blendLightColor(vertex.Pos, vertex.Normal); + vertex.Pos += origin; + } + collector->append(tile, vertices, vertex_count, + buf->getIndices(), buf->getIndexCount()); + } else { + // Don't modify the mesh, it may not be private here. + // Instead, let the collector process colors, etc. + collector->append(tile, vertices, vertex_count, + buf->getIndices(), buf->getIndexCount(), origin, + color, f->light_source); + } + } + if (private_mesh) + mesh->drop(); +} + +// also called when the drawtype is known but should have been pre-converted +void MapblockMeshGenerator::errorUnknownDrawtype() +{ + infostream << "Got drawtype " << f->drawtype << std::endl; + FATAL_ERROR("Unknown drawtype"); +} + +void MapblockMeshGenerator::drawNode() +{ + // skip some drawtypes early + switch (f->drawtype) { + case NDT_NORMAL: // Drawn by MapBlockMesh + case NDT_AIRLIKE: // Not drawn at all + case NDT_LIQUID: // Drawn by MapBlockMesh + return; + default: + break; + } + origin = intToFloat(p, BS); + if (data->m_smooth_lighting) + getSmoothLightFrame(); + else + light = LightPair(getInteriorLight(n, 1, nodedef)); + switch (f->drawtype) { + case NDT_FLOWINGLIQUID: drawLiquidNode(); break; + case NDT_GLASSLIKE: drawGlasslikeNode(); break; + case NDT_GLASSLIKE_FRAMED: drawGlasslikeFramedNode(); break; + case NDT_ALLFACES: drawAllfacesNode(); break; + case NDT_TORCHLIKE: drawTorchlikeNode(); break; + case NDT_SIGNLIKE: drawSignlikeNode(); break; + case NDT_PLANTLIKE: drawPlantlikeNode(); break; + case NDT_PLANTLIKE_ROOTED: drawPlantlikeRootedNode(); break; + case NDT_FIRELIKE: drawFirelikeNode(); break; + case NDT_FENCELIKE: drawFencelikeNode(); break; + case NDT_RAILLIKE: drawRaillikeNode(); break; + case NDT_NODEBOX: drawNodeboxNode(); break; + case NDT_MESH: drawMeshNode(); break; + default: errorUnknownDrawtype(); break; + } +} + +/* + TODO: Fix alpha blending for special nodes + Currently only the last element rendered is blended correct +*/ +void MapblockMeshGenerator::generate() +{ + for (p.Z = 0; p.Z < MAP_BLOCKSIZE; p.Z++) + for (p.Y = 0; p.Y < MAP_BLOCKSIZE; p.Y++) + for (p.X = 0; p.X < MAP_BLOCKSIZE; p.X++) { + n = data->m_vmanip.getNodeNoEx(blockpos_nodes + p); + f = &nodedef->get(n); + drawNode(); + } +} + +void MapblockMeshGenerator::renderSingle(content_t node) +{ + p = {0, 0, 0}; + n = MapNode(node, 0xff, 0x00); + f = &nodedef->get(n); + drawNode(); +} diff --git a/src/client/content_mapblock.h b/src/client/content_mapblock.h new file mode 100644 index 000000000..97947cdbe --- /dev/null +++ b/src/client/content_mapblock.h @@ -0,0 +1,178 @@ +/* +Minetest +Copyright (C) 2010-2013 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include "nodedef.h" +#include + +struct MeshMakeData; +struct MeshCollector; + +struct LightPair { + u8 lightDay; + u8 lightNight; + + LightPair() = default; + explicit LightPair(u16 value) : lightDay(value & 0xff), lightNight(value >> 8) {} + LightPair(u8 valueA, u8 valueB) : lightDay(valueA), lightNight(valueB) {} + LightPair(float valueA, float valueB) : + lightDay(core::clamp(core::round32(valueA), 0, 255)), + lightNight(core::clamp(core::round32(valueB), 0, 255)) {} + operator u16() const { return lightDay | lightNight << 8; } +}; + +struct LightInfo { + float light_day; + float light_night; + float light_boosted; + + LightPair getPair(float sunlight_boost = 0.0) const + { + return LightPair( + (1 - sunlight_boost) * light_day + + sunlight_boost * light_boosted, + light_night); + } +}; + +struct LightFrame { + f32 lightsDay[8]; + f32 lightsNight[8]; + bool sunlight[8]; +}; + +class MapblockMeshGenerator +{ +public: + MeshMakeData *data; + MeshCollector *collector; + + const NodeDefManager *nodedef; + scene::IMeshManipulator *meshmanip; + +// options + bool enable_mesh_cache; + +// current node + v3s16 blockpos_nodes; + v3s16 p; + v3f origin; + MapNode n; + const ContentFeatures *f; + LightPair light; + LightFrame frame; + video::SColor color; + TileSpec tile; + float scale; + +// lighting + void getSmoothLightFrame(); + LightInfo blendLight(const v3f &vertex_pos); + video::SColor blendLightColor(const v3f &vertex_pos); + video::SColor blendLightColor(const v3f &vertex_pos, const v3f &vertex_normal); + + void useTile(int index = 0, u8 set_flags = MATERIAL_FLAG_CRACK_OVERLAY, + u8 reset_flags = 0, bool special = false); + void getTile(int index, TileSpec *tile); + void getTile(v3s16 direction, TileSpec *tile); + void getSpecialTile(int index, TileSpec *tile, bool apply_crack = false); + +// face drawing + void drawQuad(v3f *vertices, const v3s16 &normal = v3s16(0, 0, 0), + float vertical_tiling = 1.0); + +// cuboid drawing! + void drawCuboid(const aabb3f &box, TileSpec *tiles, int tilecount, + const LightInfo *lights , const f32 *txc); + void generateCuboidTextureCoords(aabb3f const &box, f32 *coords); + void drawAutoLightedCuboid(aabb3f box, const f32 *txc = NULL, + TileSpec *tiles = NULL, int tile_count = 0); + +// liquid-specific + bool top_is_same_liquid; + bool draw_liquid_bottom; + TileSpec tile_liquid; + TileSpec tile_liquid_top; + content_t c_flowing; + content_t c_source; + video::SColor color_liquid_top; + struct NeighborData { + f32 level; + content_t content; + bool is_same_liquid; + bool top_is_same_liquid; + }; + NeighborData liquid_neighbors[3][3]; + f32 corner_levels[2][2]; + + void prepareLiquidNodeDrawing(); + void getLiquidNeighborhood(); + void calculateCornerLevels(); + f32 getCornerLevel(int i, int k); + void drawLiquidSides(); + void drawLiquidTop(); + void drawLiquidBottom(); + +// raillike-specific + // name of the group that enables connecting to raillike nodes of different kind + static const std::string raillike_groupname; + int raillike_group; + bool isSameRail(v3s16 dir); + +// plantlike-specific + PlantlikeStyle draw_style; + v3f offset; + int rotate_degree; + bool random_offset_Y; + int face_num; + float plant_height; + + void drawPlantlikeQuad(float rotation, float quad_offset = 0, + bool offset_top_only = false); + void drawPlantlike(); + +// firelike-specific + void drawFirelikeQuad(float rotation, float opening_angle, + float offset_h, float offset_v = 0.0); + +// drawtypes + void drawLiquidNode(); + void drawGlasslikeNode(); + void drawGlasslikeFramedNode(); + void drawAllfacesNode(); + void drawTorchlikeNode(); + void drawSignlikeNode(); + void drawPlantlikeNode(); + void drawPlantlikeRootedNode(); + void drawFirelikeNode(); + void drawFencelikeNode(); + void drawRaillikeNode(); + void drawNodeboxNode(); + void drawMeshNode(); + +// common + void errorUnknownDrawtype(); + void drawNode(); + +public: + MapblockMeshGenerator(MeshMakeData *input, MeshCollector *output); + void generate(); + void renderSingle(content_t node); +}; diff --git a/src/client/filecache.cpp b/src/client/filecache.cpp new file mode 100644 index 000000000..3d1b302a8 --- /dev/null +++ b/src/client/filecache.cpp @@ -0,0 +1,89 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola +Copyright (C) 2013 Jonathan Neuschäfer + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "filecache.h" + +#include "network/networkprotocol.h" +#include "log.h" +#include "filesys.h" +#include +#include +#include +#include + +bool FileCache::loadByPath(const std::string &path, std::ostream &os) +{ + std::ifstream fis(path.c_str(), std::ios_base::binary); + + if(!fis.good()){ + verbosestream<<"FileCache: File not found in cache: " + < +Copyright (C) 2013 Jonathan Neuschäfer + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include +#include + +class FileCache +{ +public: + /* + 'dir' is the file cache directory to use. + */ + FileCache(const std::string &dir) : m_dir(dir) {} + + bool update(const std::string &name, const std::string &data); + bool load(const std::string &name, std::ostream &os); + +private: + std::string m_dir; + + bool loadByPath(const std::string &path, std::ostream &os); + bool updateByPath(const std::string &path, const std::string &data); +}; diff --git a/src/client/fontengine.cpp b/src/client/fontengine.cpp new file mode 100644 index 000000000..dc98fb1e4 --- /dev/null +++ b/src/client/fontengine.cpp @@ -0,0 +1,505 @@ +/* +Minetest +Copyright (C) 2010-2014 sapier + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "fontengine.h" +#include +#include "client/renderingengine.h" +#include "config.h" +#include "porting.h" +#include "filesys.h" + +#if USE_FREETYPE +#include "gettext.h" +#include "irrlicht_changes/CGUITTFont.h" +#endif + +/** maximum size distance for getting a "similar" font size */ +#define MAX_FONT_SIZE_OFFSET 10 + +/** reference to access font engine, has to be initialized by main */ +FontEngine* g_fontengine = NULL; + +/** callback to be used on change of font size setting */ +static void font_setting_changed(const std::string &name, void *userdata) +{ + g_fontengine->readSettings(); +} + +/******************************************************************************/ +FontEngine::FontEngine(Settings* main_settings, gui::IGUIEnvironment* env) : + m_settings(main_settings), + m_env(env) +{ + + for (u32 &i : m_default_size) { + i = (FontMode) FONT_SIZE_UNSPECIFIED; + } + + assert(m_settings != NULL); // pre-condition + assert(m_env != NULL); // pre-condition + assert(m_env->getSkin() != NULL); // pre-condition + + m_currentMode = FM_Simple; + +#if USE_FREETYPE + if (g_settings->getBool("freetype")) { + m_default_size[FM_Standard] = m_settings->getU16("font_size"); + m_default_size[FM_Fallback] = m_settings->getU16("fallback_font_size"); + m_default_size[FM_Mono] = m_settings->getU16("mono_font_size"); + + if (is_yes(gettext("needs_fallback_font"))) { + m_currentMode = FM_Fallback; + } + else { + m_currentMode = FM_Standard; + } + } + + // having freetype but not using it is quite a strange case so we need to do + // special handling for it + if (m_currentMode == FM_Simple) { + std::stringstream fontsize; + fontsize << DEFAULT_FONT_SIZE; + m_settings->setDefault("font_size", fontsize.str()); + m_settings->setDefault("mono_font_size", fontsize.str()); + } +#endif + + m_default_size[FM_Simple] = m_settings->getU16("font_size"); + m_default_size[FM_SimpleMono] = m_settings->getU16("mono_font_size"); + + updateSkin(); + + if (m_currentMode == FM_Standard) { + m_settings->registerChangedCallback("font_size", font_setting_changed, NULL); + m_settings->registerChangedCallback("font_path", font_setting_changed, NULL); + m_settings->registerChangedCallback("font_shadow", font_setting_changed, NULL); + m_settings->registerChangedCallback("font_shadow_alpha", font_setting_changed, NULL); + } + else if (m_currentMode == FM_Fallback) { + m_settings->registerChangedCallback("fallback_font_size", font_setting_changed, NULL); + m_settings->registerChangedCallback("fallback_font_path", font_setting_changed, NULL); + m_settings->registerChangedCallback("fallback_font_shadow", font_setting_changed, NULL); + m_settings->registerChangedCallback("fallback_font_shadow_alpha", font_setting_changed, NULL); + } + + m_settings->registerChangedCallback("mono_font_path", font_setting_changed, NULL); + m_settings->registerChangedCallback("mono_font_size", font_setting_changed, NULL); + m_settings->registerChangedCallback("screen_dpi", font_setting_changed, NULL); + m_settings->registerChangedCallback("gui_scaling", font_setting_changed, NULL); +} + +/******************************************************************************/ +FontEngine::~FontEngine() +{ + cleanCache(); +} + +/******************************************************************************/ +void FontEngine::cleanCache() +{ + for (auto &font_cache_it : m_font_cache) { + + for (auto &font_it : font_cache_it) { + font_it.second->drop(); + font_it.second = NULL; + } + font_cache_it.clear(); + } +} + +/******************************************************************************/ +irr::gui::IGUIFont* FontEngine::getFont(unsigned int font_size, FontMode mode) +{ + if (mode == FM_Unspecified) { + mode = m_currentMode; + } + else if ((mode == FM_Mono) && (m_currentMode == FM_Simple)) { + mode = FM_SimpleMono; + } + + if (font_size == FONT_SIZE_UNSPECIFIED) { + font_size = m_default_size[mode]; + } + + if ((font_size == m_lastSize) && (mode == m_lastMode)) { + return m_lastFont; + } + + if (m_font_cache[mode].find(font_size) == m_font_cache[mode].end()) { + initFont(font_size, mode); + } + + if (m_font_cache[mode].find(font_size) == m_font_cache[mode].end()) { + return NULL; + } + + m_lastSize = font_size; + m_lastMode = mode; + m_lastFont = m_font_cache[mode][font_size]; + + return m_font_cache[mode][font_size]; +} + +/******************************************************************************/ +unsigned int FontEngine::getTextHeight(unsigned int font_size, FontMode mode) +{ + irr::gui::IGUIFont* font = getFont(font_size, mode); + + // use current skin font as fallback + if (font == NULL) { + font = m_env->getSkin()->getFont(); + } + FATAL_ERROR_IF(font == NULL, "Could not get skin font"); + + return font->getDimension(L"Some unimportant example String").Height; +} + +/******************************************************************************/ +unsigned int FontEngine::getTextWidth(const std::wstring& text, + unsigned int font_size, FontMode mode) +{ + irr::gui::IGUIFont* font = getFont(font_size, mode); + + // use current skin font as fallback + if (font == NULL) { + font = m_env->getSkin()->getFont(); + } + FATAL_ERROR_IF(font == NULL, "Could not get font"); + + return font->getDimension(text.c_str()).Width; +} + + +/** get line height for a specific font (including empty room between lines) */ +unsigned int FontEngine::getLineHeight(unsigned int font_size, FontMode mode) +{ + irr::gui::IGUIFont* font = getFont(font_size, mode); + + // use current skin font as fallback + if (font == NULL) { + font = m_env->getSkin()->getFont(); + } + FATAL_ERROR_IF(font == NULL, "Could not get font"); + + return font->getDimension(L"Some unimportant example String").Height + + font->getKerningHeight(); +} + +/******************************************************************************/ +unsigned int FontEngine::getDefaultFontSize() +{ + return m_default_size[m_currentMode]; +} + +/******************************************************************************/ +void FontEngine::readSettings() +{ +#if USE_FREETYPE + if (g_settings->getBool("freetype")) { + m_default_size[FM_Standard] = m_settings->getU16("font_size"); + m_default_size[FM_Fallback] = m_settings->getU16("fallback_font_size"); + m_default_size[FM_Mono] = m_settings->getU16("mono_font_size"); + + if (is_yes(gettext("needs_fallback_font"))) { + m_currentMode = FM_Fallback; + } + else { + m_currentMode = FM_Standard; + } + } +#endif + m_default_size[FM_Simple] = m_settings->getU16("font_size"); + m_default_size[FM_SimpleMono] = m_settings->getU16("mono_font_size"); + + cleanCache(); + updateFontCache(); + updateSkin(); +} + +/******************************************************************************/ +void FontEngine::updateSkin() +{ + gui::IGUIFont *font = getFont(); + + if (font) + m_env->getSkin()->setFont(font); + else + errorstream << "FontEngine: Default font file: " << + "\n\t\"" << m_settings->get("font_path") << "\"" << + "\n\trequired for current screen configuration was not found" << + " or was invalid file format." << + "\n\tUsing irrlicht default font." << std::endl; + + // If we did fail to create a font our own make irrlicht find a default one + font = m_env->getSkin()->getFont(); + FATAL_ERROR_IF(font == NULL, "Could not create/get font"); + + u32 text_height = font->getDimension(L"Hello, world!").Height; + infostream << "text_height=" << text_height << std::endl; +} + +/******************************************************************************/ +void FontEngine::updateFontCache() +{ + /* the only font to be initialized is default one, + * all others are re-initialized on demand */ + initFont(m_default_size[m_currentMode], m_currentMode); + + /* reset font quick access */ + m_lastMode = FM_Unspecified; + m_lastSize = 0; + m_lastFont = NULL; +} + +/******************************************************************************/ +void FontEngine::initFont(unsigned int basesize, FontMode mode) +{ + + std::string font_config_prefix; + + if (mode == FM_Unspecified) { + mode = m_currentMode; + } + + switch (mode) { + + case FM_Standard: + font_config_prefix = ""; + break; + + case FM_Fallback: + font_config_prefix = "fallback_"; + break; + + case FM_Mono: + font_config_prefix = "mono_"; + if (m_currentMode == FM_Simple) + mode = FM_SimpleMono; + break; + + case FM_Simple: /* Fallthrough */ + case FM_SimpleMono: /* Fallthrough */ + default: + font_config_prefix = ""; + + } + + if (m_font_cache[mode].find(basesize) != m_font_cache[mode].end()) + return; + + if ((mode == FM_Simple) || (mode == FM_SimpleMono)) { + initSimpleFont(basesize, mode); + return; + } +#if USE_FREETYPE + else { + if (!is_yes(m_settings->get("freetype"))) { + return; + } + u32 size = std::floor(RenderingEngine::getDisplayDensity() * + m_settings->getFloat("gui_scaling") * basesize); + u32 font_shadow = 0; + u32 font_shadow_alpha = 0; + + try { + font_shadow = + g_settings->getU16(font_config_prefix + "font_shadow"); + } catch (SettingNotFoundException&) {} + try { + font_shadow_alpha = + g_settings->getU16(font_config_prefix + "font_shadow_alpha"); + } catch (SettingNotFoundException&) {} + + std::string font_path = g_settings->get(font_config_prefix + "font_path"); + + irr::gui::IGUIFont* font = gui::CGUITTFont::createTTFont(m_env, + font_path.c_str(), size, true, true, font_shadow, + font_shadow_alpha); + + if (font) { + m_font_cache[mode][basesize] = font; + return; + } + + if (font_config_prefix == "mono_") { + const std::string &mono_font_path = m_settings->getDefault("mono_font_path"); + + if (font_path != mono_font_path) { + // try original mono font + errorstream << "FontEngine: failed to load custom mono " + "font: " << font_path << ", trying to fall back to " + "original mono font" << std::endl; + + font = gui::CGUITTFont::createTTFont(m_env, + mono_font_path.c_str(), size, true, true, + font_shadow, font_shadow_alpha); + + if (font) { + m_font_cache[mode][basesize] = font; + return; + } + } + } else { + // try fallback font + errorstream << "FontEngine: failed to load: " << font_path << + ", trying to fall back to fallback font" << std::endl; + + font_path = g_settings->get(font_config_prefix + "fallback_font_path"); + + font = gui::CGUITTFont::createTTFont(m_env, + font_path.c_str(), size, true, true, font_shadow, + font_shadow_alpha); + + if (font) { + m_font_cache[mode][basesize] = font; + return; + } + + const std::string &fallback_font_path = m_settings->getDefault("fallback_font_path"); + + if (font_path != fallback_font_path) { + // try original fallback font + errorstream << "FontEngine: failed to load custom fallback " + "font: " << font_path << ", trying to fall back to " + "original fallback font" << std::endl; + + font = gui::CGUITTFont::createTTFont(m_env, + fallback_font_path.c_str(), size, true, true, + font_shadow, font_shadow_alpha); + + if (font) { + m_font_cache[mode][basesize] = font; + return; + } + } + } + + // give up + errorstream << "FontEngine: failed to load freetype font: " + << font_path << std::endl; + errorstream << "minetest can not continue without a valid font. " + "Please correct the 'font_path' setting or install the font " + "file in the proper location" << std::endl; + abort(); + } +#endif +} + +/** initialize a font without freetype */ +void FontEngine::initSimpleFont(unsigned int basesize, FontMode mode) +{ + assert(mode == FM_Simple || mode == FM_SimpleMono); // pre-condition + + std::string font_path; + if (mode == FM_Simple) { + font_path = m_settings->get("font_path"); + } else { + font_path = m_settings->get("mono_font_path"); + } + std::string basename = font_path; + std::string ending = font_path.substr(font_path.length() -4); + + if (ending == ".ttf") { + errorstream << "FontEngine: Not trying to open \"" << font_path + << "\" which seems to be a truetype font." << std::endl; + return; + } + + if ((ending == ".xml") || (ending == ".png")) { + basename = font_path.substr(0,font_path.length()-4); + } + + if (basesize == FONT_SIZE_UNSPECIFIED) + basesize = DEFAULT_FONT_SIZE; + + u32 size = std::floor( + RenderingEngine::getDisplayDensity() * + m_settings->getFloat("gui_scaling") * + basesize); + + irr::gui::IGUIFont* font = NULL; + + for(unsigned int offset = 0; offset < MAX_FONT_SIZE_OFFSET; offset++) { + + // try opening positive offset + std::stringstream fontsize_plus_png; + fontsize_plus_png << basename << "_" << (size + offset) << ".png"; + + if (fs::PathExists(fontsize_plus_png.str())) { + font = m_env->getFont(fontsize_plus_png.str().c_str()); + + if (font) { + verbosestream << "FontEngine: found font: " << fontsize_plus_png.str() << std::endl; + break; + } + } + + std::stringstream fontsize_plus_xml; + fontsize_plus_xml << basename << "_" << (size + offset) << ".xml"; + + if (fs::PathExists(fontsize_plus_xml.str())) { + font = m_env->getFont(fontsize_plus_xml.str().c_str()); + + if (font) { + verbosestream << "FontEngine: found font: " << fontsize_plus_xml.str() << std::endl; + break; + } + } + + // try negative offset + std::stringstream fontsize_minus_png; + fontsize_minus_png << basename << "_" << (size - offset) << ".png"; + + if (fs::PathExists(fontsize_minus_png.str())) { + font = m_env->getFont(fontsize_minus_png.str().c_str()); + + if (font) { + verbosestream << "FontEngine: found font: " << fontsize_minus_png.str() << std::endl; + break; + } + } + + std::stringstream fontsize_minus_xml; + fontsize_minus_xml << basename << "_" << (size - offset) << ".xml"; + + if (fs::PathExists(fontsize_minus_xml.str())) { + font = m_env->getFont(fontsize_minus_xml.str().c_str()); + + if (font) { + verbosestream << "FontEngine: found font: " << fontsize_minus_xml.str() << std::endl; + break; + } + } + } + + // try name direct + if (font == NULL) { + if (fs::PathExists(font_path)) { + font = m_env->getFont(font_path.c_str()); + if (font) + verbosestream << "FontEngine: found font: " << font_path << std::endl; + } + } + + if (font) { + font->grab(); + m_font_cache[mode][basesize] = font; + } +} diff --git a/src/client/fontengine.h b/src/client/fontengine.h new file mode 100644 index 000000000..a75618f86 --- /dev/null +++ b/src/client/fontengine.h @@ -0,0 +1,128 @@ +/* +Minetest +Copyright (C) 2010-2014 sapier + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include +#include +#include "util/basic_macros.h" +#include +#include +#include +#include "settings.h" + +#define FONT_SIZE_UNSPECIFIED 0xFFFFFFFF + +enum FontMode { + FM_Standard = 0, + FM_Mono, + FM_Fallback, + FM_Simple, + FM_SimpleMono, + FM_MaxMode, + FM_Unspecified +}; + +class FontEngine +{ +public: + + FontEngine(Settings* main_settings, gui::IGUIEnvironment* env); + + ~FontEngine(); + + /** get Font */ + irr::gui::IGUIFont* getFont(unsigned int font_size=FONT_SIZE_UNSPECIFIED, + FontMode mode=FM_Unspecified); + + /** get text height for a specific font */ + unsigned int getTextHeight(unsigned int font_size=FONT_SIZE_UNSPECIFIED, + FontMode mode=FM_Unspecified); + + /** get text width if a text for a specific font */ + unsigned int getTextWidth(const std::string& text, + unsigned int font_size=FONT_SIZE_UNSPECIFIED, + FontMode mode=FM_Unspecified) + { + return getTextWidth(utf8_to_wide(text)); + } + + /** get text width if a text for a specific font */ + unsigned int getTextWidth(const std::wstring& text, + unsigned int font_size=FONT_SIZE_UNSPECIFIED, + FontMode mode=FM_Unspecified); + + /** get line height for a specific font (including empty room between lines) */ + unsigned int getLineHeight(unsigned int font_size=FONT_SIZE_UNSPECIFIED, + FontMode mode=FM_Unspecified); + + /** get default font size */ + unsigned int getDefaultFontSize(); + + /** initialize font engine */ + void initialize(Settings* main_settings, gui::IGUIEnvironment* env); + + /** update internal parameters from settings */ + void readSettings(); + +private: + /** update content of font cache in case of a setting change made it invalid */ + void updateFontCache(); + + /** initialize a new font */ + void initFont(unsigned int basesize, FontMode mode=FM_Unspecified); + + /** initialize a font without freetype */ + void initSimpleFont(unsigned int basesize, FontMode mode); + + /** update current minetest skin with font changes */ + void updateSkin(); + + /** clean cache */ + void cleanCache(); + + /** pointer to settings for registering callbacks or reading config */ + Settings* m_settings = nullptr; + + /** pointer to irrlicht gui environment */ + gui::IGUIEnvironment* m_env = nullptr; + + /** internal storage for caching fonts of different size */ + std::map m_font_cache[FM_MaxMode]; + + /** default font size to use */ + unsigned int m_default_size[FM_MaxMode]; + + /** current font engine mode */ + FontMode m_currentMode = FM_Standard; + + /** font mode of last request */ + FontMode m_lastMode; + + /** size of last request */ + unsigned int m_lastSize = 0; + + /** last font returned */ + irr::gui::IGUIFont* m_lastFont = nullptr; + + DISABLE_CLASS_COPY(FontEngine); +}; + +/** interface to access main font engine*/ +extern FontEngine* g_fontengine; diff --git a/src/client/game.cpp b/src/client/game.cpp new file mode 100644 index 000000000..6cf6723e9 --- /dev/null +++ b/src/client/game.cpp @@ -0,0 +1,4218 @@ +/* +Minetest +Copyright (C) 2010-2013 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "game.h" + +#include +#include +#include "client/renderingengine.h" +#include "camera.h" +#include "client.h" +#include "client/clientevent.h" +#include "client/gameui.h" +#include "client/inputhandler.h" +#include "client/tile.h" // For TextureSource +#include "client/keys.h" +#include "client/joystick_controller.h" +#include "clientmap.h" +#include "clouds.h" +#include "config.h" +#include "content_cao.h" +#include "client/event_manager.h" +#include "fontengine.h" +#include "itemdef.h" +#include "log.h" +#include "filesys.h" +#include "gettext.h" +#include "gui/guiChatConsole.h" +#include "gui/guiConfirmRegistration.h" +#include "gui/guiFormSpecMenu.h" +#include "gui/guiKeyChangeMenu.h" +#include "gui/guiPasswordChange.h" +#include "gui/guiVolumeChange.h" +#include "gui/mainmenumanager.h" +#include "gui/profilergraph.h" +#include "mapblock.h" +#include "minimap.h" +#include "nodedef.h" // Needed for determining pointing to nodes +#include "nodemetadata.h" +#include "particles.h" +#include "porting.h" +#include "profiler.h" +#include "quicktune_shortcutter.h" +#include "raycast.h" +#include "server.h" +#include "settings.h" +#include "shader.h" +#include "sky.h" +#include "translation.h" +#include "util/basic_macros.h" +#include "util/directiontables.h" +#include "util/pointedthing.h" +#include "irrlicht_changes/static_text.h" +#include "version.h" +#include "script/scripting_client.h" + +#if USE_SOUND + #include "client/sound_openal.h" +#else + #include "client/sound.h" +#endif +/* + Text input system +*/ + +struct TextDestNodeMetadata : public TextDest +{ + TextDestNodeMetadata(v3s16 p, Client *client) + { + m_p = p; + m_client = client; + } + // This is deprecated I guess? -celeron55 + void gotText(const std::wstring &text) + { + std::string ntext = wide_to_utf8(text); + infostream << "Submitting 'text' field of node at (" << m_p.X << "," + << m_p.Y << "," << m_p.Z << "): " << ntext << std::endl; + StringMap fields; + fields["text"] = ntext; + m_client->sendNodemetaFields(m_p, "", fields); + } + void gotText(const StringMap &fields) + { + m_client->sendNodemetaFields(m_p, "", fields); + } + + v3s16 m_p; + Client *m_client; +}; + +struct TextDestPlayerInventory : public TextDest +{ + TextDestPlayerInventory(Client *client) + { + m_client = client; + m_formname = ""; + } + TextDestPlayerInventory(Client *client, const std::string &formname) + { + m_client = client; + m_formname = formname; + } + void gotText(const StringMap &fields) + { + m_client->sendInventoryFields(m_formname, fields); + } + + Client *m_client; +}; + +struct LocalFormspecHandler : public TextDest +{ + LocalFormspecHandler(const std::string &formname) + { + m_formname = formname; + } + + LocalFormspecHandler(const std::string &formname, Client *client): + m_client(client) + { + m_formname = formname; + } + + void gotText(const StringMap &fields) + { + if (m_formname == "MT_PAUSE_MENU") { + if (fields.find("btn_sound") != fields.end()) { + g_gamecallback->changeVolume(); + return; + } + + if (fields.find("btn_key_config") != fields.end()) { + g_gamecallback->keyConfig(); + return; + } + + if (fields.find("btn_exit_menu") != fields.end()) { + g_gamecallback->disconnect(); + return; + } + + if (fields.find("btn_exit_os") != fields.end()) { + g_gamecallback->exitToOS(); +#ifndef __ANDROID__ + RenderingEngine::get_raw_device()->closeDevice(); +#endif + return; + } + + if (fields.find("btn_change_password") != fields.end()) { + g_gamecallback->changePassword(); + return; + } + + if (fields.find("quit") != fields.end()) { + return; + } + + if (fields.find("btn_continue") != fields.end()) { + return; + } + } + + if (m_formname == "MT_DEATH_SCREEN") { + assert(m_client != 0); + m_client->sendRespawn(); + return; + } + + if (m_client && m_client->moddingEnabled()) + m_client->getScript()->on_formspec_input(m_formname, fields); + } + + Client *m_client = nullptr; +}; + +/* Form update callback */ + +class NodeMetadataFormSource: public IFormSource +{ +public: + NodeMetadataFormSource(ClientMap *map, v3s16 p): + m_map(map), + m_p(p) + { + } + const std::string &getForm() const + { + static const std::string empty_string = ""; + NodeMetadata *meta = m_map->getNodeMetadata(m_p); + + if (!meta) + return empty_string; + + return meta->getString("formspec"); + } + + virtual std::string resolveText(const std::string &str) + { + NodeMetadata *meta = m_map->getNodeMetadata(m_p); + + if (!meta) + return str; + + return meta->resolveString(str); + } + + ClientMap *m_map; + v3s16 m_p; +}; + +class PlayerInventoryFormSource: public IFormSource +{ +public: + PlayerInventoryFormSource(Client *client): + m_client(client) + { + } + + const std::string &getForm() const + { + LocalPlayer *player = m_client->getEnv().getLocalPlayer(); + return player->inventory_formspec; + } + + Client *m_client; +}; + +class NodeDugEvent: public MtEvent +{ +public: + v3s16 p; + MapNode n; + + NodeDugEvent(v3s16 p, MapNode n): + p(p), + n(n) + {} + MtEvent::Type getType() const + { + return MtEvent::NODE_DUG; + } +}; + +class SoundMaker +{ + ISoundManager *m_sound; + const NodeDefManager *m_ndef; +public: + bool makes_footstep_sound; + float m_player_step_timer; + + SimpleSoundSpec m_player_step_sound; + SimpleSoundSpec m_player_leftpunch_sound; + SimpleSoundSpec m_player_rightpunch_sound; + + SoundMaker(ISoundManager *sound, const NodeDefManager *ndef): + m_sound(sound), + m_ndef(ndef), + makes_footstep_sound(true), + m_player_step_timer(0) + { + } + + void playPlayerStep() + { + 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); + } + } + + static void viewBobbingStep(MtEvent *e, void *data) + { + SoundMaker *sm = (SoundMaker *)data; + sm->playPlayerStep(); + } + + static void playerRegainGround(MtEvent *e, void *data) + { + SoundMaker *sm = (SoundMaker *)data; + sm->playPlayerStep(); + } + + static void playerJump(MtEvent *e, void *data) + { + //SoundMaker *sm = (SoundMaker*)data; + } + + static void cameraPunchLeft(MtEvent *e, void *data) + { + SoundMaker *sm = (SoundMaker *)data; + sm->m_sound->playSound(sm->m_player_leftpunch_sound, false); + } + + static void cameraPunchRight(MtEvent *e, void *data) + { + SoundMaker *sm = (SoundMaker *)data; + sm->m_sound->playSound(sm->m_player_rightpunch_sound, false); + } + + 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); + } + + static void playerDamage(MtEvent *e, void *data) + { + SoundMaker *sm = (SoundMaker *)data; + sm->m_sound->playSound(SimpleSoundSpec("player_damage", 0.5), false); + } + + static void playerFallingDamage(MtEvent *e, void *data) + { + SoundMaker *sm = (SoundMaker *)data; + sm->m_sound->playSound(SimpleSoundSpec("player_falling_damage", 0.5), false); + } + + void registerReceiver(MtEventManager *mgr) + { + mgr->reg(MtEvent::VIEW_BOBBING_STEP, SoundMaker::viewBobbingStep, this); + mgr->reg(MtEvent::PLAYER_REGAIN_GROUND, SoundMaker::playerRegainGround, this); + mgr->reg(MtEvent::PLAYER_JUMP, SoundMaker::playerJump, this); + mgr->reg(MtEvent::CAMERA_PUNCH_LEFT, SoundMaker::cameraPunchLeft, this); + mgr->reg(MtEvent::CAMERA_PUNCH_RIGHT, SoundMaker::cameraPunchRight, this); + mgr->reg(MtEvent::NODE_DUG, SoundMaker::nodeDug, this); + mgr->reg(MtEvent::PLAYER_DAMAGE, SoundMaker::playerDamage, this); + mgr->reg(MtEvent::PLAYER_FALLING_DAMAGE, SoundMaker::playerFallingDamage, this); + } + + void step(float dtime) + { + m_player_step_timer -= dtime; + } +}; + +// Locally stored sounds don't need to be preloaded because of this +class GameOnDemandSoundFetcher: public OnDemandSoundFetcher +{ + std::set m_fetched; +private: + void paths_insert(std::set &dst_paths, + const std::string &base, + const std::string &name) + { + dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".ogg"); + dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".0.ogg"); + dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".1.ogg"); + dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".2.ogg"); + dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".3.ogg"); + dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".4.ogg"); + dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".5.ogg"); + dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".6.ogg"); + dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".7.ogg"); + dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".8.ogg"); + dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".9.ogg"); + } +public: + void fetchSounds(const std::string &name, + std::set &dst_paths, + std::set &dst_datas) + { + if (m_fetched.count(name)) + return; + + m_fetched.insert(name); + + paths_insert(dst_paths, porting::path_share, name); + paths_insert(dst_paths, porting::path_user, name); + } +}; + + +// before 1.8 there isn't a "integer interface", only float +#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8) +typedef f32 SamplerLayer_t; +#else +typedef s32 SamplerLayer_t; +#endif + + +class GameGlobalShaderConstantSetter : public IShaderConstantSetter +{ + Sky *m_sky; + bool *m_force_fog_off; + f32 *m_fog_range; + bool m_fog_enabled; + CachedPixelShaderSetting m_sky_bg_color; + CachedPixelShaderSetting m_fog_distance; + CachedVertexShaderSetting m_animation_timer_vertex; + CachedPixelShaderSetting m_animation_timer_pixel; + CachedPixelShaderSetting m_day_light; + CachedPixelShaderSetting m_eye_position_pixel; + CachedVertexShaderSetting m_eye_position_vertex; + CachedPixelShaderSetting m_minimap_yaw; + CachedPixelShaderSetting m_base_texture; + CachedPixelShaderSetting m_normal_texture; + CachedPixelShaderSetting m_texture_flags; + Client *m_client; + +public: + void onSettingsChange(const std::string &name) + { + if (name == "enable_fog") + m_fog_enabled = g_settings->getBool("enable_fog"); + } + + static void settingsCallback(const std::string &name, void *userdata) + { + reinterpret_cast(userdata)->onSettingsChange(name); + } + + void setSky(Sky *sky) { m_sky = sky; } + + GameGlobalShaderConstantSetter(Sky *sky, bool *force_fog_off, + f32 *fog_range, Client *client) : + m_sky(sky), + m_force_fog_off(force_fog_off), + m_fog_range(fog_range), + m_sky_bg_color("skyBgColor"), + m_fog_distance("fogDistance"), + m_animation_timer_vertex("animationTimer"), + m_animation_timer_pixel("animationTimer"), + m_day_light("dayLight"), + m_eye_position_pixel("eyePosition"), + m_eye_position_vertex("eyePosition"), + m_minimap_yaw("yawVec"), + m_base_texture("baseTexture"), + m_normal_texture("normalTexture"), + m_texture_flags("textureFlags"), + m_client(client) + { + g_settings->registerChangedCallback("enable_fog", settingsCallback, this); + m_fog_enabled = g_settings->getBool("enable_fog"); + } + + ~GameGlobalShaderConstantSetter() + { + g_settings->deregisterChangedCallback("enable_fog", settingsCallback, this); + } + + virtual void onSetConstants(video::IMaterialRendererServices *services, + bool is_highlevel) + { + if (!is_highlevel) + return; + + // Background color + video::SColor bgcolor = m_sky->getBgColor(); + video::SColorf bgcolorf(bgcolor); + float bgcolorfa[4] = { + bgcolorf.r, + bgcolorf.g, + bgcolorf.b, + bgcolorf.a, + }; + m_sky_bg_color.set(bgcolorfa, services); + + // Fog distance + float fog_distance = 10000 * BS; + + if (m_fog_enabled && !*m_force_fog_off) + fog_distance = *m_fog_range; + + m_fog_distance.set(&fog_distance, services); + + u32 daynight_ratio = (float)m_client->getEnv().getDayNightRatio(); + video::SColorf sunlight; + get_sunlight_color(&sunlight, daynight_ratio); + float dnc[3] = { + sunlight.r, + sunlight.g, + sunlight.b }; + m_day_light.set(dnc, services); + + u32 animation_timer = porting::getTimeMs() % 100000; + float animation_timer_f = (float)animation_timer / 100000.f; + m_animation_timer_vertex.set(&animation_timer_f, services); + m_animation_timer_pixel.set(&animation_timer_f, services); + + float eye_position_array[3]; + v3f epos = m_client->getEnv().getLocalPlayer()->getEyePosition(); +#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8) + eye_position_array[0] = epos.X; + eye_position_array[1] = epos.Y; + eye_position_array[2] = epos.Z; +#else + epos.getAs3Values(eye_position_array); +#endif + m_eye_position_pixel.set(eye_position_array, services); + m_eye_position_vertex.set(eye_position_array, services); + + if (m_client->getMinimap()) { + float minimap_yaw_array[3]; + v3f minimap_yaw = m_client->getMinimap()->getYawVec(); +#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8) + minimap_yaw_array[0] = minimap_yaw.X; + minimap_yaw_array[1] = minimap_yaw.Y; + minimap_yaw_array[2] = minimap_yaw.Z; +#else + minimap_yaw.getAs3Values(minimap_yaw_array); +#endif + m_minimap_yaw.set(minimap_yaw_array, services); + } + + 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); + } +}; + + +class GameGlobalShaderConstantSetterFactory : public IShaderConstantSetterFactory +{ + Sky *m_sky; + bool *m_force_fog_off; + f32 *m_fog_range; + Client *m_client; + std::vector created_nosky; +public: + GameGlobalShaderConstantSetterFactory(bool *force_fog_off, + f32 *fog_range, Client *client) : + m_sky(NULL), + m_force_fog_off(force_fog_off), + m_fog_range(fog_range), + m_client(client) + {} + + void setSky(Sky *sky) { + m_sky = sky; + for (GameGlobalShaderConstantSetter *ggscs : created_nosky) { + ggscs->setSky(m_sky); + } + created_nosky.clear(); + } + + virtual IShaderConstantSetter* create() + { + GameGlobalShaderConstantSetter *scs = new GameGlobalShaderConstantSetter( + m_sky, m_force_fog_off, m_fog_range, m_client); + if (!m_sky) + created_nosky.push_back(scs); + return scs; + } +}; + +#ifdef __ANDROID__ +#define SIZE_TAG "size[11,5.5]" +#else +#define SIZE_TAG "size[11,5.5,true]" // Fixed size on desktop +#endif + +/**************************************************************************** + + ****************************************************************************/ + +const float object_hit_delay = 0.2; + +struct FpsControl { + u32 last_time, busy_time, sleep_time; +}; + + +/* The reason the following structs are not anonymous structs within the + * class is that they are not used by the majority of member functions and + * many functions that do require objects of thse types do not modify them + * (so they can be passed as a const qualified parameter) + */ + +struct GameRunData { + u16 dig_index; + u16 new_playeritem; + PointedThing pointed_old; + bool digging; + bool ldown_for_dig; + bool dig_instantly; + bool digging_blocked; + bool left_punch; + bool update_wielded_item_trigger; + bool reset_jump_timer; + float nodig_delay_timer; + float dig_time; + float dig_time_complete; + float repeat_rightclick_timer; + float object_hit_delay_timer; + float time_from_last_punch; + ClientActiveObject *selected_object; + + float jump_timer; + float damage_flash; + float update_draw_list_timer; + + f32 fog_range; + + v3f update_draw_list_last_cam_dir; + + float time_of_day_smooth; +}; + +class Game; + +struct ClientEventHandler +{ + void (Game::*handler)(ClientEvent *, CameraOrientation *); +}; + +/**************************************************************************** + THE GAME + ****************************************************************************/ + +/* This is not intended to be a public class. If a public class becomes + * desirable then it may be better to create another 'wrapper' class that + * hides most of the stuff in this class (nothing in this class is required + * by any other file) but exposes the public methods/data only. + */ +class Game { +public: + Game(); + ~Game(); + + bool startup(bool *kill, + bool random_input, + InputHandler *input, + const std::string &map_dir, + const std::string &playername, + const std::string &password, + // If address is "", local server is used and address is updated + std::string *address, + u16 port, + std::string &error_message, + bool *reconnect, + ChatBackend *chat_backend, + const SubgameSpec &gamespec, // Used for local game + bool simple_singleplayer_mode); + + void run(); + void shutdown(); + +protected: + + void extendedResourceCleanup(); + + // Basic initialisation + bool init(const std::string &map_dir, std::string *address, + u16 port, + const SubgameSpec &gamespec); + bool initSound(); + bool createSingleplayerServer(const std::string &map_dir, + const SubgameSpec &gamespec, u16 port, std::string *address); + + // Client creation + bool createClient(const std::string &playername, + const std::string &password, std::string *address, u16 port); + bool initGui(); + + // Client connection + bool connectToServer(const std::string &playername, + const std::string &password, std::string *address, u16 port, + bool *connect_ok, bool *aborted); + bool getServerContent(bool *aborted); + + // Main loop + + void updateInteractTimers(f32 dtime); + bool checkConnection(); + bool handleCallbacks(); + void processQueues(); + void updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime); + void addProfilerGraphs(const RunStats &stats, const FpsControl &draw_times, f32 dtime); + void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime); + + // Input related + void processUserInput(f32 dtime); + void processKeyInput(); + void processItemSelection(u16 *new_playeritem); + + void dropSelectedItem(bool single_item = false); + void openInventory(); + void openConsole(float scale, const wchar_t *line=NULL); + void toggleFreeMove(); + void toggleFreeMoveAlt(); + void toggleFast(); + void toggleNoClip(); + void toggleCinematic(); + void toggleAutoforward(); + + void toggleMinimap(bool shift_pressed); + void toggleFog(); + void toggleDebug(); + void toggleUpdateCamera(); + + void increaseViewRange(); + void decreaseViewRange(); + void toggleFullViewRange(); + void checkZoomEnabled(); + + void updateCameraDirection(CameraOrientation *cam, float dtime); + void updateCameraOrientation(CameraOrientation *cam, float dtime); + void updatePlayerControl(const CameraOrientation &cam); + void step(f32 *dtime); + void processClientEvents(CameraOrientation *cam); + void updateCamera(u32 busy_time, f32 dtime); + void updateSound(f32 dtime); + void processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug); + /*! + * Returns the object or node the player is pointing at. + * Also updates the selected thing in the Hud. + * + * @param[in] shootline the shootline, starting from + * the camera position. This also gives the maximal distance + * of the search. + * @param[in] liquids_pointable if false, liquids are ignored + * @param[in] look_for_object if false, objects are ignored + * @param[in] camera_offset offset of the camera + * @param[out] selected_object the selected object or + * NULL if not found + */ + PointedThing updatePointedThing( + const core::line3d &shootline, bool liquids_pointable, + bool look_for_object, const v3s16 &camera_offset); + void handlePointingAtNothing(const ItemStack &playerItem); + void handlePointingAtNode(const PointedThing &pointed, + const ItemDefinition &playeritem_def, const ItemStack &playeritem, + const ToolCapabilities &playeritem_toolcap, f32 dtime); + void handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem, + const v3f &player_position, bool show_debug); + void handleDigging(const PointedThing &pointed, const v3s16 &nodepos, + const ToolCapabilities &playeritem_toolcap, f32 dtime); + void updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, + const CameraOrientation &cam); + void updateProfilerGraphs(ProfilerGraph *graph); + + // Misc + void limitFps(FpsControl *fps_timings, f32 *dtime); + + void showOverlayMessage(const char *msg, float dtime, int percent, + bool draw_clouds = true); + + static void settingChangedCallback(const std::string &setting_name, void *data); + void readSettings(); + + inline bool isKeyDown(GameKeyType k) + { + return input->isKeyDown(k); + } + inline bool wasKeyDown(GameKeyType k) + { + return input->wasKeyDown(k); + } + +#ifdef __ANDROID__ + void handleAndroidChatInput(); +#endif + +private: + struct Flags { + bool force_fog_off = false; + bool disable_camera_update = false; + }; + + void showDeathFormspec(); + void showPauseMenu(); + + // ClientEvent handlers + void handleClientEvent_None(ClientEvent *event, CameraOrientation *cam); + void handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam); + void handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam); + void handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam); + void handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam); + void handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam); + void handleClientEvent_HandleParticleEvent(ClientEvent *event, + CameraOrientation *cam); + void handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam); + void handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam); + void handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam); + void handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam); + void handleClientEvent_OverrideDayNigthRatio(ClientEvent *event, + CameraOrientation *cam); + void handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam); + + void updateChat(f32 dtime, const v2u32 &screensize); + + bool nodePlacementPrediction(const ItemDefinition &playeritem_def, + const ItemStack &playeritem, const v3s16 &nodepos, const v3s16 &neighbourpos); + static const ClientEventHandler clientEventHandler[CLIENTEVENT_MAX]; + + InputHandler *input = nullptr; + + Client *client = nullptr; + Server *server = nullptr; + + IWritableTextureSource *texture_src = nullptr; + IWritableShaderSource *shader_src = nullptr; + + // When created, these will be filled with data received from the server + IWritableItemDefManager *itemdef_manager = nullptr; + NodeDefManager *nodedef_manager = nullptr; + + GameOnDemandSoundFetcher soundfetcher; // useful when testing + ISoundManager *sound = nullptr; + bool sound_is_dummy = false; + SoundMaker *soundmaker = nullptr; + + ChatBackend *chat_backend = nullptr; + + GUIFormSpecMenu *current_formspec = nullptr; + //default: "". If other than "", empty show_formspec packets will only close the formspec when the formname matches + std::string cur_formname; + + EventManager *eventmgr = nullptr; + QuicktuneShortcutter *quicktune = nullptr; + bool registration_confirmation_shown = false; + + std::unique_ptr m_game_ui; + GUIChatConsole *gui_chat_console = nullptr; // Free using ->Drop() + MapDrawControl *draw_control = nullptr; + Camera *camera = nullptr; + Clouds *clouds = nullptr; // Free using ->Drop() + Sky *sky = nullptr; // Free using ->Drop() + Inventory *local_inventory = nullptr; + Hud *hud = nullptr; + Minimap *mapper = nullptr; + + GameRunData runData; + Flags m_flags; + + /* 'cache' + This class does take ownership/responsibily for cleaning up etc of any of + these items (e.g. device) + */ + IrrlichtDevice *device; + video::IVideoDriver *driver; + scene::ISceneManager *smgr; + bool *kill; + std::string *error_message; + bool *reconnect_requested; + scene::ISceneNode *skybox; + + bool random_input; + bool simple_singleplayer_mode; + /* End 'cache' */ + + /* Pre-calculated values + */ + int crack_animation_length; + + IntervalLimiter profiler_interval; + + /* + * TODO: Local caching of settings is not optimal and should at some stage + * be updated to use a global settings object for getting thse values + * (as opposed to the this local caching). This can be addressed in + * a later release. + */ + bool m_cache_doubletap_jump; + bool m_cache_enable_clouds; + bool m_cache_enable_joysticks; + bool m_cache_enable_particles; + bool m_cache_enable_fog; + bool m_cache_enable_noclip; + bool m_cache_enable_free_move; + f32 m_cache_mouse_sensitivity; + f32 m_cache_joystick_frustum_sensitivity; + f32 m_repeat_right_click_time; + f32 m_cache_cam_smoothing; + f32 m_cache_fog_start; + + bool m_invert_mouse = false; + bool m_first_loop_after_window_activation = false; + bool m_camera_offset_changed = false; + + bool m_does_lost_focus_pause_game = false; + +#ifdef __ANDROID__ + bool m_cache_hold_aux1; + bool m_android_chat_open; +#endif +}; + +Game::Game() : + m_game_ui(new GameUI()) +{ + g_settings->registerChangedCallback("doubletap_jump", + &settingChangedCallback, this); + g_settings->registerChangedCallback("enable_clouds", + &settingChangedCallback, this); + g_settings->registerChangedCallback("doubletap_joysticks", + &settingChangedCallback, this); + g_settings->registerChangedCallback("enable_particles", + &settingChangedCallback, this); + g_settings->registerChangedCallback("enable_fog", + &settingChangedCallback, this); + g_settings->registerChangedCallback("mouse_sensitivity", + &settingChangedCallback, this); + g_settings->registerChangedCallback("joystick_frustum_sensitivity", + &settingChangedCallback, this); + g_settings->registerChangedCallback("repeat_rightclick_time", + &settingChangedCallback, this); + g_settings->registerChangedCallback("noclip", + &settingChangedCallback, this); + g_settings->registerChangedCallback("free_move", + &settingChangedCallback, this); + g_settings->registerChangedCallback("cinematic", + &settingChangedCallback, this); + g_settings->registerChangedCallback("cinematic_camera_smoothing", + &settingChangedCallback, this); + g_settings->registerChangedCallback("camera_smoothing", + &settingChangedCallback, this); + + readSettings(); + +#ifdef __ANDROID__ + m_cache_hold_aux1 = false; // This is initialised properly later +#endif + +} + + + +/**************************************************************************** + MinetestApp Public + ****************************************************************************/ + +Game::~Game() +{ + delete client; + delete soundmaker; + if (!sound_is_dummy) + delete sound; + + delete server; // deleted first to stop all server threads + + delete hud; + delete local_inventory; + delete camera; + delete quicktune; + delete eventmgr; + delete texture_src; + delete shader_src; + delete nodedef_manager; + delete itemdef_manager; + delete draw_control; + + extendedResourceCleanup(); + + g_settings->deregisterChangedCallback("doubletap_jump", + &settingChangedCallback, this); + g_settings->deregisterChangedCallback("enable_clouds", + &settingChangedCallback, this); + g_settings->deregisterChangedCallback("enable_particles", + &settingChangedCallback, this); + g_settings->deregisterChangedCallback("enable_fog", + &settingChangedCallback, this); + g_settings->deregisterChangedCallback("mouse_sensitivity", + &settingChangedCallback, this); + g_settings->deregisterChangedCallback("repeat_rightclick_time", + &settingChangedCallback, this); + g_settings->deregisterChangedCallback("noclip", + &settingChangedCallback, this); + g_settings->deregisterChangedCallback("free_move", + &settingChangedCallback, this); + g_settings->deregisterChangedCallback("cinematic", + &settingChangedCallback, this); + g_settings->deregisterChangedCallback("cinematic_camera_smoothing", + &settingChangedCallback, this); + g_settings->deregisterChangedCallback("camera_smoothing", + &settingChangedCallback, this); +} + +bool Game::startup(bool *kill, + bool random_input, + InputHandler *input, + const std::string &map_dir, + const std::string &playername, + const std::string &password, + std::string *address, // can change if simple_singleplayer_mode + u16 port, + std::string &error_message, + bool *reconnect, + ChatBackend *chat_backend, + const SubgameSpec &gamespec, + bool simple_singleplayer_mode) +{ + // "cache" + this->device = RenderingEngine::get_raw_device(); + this->kill = kill; + this->error_message = &error_message; + this->reconnect_requested = reconnect; + this->random_input = random_input; + this->input = input; + this->chat_backend = chat_backend; + this->simple_singleplayer_mode = simple_singleplayer_mode; + + input->keycache.populate(); + + driver = device->getVideoDriver(); + smgr = RenderingEngine::get_scene_manager(); + + RenderingEngine::get_scene_manager()->getParameters()-> + setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true); + + // Reinit runData + runData = GameRunData(); + runData.time_from_last_punch = 10.0; + runData.update_wielded_item_trigger = true; + + m_game_ui->initFlags(); + + m_invert_mouse = g_settings->getBool("invert_mouse"); + m_first_loop_after_window_activation = true; + + g_translations->clear(); + + if (!init(map_dir, address, port, gamespec)) + return false; + + if (!createClient(playername, password, address, port)) + return false; + + RenderingEngine::initialize(client, hud); + + return true; +} + + +void Game::run() +{ + ProfilerGraph graph; + RunStats stats = { 0 }; + CameraOrientation cam_view_target = { 0 }; + CameraOrientation cam_view = { 0 }; + FpsControl draw_times = { 0 }; + f32 dtime; // in seconds + + /* Clear the profiler */ + Profiler::GraphValues dummyvalues; + g_profiler->graphGet(dummyvalues); + + draw_times.last_time = RenderingEngine::get_timer_time(); + + set_light_table(g_settings->getFloat("display_gamma")); + +#ifdef __ANDROID__ + m_cache_hold_aux1 = g_settings->getBool("fast_move") + && client->checkPrivilege("fast"); +#endif + + irr::core::dimension2d previous_screen_size(g_settings->getU16("screen_w"), + g_settings->getU16("screen_h")); + + while (RenderingEngine::run() + && !(*kill || g_gamecallback->shutdown_requested + || (server && server->isShutdownRequested()))) { + + const irr::core::dimension2d ¤t_screen_size = + RenderingEngine::get_video_driver()->getScreenSize(); + // Verify if window size has changed and save it if it's the case + // Ensure evaluating settings->getBool after verifying screensize + // First condition is cheaper + if (previous_screen_size != current_screen_size && + current_screen_size != irr::core::dimension2d(0,0) && + g_settings->getBool("autosave_screensize")) { + g_settings->setU16("screen_w", current_screen_size.Width); + g_settings->setU16("screen_h", current_screen_size.Height); + previous_screen_size = current_screen_size; + } + + /* Must be called immediately after a device->run() call because it + * uses device->getTimer()->getTime() + */ + limitFps(&draw_times, &dtime); + + updateStats(&stats, draw_times, dtime); + updateInteractTimers(dtime); + + if (!checkConnection()) + break; + if (!handleCallbacks()) + break; + + processQueues(); + + m_game_ui->clearInfoText(); + hud->resizeHotbar(); + + updateProfilers(stats, draw_times, dtime); + processUserInput(dtime); + // Update camera before player movement to avoid camera lag of one frame + updateCameraDirection(&cam_view_target, dtime); + cam_view.camera_yaw += (cam_view_target.camera_yaw - + cam_view.camera_yaw) * m_cache_cam_smoothing; + cam_view.camera_pitch += (cam_view_target.camera_pitch - + cam_view.camera_pitch) * m_cache_cam_smoothing; + updatePlayerControl(cam_view); + step(&dtime); + processClientEvents(&cam_view_target); + updateCamera(draw_times.busy_time, dtime); + updateSound(dtime); + processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud, + m_game_ui->m_flags.show_debug); + updateFrame(&graph, &stats, dtime, cam_view); + updateProfilerGraphs(&graph); + + // Update if minimap has been disabled by the server + m_game_ui->m_flags.show_minimap &= client->shouldShowMinimap(); + + if (m_does_lost_focus_pause_game && !device->isWindowFocused() && !isMenuActive()) { + showPauseMenu(); + } + } +} + + +void Game::shutdown() +{ + RenderingEngine::finalize(); +#if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 8 + if (g_settings->get("3d_mode") == "pageflip") { + driver->setRenderTarget(irr::video::ERT_STEREO_BOTH_BUFFERS); + } +#endif + if (current_formspec) + current_formspec->quitMenu(); + + showOverlayMessage("Shutting down...", 0, 0, false); + + if (clouds) + clouds->drop(); + + if (gui_chat_console) + gui_chat_console->drop(); + + if (sky) + sky->drop(); + + /* cleanup menus */ + while (g_menumgr.menuCount() > 0) { + g_menumgr.m_stack.front()->setVisible(false); + g_menumgr.deletingMenu(g_menumgr.m_stack.front()); + } + + if (current_formspec) { + current_formspec->drop(); + current_formspec = NULL; + } + + chat_backend->addMessage(L"", L"# Disconnected."); + chat_backend->addMessage(L"", L""); + + if (client) { + client->Stop(); + while (!client->isShutdown()) { + assert(texture_src != NULL); + assert(shader_src != NULL); + texture_src->processQueue(); + shader_src->processQueue(); + sleep_ms(100); + } + } +} + + +/****************************************************************************/ +/**************************************************************************** + Startup + ****************************************************************************/ +/****************************************************************************/ + +bool Game::init( + const std::string &map_dir, + std::string *address, + u16 port, + const SubgameSpec &gamespec) +{ + texture_src = createTextureSource(); + + showOverlayMessage("Loading...", 0, 0); + + shader_src = createShaderSource(); + + itemdef_manager = createItemDefManager(); + nodedef_manager = createNodeDefManager(); + + eventmgr = new EventManager(); + quicktune = new QuicktuneShortcutter(); + + if (!(texture_src && shader_src && itemdef_manager && nodedef_manager + && eventmgr && quicktune)) + return false; + + if (!initSound()) + return false; + + // Create a server if not connecting to an existing one + if (address->empty()) { + if (!createSingleplayerServer(map_dir, gamespec, port, address)) + return false; + } + + return true; +} + +bool Game::initSound() +{ +#if USE_SOUND + if (g_settings->getBool("enable_sound")) { + infostream << "Attempting to use OpenAL audio" << std::endl; + sound = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher); + if (!sound) + infostream << "Failed to initialize OpenAL audio" << std::endl; + } else + infostream << "Sound disabled." << std::endl; +#endif + + if (!sound) { + infostream << "Using dummy audio." << std::endl; + sound = &dummySoundManager; + sound_is_dummy = true; + } + + soundmaker = new SoundMaker(sound, nodedef_manager); + if (!soundmaker) + return false; + + soundmaker->registerReceiver(eventmgr); + + return true; +} + +bool Game::createSingleplayerServer(const std::string &map_dir, + const SubgameSpec &gamespec, u16 port, std::string *address) +{ + showOverlayMessage("Creating server...", 0, 5); + + std::string bind_str = g_settings->get("bind_address"); + Address bind_addr(0, 0, 0, 0, port); + + if (g_settings->getBool("ipv6_server")) { + bind_addr.setAddress((IPv6AddressBytes *) NULL); + } + + try { + bind_addr.Resolve(bind_str.c_str()); + } catch (ResolveError &e) { + infostream << "Resolving bind address \"" << bind_str + << "\" failed: " << e.what() + << " -- Listening on all addresses." << std::endl; + } + + if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) { + *error_message = "Unable to listen on " + + bind_addr.serializeString() + + " because IPv6 is disabled"; + errorstream << *error_message << std::endl; + return false; + } + + server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr, false); + server->init(); + server->start(); + + return true; +} + +bool Game::createClient(const std::string &playername, + const std::string &password, std::string *address, u16 port) +{ + showOverlayMessage("Creating client...", 0, 10); + + draw_control = new MapDrawControl; + if (!draw_control) + return false; + + bool could_connect, connect_aborted; +#ifdef HAVE_TOUCHSCREENGUI + if (g_touchscreengui) { + g_touchscreengui->init(texture_src); + g_touchscreengui->hide(); + } +#endif + if (!connectToServer(playername, password, address, port, + &could_connect, &connect_aborted)) + return false; + + if (!could_connect) { + if (error_message->empty() && !connect_aborted) { + // Should not happen if error messages are set properly + *error_message = "Connection failed for unknown reason"; + errorstream << *error_message << std::endl; + } + return false; + } + + if (!getServerContent(&connect_aborted)) { + if (error_message->empty() && !connect_aborted) { + // Should not happen if error messages are set properly + *error_message = "Connection failed for unknown reason"; + errorstream << *error_message << std::endl; + } + return false; + } + + GameGlobalShaderConstantSetterFactory *scsf = new GameGlobalShaderConstantSetterFactory( + &m_flags.force_fog_off, &runData.fog_range, client); + shader_src->addShaderConstantSetterFactory(scsf); + + // Update cached textures, meshes and materials + client->afterContentReceived(); + + /* Camera + */ + camera = new Camera(*draw_control, client); + if (!camera || !camera->successfullyCreated(*error_message)) + return false; + client->setCamera(camera); + + /* Clouds + */ + if (m_cache_enable_clouds) { + clouds = new Clouds(smgr, -1, time(0)); + if (!clouds) { + *error_message = "Memory allocation error (clouds)"; + errorstream << *error_message << std::endl; + return false; + } + } + + /* Skybox + */ + sky = new Sky(-1, texture_src); + scsf->setSky(sky); + skybox = NULL; // This is used/set later on in the main run loop + + local_inventory = new Inventory(itemdef_manager); + + if (!(sky && local_inventory)) { + *error_message = "Memory allocation error (sky or local inventory)"; + errorstream << *error_message << std::endl; + return false; + } + + /* Pre-calculated values + */ + video::ITexture *t = texture_src->getTexture("crack_anylength.png"); + if (t) { + v2u32 size = t->getOriginalSize(); + crack_animation_length = size.Y / size.X; + } else { + crack_animation_length = 5; + } + + if (!initGui()) + return false; + + /* Set window caption + */ + std::wstring str = utf8_to_wide(PROJECT_NAME_C); + str += L" "; + str += utf8_to_wide(g_version_hash); + str += L" ["; + str += driver->getName(); + str += L"]"; + device->setWindowCaption(str.c_str()); + + LocalPlayer *player = client->getEnv().getLocalPlayer(); + player->hurt_tilt_timer = 0; + player->hurt_tilt_strength = 0; + + hud = new Hud(guienv, client, player, local_inventory); + + if (!hud) { + *error_message = "Memory error: could not create HUD"; + errorstream << *error_message << std::endl; + return false; + } + + mapper = client->getMinimap(); + if (mapper) + mapper->setMinimapMode(MINIMAP_MODE_OFF); + + return true; +} + +bool Game::initGui() +{ + m_game_ui->init(); + + // Remove stale "recent" chat messages from previous connections + chat_backend->clearRecentChat(); + + // Make sure the size of the recent messages buffer is right + chat_backend->applySettings(); + + // Chat backend and console + gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(), + -1, chat_backend, client, &g_menumgr); + if (!gui_chat_console) { + *error_message = "Could not allocate memory for chat console"; + errorstream << *error_message << std::endl; + return false; + } + +#ifdef HAVE_TOUCHSCREENGUI + + if (g_touchscreengui) + g_touchscreengui->show(); + +#endif + + return true; +} + +bool Game::connectToServer(const std::string &playername, + const std::string &password, std::string *address, u16 port, + bool *connect_ok, bool *connection_aborted) +{ + *connect_ok = false; // Let's not be overly optimistic + *connection_aborted = false; + bool local_server_mode = false; + + showOverlayMessage("Resolving address...", 0, 15); + + Address connect_address(0, 0, 0, 0, port); + + try { + connect_address.Resolve(address->c_str()); + + if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY + //connect_address.Resolve("localhost"); + if (connect_address.isIPv6()) { + IPv6AddressBytes addr_bytes; + addr_bytes.bytes[15] = 1; + connect_address.setAddress(&addr_bytes); + } else { + connect_address.setAddress(127, 0, 0, 1); + } + local_server_mode = true; + } + } catch (ResolveError &e) { + *error_message = std::string("Couldn't resolve address: ") + e.what(); + errorstream << *error_message << std::endl; + return false; + } + + if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) { + *error_message = "Unable to connect to " + + connect_address.serializeString() + + " because IPv6 is disabled"; + errorstream << *error_message << std::endl; + return false; + } + + client = new Client(playername.c_str(), password, *address, + *draw_control, texture_src, shader_src, + itemdef_manager, nodedef_manager, sound, eventmgr, + connect_address.isIPv6(), m_game_ui.get()); + + if (!client) + return false; + + client->m_simple_singleplayer_mode = simple_singleplayer_mode; + + infostream << "Connecting to server at "; + connect_address.print(&infostream); + infostream << std::endl; + + client->connect(connect_address, + simple_singleplayer_mode || local_server_mode); + + /* + Wait for server to accept connection + */ + + try { + input->clear(); + + FpsControl fps_control = { 0 }; + f32 dtime; + f32 wait_time = 0; // in seconds + + fps_control.last_time = RenderingEngine::get_timer_time(); + + while (RenderingEngine::run()) { + + limitFps(&fps_control, &dtime); + + // Update client and server + client->step(dtime); + + if (server != NULL) + server->step(dtime); + + // End condition + if (client->getState() == LC_Init) { + *connect_ok = true; + break; + } + + // Break conditions + if (*connection_aborted) + break; + + if (client->accessDenied()) { + *error_message = "Access denied. Reason: " + + client->accessDeniedReason(); + *reconnect_requested = client->reconnectRequested(); + errorstream << *error_message << std::endl; + break; + } + + if (input->cancelPressed()) { + *connection_aborted = true; + infostream << "Connect aborted [Escape]" << std::endl; + break; + } + + if (client->m_is_registration_confirmation_state) { + if (registration_confirmation_shown) { + // Keep drawing the GUI + RenderingEngine::draw_menu_scene(guienv, dtime, true); + } else { + registration_confirmation_shown = true; + (new GUIConfirmRegistration(guienv, guienv->getRootGUIElement(), -1, + &g_menumgr, client, playername, password, *address, connection_aborted))->drop(); + } + } else { + wait_time += dtime; + // Only time out if we aren't waiting for the server we started + if (!address->empty() && wait_time > 10) { + *error_message = "Connection timed out."; + errorstream << *error_message << std::endl; + break; + } + + // Update status + showOverlayMessage("Connecting to server...", dtime, 20); + } + } + } catch (con::PeerNotFoundException &e) { + // TODO: Should something be done here? At least an info/error + // message? + return false; + } + + return true; +} + +bool Game::getServerContent(bool *aborted) +{ + input->clear(); + + FpsControl fps_control = { 0 }; + f32 dtime; // in seconds + + fps_control.last_time = RenderingEngine::get_timer_time(); + + while (RenderingEngine::run()) { + + limitFps(&fps_control, &dtime); + + // Update client and server + client->step(dtime); + + if (server != NULL) + server->step(dtime); + + // End condition + if (client->mediaReceived() && client->itemdefReceived() && + client->nodedefReceived()) { + break; + } + + // Error conditions + if (!checkConnection()) + return false; + + if (client->getState() < LC_Init) { + *error_message = "Client disconnected"; + errorstream << *error_message << std::endl; + return false; + } + + if (input->cancelPressed()) { + *aborted = true; + infostream << "Connect aborted [Escape]" << std::endl; + return false; + } + + // Display status + int progress = 25; + + if (!client->itemdefReceived()) { + const wchar_t *text = wgettext("Item definitions..."); + progress = 25; + RenderingEngine::draw_load_screen(text, guienv, texture_src, + dtime, progress); + delete[] text; + } else if (!client->nodedefReceived()) { + const wchar_t *text = wgettext("Node definitions..."); + progress = 30; + RenderingEngine::draw_load_screen(text, guienv, texture_src, + dtime, progress); + delete[] text; + } else { + std::stringstream message; + std::fixed(message); + message.precision(0); + message << gettext("Media...") << " " << (client->mediaReceiveProgress()*100) << "%"; + message.precision(2); + + if ((USE_CURL == 0) || + (!g_settings->getBool("enable_remote_media_server"))) { + float cur = client->getCurRate(); + std::string cur_unit = gettext("KiB/s"); + + if (cur > 900) { + cur /= 1024.0; + cur_unit = gettext("MiB/s"); + } + + message << " (" << cur << ' ' << cur_unit << ")"; + } + + progress = 30 + client->mediaReceiveProgress() * 35 + 0.5; + RenderingEngine::draw_load_screen(utf8_to_wide(message.str()), guienv, + texture_src, dtime, progress); + } + } + + return true; +} + + +/****************************************************************************/ +/**************************************************************************** + Run + ****************************************************************************/ +/****************************************************************************/ + +inline void Game::updateInteractTimers(f32 dtime) +{ + if (runData.nodig_delay_timer >= 0) + runData.nodig_delay_timer -= dtime; + + if (runData.object_hit_delay_timer >= 0) + runData.object_hit_delay_timer -= dtime; + + runData.time_from_last_punch += dtime; +} + + +/* returns false if game should exit, otherwise true + */ +inline bool Game::checkConnection() +{ + if (client->accessDenied()) { + *error_message = "Access denied. Reason: " + + client->accessDeniedReason(); + *reconnect_requested = client->reconnectRequested(); + errorstream << *error_message << std::endl; + return false; + } + + return true; +} + + +/* returns false if game should exit, otherwise true + */ +inline bool Game::handleCallbacks() +{ + if (g_gamecallback->disconnect_requested) { + g_gamecallback->disconnect_requested = false; + return false; + } + + if (g_gamecallback->changepassword_requested) { + (new GUIPasswordChange(guienv, guiroot, -1, + &g_menumgr, client))->drop(); + g_gamecallback->changepassword_requested = false; + } + + if (g_gamecallback->changevolume_requested) { + (new GUIVolumeChange(guienv, guiroot, -1, + &g_menumgr))->drop(); + g_gamecallback->changevolume_requested = false; + } + + if (g_gamecallback->keyconfig_requested) { + (new GUIKeyChangeMenu(guienv, guiroot, -1, + &g_menumgr))->drop(); + g_gamecallback->keyconfig_requested = false; + } + + if (g_gamecallback->keyconfig_changed) { + input->keycache.populate(); // update the cache with new settings + g_gamecallback->keyconfig_changed = false; + } + + return true; +} + + +void Game::processQueues() +{ + texture_src->processQueue(); + itemdef_manager->processQueue(client); + shader_src->processQueue(); +} + + +void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime) +{ + float profiler_print_interval = + g_settings->getFloat("profiler_print_interval"); + bool print_to_log = true; + + if (profiler_print_interval == 0) { + print_to_log = false; + profiler_print_interval = 5; + } + + if (profiler_interval.step(dtime, profiler_print_interval)) { + if (print_to_log) { + infostream << "Profiler:" << std::endl; + g_profiler->print(infostream); + } + + m_game_ui->updateProfiler(); + g_profiler->clear(); + } + + addProfilerGraphs(stats, draw_times, dtime); +} + + +void Game::addProfilerGraphs(const RunStats &stats, + const FpsControl &draw_times, f32 dtime) +{ + g_profiler->graphAdd("mainloop_other", + draw_times.busy_time / 1000.0f - stats.drawtime / 1000.0f); + + if (draw_times.sleep_time != 0) + g_profiler->graphAdd("mainloop_sleep", draw_times.sleep_time / 1000.0f); + g_profiler->graphAdd("mainloop_dtime", dtime); + + g_profiler->add("Elapsed time", dtime); + g_profiler->avg("FPS", 1. / dtime); +} + + +void Game::updateStats(RunStats *stats, const FpsControl &draw_times, + f32 dtime) +{ + + f32 jitter; + Jitter *jp; + + /* Time average and jitter calculation + */ + jp = &stats->dtime_jitter; + jp->avg = jp->avg * 0.96 + dtime * 0.04; + + jitter = dtime - jp->avg; + + if (jitter > jp->max) + jp->max = jitter; + + jp->counter += dtime; + + if (jp->counter > 0.0) { + jp->counter -= 3.0; + jp->max_sample = jp->max; + jp->max_fraction = jp->max_sample / (jp->avg + 0.001); + jp->max = 0.0; + } + + /* Busytime average and jitter calculation + */ + jp = &stats->busy_time_jitter; + jp->avg = jp->avg + draw_times.busy_time * 0.02; + + jitter = draw_times.busy_time - jp->avg; + + if (jitter > jp->max) + jp->max = jitter; + if (jitter < jp->min) + jp->min = jitter; + + jp->counter += dtime; + + if (jp->counter > 0.0) { + jp->counter -= 3.0; + jp->max_sample = jp->max; + jp->min_sample = jp->min; + jp->max = 0.0; + jp->min = 0.0; + } +} + + + +/**************************************************************************** + Input handling + ****************************************************************************/ + +void Game::processUserInput(f32 dtime) +{ + // Reset input if window not active or some menu is active + if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console)) { + input->clear(); +#ifdef HAVE_TOUCHSCREENGUI + g_touchscreengui->hide(); +#endif + } +#ifdef HAVE_TOUCHSCREENGUI + else if (g_touchscreengui) { + /* on touchscreengui step may generate own input events which ain't + * what we want in case we just did clear them */ + g_touchscreengui->step(dtime); + } +#endif + + if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) { + gui_chat_console->closeConsoleAtOnce(); + } + + // Input handler step() (used by the random input generator) + input->step(dtime); + +#ifdef __ANDROID__ + if (current_formspec != NULL) + current_formspec->getAndroidUIInput(); + else + handleAndroidChatInput(); +#endif + + // Increase timer for double tap of "keymap_jump" + if (m_cache_doubletap_jump && runData.jump_timer <= 0.2f) + runData.jump_timer += dtime; + + processKeyInput(); + processItemSelection(&runData.new_playeritem); +} + + +void Game::processKeyInput() +{ + if (wasKeyDown(KeyType::DROP)) { + dropSelectedItem(isKeyDown(KeyType::SNEAK)); + } else if (wasKeyDown(KeyType::AUTOFORWARD)) { + toggleAutoforward(); + } else if (wasKeyDown(KeyType::BACKWARD)) { + if (g_settings->getBool("continuous_forward")) + toggleAutoforward(); + } else if (wasKeyDown(KeyType::INVENTORY)) { + openInventory(); + } else if (input->cancelPressed()) { + if (!gui_chat_console->isOpenInhibited()) { + showPauseMenu(); + } + } else if (wasKeyDown(KeyType::CHAT)) { + openConsole(0.2, L""); + } else if (wasKeyDown(KeyType::CMD)) { + openConsole(0.2, L"/"); + } else if (wasKeyDown(KeyType::CMD_LOCAL)) { + if (client->moddingEnabled()) + openConsole(0.2, L"."); + else + m_game_ui->showStatusText(wgettext("CSM is disabled")); + } else if (wasKeyDown(KeyType::CONSOLE)) { + openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f)); + } else if (wasKeyDown(KeyType::FREEMOVE)) { + toggleFreeMove(); + } else if (wasKeyDown(KeyType::JUMP)) { + toggleFreeMoveAlt(); + } else if (wasKeyDown(KeyType::FASTMOVE)) { + toggleFast(); + } else if (wasKeyDown(KeyType::NOCLIP)) { + toggleNoClip(); + } else if (wasKeyDown(KeyType::MUTE)) { + bool new_mute_sound = !g_settings->getBool("mute_sound"); + g_settings->setBool("mute_sound", new_mute_sound); + if (new_mute_sound) + m_game_ui->showTranslatedStatusText("Sound muted"); + else + m_game_ui->showTranslatedStatusText("Sound unmuted"); + } else if (wasKeyDown(KeyType::INC_VOLUME)) { + float new_volume = rangelim(g_settings->getFloat("sound_volume") + 0.1f, 0.0f, 1.0f); + wchar_t buf[100]; + g_settings->setFloat("sound_volume", new_volume); + const wchar_t *str = wgettext("Volume changed to %d%%"); + swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100)); + delete[] str; + m_game_ui->showStatusText(buf); + } else if (wasKeyDown(KeyType::DEC_VOLUME)) { + float new_volume = rangelim(g_settings->getFloat("sound_volume") - 0.1f, 0.0f, 1.0f); + wchar_t buf[100]; + g_settings->setFloat("sound_volume", new_volume); + const wchar_t *str = wgettext("Volume changed to %d%%"); + swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100)); + delete[] str; + m_game_ui->showStatusText(buf); + } else if (wasKeyDown(KeyType::CINEMATIC)) { + toggleCinematic(); + } else if (wasKeyDown(KeyType::SCREENSHOT)) { + client->makeScreenshot(); + } else if (wasKeyDown(KeyType::TOGGLE_HUD)) { + m_game_ui->toggleHud(); + } else if (wasKeyDown(KeyType::MINIMAP)) { + toggleMinimap(isKeyDown(KeyType::SNEAK)); + } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) { + m_game_ui->toggleChat(); + } else if (wasKeyDown(KeyType::TOGGLE_FOG)) { + toggleFog(); + } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) { + toggleUpdateCamera(); + } else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) { + toggleDebug(); + } else if (wasKeyDown(KeyType::TOGGLE_PROFILER)) { + m_game_ui->toggleProfiler(); + } else if (wasKeyDown(KeyType::INCREASE_VIEWING_RANGE)) { + increaseViewRange(); + } else if (wasKeyDown(KeyType::DECREASE_VIEWING_RANGE)) { + decreaseViewRange(); + } else if (wasKeyDown(KeyType::RANGESELECT)) { + toggleFullViewRange(); + } else if (wasKeyDown(KeyType::ZOOM)) { + checkZoomEnabled(); + } else if (wasKeyDown(KeyType::QUICKTUNE_NEXT)) { + quicktune->next(); + } else if (wasKeyDown(KeyType::QUICKTUNE_PREV)) { + quicktune->prev(); + } else if (wasKeyDown(KeyType::QUICKTUNE_INC)) { + quicktune->inc(); + } else if (wasKeyDown(KeyType::QUICKTUNE_DEC)) { + quicktune->dec(); + } + + if (!isKeyDown(KeyType::JUMP) && runData.reset_jump_timer) { + runData.reset_jump_timer = false; + runData.jump_timer = 0.0f; + } + + if (quicktune->hasMessage()) { + m_game_ui->showStatusText(utf8_to_wide(quicktune->getMessage())); + } +} + +void Game::processItemSelection(u16 *new_playeritem) +{ + LocalPlayer *player = client->getEnv().getLocalPlayer(); + + /* Item selection using mouse wheel + */ + *new_playeritem = client->getPlayerItem(); + + s32 wheel = input->getMouseWheel(); + u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1, + player->hud_hotbar_itemcount - 1); + + s32 dir = wheel; + + if (input->joystick.wasKeyDown(KeyType::SCROLL_DOWN) || + wasKeyDown(KeyType::HOTBAR_NEXT)) { + dir = -1; + } + + if (input->joystick.wasKeyDown(KeyType::SCROLL_UP) || + wasKeyDown(KeyType::HOTBAR_PREV)) { + dir = 1; + } + + if (dir < 0) + *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0; + else if (dir > 0) + *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item; + // else dir == 0 + + /* Item selection using hotbar slot keys + */ + for (u16 i = 0; i < 23; i++) { + if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) { + if (i < PLAYER_INVENTORY_SIZE && i < player->hud_hotbar_itemcount) { + *new_playeritem = i; + infostream << "Selected item: " << new_playeritem << std::endl; + } + break; + } + } +} + + +void Game::dropSelectedItem(bool single_item) +{ + IDropAction *a = new IDropAction(); + a->count = single_item ? 1 : 0; + a->from_inv.setCurrentPlayer(); + a->from_list = "main"; + a->from_i = client->getPlayerItem(); + client->inventoryAction(a); +} + + +void Game::openInventory() +{ + /* + * Don't permit to open inventory is CAO or player doesn't exists. + * This prevent showing an empty inventory at player load + */ + + LocalPlayer *player = client->getEnv().getLocalPlayer(); + if (!player || !player->getCAO()) + return; + + infostream << "the_game: " << "Launching inventory" << std::endl; + + PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client); + + InventoryLocation inventoryloc; + inventoryloc.setCurrentPlayer(); + + if (!client->moddingEnabled() + || !client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) { + TextDest *txt_dst = new TextDestPlayerInventory(client); + GUIFormSpecMenu::create(current_formspec, client, &input->joystick, fs_src, + txt_dst, client->getFormspecPrepend()); + cur_formname = ""; + current_formspec->setFormSpec(fs_src->getForm(), inventoryloc); + } +} + + +void Game::openConsole(float scale, const wchar_t *line) +{ + assert(scale > 0.0f && scale <= 1.0f); + +#ifdef __ANDROID__ + porting::showInputDialog(gettext("ok"), "", "", 2); + m_android_chat_open = true; +#else + if (gui_chat_console->isOpenInhibited()) + return; + gui_chat_console->openConsole(scale); + if (line) { + gui_chat_console->setCloseOnEnter(true); + gui_chat_console->replaceAndAddToHistory(line); + } +#endif +} + +#ifdef __ANDROID__ +void Game::handleAndroidChatInput() +{ + if (m_android_chat_open && porting::getInputDialogState() == 0) { + std::string text = porting::getInputDialogValue(); + client->typeChatMessage(utf8_to_wide(text)); + } +} +#endif + + +void Game::toggleFreeMove() +{ + bool free_move = !g_settings->getBool("free_move"); + g_settings->set("free_move", bool_to_cstr(free_move)); + + if (free_move) { + if (client->checkPrivilege("fly")) { + m_game_ui->showTranslatedStatusText("Fly mode enabled"); + } else { + m_game_ui->showTranslatedStatusText("Fly mode enabled (note: no 'fly' privilege)"); + } + } else { + m_game_ui->showTranslatedStatusText("Fly mode disabled"); + } +} + +void Game::toggleFreeMoveAlt() +{ + if (m_cache_doubletap_jump && runData.jump_timer < 0.2f) + toggleFreeMove(); + + runData.reset_jump_timer = true; +} + + +void Game::toggleFast() +{ + bool fast_move = !g_settings->getBool("fast_move"); + bool has_fast_privs = client->checkPrivilege("fast"); + g_settings->set("fast_move", bool_to_cstr(fast_move)); + + if (fast_move) { + if (has_fast_privs) { + m_game_ui->showTranslatedStatusText("Fast mode enabled"); + } else { + m_game_ui->showTranslatedStatusText("Fast mode enabled (note: no 'fast' privilege)"); + } + } else { + m_game_ui->showTranslatedStatusText("Fast mode disabled"); + } + +#ifdef __ANDROID__ + m_cache_hold_aux1 = fast_move && has_fast_privs; +#endif +} + + +void Game::toggleNoClip() +{ + bool noclip = !g_settings->getBool("noclip"); + g_settings->set("noclip", bool_to_cstr(noclip)); + + if (noclip) { + if (client->checkPrivilege("noclip")) { + m_game_ui->showTranslatedStatusText("Noclip mode enabled"); + } else { + m_game_ui->showTranslatedStatusText("Noclip mode enabled (note: no 'noclip' privilege)"); + } + } else { + m_game_ui->showTranslatedStatusText("Noclip mode disabled"); + } +} + +void Game::toggleCinematic() +{ + bool cinematic = !g_settings->getBool("cinematic"); + g_settings->set("cinematic", bool_to_cstr(cinematic)); + + if (cinematic) + m_game_ui->showTranslatedStatusText("Cinematic mode enabled"); + else + m_game_ui->showTranslatedStatusText("Cinematic mode disabled"); +} + +// Autoforward by toggling continuous forward. +void Game::toggleAutoforward() +{ + bool autorun_enabled = !g_settings->getBool("continuous_forward"); + g_settings->set("continuous_forward", bool_to_cstr(autorun_enabled)); + + if (autorun_enabled) + m_game_ui->showTranslatedStatusText("Automatic forwards enabled"); + else + m_game_ui->showTranslatedStatusText("Automatic forwards disabled"); +} + +void Game::toggleMinimap(bool shift_pressed) +{ + if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap")) + return; + + if (shift_pressed) { + mapper->toggleMinimapShape(); + return; + } + + u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags; + + MinimapMode mode = MINIMAP_MODE_OFF; + if (hud_flags & HUD_FLAG_MINIMAP_VISIBLE) { + mode = mapper->getMinimapMode(); + mode = (MinimapMode)((int)mode + 1); + // If radar is disabled and in, or switching to, radar mode + if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE) && mode > 3) + mode = MINIMAP_MODE_OFF; + } + + m_game_ui->m_flags.show_minimap = true; + switch (mode) { + case MINIMAP_MODE_SURFACEx1: + m_game_ui->showTranslatedStatusText("Minimap in surface mode, Zoom x1"); + break; + case MINIMAP_MODE_SURFACEx2: + m_game_ui->showTranslatedStatusText("Minimap in surface mode, Zoom x2"); + break; + case MINIMAP_MODE_SURFACEx4: + m_game_ui->showTranslatedStatusText("Minimap in surface mode, Zoom x4"); + break; + case MINIMAP_MODE_RADARx1: + m_game_ui->showTranslatedStatusText("Minimap in radar mode, Zoom x1"); + break; + case MINIMAP_MODE_RADARx2: + m_game_ui->showTranslatedStatusText("Minimap in radar mode, Zoom x2"); + break; + case MINIMAP_MODE_RADARx4: + m_game_ui->showTranslatedStatusText("Minimap in radar mode, Zoom x4"); + break; + default: + mode = MINIMAP_MODE_OFF; + m_game_ui->m_flags.show_minimap = false; + if (hud_flags & HUD_FLAG_MINIMAP_VISIBLE) + m_game_ui->showTranslatedStatusText("Minimap hidden"); + else + m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod"); + } + + mapper->setMinimapMode(mode); +} + +void Game::toggleFog() +{ + bool fog_enabled = g_settings->getBool("enable_fog"); + g_settings->setBool("enable_fog", !fog_enabled); + if (fog_enabled) + m_game_ui->showTranslatedStatusText("Fog disabled"); + else + m_game_ui->showTranslatedStatusText("Fog enabled"); +} + + +void Game::toggleDebug() +{ + // Initial / 4x toggle: Chat only + // 1x toggle: Debug text with chat + // 2x toggle: Debug text with profiler graph + // 3x toggle: Debug text and wireframe + if (!m_game_ui->m_flags.show_debug) { + m_game_ui->m_flags.show_debug = true; + m_game_ui->m_flags.show_profiler_graph = false; + draw_control->show_wireframe = false; + m_game_ui->showTranslatedStatusText("Debug info shown"); + } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) { + m_game_ui->m_flags.show_profiler_graph = true; + m_game_ui->showTranslatedStatusText("Profiler graph shown"); + } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) { + m_game_ui->m_flags.show_profiler_graph = false; + draw_control->show_wireframe = true; + m_game_ui->showTranslatedStatusText("Wireframe shown"); + } else { + m_game_ui->m_flags.show_debug = false; + m_game_ui->m_flags.show_profiler_graph = false; + draw_control->show_wireframe = false; + if (client->checkPrivilege("debug")) { + m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden"); + } else { + m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden"); + } + } +} + + +void Game::toggleUpdateCamera() +{ + m_flags.disable_camera_update = !m_flags.disable_camera_update; + if (m_flags.disable_camera_update) + m_game_ui->showTranslatedStatusText("Camera update disabled"); + else + m_game_ui->showTranslatedStatusText("Camera update enabled"); +} + + +void Game::increaseViewRange() +{ + s16 range = g_settings->getS16("viewing_range"); + s16 range_new = range + 10; + + wchar_t buf[255]; + const wchar_t *str; + if (range_new > 4000) { + range_new = 4000; + str = wgettext("Viewing range is at maximum: %d"); + swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new); + delete[] str; + m_game_ui->showStatusText(buf); + + } else { + str = wgettext("Viewing range changed to %d"); + swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new); + delete[] str; + m_game_ui->showStatusText(buf); + } + g_settings->set("viewing_range", itos(range_new)); +} + + +void Game::decreaseViewRange() +{ + s16 range = g_settings->getS16("viewing_range"); + s16 range_new = range - 10; + + wchar_t buf[255]; + const wchar_t *str; + if (range_new < 20) { + range_new = 20; + str = wgettext("Viewing range is at minimum: %d"); + swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new); + delete[] str; + m_game_ui->showStatusText(buf); + } else { + str = wgettext("Viewing range changed to %d"); + swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new); + delete[] str; + m_game_ui->showStatusText(buf); + } + g_settings->set("viewing_range", itos(range_new)); +} + + +void Game::toggleFullViewRange() +{ + draw_control->range_all = !draw_control->range_all; + if (draw_control->range_all) + m_game_ui->showTranslatedStatusText("Enabled unlimited viewing range"); + else + m_game_ui->showTranslatedStatusText("Disabled unlimited viewing range"); +} + + +void Game::checkZoomEnabled() +{ + LocalPlayer *player = client->getEnv().getLocalPlayer(); + if (player->getZoomFOV() < 0.001f) + m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod"); +} + + +void Game::updateCameraDirection(CameraOrientation *cam, float dtime) +{ + if ((device->isWindowActive() && device->isWindowFocused() + && !isMenuActive()) || random_input) { + +#ifndef __ANDROID__ + if (!random_input) { + // Mac OSX gets upset if this is set every frame + if (device->getCursorControl()->isVisible()) + device->getCursorControl()->setVisible(false); + } +#endif + + if (m_first_loop_after_window_activation) { + m_first_loop_after_window_activation = false; + + input->setMousePos(driver->getScreenSize().Width / 2, + driver->getScreenSize().Height / 2); + } else { + updateCameraOrientation(cam, dtime); + } + + } else { + +#ifndef ANDROID + // Mac OSX gets upset if this is set every frame + if (!device->getCursorControl()->isVisible()) + device->getCursorControl()->setVisible(true); +#endif + + m_first_loop_after_window_activation = true; + + } +} + +void Game::updateCameraOrientation(CameraOrientation *cam, float dtime) +{ +#ifdef HAVE_TOUCHSCREENGUI + if (g_touchscreengui) { + cam->camera_yaw += g_touchscreengui->getYawChange(); + cam->camera_pitch = g_touchscreengui->getPitch(); + } else { +#endif + v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2); + v2s32 dist = input->getMousePos() - center; + + if (m_invert_mouse || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) { + dist.Y = -dist.Y; + } + + cam->camera_yaw -= dist.X * m_cache_mouse_sensitivity; + cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity; + + if (dist.X != 0 || dist.Y != 0) + input->setMousePos(center.X, center.Y); +#ifdef HAVE_TOUCHSCREENGUI + } +#endif + + if (m_cache_enable_joysticks) { + f32 c = m_cache_joystick_frustum_sensitivity * (1.f / 32767.f) * dtime; + cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c; + cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c; + } + + cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5); +} + + +void Game::updatePlayerControl(const CameraOrientation &cam) +{ + //TimeTaker tt("update player control", NULL, PRECISION_NANO); + + // DO NOT use the isKeyDown method for the forward, backward, left, right + // buttons, as the code that uses the controls needs to be able to + // distinguish between the two in order to know when to use joysticks. + + PlayerControl control( + input->isKeyDown(KeyType::FORWARD), + input->isKeyDown(KeyType::BACKWARD), + input->isKeyDown(KeyType::LEFT), + input->isKeyDown(KeyType::RIGHT), + isKeyDown(KeyType::JUMP), + isKeyDown(KeyType::SPECIAL1), + isKeyDown(KeyType::SNEAK), + isKeyDown(KeyType::ZOOM), + input->getLeftState(), + input->getRightState(), + cam.camera_pitch, + cam.camera_yaw, + input->joystick.getAxisWithoutDead(JA_SIDEWARD_MOVE), + input->joystick.getAxisWithoutDead(JA_FORWARD_MOVE) + ); + + u32 keypress_bits = + ( (u32)(isKeyDown(KeyType::FORWARD) & 0x1) << 0) | + ( (u32)(isKeyDown(KeyType::BACKWARD) & 0x1) << 1) | + ( (u32)(isKeyDown(KeyType::LEFT) & 0x1) << 2) | + ( (u32)(isKeyDown(KeyType::RIGHT) & 0x1) << 3) | + ( (u32)(isKeyDown(KeyType::JUMP) & 0x1) << 4) | + ( (u32)(isKeyDown(KeyType::SPECIAL1) & 0x1) << 5) | + ( (u32)(isKeyDown(KeyType::SNEAK) & 0x1) << 6) | + ( (u32)(input->getLeftState() & 0x1) << 7) | + ( (u32)(input->getRightState() & 0x1) << 8 + ); + +#ifdef ANDROID + /* For Android, simulate holding down AUX1 (fast move) if the user has + * the fast_move setting toggled on. If there is an aux1 key defined for + * Android then its meaning is inverted (i.e. holding aux1 means walk and + * not fast) + */ + if (m_cache_hold_aux1) { + control.aux1 = control.aux1 ^ true; + keypress_bits ^= ((u32)(1U << 5)); + } +#endif + + LocalPlayer *player = client->getEnv().getLocalPlayer(); + + // autojump if set: simulate "jump" key + if (player->getAutojump()) { + control.jump = true; + keypress_bits |= 1U << 4; + } + + client->setPlayerControl(control); + player->keyPressed = keypress_bits; + + //tt.stop(); +} + + +inline void Game::step(f32 *dtime) +{ + bool can_be_and_is_paused = + (simple_singleplayer_mode && g_menumgr.pausesGame()); + + if (can_be_and_is_paused) { // This is for a singleplayer server + *dtime = 0; // No time passes + } else { + if (server) + server->step(*dtime); + + client->step(*dtime); + } +} + +const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = { + {&Game::handleClientEvent_None}, + {&Game::handleClientEvent_PlayerDamage}, + {&Game::handleClientEvent_PlayerForceMove}, + {&Game::handleClientEvent_Deathscreen}, + {&Game::handleClientEvent_ShowFormSpec}, + {&Game::handleClientEvent_ShowLocalFormSpec}, + {&Game::handleClientEvent_HandleParticleEvent}, + {&Game::handleClientEvent_HandleParticleEvent}, + {&Game::handleClientEvent_HandleParticleEvent}, + {&Game::handleClientEvent_HudAdd}, + {&Game::handleClientEvent_HudRemove}, + {&Game::handleClientEvent_HudChange}, + {&Game::handleClientEvent_SetSky}, + {&Game::handleClientEvent_OverrideDayNigthRatio}, + {&Game::handleClientEvent_CloudParams}, +}; + +void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam) +{ + FATAL_ERROR("ClientEvent type None received"); +} + +void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam) +{ + if (client->moddingEnabled()) { + client->getScript()->on_damage_taken(event->player_damage.amount); + } + + // Damage flash and hurt tilt are not used at death + if (client->getHP() > 0) { + runData.damage_flash += 95.0f + 3.2f * event->player_damage.amount; + runData.damage_flash = MYMIN(runData.damage_flash, 127.0f); + + LocalPlayer *player = client->getEnv().getLocalPlayer(); + + player->hurt_tilt_timer = 1.5f; + player->hurt_tilt_strength = + rangelim(event->player_damage.amount / 4.0f, 1.0f, 4.0f); + } + + // Play damage sound + client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_DAMAGE)); +} + +void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam) +{ + cam->camera_yaw = event->player_force_move.yaw; + cam->camera_pitch = event->player_force_move.pitch; +} + +void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam) +{ + // If CSM enabled, deathscreen is handled by CSM code in + // builtin/client/init.lua + if (client->moddingEnabled()) + client->getScript()->on_death(); + else + showDeathFormspec(); + + /* Handle visualization */ + LocalPlayer *player = client->getEnv().getLocalPlayer(); + runData.damage_flash = 0; + player->hurt_tilt_timer = 0; + player->hurt_tilt_strength = 0; +} + +void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam) +{ + if (event->show_formspec.formspec->empty()) { + if (current_formspec && (event->show_formspec.formname->empty() + || *(event->show_formspec.formname) == cur_formname)) { + current_formspec->quitMenu(); + } + } else { + FormspecFormSource *fs_src = + new FormspecFormSource(*(event->show_formspec.formspec)); + TextDestPlayerInventory *txt_dst = + new TextDestPlayerInventory(client, *(event->show_formspec.formname)); + + GUIFormSpecMenu::create(current_formspec, client, &input->joystick, + fs_src, txt_dst, client->getFormspecPrepend()); + cur_formname = *(event->show_formspec.formname); + } + + delete event->show_formspec.formspec; + delete event->show_formspec.formname; +} + +void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam) +{ + FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec); + LocalFormspecHandler *txt_dst = + new LocalFormspecHandler(*event->show_formspec.formname, client); + GUIFormSpecMenu::create(current_formspec, client, &input->joystick, + fs_src, txt_dst, client->getFormspecPrepend()); + + delete event->show_formspec.formspec; + delete event->show_formspec.formname; +} + +void Game::handleClientEvent_HandleParticleEvent(ClientEvent *event, + CameraOrientation *cam) +{ + LocalPlayer *player = client->getEnv().getLocalPlayer(); + client->getParticleManager()->handleParticleEvent(event, client, player); +} + +void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam) +{ + LocalPlayer *player = client->getEnv().getLocalPlayer(); + auto &hud_server_to_client = client->getHUDTranslationMap(); + + u32 server_id = event->hudadd.server_id; + // ignore if we already have a HUD with that ID + auto i = hud_server_to_client.find(server_id); + if (i != hud_server_to_client.end()) { + delete event->hudadd.pos; + delete event->hudadd.name; + delete event->hudadd.scale; + delete event->hudadd.text; + delete event->hudadd.align; + delete event->hudadd.offset; + delete event->hudadd.world_pos; + delete event->hudadd.size; + return; + } + + HudElement *e = new HudElement; + e->type = (HudElementType)event->hudadd.type; + e->pos = *event->hudadd.pos; + e->name = *event->hudadd.name; + e->scale = *event->hudadd.scale; + e->text = *event->hudadd.text; + e->number = event->hudadd.number; + e->item = event->hudadd.item; + e->dir = event->hudadd.dir; + e->align = *event->hudadd.align; + e->offset = *event->hudadd.offset; + e->world_pos = *event->hudadd.world_pos; + e->size = *event->hudadd.size; + hud_server_to_client[server_id] = player->addHud(e); + + delete event->hudadd.pos; + delete event->hudadd.name; + delete event->hudadd.scale; + delete event->hudadd.text; + delete event->hudadd.align; + delete event->hudadd.offset; + delete event->hudadd.world_pos; + delete event->hudadd.size; +} + +void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam) +{ + LocalPlayer *player = client->getEnv().getLocalPlayer(); + HudElement *e = player->removeHud(event->hudrm.id); + delete e; +} + +void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam) +{ + LocalPlayer *player = client->getEnv().getLocalPlayer(); + + u32 id = event->hudchange.id; + HudElement *e = player->getHud(id); + + if (e == NULL) { + delete event->hudchange.v3fdata; + delete event->hudchange.v2fdata; + delete event->hudchange.sdata; + delete event->hudchange.v2s32data; + return; + } + + switch (event->hudchange.stat) { + case HUD_STAT_POS: + e->pos = *event->hudchange.v2fdata; + break; + + case HUD_STAT_NAME: + e->name = *event->hudchange.sdata; + break; + + case HUD_STAT_SCALE: + e->scale = *event->hudchange.v2fdata; + break; + + case HUD_STAT_TEXT: + e->text = *event->hudchange.sdata; + break; + + case HUD_STAT_NUMBER: + e->number = event->hudchange.data; + break; + + case HUD_STAT_ITEM: + e->item = event->hudchange.data; + break; + + case HUD_STAT_DIR: + e->dir = event->hudchange.data; + break; + + case HUD_STAT_ALIGN: + e->align = *event->hudchange.v2fdata; + break; + + case HUD_STAT_OFFSET: + e->offset = *event->hudchange.v2fdata; + break; + + case HUD_STAT_WORLD_POS: + e->world_pos = *event->hudchange.v3fdata; + break; + + case HUD_STAT_SIZE: + e->size = *event->hudchange.v2s32data; + break; + } + + delete event->hudchange.v3fdata; + delete event->hudchange.v2fdata; + delete event->hudchange.sdata; + delete event->hudchange.v2s32data; +} + +void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam) +{ + sky->setVisible(false); + // Whether clouds are visible in front of a custom skybox + sky->setCloudsEnabled(event->set_sky.clouds); + + if (skybox) { + skybox->remove(); + skybox = NULL; + } + + // Handle according to type + if (*event->set_sky.type == "regular") { + sky->setVisible(true); + sky->setCloudsEnabled(true); + } else if (*event->set_sky.type == "skybox" && + event->set_sky.params->size() == 6) { + sky->setFallbackBgColor(*event->set_sky.bgcolor); + skybox = RenderingEngine::get_scene_manager()->addSkyBoxSceneNode( + texture_src->getTextureForMesh((*event->set_sky.params)[0]), + texture_src->getTextureForMesh((*event->set_sky.params)[1]), + texture_src->getTextureForMesh((*event->set_sky.params)[2]), + texture_src->getTextureForMesh((*event->set_sky.params)[3]), + texture_src->getTextureForMesh((*event->set_sky.params)[4]), + texture_src->getTextureForMesh((*event->set_sky.params)[5])); + } + // Handle everything else as plain color + else { + if (*event->set_sky.type != "plain") + infostream << "Unknown sky type: " + << (*event->set_sky.type) << std::endl; + + sky->setFallbackBgColor(*event->set_sky.bgcolor); + } + + delete event->set_sky.bgcolor; + delete event->set_sky.type; + delete event->set_sky.params; +} + +void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event, + CameraOrientation *cam) +{ + client->getEnv().setDayNightRatioOverride( + event->override_day_night_ratio.do_override, + event->override_day_night_ratio.ratio_f * 1000.0f); +} + +void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam) +{ + if (!clouds) + return; + + clouds->setDensity(event->cloud_params.density); + clouds->setColorBright(video::SColor(event->cloud_params.color_bright)); + clouds->setColorAmbient(video::SColor(event->cloud_params.color_ambient)); + clouds->setHeight(event->cloud_params.height); + clouds->setThickness(event->cloud_params.thickness); + clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y)); +} + +void Game::processClientEvents(CameraOrientation *cam) +{ + while (client->hasClientEvents()) { + std::unique_ptr event(client->getClientEvent()); + FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, "Invalid clientevent type"); + const ClientEventHandler& evHandler = clientEventHandler[event->type]; + (this->*evHandler.handler)(event.get(), cam); + } +} + +void Game::updateChat(f32 dtime, const v2u32 &screensize) +{ + // Add chat log output for errors to be shown in chat + static LogOutputBuffer chat_log_error_buf(g_logger, LL_ERROR); + + // Get new messages from error log buffer + while (!chat_log_error_buf.empty()) { + std::wstring error_message = utf8_to_wide(chat_log_error_buf.get()); + if (!g_settings->getBool("disable_escape_sequences")) { + error_message.insert(0, L"\x1b(c@red)"); + error_message.append(L"\x1b(c@white)"); + } + chat_backend->addMessage(L"", error_message); + } + + // Get new messages from client + std::wstring message; + while (client->getChatMessage(message)) { + chat_backend->addUnparsedMessage(message); + } + + // Remove old messages + chat_backend->step(dtime); + + // Display all messages in a static text element + m_game_ui->setChatText(chat_backend->getRecentChat(), + chat_backend->getRecentBuffer().getLineCount()); +} + +void Game::updateCamera(u32 busy_time, f32 dtime) +{ + LocalPlayer *player = client->getEnv().getLocalPlayer(); + + /* + For interaction purposes, get info about the held item + - What item is it? + - Is it a usable item? + - Can it point to liquids? + */ + ItemStack playeritem; + { + InventoryList *mlist = local_inventory->getList("main"); + + if (mlist && client->getPlayerItem() < mlist->getSize()) + playeritem = mlist->getItem(client->getPlayerItem()); + } + + if (playeritem.getDefinition(itemdef_manager).name.empty()) { // override the hand + InventoryList *hlist = local_inventory->getList("hand"); + if (hlist) + playeritem = hlist->getItem(0); + } + + + ToolCapabilities playeritem_toolcap = + playeritem.getToolCapabilities(itemdef_manager); + + v3s16 old_camera_offset = camera->getOffset(); + + if (wasKeyDown(KeyType::CAMERA_MODE)) { + GenericCAO *playercao = player->getCAO(); + + // If playercao not loaded, don't change camera + if (!playercao) + return; + + camera->toggleCameraMode(); + + playercao->setVisible(camera->getCameraMode() > CAMERA_MODE_FIRST); + playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST); + } + + float full_punch_interval = playeritem_toolcap.full_punch_interval; + float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval; + + tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0); + camera->update(player, dtime, busy_time / 1000.0f, tool_reload_ratio); + camera->step(dtime); + + v3f camera_position = camera->getPosition(); + v3f camera_direction = camera->getDirection(); + f32 camera_fov = camera->getFovMax(); + v3s16 camera_offset = camera->getOffset(); + + m_camera_offset_changed = (camera_offset != old_camera_offset); + + if (!m_flags.disable_camera_update) { + client->getEnv().getClientMap().updateCamera(camera_position, + camera_direction, camera_fov, camera_offset); + + if (m_camera_offset_changed) { + client->updateCameraOffset(camera_offset); + client->getEnv().updateCameraOffset(camera_offset); + + if (clouds) + clouds->updateCameraOffset(camera_offset); + } + } +} + + +void Game::updateSound(f32 dtime) +{ + // Update sound listener + v3s16 camera_offset = camera->getOffset(); + sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS), + v3f(0, 0, 0), // velocity + camera->getDirection(), + camera->getCameraNode()->getUpVector()); + + bool mute_sound = g_settings->getBool("mute_sound"); + if (mute_sound) { + sound->setListenerGain(0.0f); + } else { + // Check if volume is in the proper range, else fix it. + float old_volume = g_settings->getFloat("sound_volume"); + float new_volume = rangelim(old_volume, 0.0f, 1.0f); + sound->setListenerGain(new_volume); + + if (old_volume != new_volume) { + g_settings->setFloat("sound_volume", new_volume); + } + } + + LocalPlayer *player = client->getEnv().getLocalPlayer(); + + // Tell the sound maker whether to make footstep sounds + soundmaker->makes_footstep_sound = player->makes_footstep_sound; + + // Update sound maker + if (player->makes_footstep_sound) + soundmaker->step(dtime); + + ClientMap &map = client->getEnv().getClientMap(); + MapNode n = map.getNodeNoEx(player->getFootstepNodePos()); + soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep; +} + + +void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug) +{ + LocalPlayer *player = client->getEnv().getLocalPlayer(); + + ItemStack playeritem; + { + InventoryList *mlist = local_inventory->getList("main"); + + if (mlist && client->getPlayerItem() < mlist->getSize()) + playeritem = mlist->getItem(client->getPlayerItem()); + } + + const ItemDefinition &playeritem_def = + playeritem.getDefinition(itemdef_manager); + InventoryList *hlist = local_inventory->getList("hand"); + const ItemDefinition &hand_def = + hlist ? hlist->getItem(0).getDefinition(itemdef_manager) : itemdef_manager->get(""); + + v3f player_position = player->getPosition(); + v3f camera_position = camera->getPosition(); + v3f camera_direction = camera->getDirection(); + v3s16 camera_offset = camera->getOffset(); + + + /* + Calculate what block is the crosshair pointing to + */ + + f32 d = playeritem_def.range; // max. distance + f32 d_hand = hand_def.range; + + if (d < 0 && d_hand >= 0) + d = d_hand; + else if (d < 0) + d = 4.0; + + core::line3d shootline; + + if (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT) { + shootline = core::line3d(camera_position, + camera_position + camera_direction * BS * d); + } else { + // prevent player pointing anything in front-view + shootline = core::line3d(camera_position,camera_position); + } + +#ifdef HAVE_TOUCHSCREENGUI + + if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) { + shootline = g_touchscreengui->getShootline(); + // Scale shootline to the acual distance the player can reach + shootline.end = shootline.start + + shootline.getVector().normalize() * BS * d; + shootline.start += intToFloat(camera_offset, BS); + shootline.end += intToFloat(camera_offset, BS); + } + +#endif + + PointedThing pointed = updatePointedThing(shootline, + playeritem_def.liquids_pointable, + !runData.ldown_for_dig, + camera_offset); + + if (pointed != runData.pointed_old) { + infostream << "Pointing at " << pointed.dump() << std::endl; + hud->updateSelectionMesh(camera_offset); + } + + if (runData.digging_blocked && !input->getLeftState()) { + // allow digging again if button is not pressed + runData.digging_blocked = false; + } + + /* + Stop digging when + - releasing left mouse button + - pointing away from node + */ + if (runData.digging) { + if (input->getLeftReleased()) { + infostream << "Left button released" + << " (stopped digging)" << std::endl; + runData.digging = false; + } else if (pointed != runData.pointed_old) { + if (pointed.type == POINTEDTHING_NODE + && runData.pointed_old.type == POINTEDTHING_NODE + && pointed.node_undersurface + == runData.pointed_old.node_undersurface) { + // Still pointing to the same node, but a different face. + // Don't reset. + } else { + infostream << "Pointing away from node" + << " (stopped digging)" << std::endl; + runData.digging = false; + hud->updateSelectionMesh(camera_offset); + } + } + + if (!runData.digging) { + client->interact(1, runData.pointed_old); + client->setCrack(-1, v3s16(0, 0, 0)); + runData.dig_time = 0.0; + } + } else if (runData.dig_instantly && input->getLeftReleased()) { + // Remove e.g. torches faster when clicking instead of holding LMB + runData.nodig_delay_timer = 0; + runData.dig_instantly = false; + } + + if (!runData.digging && runData.ldown_for_dig && !input->getLeftState()) { + runData.ldown_for_dig = false; + } + + runData.left_punch = false; + + soundmaker->m_player_leftpunch_sound.name = ""; + + // Prepare for repeating, unless we're not supposed to + if (input->getRightState() && !g_settings->getBool("safe_dig_and_place")) + runData.repeat_rightclick_timer += dtime; + else + runData.repeat_rightclick_timer = 0; + + if (playeritem_def.usable && input->getLeftState()) { + if (input->getLeftClicked() && (!client->moddingEnabled() + || !client->getScript()->on_item_use(playeritem, pointed))) + client->interact(4, pointed); + } else if (pointed.type == POINTEDTHING_NODE) { + ToolCapabilities playeritem_toolcap = + playeritem.getToolCapabilities(itemdef_manager); + if (playeritem.name.empty()) { + const ToolCapabilities *handToolcap = hlist + ? &hlist->getItem(0).getToolCapabilities(itemdef_manager) + : itemdef_manager->get("").tool_capabilities; + + if (handToolcap != nullptr) + playeritem_toolcap = *handToolcap; + } + handlePointingAtNode(pointed, playeritem_def, playeritem, + playeritem_toolcap, dtime); + } else if (pointed.type == POINTEDTHING_OBJECT) { + handlePointingAtObject(pointed, playeritem, player_position, show_debug); + } else if (input->getLeftState()) { + // When button is held down in air, show continuous animation + runData.left_punch = true; + } else if (input->getRightClicked()) { + handlePointingAtNothing(playeritem); + } + + runData.pointed_old = pointed; + + if (runData.left_punch || input->getLeftClicked()) + camera->setDigging(0); // left click animation + + input->resetLeftClicked(); + input->resetRightClicked(); + + input->resetLeftReleased(); + input->resetRightReleased(); +} + + +PointedThing Game::updatePointedThing( + const core::line3d &shootline, + bool liquids_pointable, + bool look_for_object, + const v3s16 &camera_offset) +{ + std::vector *selectionboxes = hud->getSelectionBoxes(); + selectionboxes->clear(); + hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0)); + static thread_local const bool show_entity_selectionbox = g_settings->getBool( + "show_entity_selectionbox"); + + ClientEnvironment &env = client->getEnv(); + ClientMap &map = env.getClientMap(); + const NodeDefManager *nodedef = map.getNodeDefManager(); + + runData.selected_object = NULL; + + RaycastState s(shootline, look_for_object, liquids_pointable); + PointedThing result; + env.continueRaycast(&s, &result); + if (result.type == POINTEDTHING_OBJECT) { + runData.selected_object = client->getEnv().getActiveObject(result.object_id); + aabb3f selection_box; + if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() && + runData.selected_object->getSelectionBox(&selection_box)) { + v3f pos = runData.selected_object->getPosition(); + selectionboxes->push_back(aabb3f(selection_box)); + hud->setSelectionPos(pos, camera_offset); + } + } else if (result.type == POINTEDTHING_NODE) { + // Update selection boxes + MapNode n = map.getNodeNoEx(result.node_undersurface); + std::vector boxes; + n.getSelectionBoxes(nodedef, &boxes, + n.getNeighbors(result.node_undersurface, &map)); + + f32 d = 0.002 * BS; + for (std::vector::const_iterator i = boxes.begin(); + i != boxes.end(); ++i) { + aabb3f box = *i; + box.MinEdge -= v3f(d, d, d); + box.MaxEdge += v3f(d, d, d); + selectionboxes->push_back(box); + } + hud->setSelectionPos(intToFloat(result.node_undersurface, BS), + camera_offset); + hud->setSelectedFaceNormal(v3f( + result.intersection_normal.X, + result.intersection_normal.Y, + result.intersection_normal.Z)); + } + + // Update selection mesh light level and vertex colors + if (!selectionboxes->empty()) { + v3f pf = hud->getSelectionPos(); + v3s16 p = floatToInt(pf, BS); + + // Get selection mesh light level + MapNode n = map.getNodeNoEx(p); + u16 node_light = getInteriorLight(n, -1, nodedef); + u16 light_level = node_light; + + for (const v3s16 &dir : g_6dirs) { + n = map.getNodeNoEx(p + dir); + node_light = getInteriorLight(n, -1, nodedef); + if (node_light > light_level) + light_level = node_light; + } + + u32 daynight_ratio = client->getEnv().getDayNightRatio(); + video::SColor c; + final_color_blend(&c, light_level, daynight_ratio); + + // Modify final color a bit with time + u32 timer = porting::getTimeMs() % 5000; + float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5)); + float sin_r = 0.08f * std::sin(timerf); + float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f); + float sin_b = 0.08f * std::sin(timerf + irr::core::PI); + c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255)); + c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255)); + c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255)); + + // Set mesh final color + hud->setSelectionMeshColor(c); + } + return result; +} + + +void Game::handlePointingAtNothing(const ItemStack &playerItem) +{ + infostream << "Right Clicked in Air" << std::endl; + PointedThing fauxPointed; + fauxPointed.type = POINTEDTHING_NOTHING; + client->interact(5, fauxPointed); +} + + +void Game::handlePointingAtNode(const PointedThing &pointed, + const ItemDefinition &playeritem_def, const ItemStack &playeritem, + const ToolCapabilities &playeritem_toolcap, f32 dtime) +{ + v3s16 nodepos = pointed.node_undersurface; + v3s16 neighbourpos = pointed.node_abovesurface; + + /* + Check information text of node + */ + + ClientMap &map = client->getEnv().getClientMap(); + + if (runData.nodig_delay_timer <= 0.0 && input->getLeftState() + && !runData.digging_blocked + && client->checkPrivilege("interact")) { + handleDigging(pointed, nodepos, playeritem_toolcap, dtime); + } + + // This should be done after digging handling + NodeMetadata *meta = map.getNodeMetadata(nodepos); + + if (meta) { + m_game_ui->setInfoText(unescape_translate(utf8_to_wide( + meta->getString("infotext")))); + } else { + MapNode n = map.getNodeNoEx(nodepos); + + if (nodedef_manager->get(n).tiledef[0].name == "unknown_node.png") { + m_game_ui->setInfoText(L"Unknown node: " + + utf8_to_wide(nodedef_manager->get(n).name)); + } + } + + if ((input->getRightClicked() || + runData.repeat_rightclick_timer >= m_repeat_right_click_time) && + client->checkPrivilege("interact")) { + runData.repeat_rightclick_timer = 0; + infostream << "Ground right-clicked" << std::endl; + + if (meta && !meta->getString("formspec").empty() && !random_input + && !isKeyDown(KeyType::SNEAK)) { + // Report right click to server + if (nodedef_manager->get(map.getNodeNoEx(nodepos)).rightclickable) { + client->interact(3, pointed); + } + + infostream << "Launching custom inventory view" << std::endl; + + InventoryLocation inventoryloc; + inventoryloc.setNodeMeta(nodepos); + + NodeMetadataFormSource *fs_src = new NodeMetadataFormSource( + &client->getEnv().getClientMap(), nodepos); + TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client); + + GUIFormSpecMenu::create(current_formspec, client, &input->joystick, fs_src, + txt_dst, client->getFormspecPrepend()); + cur_formname.clear(); + + current_formspec->setFormSpec(meta->getString("formspec"), inventoryloc); + } else { + // Report right click to server + + camera->setDigging(1); // right click animation (always shown for feedback) + + // If the wielded item has node placement prediction, + // make that happen + bool placed = nodePlacementPrediction(playeritem_def, playeritem, nodepos, + neighbourpos); + + if (placed) { + // Report to server + client->interact(3, pointed); + // Read the sound + soundmaker->m_player_rightpunch_sound = + playeritem_def.sound_place; + + if (client->moddingEnabled()) + client->getScript()->on_placenode(pointed, playeritem_def); + } else { + soundmaker->m_player_rightpunch_sound = + SimpleSoundSpec(); + + if (playeritem_def.node_placement_prediction.empty() || + nodedef_manager->get(map.getNodeNoEx(nodepos)).rightclickable) { + client->interact(3, pointed); // Report to server + } else { + soundmaker->m_player_rightpunch_sound = + playeritem_def.sound_place_failed; + } + } + } + } +} + +bool Game::nodePlacementPrediction(const ItemDefinition &playeritem_def, + const ItemStack &playeritem, const v3s16 &nodepos, const v3s16 &neighbourpos) +{ + std::string prediction = playeritem_def.node_placement_prediction; + const NodeDefManager *nodedef = client->ndef(); + ClientMap &map = client->getEnv().getClientMap(); + MapNode node; + bool is_valid_position; + + node = map.getNodeNoEx(nodepos, &is_valid_position); + if (!is_valid_position) + return false; + + if (!prediction.empty() && !nodedef->get(node).rightclickable) { + verbosestream << "Node placement prediction for " + << playeritem_def.name << " is " + << prediction << std::endl; + v3s16 p = neighbourpos; + + // Place inside node itself if buildable_to + MapNode n_under = map.getNodeNoEx(nodepos, &is_valid_position); + if (is_valid_position) + { + if (nodedef->get(n_under).buildable_to) + p = nodepos; + else { + node = map.getNodeNoEx(p, &is_valid_position); + if (is_valid_position &&!nodedef->get(node).buildable_to) + return false; + } + } + + // Find id of predicted node + content_t id; + bool found = nodedef->getId(prediction, id); + + if (!found) { + errorstream << "Node placement prediction failed for " + << playeritem_def.name << " (places " + << prediction + << ") - Name not known" << std::endl; + return false; + } + + const ContentFeatures &predicted_f = nodedef->get(id); + + // Predict param2 for facedir and wallmounted nodes + u8 param2 = 0; + + if (predicted_f.param_type_2 == CPT2_WALLMOUNTED || + predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) { + v3s16 dir = nodepos - neighbourpos; + + if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) { + param2 = dir.Y < 0 ? 1 : 0; + } else if (abs(dir.X) > abs(dir.Z)) { + param2 = dir.X < 0 ? 3 : 2; + } else { + param2 = dir.Z < 0 ? 5 : 4; + } + } + + if (predicted_f.param_type_2 == CPT2_FACEDIR || + predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) { + v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS); + + if (abs(dir.X) > abs(dir.Z)) { + param2 = dir.X < 0 ? 3 : 1; + } else { + param2 = dir.Z < 0 ? 2 : 0; + } + } + + assert(param2 <= 5); + + //Check attachment if node is in group attached_node + if (((ItemGroupList) predicted_f.groups)["attached_node"] != 0) { + static v3s16 wallmounted_dirs[8] = { + v3s16(0, 1, 0), + v3s16(0, -1, 0), + v3s16(1, 0, 0), + v3s16(-1, 0, 0), + v3s16(0, 0, 1), + v3s16(0, 0, -1), + }; + v3s16 pp; + + if (predicted_f.param_type_2 == CPT2_WALLMOUNTED || + predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) + pp = p + wallmounted_dirs[param2]; + else + pp = p + v3s16(0, -1, 0); + + if (!nodedef->get(map.getNodeNoEx(pp)).walkable) + return false; + } + + // Apply color + if ((predicted_f.param_type_2 == CPT2_COLOR + || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR + || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) { + const std::string &indexstr = playeritem.metadata.getString( + "palette_index", 0); + if (!indexstr.empty()) { + s32 index = mystoi(indexstr); + if (predicted_f.param_type_2 == CPT2_COLOR) { + param2 = index; + } else if (predicted_f.param_type_2 + == CPT2_COLORED_WALLMOUNTED) { + // param2 = pure palette index + other + param2 = (index & 0xf8) | (param2 & 0x07); + } else if (predicted_f.param_type_2 + == CPT2_COLORED_FACEDIR) { + // param2 = pure palette index + other + param2 = (index & 0xe0) | (param2 & 0x1f); + } + } + } + + // Add node to client map + MapNode n(id, 0, param2); + + try { + LocalPlayer *player = client->getEnv().getLocalPlayer(); + + // Dont place node when player would be inside new node + // NOTE: This is to be eventually implemented by a mod as client-side Lua + if (!nodedef->get(n).walkable || + g_settings->getBool("enable_build_where_you_stand") || + (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) || + (nodedef->get(n).walkable && + neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) && + neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) { + + // This triggers the required mesh update too + client->addNode(p, n); + return true; + } + } catch (InvalidPositionException &e) { + errorstream << "Node placement prediction failed for " + << playeritem_def.name << " (places " + << prediction + << ") - Position not loaded" << std::endl; + } + } + + return false; +} + +void Game::handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem, + const v3f &player_position, bool show_debug) +{ + std::wstring infotext = unescape_translate( + utf8_to_wide(runData.selected_object->infoText())); + + if (show_debug) { + if (!infotext.empty()) { + infotext += L"\n"; + } + infotext += utf8_to_wide(runData.selected_object->debugInfoText()); + } + + m_game_ui->setInfoText(infotext); + + if (input->getLeftState()) { + bool do_punch = false; + bool do_punch_damage = false; + + if (runData.object_hit_delay_timer <= 0.0) { + do_punch = true; + do_punch_damage = true; + runData.object_hit_delay_timer = object_hit_delay; + } + + if (input->getLeftClicked()) + do_punch = true; + + if (do_punch) { + infostream << "Left-clicked object" << std::endl; + runData.left_punch = true; + } + + if (do_punch_damage) { + // Report direct punch + v3f objpos = runData.selected_object->getPosition(); + v3f dir = (objpos - player_position).normalize(); + ItemStack item = playeritem; + if (playeritem.name.empty()) { + InventoryList *hlist = local_inventory->getList("hand"); + if (hlist) { + item = hlist->getItem(0); + } + } + + bool disable_send = runData.selected_object->directReportPunch( + dir, &item, runData.time_from_last_punch); + runData.time_from_last_punch = 0; + + if (!disable_send) + client->interact(0, pointed); + } + } else if (input->getRightClicked()) { + infostream << "Right-clicked object" << std::endl; + client->interact(3, pointed); // place + } +} + + +void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos, + const ToolCapabilities &playeritem_toolcap, f32 dtime) +{ + LocalPlayer *player = client->getEnv().getLocalPlayer(); + ClientMap &map = client->getEnv().getClientMap(); + MapNode n = client->getEnv().getClientMap().getNodeNoEx(nodepos); + + // NOTE: Similar piece of code exists on the server side for + // cheat detection. + // Get digging parameters + DigParams params = getDigParams(nodedef_manager->get(n).groups, + &playeritem_toolcap); + + // If can't dig, try hand + if (!params.diggable) { + InventoryList *hlist = local_inventory->getList("hand"); + const ToolCapabilities *tp = hlist + ? &hlist->getItem(0).getToolCapabilities(itemdef_manager) + : itemdef_manager->get("").tool_capabilities; + + if (tp) + params = getDigParams(nodedef_manager->get(n).groups, tp); + } + + if (!params.diggable) { + // I guess nobody will wait for this long + runData.dig_time_complete = 10000000.0; + } else { + runData.dig_time_complete = params.time; + + if (m_cache_enable_particles) { + const ContentFeatures &features = client->getNodeDefManager()->get(n); + client->getParticleManager()->addNodeParticle(client, + player, nodepos, n, features); + } + } + + if (!runData.digging) { + infostream << "Started digging" << std::endl; + runData.dig_instantly = runData.dig_time_complete == 0; + if (client->moddingEnabled() && client->getScript()->on_punchnode(nodepos, n)) + return; + client->interact(0, pointed); + runData.digging = true; + runData.ldown_for_dig = true; + } + + if (!runData.dig_instantly) { + runData.dig_index = (float)crack_animation_length + * runData.dig_time + / runData.dig_time_complete; + } else { + // This is for e.g. torches + runData.dig_index = crack_animation_length; + } + + SimpleSoundSpec sound_dig = nodedef_manager->get(n).sound_dig; + + if (sound_dig.exists() && params.diggable) { + if (sound_dig.name == "__group") { + if (!params.main_group.empty()) { + soundmaker->m_player_leftpunch_sound.gain = 0.5; + soundmaker->m_player_leftpunch_sound.name = + std::string("default_dig_") + + params.main_group; + } + } else { + soundmaker->m_player_leftpunch_sound = sound_dig; + } + } + + // Don't show cracks if not diggable + if (runData.dig_time_complete >= 100000.0) { + } else if (runData.dig_index < crack_animation_length) { + //TimeTaker timer("client.setTempMod"); + //infostream<<"dig_index="<setCrack(runData.dig_index, nodepos); + } else { + infostream << "Digging completed" << std::endl; + client->setCrack(-1, v3s16(0, 0, 0)); + + runData.dig_time = 0; + runData.digging = false; + // we successfully dug, now block it from repeating if we want to be safe + if (g_settings->getBool("safe_dig_and_place")) + runData.digging_blocked = true; + + runData.nodig_delay_timer = + runData.dig_time_complete / (float)crack_animation_length; + + // We don't want a corresponding delay to very time consuming nodes + // and nodes without digging time (e.g. torches) get a fixed delay. + if (runData.nodig_delay_timer > 0.3) + runData.nodig_delay_timer = 0.3; + else if (runData.dig_instantly) + runData.nodig_delay_timer = 0.15; + + bool is_valid_position; + MapNode wasnode = map.getNodeNoEx(nodepos, &is_valid_position); + if (is_valid_position) { + if (client->moddingEnabled() && + client->getScript()->on_dignode(nodepos, wasnode)) { + return; + } + + const ContentFeatures &f = client->ndef()->get(wasnode); + if (f.node_dig_prediction == "air") { + client->removeNode(nodepos); + } else if (!f.node_dig_prediction.empty()) { + content_t id; + bool found = client->ndef()->getId(f.node_dig_prediction, id); + if (found) + client->addNode(nodepos, id, true); + } + // implicit else: no prediction + } + + client->interact(2, pointed); + + if (m_cache_enable_particles) { + const ContentFeatures &features = + client->getNodeDefManager()->get(wasnode); + client->getParticleManager()->addDiggingParticles(client, + player, nodepos, wasnode, features); + } + + + // Send event to trigger sound + client->getEventManager()->put(new NodeDugEvent(nodepos, wasnode)); + } + + if (runData.dig_time_complete < 100000.0) { + runData.dig_time += dtime; + } else { + runData.dig_time = 0; + client->setCrack(-1, nodepos); + } + + camera->setDigging(0); // left click animation +} + + +void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, + const CameraOrientation &cam) +{ + LocalPlayer *player = client->getEnv().getLocalPlayer(); + + /* + Fog range + */ + + if (draw_control->range_all) { + runData.fog_range = 100000 * BS; + } else { + runData.fog_range = draw_control->wanted_range * BS; + } + + /* + Calculate general brightness + */ + u32 daynight_ratio = client->getEnv().getDayNightRatio(); + float time_brightness = decode_light_f((float)daynight_ratio / 1000.0); + float direct_brightness; + bool sunlight_seen; + + if (m_cache_enable_noclip && m_cache_enable_free_move) { + direct_brightness = time_brightness; + sunlight_seen = true; + } else { + ScopeProfiler sp(g_profiler, "Detecting background light", SPT_AVG); + float old_brightness = sky->getBrightness(); + direct_brightness = client->getEnv().getClientMap() + .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS), + daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen) + / 255.0; + } + + float time_of_day_smooth = runData.time_of_day_smooth; + float time_of_day = client->getEnv().getTimeOfDayF(); + + static const float maxsm = 0.05f; + static const float todsm = 0.05f; + + if (std::fabs(time_of_day - time_of_day_smooth) > maxsm && + std::fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm && + std::fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm) + time_of_day_smooth = time_of_day; + + if (time_of_day_smooth > 0.8 && time_of_day < 0.2) + time_of_day_smooth = time_of_day_smooth * (1.0 - todsm) + + (time_of_day + 1.0) * todsm; + else + time_of_day_smooth = time_of_day_smooth * (1.0 - todsm) + + time_of_day * todsm; + + runData.time_of_day_smooth = time_of_day_smooth; + + sky->update(time_of_day_smooth, time_brightness, direct_brightness, + sunlight_seen, camera->getCameraMode(), player->getYaw(), + player->getPitch()); + + /* + Update clouds + */ + if (clouds) { + if (sky->getCloudsVisible()) { + clouds->setVisible(true); + clouds->step(dtime); + // camera->getPosition is not enough for 3rd person views + v3f camera_node_position = camera->getCameraNode()->getPosition(); + v3s16 camera_offset = camera->getOffset(); + camera_node_position.X = camera_node_position.X + camera_offset.X * BS; + camera_node_position.Y = camera_node_position.Y + camera_offset.Y * BS; + camera_node_position.Z = camera_node_position.Z + camera_offset.Z * BS; + clouds->update(camera_node_position, + sky->getCloudColor()); + if (clouds->isCameraInsideCloud() && m_cache_enable_fog) { + // if inside clouds, and fog enabled, use that as sky + // color(s) + video::SColor clouds_dark = clouds->getColor() + .getInterpolated(video::SColor(255, 0, 0, 0), 0.9); + sky->overrideColors(clouds_dark, clouds->getColor()); + sky->setBodiesVisible(false); + runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS); + // do not draw clouds after all + clouds->setVisible(false); + } + } else { + clouds->setVisible(false); + } + } + + /* + Update particles + */ + client->getParticleManager()->step(dtime); + + /* + Fog + */ + + if (m_cache_enable_fog) { + driver->setFog( + sky->getBgColor(), + video::EFT_FOG_LINEAR, + runData.fog_range * m_cache_fog_start, + runData.fog_range * 1.0, + 0.01, + false, // pixel fog + true // range fog + ); + } else { + driver->setFog( + sky->getBgColor(), + video::EFT_FOG_LINEAR, + 100000 * BS, + 110000 * BS, + 0.01f, + false, // pixel fog + false // range fog + ); + } + + /* + Get chat messages from client + */ + + v2u32 screensize = driver->getScreenSize(); + + updateChat(dtime, screensize); + + /* + Inventory + */ + + if (client->getPlayerItem() != runData.new_playeritem) + client->selectPlayerItem(runData.new_playeritem); + + // Update local inventory if it has changed + if (client->getLocalInventoryUpdated()) { + //infostream<<"Updating local inventory"<getLocalInventory(*local_inventory); + runData.update_wielded_item_trigger = true; + } + + if (runData.update_wielded_item_trigger) { + // Update wielded tool + InventoryList *mlist = local_inventory->getList("main"); + + if (mlist && (client->getPlayerItem() < mlist->getSize())) { + ItemStack item = mlist->getItem(client->getPlayerItem()); + if (item.getDefinition(itemdef_manager).name.empty()) { // override the hand + InventoryList *hlist = local_inventory->getList("hand"); + if (hlist) + item = hlist->getItem(0); + } + camera->wield(item); + } + + runData.update_wielded_item_trigger = false; + } + + /* + Update block draw list every 200ms or when camera direction has + changed much + */ + runData.update_draw_list_timer += dtime; + + v3f camera_direction = camera->getDirection(); + if (runData.update_draw_list_timer >= 0.2 + || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2 + || m_camera_offset_changed) { + runData.update_draw_list_timer = 0; + client->getEnv().getClientMap().updateDrawList(); + runData.update_draw_list_last_cam_dir = camera_direction; + } + + m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, dtime); + + /* + make sure menu is on top + 1. Delete formspec menu reference if menu was removed + 2. Else, make sure formspec menu is on top + */ + if (current_formspec) { + if (current_formspec->getReferenceCount() == 1) { + current_formspec->drop(); + current_formspec = NULL; + } else if (isMenuActive()) { + guiroot->bringToFront(current_formspec); + } + } + + /* + Drawing begins + */ + const video::SColor &skycolor = sky->getSkyColor(); + + TimeTaker tt_draw("mainloop: draw"); + driver->beginScene(true, true, skycolor); + + bool draw_wield_tool = (m_game_ui->m_flags.show_hud && + (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) && + (camera->getCameraMode() == CAMERA_MODE_FIRST)); + bool draw_crosshair = ( + (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) && + (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT)); +#ifdef HAVE_TOUCHSCREENGUI + try { + draw_crosshair = !g_settings->getBool("touchtarget"); + } catch (SettingNotFoundException) { + } +#endif + RenderingEngine::draw_scene(skycolor, m_game_ui->m_flags.show_hud, + m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair); + + /* + Profiler graph + */ + if (m_game_ui->m_flags.show_profiler_graph) + graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont()); + + /* + Damage flash + */ + if (runData.damage_flash > 0.0f) { + video::SColor color(runData.damage_flash, 180, 0, 0); + driver->draw2DRectangle(color, + core::rect(0, 0, screensize.X, screensize.Y), + NULL); + + runData.damage_flash -= 384.0f * dtime; + } + + /* + Damage camera tilt + */ + if (player->hurt_tilt_timer > 0.0f) { + player->hurt_tilt_timer -= dtime * 6.0f; + + if (player->hurt_tilt_timer < 0.0f) + player->hurt_tilt_strength = 0.0f; + } + + /* + Update minimap pos and rotation + */ + if (mapper && m_game_ui->m_flags.show_minimap && m_game_ui->m_flags.show_hud) { + mapper->setPos(floatToInt(player->getPosition(), BS)); + mapper->setAngle(player->getYaw()); + } + + /* + End scene + */ + driver->endScene(); + + stats->drawtime = tt_draw.stop(true); + g_profiler->graphAdd("mainloop_draw", stats->drawtime / 1000.0f); +} + +/* Log times and stuff for visualization */ +inline void Game::updateProfilerGraphs(ProfilerGraph *graph) +{ + Profiler::GraphValues values; + g_profiler->graphGet(values); + graph->put(values); +} + + + +/**************************************************************************** + Misc + ****************************************************************************/ + +/* On some computers framerate doesn't seem to be automatically limited + */ +inline void Game::limitFps(FpsControl *fps_timings, f32 *dtime) +{ + // not using getRealTime is necessary for wine + device->getTimer()->tick(); // Maker sure device time is up-to-date + u32 time = device->getTimer()->getTime(); + u32 last_time = fps_timings->last_time; + + if (time > last_time) // Make sure time hasn't overflowed + fps_timings->busy_time = time - last_time; + else + fps_timings->busy_time = 0; + + u32 frametime_min = 1000 / (g_menumgr.pausesGame() + ? g_settings->getFloat("pause_fps_max") + : g_settings->getFloat("fps_max")); + + if (fps_timings->busy_time < frametime_min) { + fps_timings->sleep_time = frametime_min - fps_timings->busy_time; + device->sleep(fps_timings->sleep_time); + } else { + fps_timings->sleep_time = 0; + } + + /* Get the new value of the device timer. Note that device->sleep() may + * not sleep for the entire requested time as sleep may be interrupted and + * therefore it is arguably more accurate to get the new time from the + * device rather than calculating it by adding sleep_time to time. + */ + + device->getTimer()->tick(); // Update device timer + time = device->getTimer()->getTime(); + + if (time > last_time) // Make sure last_time hasn't overflowed + *dtime = (time - last_time) / 1000.0; + else + *dtime = 0; + + fps_timings->last_time = time; +} + +void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds) +{ + const wchar_t *wmsg = wgettext(msg); + RenderingEngine::draw_load_screen(wmsg, guienv, texture_src, dtime, percent, + draw_clouds); + delete[] wmsg; +} + +void Game::settingChangedCallback(const std::string &setting_name, void *data) +{ + ((Game *)data)->readSettings(); +} + +void Game::readSettings() +{ + m_cache_doubletap_jump = g_settings->getBool("doubletap_jump"); + m_cache_enable_clouds = g_settings->getBool("enable_clouds"); + 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_right_click_time = g_settings->getFloat("repeat_rightclick_time"); + + m_cache_enable_noclip = g_settings->getBool("noclip"); + m_cache_enable_free_move = g_settings->getBool("free_move"); + + m_cache_fog_start = g_settings->getFloat("fog_start"); + + m_cache_cam_smoothing = 0; + if (g_settings->getBool("cinematic")) + m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing"); + else + m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing"); + + m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f); + m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f); + m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0); + + m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus"); +} + +/****************************************************************************/ +/**************************************************************************** + Shutdown / cleanup + ****************************************************************************/ +/****************************************************************************/ + +void Game::extendedResourceCleanup() +{ + // Extended resource accounting + infostream << "Irrlicht resources after cleanup:" << std::endl; + infostream << "\tRemaining meshes : " + << RenderingEngine::get_mesh_cache()->getMeshCount() << std::endl; + infostream << "\tRemaining textures : " + << driver->getTextureCount() << std::endl; + + for (unsigned int i = 0; i < driver->getTextureCount(); i++) { + irr::video::ITexture *texture = driver->getTextureByIndex(i); + infostream << "\t\t" << i << ":" << texture->getName().getPath().c_str() + << std::endl; + } + + clearTextureNameCache(); + infostream << "\tRemaining materials: " + << driver-> getMaterialRendererCount() + << " (note: irrlicht doesn't support removing renderers)" << std::endl; +} + +void Game::showDeathFormspec() +{ + static std::string formspec = + std::string(FORMSPEC_VERSION_STRING) + + SIZE_TAG + "bgcolor[#320000b4;true]" + "label[4.85,1.35;" + gettext("You died") + "]" + "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]" + ; + + /* Create menu */ + /* Note: FormspecFormSource and LocalFormspecHandler * + * are deleted by guiFormSpecMenu */ + FormspecFormSource *fs_src = new FormspecFormSource(formspec); + LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client); + + GUIFormSpecMenu::create(current_formspec, client, &input->joystick, fs_src, + txt_dst, client->getFormspecPrepend()); + current_formspec->setFocus("btn_respawn"); +} + +#define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name()) +void Game::showPauseMenu() +{ +#ifdef __ANDROID__ + static const std::string control_text = strgettext("Default Controls:\n" + "No menu visible:\n" + "- single tap: button activate\n" + "- double tap: place/use\n" + "- slide finger: look around\n" + "Menu/Inventory visible:\n" + "- double tap (outside):\n" + " -->close\n" + "- touch stack, touch slot:\n" + " --> move stack\n" + "- touch&drag, tap 2nd finger\n" + " --> place single item to slot\n" + ); +#else + static const std::string control_text_template = strgettext("Controls:\n" + "- %s: move forwards\n" + "- %s: move backwards\n" + "- %s: move left\n" + "- %s: move right\n" + "- %s: jump/climb\n" + "- %s: sneak/go down\n" + "- %s: drop item\n" + "- %s: inventory\n" + "- Mouse: turn/look\n" + "- Mouse left: dig/punch\n" + "- Mouse right: place/use\n" + "- Mouse wheel: select item\n" + "- %s: chat\n" + ); + + char control_text_buf[600]; + + porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(), + GET_KEY_NAME(keymap_forward), + GET_KEY_NAME(keymap_backward), + GET_KEY_NAME(keymap_left), + GET_KEY_NAME(keymap_right), + GET_KEY_NAME(keymap_jump), + GET_KEY_NAME(keymap_sneak), + GET_KEY_NAME(keymap_drop), + GET_KEY_NAME(keymap_inventory), + GET_KEY_NAME(keymap_chat) + ); + + std::string control_text = std::string(control_text_buf); + str_formspec_escape(control_text); +#endif + + float ypos = simple_singleplayer_mode ? 0.7f : 0.1f; + std::ostringstream os; + + os << FORMSPEC_VERSION_STRING << SIZE_TAG + << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;" + << strgettext("Continue") << "]"; + + if (!simple_singleplayer_mode) { + os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;" + << strgettext("Change Password") << "]"; + } else { + os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]"; + } + +#ifndef __ANDROID__ + os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;" + << strgettext("Sound Volume") << "]"; + os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;" + << strgettext("Change Keys") << "]"; +#endif + os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;" + << strgettext("Exit to Menu") << "]"; + os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;" + << strgettext("Exit to OS") << "]" + << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]" + << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n" + << "\n" + << strgettext("Game info:") << "\n"; + const std::string &address = client->getAddressName(); + static const std::string mode = strgettext("- Mode: "); + if (!simple_singleplayer_mode) { + Address serverAddress = client->getServerAddress(); + if (!address.empty()) { + os << mode << strgettext("Remote server") << "\n" + << strgettext("- Address: ") << address; + } else { + os << mode << strgettext("Hosting server"); + } + os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n"; + } else { + os << mode << strgettext("Singleplayer") << "\n"; + } + if (simple_singleplayer_mode || address.empty()) { + static const std::string on = strgettext("On"); + static const std::string off = strgettext("Off"); + const std::string &damage = g_settings->getBool("enable_damage") ? on : off; + const std::string &creative = g_settings->getBool("creative_mode") ? on : off; + const std::string &announced = g_settings->getBool("server_announce") ? on : off; + os << strgettext("- Damage: ") << damage << "\n" + << strgettext("- Creative Mode: ") << creative << "\n"; + if (!simple_singleplayer_mode) { + const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off; + os << strgettext("- PvP: ") << pvp << "\n" + << strgettext("- Public: ") << announced << "\n"; + std::string server_name = g_settings->get("server_name"); + str_formspec_escape(server_name); + if (announced == on && !server_name.empty()) + os << strgettext("- Server Name: ") << server_name; + + } + } + os << ";]"; + + /* Create menu */ + /* Note: FormspecFormSource and LocalFormspecHandler * + * are deleted by guiFormSpecMenu */ + FormspecFormSource *fs_src = new FormspecFormSource(os.str()); + LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU"); + + GUIFormSpecMenu::create(current_formspec, client, &input->joystick, + fs_src, txt_dst, client->getFormspecPrepend()); + current_formspec->setFocus("btn_continue"); + current_formspec->doPause = true; +} + +/****************************************************************************/ +/**************************************************************************** + extern function for launching the game + ****************************************************************************/ +/****************************************************************************/ + +void the_game(bool *kill, + bool random_input, + InputHandler *input, + const std::string &map_dir, + const std::string &playername, + const std::string &password, + const std::string &address, // If empty local server is created + u16 port, + + std::string &error_message, + ChatBackend &chat_backend, + bool *reconnect_requested, + const SubgameSpec &gamespec, // Used for local game + bool simple_singleplayer_mode) +{ + Game game; + + /* Make a copy of the server address because if a local singleplayer server + * is created then this is updated and we don't want to change the value + * passed to us by the calling function + */ + std::string server_address = address; + + try { + + if (game.startup(kill, random_input, input, map_dir, + playername, password, &server_address, port, error_message, + reconnect_requested, &chat_backend, gamespec, + simple_singleplayer_mode)) { + game.run(); + game.shutdown(); + } + + } catch (SerializationError &e) { + error_message = std::string("A serialization error occurred:\n") + + e.what() + "\n\nThe server is probably " + " running a different version of " PROJECT_NAME_C "."; + errorstream << error_message << std::endl; + } catch (ServerError &e) { + error_message = e.what(); + errorstream << "ServerError: " << error_message << std::endl; + } catch (ModError &e) { + error_message = e.what() + strgettext("\nCheck debug.txt for details."); + errorstream << "ModError: " << error_message << std::endl; + } +} diff --git a/src/client/game.h b/src/client/game.h new file mode 100644 index 000000000..69e6eed0b --- /dev/null +++ b/src/client/game.h @@ -0,0 +1,56 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include "irrlichttypes.h" +#include + +class InputHandler; +class ChatBackend; /* to avoid having to include chat.h */ +struct SubgameSpec; + +struct Jitter { + f32 max, min, avg, counter, max_sample, min_sample, max_fraction; +}; + +struct RunStats { + u32 drawtime; + + Jitter dtime_jitter, busy_time_jitter; +}; + +struct CameraOrientation { + f32 camera_yaw; // "right/left" + f32 camera_pitch; // "up/down" +}; + +void the_game(bool *kill, + bool random_input, + InputHandler *input, + const std::string &map_dir, + const std::string &playername, + const std::string &password, + const std::string &address, // If "", local server is used + u16 port, + std::string &error_message, + ChatBackend &chat_backend, + bool *reconnect_requested, + const SubgameSpec &gamespec, // Used for local game + bool simple_singleplayer_mode); diff --git a/src/client/guiscalingfilter.cpp b/src/client/guiscalingfilter.cpp new file mode 100644 index 000000000..3b4377da5 --- /dev/null +++ b/src/client/guiscalingfilter.cpp @@ -0,0 +1,169 @@ +/* +Copyright (C) 2015 Aaron Suen + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "guiscalingfilter.h" +#include "imagefilters.h" +#include "porting.h" +#include "settings.h" +#include "util/numeric.h" +#include +#include "client/renderingengine.h" + +/* Maintain a static cache to store the images that correspond to textures + * in a format that's manipulable by code. Some platforms exhibit issues + * converting textures back into images repeatedly, and some don't even + * allow it at all. + */ +std::map g_imgCache; + +/* Maintain a static cache of all pre-scaled textures. These need to be + * cleared as well when the cached images. + */ +std::map g_txrCache; + +/* Manually insert an image into the cache, useful to avoid texture-to-image + * conversion whenever we can intercept it. + */ +void guiScalingCache(io::path key, video::IVideoDriver *driver, video::IImage *value) +{ + if (!g_settings->getBool("gui_scaling_filter")) + return; + video::IImage *copied = driver->createImage(value->getColorFormat(), + value->getDimension()); + value->copyTo(copied); + g_imgCache[key] = copied; +} + +// Manually clear the cache, e.g. when switching to different worlds. +void guiScalingCacheClear() +{ + for (auto &it : g_imgCache) { + if (it.second) + it.second->drop(); + } + g_imgCache.clear(); + for (auto &it : g_txrCache) { + if (it.second) + RenderingEngine::get_video_driver()->removeTexture(it.second); + } + g_txrCache.clear(); +} + +/* Get a cached, high-quality pre-scaled texture for display purposes. If the + * texture is not already cached, attempt to create it. Returns a pre-scaled texture, + * or the original texture if unable to pre-scale it. + */ +video::ITexture *guiScalingResizeCached(video::IVideoDriver *driver, + video::ITexture *src, const core::rect &srcrect, + const core::rect &destrect) +{ + if (src == NULL) + return src; + if (!g_settings->getBool("gui_scaling_filter")) + return src; + + // Calculate scaled texture name. + char rectstr[200]; + porting::mt_snprintf(rectstr, sizeof(rectstr), "%d:%d:%d:%d:%d:%d", + srcrect.UpperLeftCorner.X, + srcrect.UpperLeftCorner.Y, + srcrect.getWidth(), + srcrect.getHeight(), + destrect.getWidth(), + destrect.getHeight()); + io::path origname = src->getName().getPath(); + io::path scalename = origname + "@guiScalingFilter:" + rectstr; + + // Search for existing scaled texture. + video::ITexture *scaled = g_txrCache[scalename]; + if (scaled) + return scaled; + + // Try to find the texture converted to an image in the cache. + // If the image was not found, try to extract it from the texture. + video::IImage* srcimg = g_imgCache[origname]; + if (srcimg == NULL) { + if (!g_settings->getBool("gui_scaling_filter_txr2img")) + return src; + srcimg = driver->createImageFromData(src->getColorFormat(), + src->getSize(), src->lock(), false); + src->unlock(); + g_imgCache[origname] = srcimg; + } + + // Create a new destination image and scale the source into it. + imageCleanTransparent(srcimg, 0); + video::IImage *destimg = driver->createImage(src->getColorFormat(), + core::dimension2d((u32)destrect.getWidth(), + (u32)destrect.getHeight())); + imageScaleNNAA(srcimg, srcrect, destimg); + +#ifdef __ANDROID__ + // Android is very picky about textures being powers of 2, so expand + // the image dimensions to the next power of 2, if necessary, for + // that platform. + video::IImage *po2img = driver->createImage(src->getColorFormat(), + core::dimension2d(npot2((u32)destrect.getWidth()), + npot2((u32)destrect.getHeight()))); + po2img->fill(video::SColor(0, 0, 0, 0)); + destimg->copyTo(po2img); + destimg->drop(); + destimg = po2img; +#endif + + // Convert the scaled image back into a texture. + scaled = driver->addTexture(scalename, destimg, NULL); + destimg->drop(); + g_txrCache[scalename] = scaled; + + return scaled; +} + +/* Convenience wrapper for guiScalingResizeCached that accepts parameters that + * are available at GUI imagebutton creation time. + */ +video::ITexture *guiScalingImageButton(video::IVideoDriver *driver, + video::ITexture *src, s32 width, s32 height) +{ + if (src == NULL) + return src; + return guiScalingResizeCached(driver, src, + core::rect(0, 0, src->getSize().Width, src->getSize().Height), + core::rect(0, 0, width, height)); +} + +/* Replacement for driver->draw2DImage() that uses the high-quality pre-scaled + * texture, if configured. + */ +void draw2DImageFilterScaled(video::IVideoDriver *driver, video::ITexture *txr, + const core::rect &destrect, const core::rect &srcrect, + const core::rect *cliprect, const video::SColor *const colors, + bool usealpha) +{ + // Attempt to pre-scale image in software in high quality. + video::ITexture *scaled = guiScalingResizeCached(driver, txr, srcrect, destrect); + if (scaled == NULL) + return; + + // Correct source rect based on scaled image. + const core::rect mysrcrect = (scaled != txr) + ? core::rect(0, 0, destrect.getWidth(), destrect.getHeight()) + : srcrect; + + driver->draw2DImage(scaled, destrect, mysrcrect, cliprect, colors, usealpha); +} diff --git a/src/client/guiscalingfilter.h b/src/client/guiscalingfilter.h new file mode 100644 index 000000000..4661bf8da --- /dev/null +++ b/src/client/guiscalingfilter.h @@ -0,0 +1,50 @@ +/* +Copyright (C) 2015 Aaron Suen + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include "irrlichttypes_extrabloated.h" + +/* Manually insert an image into the cache, useful to avoid texture-to-image + * conversion whenever we can intercept it. + */ +void guiScalingCache(io::path key, video::IVideoDriver *driver, video::IImage *value); + +// Manually clear the cache, e.g. when switching to different worlds. +void guiScalingCacheClear(); + +/* Get a cached, high-quality pre-scaled texture for display purposes. If the + * texture is not already cached, attempt to create it. Returns a pre-scaled texture, + * or the original texture if unable to pre-scale it. + */ +video::ITexture *guiScalingResizeCached(video::IVideoDriver *driver, video::ITexture *src, + const core::rect &srcrect, const core::rect &destrect); + +/* Convenience wrapper for guiScalingResizeCached that accepts parameters that + * are available at GUI imagebutton creation time. + */ +video::ITexture *guiScalingImageButton(video::IVideoDriver *driver, video::ITexture *src, + s32 width, s32 height); + +/* Replacement for driver->draw2DImage() that uses the high-quality pre-scaled + * texture, if configured. + */ +void draw2DImageFilterScaled(video::IVideoDriver *driver, video::ITexture *txr, + const core::rect &destrect, const core::rect &srcrect, + const core::rect *cliprect = 0, const video::SColor *const colors = 0, + bool usealpha = false); diff --git a/src/client/imagefilters.cpp b/src/client/imagefilters.cpp new file mode 100644 index 000000000..dd029628f --- /dev/null +++ b/src/client/imagefilters.cpp @@ -0,0 +1,172 @@ +/* +Copyright (C) 2015 Aaron Suen + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "imagefilters.h" +#include "util/numeric.h" +#include + +/* Fill in RGB values for transparent pixels, to correct for odd colors + * appearing at borders when blending. This is because many PNG optimizers + * like to discard RGB values of transparent pixels, but when blending then + * with non-transparent neighbors, their RGB values will shpw up nonetheless. + * + * This function modifies the original image in-place. + * + * Parameter "threshold" is the alpha level below which pixels are considered + * transparent. Should be 127 for 3d where alpha is threshold, but 0 for + * 2d where alpha is blended. + */ +void imageCleanTransparent(video::IImage *src, u32 threshold) +{ + core::dimension2d dim = src->getDimension(); + + // Walk each pixel looking for fully transparent ones. + // Note: loop y around x for better cache locality. + for (u32 ctry = 0; ctry < dim.Height; ctry++) + for (u32 ctrx = 0; ctrx < dim.Width; ctrx++) { + + // Ignore opaque pixels. + irr::video::SColor c = src->getPixel(ctrx, ctry); + if (c.getAlpha() > threshold) + continue; + + // Sample size and total weighted r, g, b values. + u32 ss = 0, sr = 0, sg = 0, sb = 0; + + // Walk each neighbor pixel (clipped to image bounds). + for (u32 sy = (ctry < 1) ? 0 : (ctry - 1); + sy <= (ctry + 1) && sy < dim.Height; sy++) + for (u32 sx = (ctrx < 1) ? 0 : (ctrx - 1); + sx <= (ctrx + 1) && sx < dim.Width; sx++) { + + // Ignore transparent pixels. + irr::video::SColor d = src->getPixel(sx, sy); + if (d.getAlpha() <= threshold) + continue; + + // Add RGB values weighted by alpha. + u32 a = d.getAlpha(); + ss += a; + sr += a * d.getRed(); + sg += a * d.getGreen(); + sb += a * d.getBlue(); + } + + // If we found any neighbor RGB data, set pixel to average + // weighted by alpha. + if (ss > 0) { + c.setRed(sr / ss); + c.setGreen(sg / ss); + c.setBlue(sb / ss); + src->setPixel(ctrx, ctry, c); + } + } +} + +/* Scale a region of an image into another image, using nearest-neighbor with + * anti-aliasing; treat pixels as crisp rectangles, but blend them at boundaries + * to prevent non-integer scaling ratio artifacts. Note that this may cause + * some blending at the edges where pixels don't line up perfectly, but this + * filter is designed to produce the most accurate results for both upscaling + * and downscaling. + */ +void imageScaleNNAA(video::IImage *src, const core::rect &srcrect, video::IImage *dest) +{ + double sx, sy, minsx, maxsx, minsy, maxsy, area, ra, ga, ba, aa, pw, ph, pa; + u32 dy, dx; + video::SColor pxl; + + // Cache rectsngle boundaries. + double sox = srcrect.UpperLeftCorner.X * 1.0; + double soy = srcrect.UpperLeftCorner.Y * 1.0; + double sw = srcrect.getWidth() * 1.0; + double sh = srcrect.getHeight() * 1.0; + + // Walk each destination image pixel. + // Note: loop y around x for better cache locality. + core::dimension2d dim = dest->getDimension(); + for (dy = 0; dy < dim.Height; dy++) + for (dx = 0; dx < dim.Width; dx++) { + + // Calculate floating-point source rectangle bounds. + // Do some basic clipping, and for mirrored/flipped rects, + // make sure min/max are in the right order. + minsx = sox + (dx * sw / dim.Width); + minsx = rangelim(minsx, 0, sw); + maxsx = minsx + sw / dim.Width; + maxsx = rangelim(maxsx, 0, sw); + if (minsx > maxsx) + SWAP(double, minsx, maxsx); + minsy = soy + (dy * sh / dim.Height); + minsy = rangelim(minsy, 0, sh); + maxsy = minsy + sh / dim.Height; + maxsy = rangelim(maxsy, 0, sh); + if (minsy > maxsy) + SWAP(double, minsy, maxsy); + + // Total area, and integral of r, g, b values over that area, + // initialized to zero, to be summed up in next loops. + area = 0; + ra = 0; + ga = 0; + ba = 0; + aa = 0; + + // Loop over the integral pixel positions described by those bounds. + for (sy = floor(minsy); sy < maxsy; sy++) + for (sx = floor(minsx); sx < maxsx; sx++) { + + // Calculate width, height, then area of dest pixel + // that's covered by this source pixel. + pw = 1; + if (minsx > sx) + pw += sx - minsx; + if (maxsx < (sx + 1)) + pw += maxsx - sx - 1; + ph = 1; + if (minsy > sy) + ph += sy - minsy; + if (maxsy < (sy + 1)) + ph += maxsy - sy - 1; + pa = pw * ph; + + // Get source pixel and add it to totals, weighted + // by covered area and alpha. + pxl = src->getPixel((u32)sx, (u32)sy); + area += pa; + ra += pa * pxl.getRed(); + ga += pa * pxl.getGreen(); + ba += pa * pxl.getBlue(); + aa += pa * pxl.getAlpha(); + } + + // Set the destination image pixel to the average color. + if (area > 0) { + pxl.setRed(ra / area + 0.5); + pxl.setGreen(ga / area + 0.5); + pxl.setBlue(ba / area + 0.5); + pxl.setAlpha(aa / area + 0.5); + } else { + pxl.setRed(0); + pxl.setGreen(0); + pxl.setBlue(0); + pxl.setAlpha(0); + } + dest->setPixel(dx, dy, pxl); + } +} diff --git a/src/client/imagefilters.h b/src/client/imagefilters.h new file mode 100644 index 000000000..5676faf85 --- /dev/null +++ b/src/client/imagefilters.h @@ -0,0 +1,43 @@ +/* +Copyright (C) 2015 Aaron Suen + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include "irrlichttypes_extrabloated.h" + +/* Fill in RGB values for transparent pixels, to correct for odd colors + * appearing at borders when blending. This is because many PNG optimizers + * like to discard RGB values of transparent pixels, but when blending then + * with non-transparent neighbors, their RGB values will shpw up nonetheless. + * + * This function modifies the original image in-place. + * + * Parameter "threshold" is the alpha level below which pixels are considered + * transparent. Should be 127 for 3d where alpha is threshold, but 0 for + * 2d where alpha is blended. + */ +void imageCleanTransparent(video::IImage *src, u32 threshold); + +/* Scale a region of an image into another image, using nearest-neighbor with + * anti-aliasing; treat pixels as crisp rectangles, but blend them at boundaries + * to prevent non-integer scaling ratio artifacts. Note that this may cause + * some blending at the edges where pixels don't line up perfectly, but this + * filter is designed to produce the most accurate results for both upscaling + * and downscaling. + */ +void imageScaleNNAA(video::IImage *src, const core::rect &srcrect, video::IImage *dest); diff --git a/src/client/keycode.cpp b/src/client/keycode.cpp new file mode 100644 index 000000000..646d181e0 --- /dev/null +++ b/src/client/keycode.cpp @@ -0,0 +1,384 @@ +/* +Minetest +Copyright (C) 2010-2013 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "keycode.h" +#include "exceptions.h" +#include "settings.h" +#include "log.h" +#include "debug.h" +#include "util/hex.h" +#include "util/string.h" +#include "util/basic_macros.h" + +class UnknownKeycode : public BaseException +{ +public: + UnknownKeycode(const char *s) : + BaseException(s) {}; +}; + +struct table_key { + const char *Name; + irr::EKEY_CODE Key; + wchar_t Char; // L'\0' means no character assigned + const char *LangName; // NULL means it doesn't have a human description +}; + +#define DEFINEKEY1(x, lang) /* Irrlicht key without character */ \ + { #x, irr::x, L'\0', lang }, +#define DEFINEKEY2(x, ch, lang) /* Irrlicht key with character */ \ + { #x, irr::x, ch, lang }, +#define DEFINEKEY3(ch) /* single Irrlicht key (e.g. KEY_KEY_X) */ \ + { "KEY_KEY_" TOSTRING(ch), irr::KEY_KEY_ ## ch, (wchar_t) *TOSTRING(ch), TOSTRING(ch) }, +#define DEFINEKEY4(ch) /* single Irrlicht function key (e.g. KEY_F3) */ \ + { "KEY_F" TOSTRING(ch), irr::KEY_F ## ch, L'\0', "F" TOSTRING(ch) }, +#define DEFINEKEY5(ch) /* key without Irrlicht keycode */ \ + { ch, irr::KEY_KEY_CODES_COUNT, (wchar_t) *ch, ch }, + +#define N_(text) text + +static const struct table_key table[] = { + // Keys that can be reliably mapped between Char and Key + DEFINEKEY3(0) + DEFINEKEY3(1) + DEFINEKEY3(2) + DEFINEKEY3(3) + DEFINEKEY3(4) + DEFINEKEY3(5) + DEFINEKEY3(6) + DEFINEKEY3(7) + DEFINEKEY3(8) + DEFINEKEY3(9) + DEFINEKEY3(A) + DEFINEKEY3(B) + DEFINEKEY3(C) + DEFINEKEY3(D) + DEFINEKEY3(E) + DEFINEKEY3(F) + DEFINEKEY3(G) + DEFINEKEY3(H) + DEFINEKEY3(I) + DEFINEKEY3(J) + DEFINEKEY3(K) + DEFINEKEY3(L) + DEFINEKEY3(M) + DEFINEKEY3(N) + DEFINEKEY3(O) + DEFINEKEY3(P) + DEFINEKEY3(Q) + DEFINEKEY3(R) + DEFINEKEY3(S) + DEFINEKEY3(T) + DEFINEKEY3(U) + DEFINEKEY3(V) + DEFINEKEY3(W) + DEFINEKEY3(X) + DEFINEKEY3(Y) + DEFINEKEY3(Z) + DEFINEKEY2(KEY_PLUS, L'+', "+") + DEFINEKEY2(KEY_COMMA, L',', ",") + DEFINEKEY2(KEY_MINUS, L'-', "-") + DEFINEKEY2(KEY_PERIOD, L'.', ".") + + // Keys without a Char + DEFINEKEY1(KEY_LBUTTON, N_("Left Button")) + DEFINEKEY1(KEY_RBUTTON, N_("Right Button")) + DEFINEKEY1(KEY_CANCEL, N_("Cancel")) + DEFINEKEY1(KEY_MBUTTON, N_("Middle Button")) + DEFINEKEY1(KEY_XBUTTON1, N_("X Button 1")) + DEFINEKEY1(KEY_XBUTTON2, N_("X Button 2")) + DEFINEKEY1(KEY_BACK, N_("Backspace")) + DEFINEKEY1(KEY_TAB, N_("Tab")) + DEFINEKEY1(KEY_CLEAR, N_("Clear")) + DEFINEKEY1(KEY_RETURN, N_("Return")) + DEFINEKEY1(KEY_SHIFT, N_("Shift")) + DEFINEKEY1(KEY_CONTROL, N_("Control")) + DEFINEKEY1(KEY_MENU, N_("Menu")) + DEFINEKEY1(KEY_PAUSE, N_("Pause")) + DEFINEKEY1(KEY_CAPITAL, N_("Caps Lock")) + DEFINEKEY1(KEY_SPACE, N_("Space")) + DEFINEKEY1(KEY_PRIOR, N_("Page up")) + DEFINEKEY1(KEY_NEXT, N_("Page down")) + DEFINEKEY1(KEY_END, N_("End")) + DEFINEKEY1(KEY_HOME, N_("Home")) + DEFINEKEY1(KEY_LEFT, N_("Left")) + DEFINEKEY1(KEY_UP, N_("Up")) + DEFINEKEY1(KEY_RIGHT, N_("Right")) + DEFINEKEY1(KEY_DOWN, N_("Down")) + DEFINEKEY1(KEY_SELECT, N_("Select")) + DEFINEKEY1(KEY_PRINT, N_("Print")) + DEFINEKEY1(KEY_EXECUT, N_("Execute")) + DEFINEKEY1(KEY_SNAPSHOT, N_("Snapshot")) + DEFINEKEY1(KEY_INSERT, N_("Insert")) + DEFINEKEY1(KEY_DELETE, N_("Delete")) + DEFINEKEY1(KEY_HELP, N_("Help")) + DEFINEKEY1(KEY_LWIN, N_("Left Windows")) + DEFINEKEY1(KEY_RWIN, N_("Right Windows")) + DEFINEKEY1(KEY_NUMPAD0, N_("Numpad 0")) // These are not assigned to a char + DEFINEKEY1(KEY_NUMPAD1, N_("Numpad 1")) // to prevent interference with KEY_KEY_[0-9]. + DEFINEKEY1(KEY_NUMPAD2, N_("Numpad 2")) + DEFINEKEY1(KEY_NUMPAD3, N_("Numpad 3")) + DEFINEKEY1(KEY_NUMPAD4, N_("Numpad 4")) + DEFINEKEY1(KEY_NUMPAD5, N_("Numpad 5")) + DEFINEKEY1(KEY_NUMPAD6, N_("Numpad 6")) + DEFINEKEY1(KEY_NUMPAD7, N_("Numpad 7")) + DEFINEKEY1(KEY_NUMPAD8, N_("Numpad 8")) + DEFINEKEY1(KEY_NUMPAD9, N_("Numpad 9")) + DEFINEKEY1(KEY_MULTIPLY, N_("Numpad *")) + DEFINEKEY1(KEY_ADD, N_("Numpad +")) + DEFINEKEY1(KEY_SEPARATOR, N_("Numpad .")) + DEFINEKEY1(KEY_SUBTRACT, N_("Numpad -")) + DEFINEKEY1(KEY_DECIMAL, NULL) + DEFINEKEY1(KEY_DIVIDE, N_("Numpad /")) + DEFINEKEY4(1) + DEFINEKEY4(2) + DEFINEKEY4(3) + DEFINEKEY4(4) + DEFINEKEY4(5) + DEFINEKEY4(6) + DEFINEKEY4(7) + DEFINEKEY4(8) + DEFINEKEY4(9) + DEFINEKEY4(10) + DEFINEKEY4(11) + DEFINEKEY4(12) + DEFINEKEY4(13) + DEFINEKEY4(14) + DEFINEKEY4(15) + DEFINEKEY4(16) + DEFINEKEY4(17) + DEFINEKEY4(18) + DEFINEKEY4(19) + DEFINEKEY4(20) + DEFINEKEY4(21) + DEFINEKEY4(22) + DEFINEKEY4(23) + DEFINEKEY4(24) + DEFINEKEY1(KEY_NUMLOCK, N_("Num Lock")) + DEFINEKEY1(KEY_SCROLL, N_("Scroll Lock")) + DEFINEKEY1(KEY_LSHIFT, N_("Left Shift")) + DEFINEKEY1(KEY_RSHIFT, N_("Right Shift")) + DEFINEKEY1(KEY_LCONTROL, N_("Left Control")) + DEFINEKEY1(KEY_RCONTROL, N_("Right Control")) + DEFINEKEY1(KEY_LMENU, N_("Left Menu")) + DEFINEKEY1(KEY_RMENU, N_("Right Menu")) + + // Rare/weird keys + DEFINEKEY1(KEY_KANA, "Kana") + DEFINEKEY1(KEY_HANGUEL, "Hangul") + DEFINEKEY1(KEY_HANGUL, "Hangul") + DEFINEKEY1(KEY_JUNJA, "Junja") + DEFINEKEY1(KEY_FINAL, "Final") + DEFINEKEY1(KEY_KANJI, "Kanji") + DEFINEKEY1(KEY_HANJA, "Hanja") + DEFINEKEY1(KEY_ESCAPE, N_("IME Escape")) + DEFINEKEY1(KEY_CONVERT, N_("IME Convert")) + DEFINEKEY1(KEY_NONCONVERT, N_("IME Nonconvert")) + DEFINEKEY1(KEY_ACCEPT, N_("IME Accept")) + DEFINEKEY1(KEY_MODECHANGE, N_("IME Mode Change")) + DEFINEKEY1(KEY_APPS, N_("Apps")) + DEFINEKEY1(KEY_SLEEP, N_("Sleep")) +#if !(IRRLICHT_VERSION_MAJOR <= 1 && IRRLICHT_VERSION_MINOR <= 7 && IRRLICHT_VERSION_REVISION < 3) + DEFINEKEY1(KEY_OEM_1, "OEM 1") // KEY_OEM_[0-9] and KEY_OEM_102 are assigned to multiple + DEFINEKEY1(KEY_OEM_2, "OEM 2") // different chars (on different platforms too) and thus w/o char + DEFINEKEY1(KEY_OEM_3, "OEM 3") + DEFINEKEY1(KEY_OEM_4, "OEM 4") + DEFINEKEY1(KEY_OEM_5, "OEM 5") + DEFINEKEY1(KEY_OEM_6, "OEM 6") + DEFINEKEY1(KEY_OEM_7, "OEM 7") + DEFINEKEY1(KEY_OEM_8, "OEM 8") + DEFINEKEY1(KEY_OEM_AX, "OEM AX") + DEFINEKEY1(KEY_OEM_102, "OEM 102") +#endif + DEFINEKEY1(KEY_ATTN, "Attn") + DEFINEKEY1(KEY_CRSEL, "CrSel") + DEFINEKEY1(KEY_EXSEL, "ExSel") + DEFINEKEY1(KEY_EREOF, N_("Erase EOF")) + DEFINEKEY1(KEY_PLAY, N_("Play")) + DEFINEKEY1(KEY_ZOOM, N_("Zoom")) + DEFINEKEY1(KEY_PA1, "PA1") + DEFINEKEY1(KEY_OEM_CLEAR, N_("OEM Clear")) + + // Keys without Irrlicht keycode + DEFINEKEY5("!") + DEFINEKEY5("\"") + DEFINEKEY5("#") + DEFINEKEY5("$") + DEFINEKEY5("%") + DEFINEKEY5("&") + DEFINEKEY5("'") + DEFINEKEY5("(") + DEFINEKEY5(")") + DEFINEKEY5("*") + DEFINEKEY5("/") + DEFINEKEY5(":") + DEFINEKEY5(";") + DEFINEKEY5("<") + DEFINEKEY5("=") + DEFINEKEY5(">") + DEFINEKEY5("?") + DEFINEKEY5("@") + DEFINEKEY5("[") + DEFINEKEY5("\\") + DEFINEKEY5("]") + DEFINEKEY5("^") + DEFINEKEY5("_") +}; + +#undef N_ + + +struct table_key lookup_keyname(const char *name) +{ + for (const auto &table_key : table) { + if (strcmp(table_key.Name, name) == 0) + return table_key; + } + + throw UnknownKeycode(name); +} + +struct table_key lookup_keykey(irr::EKEY_CODE key) +{ + for (const auto &table_key : table) { + if (table_key.Key == key) + return table_key; + } + + std::ostringstream os; + os << ""; + throw UnknownKeycode(os.str().c_str()); +} + +struct table_key lookup_keychar(wchar_t Char) +{ + for (const auto &table_key : table) { + if (table_key.Char == Char) + return table_key; + } + + std::ostringstream os; + os << ""; + throw UnknownKeycode(os.str().c_str()); +} + +KeyPress::KeyPress(const char *name) +{ + if (strlen(name) == 0) { + Key = irr::KEY_KEY_CODES_COUNT; + Char = L'\0'; + m_name = ""; + return; + } + + if (strlen(name) <= 4) { + // Lookup by resulting character + int chars_read = mbtowc(&Char, name, 1); + FATAL_ERROR_IF(chars_read != 1, "Unexpected multibyte character"); + try { + struct table_key k = lookup_keychar(Char); + m_name = k.Name; + Key = k.Key; + return; + } catch (UnknownKeycode &e) {}; + } else { + // Lookup by name + m_name = name; + try { + struct table_key k = lookup_keyname(name); + Key = k.Key; + Char = k.Char; + return; + } catch (UnknownKeycode &e) {}; + } + + // It's not a known key, complain and try to do something + Key = irr::KEY_KEY_CODES_COUNT; + int chars_read = mbtowc(&Char, name, 1); + FATAL_ERROR_IF(chars_read != 1, "Unexpected multibyte character"); + m_name = ""; + warningstream << "KeyPress: Unknown key '" << name << "', falling back to first char."; +} + +KeyPress::KeyPress(const irr::SEvent::SKeyInput &in, bool prefer_character) +{ + if (prefer_character) + Key = irr::KEY_KEY_CODES_COUNT; + else + Key = in.Key; + Char = in.Char; + + try { + if (valid_kcode(Key)) + m_name = lookup_keykey(Key).Name; + else + m_name = lookup_keychar(Char).Name; + } catch (UnknownKeycode &e) { + m_name = ""; + }; +} + +const char *KeyPress::sym() const +{ + return m_name.c_str(); +} + +const char *KeyPress::name() const +{ + if (m_name.empty()) + return ""; + const char *ret; + if (valid_kcode(Key)) + ret = lookup_keykey(Key).LangName; + else + ret = lookup_keychar(Char).LangName; + return ret ? ret : ""; +} + +const KeyPress EscapeKey("KEY_ESCAPE"); +const KeyPress CancelKey("KEY_CANCEL"); + +/* + Key config +*/ + +// A simple cache for quicker lookup +std::unordered_map g_key_setting_cache; + +KeyPress getKeySetting(const char *settingname) +{ + std::unordered_map::iterator n; + n = g_key_setting_cache.find(settingname); + if (n != g_key_setting_cache.end()) + return n->second; + + KeyPress k(g_settings->get(settingname).c_str()); + g_key_setting_cache[settingname] = k; + return k; +} + +void clearKeyCache() +{ + g_key_setting_cache.clear(); +} + +irr::EKEY_CODE keyname_to_keycode(const char *name) +{ + return lookup_keyname(name).Key; +} diff --git a/src/client/keycode.h b/src/client/keycode.h new file mode 100644 index 000000000..7036705d1 --- /dev/null +++ b/src/client/keycode.h @@ -0,0 +1,67 @@ +/* +Minetest +Copyright (C) 2010-2013 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include "irrlichttypes.h" +#include "Keycodes.h" +#include +#include + +/* A key press, consisting of either an Irrlicht keycode + or an actual char */ + +class KeyPress +{ +public: + KeyPress() = default; + + KeyPress(const char *name); + + KeyPress(const irr::SEvent::SKeyInput &in, bool prefer_character = false); + + bool operator==(const KeyPress &o) const + { + return (Char > 0 && Char == o.Char) || (valid_kcode(Key) && Key == o.Key); + } + + const char *sym() const; + const char *name() const; + +protected: + static bool valid_kcode(irr::EKEY_CODE k) + { + return k > 0 && k < irr::KEY_KEY_CODES_COUNT; + } + + irr::EKEY_CODE Key = irr::KEY_KEY_CODES_COUNT; + wchar_t Char = L'\0'; + std::string m_name = ""; +}; + +extern const KeyPress EscapeKey; +extern const KeyPress CancelKey; + +// Key configuration getter +KeyPress getKeySetting(const char *settingname); + +// Clear fast lookup cache +void clearKeyCache(); + +irr::EKEY_CODE keyname_to_keycode(const char *name); diff --git a/src/client/localplayer.cpp b/src/client/localplayer.cpp new file mode 100644 index 000000000..1c65d3b4d --- /dev/null +++ b/src/client/localplayer.cpp @@ -0,0 +1,1158 @@ +/* +Minetest +Copyright (C) 2010-2013 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "localplayer.h" +#include +#include "event.h" +#include "collision.h" +#include "nodedef.h" +#include "settings.h" +#include "environment.h" +#include "map.h" +#include "client.h" +#include "content_cao.h" + +/* + LocalPlayer +*/ + +LocalPlayer::LocalPlayer(Client *client, const char *name): + Player(name, client->idef()), + m_client(client) +{ +} + +static aabb3f getNodeBoundingBox(const std::vector &nodeboxes) +{ + if (nodeboxes.empty()) + return aabb3f(0, 0, 0, 0, 0, 0); + + aabb3f b_max; + + std::vector::const_iterator it = nodeboxes.begin(); + b_max = aabb3f(it->MinEdge, it->MaxEdge); + + ++it; + for (; it != nodeboxes.end(); ++it) + b_max.addInternalBox(*it); + + return b_max; +} + +bool LocalPlayer::updateSneakNode(Map *map, const v3f &position, + const v3f &sneak_max) +{ + static const v3s16 dir9_center[9] = { + v3s16( 0, 0, 0), + v3s16( 1, 0, 0), + v3s16(-1, 0, 0), + v3s16( 0, 0, 1), + v3s16( 0, 0, -1), + v3s16( 1, 0, 1), + v3s16(-1, 0, 1), + v3s16( 1, 0, -1), + v3s16(-1, 0, -1) + }; + + const NodeDefManager *nodemgr = m_client->ndef(); + MapNode node; + bool is_valid_position; + bool new_sneak_node_exists = m_sneak_node_exists; + + // We want the top of the sneak node to be below the players feet + f32 position_y_mod = 0.05 * BS; + if (m_sneak_node_exists) + position_y_mod = m_sneak_node_bb_top.MaxEdge.Y - position_y_mod; + + // Get position of current standing node + const v3s16 current_node = floatToInt(position - v3f(0, position_y_mod, 0), BS); + + if (current_node != m_sneak_node) { + new_sneak_node_exists = false; + } else { + node = map->getNodeNoEx(current_node, &is_valid_position); + if (!is_valid_position || !nodemgr->get(node).walkable) + new_sneak_node_exists = false; + } + + // Keep old sneak node + if (new_sneak_node_exists) + return true; + + // Get new sneak node + m_sneak_ladder_detected = false; + f32 min_distance_f = 100000.0 * BS; + + for (const auto &d : dir9_center) { + const v3s16 p = current_node + d; + const v3f pf = intToFloat(p, BS); + const v2f diff(position.X - pf.X, position.Z - pf.Z); + f32 distance_f = diff.getLength(); + + if (distance_f > min_distance_f || + fabs(diff.X) > (.5 + .1) * BS + sneak_max.X || + fabs(diff.Y) > (.5 + .1) * BS + sneak_max.Z) + continue; + + + // The node to be sneaked on has to be walkable + node = map->getNodeNoEx(p, &is_valid_position); + if (!is_valid_position || !nodemgr->get(node).walkable) + continue; + // And the node(s) above have to be nonwalkable + bool ok = true; + if (!physics_override_sneak_glitch) { + u16 height = ceilf( + (m_collisionbox.MaxEdge.Y - m_collisionbox.MinEdge.Y) / BS + ); + for (u16 y = 1; y <= height; y++) { + node = map->getNodeNoEx(p + v3s16(0, y, 0), &is_valid_position); + if (!is_valid_position || nodemgr->get(node).walkable) { + ok = false; + break; + } + } + } else { + // legacy behaviour: check just one node + node = map->getNodeNoEx(p + v3s16(0, 1, 0), &is_valid_position); + ok = is_valid_position && !nodemgr->get(node).walkable; + } + if (!ok) + continue; + + min_distance_f = distance_f; + m_sneak_node = p; + new_sneak_node_exists = true; + } + + if (!new_sneak_node_exists) + return false; + + // Update saved top bounding box of sneak node + node = map->getNodeNoEx(m_sneak_node); + std::vector nodeboxes; + node.getCollisionBoxes(nodemgr, &nodeboxes); + m_sneak_node_bb_top = getNodeBoundingBox(nodeboxes); + + if (physics_override_sneak_glitch) { + // Detect sneak ladder: + // Node two meters above sneak node must be solid + node = map->getNodeNoEx(m_sneak_node + v3s16(0, 2, 0), + &is_valid_position); + if (is_valid_position && nodemgr->get(node).walkable) { + // Node three meters above: must be non-solid + node = map->getNodeNoEx(m_sneak_node + v3s16(0, 3, 0), + &is_valid_position); + m_sneak_ladder_detected = is_valid_position && + !nodemgr->get(node).walkable; + } + } + return true; +} + +void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, + std::vector *collision_info) +{ + if (!collision_info || collision_info->empty()) { + // Node below the feet, update each ClientEnvironment::step() + m_standing_node = floatToInt(m_position, BS) - v3s16(0, 1, 0); + } + + // Temporary option for old move code + if (!physics_override_new_move) { + old_move(dtime, env, pos_max_d, collision_info); + return; + } + + Map *map = &env->getMap(); + const NodeDefManager *nodemgr = m_client->ndef(); + + v3f position = getPosition(); + + // Copy parent position if local player is attached + if (isAttached) { + setPosition(overridePosition); + return; + } + + PlayerSettings &player_settings = getPlayerSettings(); + + // Skip collision detection if noclip mode is used + bool fly_allowed = m_client->checkLocalPrivilege("fly"); + bool noclip = m_client->checkLocalPrivilege("noclip") && player_settings.noclip; + bool free_move = player_settings.free_move && fly_allowed; + + if (noclip && free_move) { + position += m_speed * dtime; + setPosition(position); + return; + } + + /* + Collision detection + */ + + bool is_valid_position; + MapNode node; + v3s16 pp; + + /* + Check if player is in liquid (the oscillating value) + */ + + // If in liquid, the threshold of coming out is at higher y + if (in_liquid) + { + pp = floatToInt(position + v3f(0,BS*0.1,0), BS); + node = map->getNodeNoEx(pp, &is_valid_position); + if (is_valid_position) { + in_liquid = nodemgr->get(node.getContent()).isLiquid(); + liquid_viscosity = nodemgr->get(node.getContent()).liquid_viscosity; + } else { + in_liquid = false; + } + } + // If not in liquid, the threshold of going in is at lower y + else + { + pp = floatToInt(position + v3f(0,BS*0.5,0), BS); + node = map->getNodeNoEx(pp, &is_valid_position); + if (is_valid_position) { + in_liquid = nodemgr->get(node.getContent()).isLiquid(); + liquid_viscosity = nodemgr->get(node.getContent()).liquid_viscosity; + } else { + in_liquid = false; + } + } + + + /* + Check if player is in liquid (the stable value) + */ + pp = floatToInt(position + v3f(0,0,0), BS); + node = map->getNodeNoEx(pp, &is_valid_position); + if (is_valid_position) { + in_liquid_stable = nodemgr->get(node.getContent()).isLiquid(); + } else { + in_liquid_stable = false; + } + + /* + Check if player is climbing + */ + + + pp = floatToInt(position + v3f(0,0.5*BS,0), BS); + v3s16 pp2 = floatToInt(position + v3f(0,-0.2*BS,0), BS); + node = map->getNodeNoEx(pp, &is_valid_position); + bool is_valid_position2; + MapNode node2 = map->getNodeNoEx(pp2, &is_valid_position2); + + if (!(is_valid_position && is_valid_position2)) { + is_climbing = false; + } else { + is_climbing = (nodemgr->get(node.getContent()).climbable + || nodemgr->get(node2.getContent()).climbable) && !free_move; + } + + /* + Collision uncertainty radius + Make it a bit larger than the maximum distance of movement + */ + //f32 d = pos_max_d * 1.1; + // A fairly large value in here makes moving smoother + f32 d = 0.15*BS; + + // This should always apply, otherwise there are glitches + sanity_check(d > pos_max_d); + + // Player object property step height is multiplied by BS in + // /src/script/common/c_content.cpp and /src/content_sao.cpp + float player_stepheight = (m_cao == nullptr) ? 0.0f : + (touching_ground ? m_cao->getStepHeight() : (0.2f * BS)); + + v3f accel_f = v3f(0,0,0); + const v3f initial_position = position; + const v3f initial_speed = m_speed; + + collisionMoveResult result = collisionMoveSimple(env, m_client, + pos_max_d, m_collisionbox, player_stepheight, dtime, + &position, &m_speed, accel_f); + + bool could_sneak = control.sneak && !free_move && !in_liquid && + !is_climbing && physics_override_sneak; + + // Add new collisions to the vector + if (collision_info && !free_move) { + v3f diff = intToFloat(m_standing_node, BS) - position; + f32 distance = diff.getLength(); + // Force update each ClientEnvironment::step() + bool is_first = collision_info->empty(); + + for (const auto &colinfo : result.collisions) { + collision_info->push_back(colinfo); + + if (colinfo.type != COLLISION_NODE || + colinfo.new_speed.Y != 0 || + (could_sneak && m_sneak_node_exists)) + continue; + + diff = intToFloat(colinfo.node_p, BS) - position; + + // Find nearest colliding node + f32 len = diff.getLength(); + if (is_first || len < distance) { + m_standing_node = colinfo.node_p; + distance = len; + } + } + } + + /* + If the player's feet touch the topside of any node, this is + set to true. + + Player is allowed to jump when this is true. + */ + bool touching_ground_was = touching_ground; + touching_ground = result.touching_ground; + bool sneak_can_jump = false; + + // Max. distance (X, Z) over border for sneaking determined by collision box + // * 0.49 to keep the center just barely on the node + v3f sneak_max = m_collisionbox.getExtent() * 0.49; + + if (m_sneak_ladder_detected) { + // restore legacy behaviour (this makes the m_speed.Y hack necessary) + sneak_max = v3f(0.4 * BS, 0, 0.4 * BS); + } + + /* + If sneaking, keep on top of last walked node and don't fall off + */ + if (could_sneak && m_sneak_node_exists) { + const v3f sn_f = intToFloat(m_sneak_node, BS); + const v3f bmin = sn_f + m_sneak_node_bb_top.MinEdge; + const v3f bmax = sn_f + m_sneak_node_bb_top.MaxEdge; + const v3f old_pos = position; + const v3f old_speed = m_speed; + f32 y_diff = bmax.Y - position.Y; + m_standing_node = m_sneak_node; + + // (BS * 0.6f) is the basic stepheight while standing on ground + if (y_diff < BS * 0.6f) { + // Only center player when they're on the node + position.X = rangelim(position.X, + bmin.X - sneak_max.X, bmax.X + sneak_max.X); + position.Z = rangelim(position.Z, + bmin.Z - sneak_max.Z, bmax.Z + sneak_max.Z); + + if (position.X != old_pos.X) + m_speed.X = 0; + if (position.Z != old_pos.Z) + m_speed.Z = 0; + } + + if (y_diff > 0 && m_speed.Y <= 0 && + (physics_override_sneak_glitch || y_diff < BS * 0.6f)) { + // Move player to the maximal height when falling or when + // the ledge is climbed on the next step. + + // Smoothen the movement (based on 'position.Y = bmax.Y') + position.Y += y_diff * dtime * 22.0f + BS * 0.01f; + position.Y = std::min(position.Y, bmax.Y); + m_speed.Y = 0; + } + + // Allow jumping on node edges while sneaking + if (m_speed.Y == 0 || m_sneak_ladder_detected) + sneak_can_jump = true; + + if (collision_info && + m_speed.Y - old_speed.Y > BS) { + // Collide with sneak node, report fall damage + CollisionInfo sn_info; + sn_info.node_p = m_sneak_node; + sn_info.old_speed = old_speed; + sn_info.new_speed = m_speed; + collision_info->push_back(sn_info); + } + } + + /* + Find the next sneak node if necessary + */ + bool new_sneak_node_exists = false; + + if (could_sneak) + new_sneak_node_exists = updateSneakNode(map, position, sneak_max); + + /* + Set new position but keep sneak node set + */ + setPosition(position); + m_sneak_node_exists = new_sneak_node_exists; + + /* + Report collisions + */ + + if(!result.standing_on_object && !touching_ground_was && touching_ground) { + m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_REGAIN_GROUND)); + + // Set camera impact value to be used for view bobbing + camera_impact = getSpeed().Y * -1; + } + + { + camera_barely_in_ceiling = false; + v3s16 camera_np = floatToInt(getEyePosition(), BS); + MapNode n = map->getNodeNoEx(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 + */ + const ContentFeatures &f = nodemgr->get(map->getNodeNoEx(m_standing_node)); + // Determine if jumping is possible + m_can_jump = (touching_ground && !in_liquid && !is_climbing) + || sneak_can_jump; + if (itemgroup_get(f.groups, "disable_jump")) + m_can_jump = false; + + // Jump key pressed while jumping off from a bouncy block + if (m_can_jump && control.jump && itemgroup_get(f.groups, "bouncy") && + m_speed.Y >= -0.5 * BS) { + float jumpspeed = movement_speed_jump * physics_override_jump; + if (m_speed.Y > 1) { + // Reduce boost when speed already is high + m_speed.Y += jumpspeed / (1 + (m_speed.Y / 16 )); + } else { + m_speed.Y += jumpspeed; + } + setSpeed(m_speed); + m_can_jump = false; + } + + // Autojump + handleAutojump(dtime, env, result, initial_position, initial_speed, pos_max_d); +} + +void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d) +{ + move(dtime, env, pos_max_d, NULL); +} + +void LocalPlayer::applyControl(float dtime, Environment *env) +{ + // Clear stuff + swimming_vertical = false; + + setPitch(control.pitch); + setYaw(control.yaw); + + // Nullify speed and don't run positioning code if the player is attached + if(isAttached) + { + setSpeed(v3f(0,0,0)); + return; + } + + PlayerSettings &player_settings = getPlayerSettings(); + + v3f move_direction = v3f(0,0,1); + move_direction.rotateXZBy(getYaw()); + + v3f speedH = v3f(0,0,0); // Horizontal (X, Z) + v3f speedV = v3f(0,0,0); // Vertical (Y) + + bool fly_allowed = m_client->checkLocalPrivilege("fly"); + bool fast_allowed = m_client->checkLocalPrivilege("fast"); + + bool free_move = fly_allowed && player_settings.free_move; + bool fast_move = fast_allowed && player_settings.fast_move; + // When aux1_descends is enabled the fast key is used to go down, so fast isn't possible + bool fast_climb = fast_move && control.aux1 && !player_settings.aux1_descends; + bool continuous_forward = player_settings.continuous_forward; + bool always_fly_fast = player_settings.always_fly_fast; + + // Whether superspeed mode is used or not + bool superspeed = false; + + if (always_fly_fast && free_move && fast_move) + superspeed = true; + + // Old descend control + if (player_settings.aux1_descends) + { + // If free movement and fast movement, always move fast + if(free_move && fast_move) + superspeed = true; + + // Auxiliary button 1 (E) + if(control.aux1) + { + if(free_move) + { + // In free movement mode, aux1 descends + if(fast_move) + speedV.Y = -movement_speed_fast; + else + speedV.Y = -movement_speed_walk; + } + else if(in_liquid || in_liquid_stable) + { + speedV.Y = -movement_speed_walk; + swimming_vertical = true; + } + else if(is_climbing) + { + speedV.Y = -movement_speed_climb; + } + else + { + // If not free movement but fast is allowed, aux1 is + // "Turbo button" + if(fast_move) + superspeed = true; + } + } + } + // New minecraft-like descend control + else + { + // Auxiliary button 1 (E) + if(control.aux1) + { + if(!is_climbing) + { + // aux1 is "Turbo button" + if(fast_move) + superspeed = true; + } + } + + if(control.sneak) + { + if(free_move) + { + // In free movement mode, sneak descends + if (fast_move && (control.aux1 || always_fly_fast)) + speedV.Y = -movement_speed_fast; + else + speedV.Y = -movement_speed_walk; + } + else if(in_liquid || in_liquid_stable) + { + if(fast_climb) + speedV.Y = -movement_speed_fast; + else + speedV.Y = -movement_speed_walk; + swimming_vertical = true; + } + else if(is_climbing) + { + if(fast_climb) + speedV.Y = -movement_speed_fast; + else + speedV.Y = -movement_speed_climb; + } + } + } + + if (continuous_forward) + speedH += move_direction; + + if (control.up) { + if (continuous_forward) { + if (fast_move) + superspeed = true; + } else { + speedH += move_direction; + } + } + if (control.down) { + speedH -= move_direction; + } + if (!control.up && !control.down) { + speedH -= move_direction * + (control.forw_move_joystick_axis / 32767.f); + } + if (control.left) { + speedH += move_direction.crossProduct(v3f(0,1,0)); + } + if (control.right) { + speedH += move_direction.crossProduct(v3f(0,-1,0)); + } + if (!control.left && !control.right) { + speedH -= move_direction.crossProduct(v3f(0,1,0)) * + (control.sidew_move_joystick_axis / 32767.f); + } + if(control.jump) + { + if (free_move) { + if (player_settings.aux1_descends || always_fly_fast) { + if (fast_move) + speedV.Y = movement_speed_fast; + else + speedV.Y = movement_speed_walk; + } else { + if(fast_move && control.aux1) + speedV.Y = movement_speed_fast; + else + speedV.Y = movement_speed_walk; + } + } + else if(m_can_jump) + { + /* + NOTE: The d value in move() affects jump height by + raising the height at which the jump speed is kept + at its starting value + */ + v3f speedJ = getSpeed(); + if(speedJ.Y >= -0.5 * BS) { + speedJ.Y = movement_speed_jump * physics_override_jump; + setSpeed(speedJ); + m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_JUMP)); + } + } + else if(in_liquid) + { + if(fast_climb) + speedV.Y = movement_speed_fast; + else + speedV.Y = movement_speed_walk; + swimming_vertical = true; + } + else if(is_climbing) + { + if(fast_climb) + speedV.Y = movement_speed_fast; + else + speedV.Y = movement_speed_climb; + } + } + + // The speed of the player (Y is ignored) + if(superspeed || (is_climbing && fast_climb) || ((in_liquid || in_liquid_stable) && fast_climb)) + speedH = speedH.normalize() * movement_speed_fast; + else if(control.sneak && !free_move && !in_liquid && !in_liquid_stable) + speedH = speedH.normalize() * movement_speed_crouch; + else + speedH = speedH.normalize() * movement_speed_walk; + + // Acceleration increase + f32 incH = 0; // Horizontal (X, Z) + f32 incV = 0; // Vertical (Y) + if((!touching_ground && !free_move && !is_climbing && !in_liquid) || (!free_move && m_can_jump && control.jump)) + { + // Jumping and falling + if(superspeed || (fast_move && control.aux1)) + incH = movement_acceleration_fast * BS * dtime; + else + incH = movement_acceleration_air * BS * dtime; + incV = 0; // No vertical acceleration in air + } + else if (superspeed || (is_climbing && fast_climb) || ((in_liquid || in_liquid_stable) && fast_climb)) + incH = incV = movement_acceleration_fast * BS * dtime; + else + incH = incV = movement_acceleration_default * BS * dtime; + + float slip_factor = 1.0f; + if (!free_move) + slip_factor = getSlipFactor(env, speedH); + + // Accelerate to target speed with maximum increment + accelerateHorizontal(speedH * physics_override_speed, + incH * physics_override_speed * slip_factor); + accelerateVertical(speedV * physics_override_speed, + incV * physics_override_speed); +} + +v3s16 LocalPlayer::getStandingNodePos() +{ + if(m_sneak_node_exists) + return m_sneak_node; + return m_standing_node; +} + +v3s16 LocalPlayer::getFootstepNodePos() +{ + if (in_liquid_stable) + // Emit swimming sound if the player is in liquid + return floatToInt(getPosition(), BS); + if (touching_ground) + // BS * 0.05 below the player's feet ensures a 1/16th height + // nodebox is detected instead of the node below it. + return floatToInt(getPosition() - v3f(0, BS * 0.05f, 0), BS); + // A larger distance below is necessary for a footstep sound + // when landing after a jump or fall. BS * 0.5 ensures water + // sounds when swimming in 1 node deep water. + return floatToInt(getPosition() - v3f(0, BS * 0.5f, 0), BS); +} + +v3s16 LocalPlayer::getLightPosition() const +{ + return floatToInt(m_position + v3f(0,BS+BS/2,0), BS); +} + +v3f LocalPlayer::getEyeOffset() const +{ + float eye_height = camera_barely_in_ceiling ? + m_eye_height - 0.125f : m_eye_height; + return v3f(0, BS * eye_height, 0); +} + +// Horizontal acceleration (X and Z), Y direction is ignored +void LocalPlayer::accelerateHorizontal(const v3f &target_speed, + const f32 max_increase) +{ + if (max_increase == 0) + return; + + v3f d_wanted = target_speed - m_speed; + d_wanted.Y = 0.0f; + f32 dl = d_wanted.getLength(); + if (dl > max_increase) + dl = max_increase; + + v3f d = d_wanted.normalize() * dl; + + m_speed.X += d.X; + m_speed.Z += d.Z; +} + +// Vertical acceleration (Y), X and Z directions are ignored +void LocalPlayer::accelerateVertical(const v3f &target_speed, const f32 max_increase) +{ + if (max_increase == 0) + return; + + f32 d_wanted = target_speed.Y - m_speed.Y; + if (d_wanted > max_increase) + d_wanted = max_increase; + else if (d_wanted < -max_increase) + d_wanted = -max_increase; + + m_speed.Y += d_wanted; +} + +// Temporary option for old move code +void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d, + std::vector *collision_info) +{ + Map *map = &env->getMap(); + const NodeDefManager *nodemgr = m_client->ndef(); + + v3f position = getPosition(); + + // Copy parent position if local player is attached + if (isAttached) { + setPosition(overridePosition); + m_sneak_node_exists = false; + return; + } + + PlayerSettings &player_settings = getPlayerSettings(); + + // Skip collision detection if noclip mode is used + bool fly_allowed = m_client->checkLocalPrivilege("fly"); + bool noclip = m_client->checkLocalPrivilege("noclip") && player_settings.noclip; + bool free_move = noclip && fly_allowed && player_settings.free_move; + if (free_move) { + position += m_speed * dtime; + setPosition(position); + m_sneak_node_exists = false; + return; + } + + /* + Collision detection + */ + bool is_valid_position; + MapNode node; + v3s16 pp; + + /* + Check if player is in liquid (the oscillating value) + */ + if (in_liquid) { + // If in liquid, the threshold of coming out is at higher y + pp = floatToInt(position + v3f(0, BS * 0.1, 0), BS); + node = map->getNodeNoEx(pp, &is_valid_position); + if (is_valid_position) { + in_liquid = nodemgr->get(node.getContent()).isLiquid(); + liquid_viscosity = nodemgr->get(node.getContent()).liquid_viscosity; + } else { + in_liquid = false; + } + } else { + // If not in liquid, the threshold of going in is at lower y + pp = floatToInt(position + v3f(0, BS * 0.5, 0), BS); + node = map->getNodeNoEx(pp, &is_valid_position); + if (is_valid_position) { + in_liquid = nodemgr->get(node.getContent()).isLiquid(); + liquid_viscosity = nodemgr->get(node.getContent()).liquid_viscosity; + } else { + in_liquid = false; + } + } + + /* + Check if player is in liquid (the stable value) + */ + pp = floatToInt(position + v3f(0, 0, 0), BS); + node = map->getNodeNoEx(pp, &is_valid_position); + if (is_valid_position) + in_liquid_stable = nodemgr->get(node.getContent()).isLiquid(); + else + in_liquid_stable = false; + + /* + Check if player is climbing + */ + pp = floatToInt(position + v3f(0, 0.5 * BS, 0), BS); + v3s16 pp2 = floatToInt(position + v3f(0, -0.2 * BS, 0), BS); + node = map->getNodeNoEx(pp, &is_valid_position); + bool is_valid_position2; + MapNode node2 = map->getNodeNoEx(pp2, &is_valid_position2); + + if (!(is_valid_position && is_valid_position2)) + is_climbing = false; + else + is_climbing = (nodemgr->get(node.getContent()).climbable || + nodemgr->get(node2.getContent()).climbable) && !free_move; + + /* + Collision uncertainty radius + Make it a bit larger than the maximum distance of movement + */ + //f32 d = pos_max_d * 1.1; + // A fairly large value in here makes moving smoother + f32 d = 0.15 * BS; + // This should always apply, otherwise there are glitches + sanity_check(d > pos_max_d); + // Maximum distance over border for sneaking + f32 sneak_max = BS * 0.4; + + /* + If sneaking, keep in range from the last walked node and don't + fall off from it + */ + if (control.sneak && m_sneak_node_exists && + !(fly_allowed && player_settings.free_move) && !in_liquid && + physics_override_sneak) { + f32 maxd = 0.5 * BS + sneak_max; + v3f lwn_f = intToFloat(m_sneak_node, BS); + position.X = rangelim(position.X, lwn_f.X - maxd, lwn_f.X + maxd); + position.Z = rangelim(position.Z, lwn_f.Z - maxd, lwn_f.Z + maxd); + + if (!is_climbing) { + // Move up if necessary + f32 new_y = (lwn_f.Y - 0.5 * BS) + m_sneak_node_bb_ymax; + if (position.Y < new_y) + position.Y = new_y; + /* + Collision seems broken, since player is sinking when + sneaking over the edges of current sneaking_node. + TODO (when fixed): Set Y-speed only to 0 when position.Y < new_y. + */ + if (m_speed.Y < 0) + m_speed.Y = 0; + } + } + + // this shouldn't be hardcoded but transmitted from server + float player_stepheight = touching_ground ? (BS * 0.6) : (BS * 0.2); + + v3f accel_f = v3f(0, 0, 0); + const v3f initial_position = position; + const v3f initial_speed = m_speed; + + collisionMoveResult result = collisionMoveSimple(env, m_client, + pos_max_d, m_collisionbox, player_stepheight, dtime, + &position, &m_speed, accel_f); + + /* + If the player's feet touch the topside of any node, this is + set to true. + + Player is allowed to jump when this is true. + */ + bool touching_ground_was = touching_ground; + touching_ground = result.touching_ground; + + //bool standing_on_unloaded = result.standing_on_unloaded; + + /* + Check the nodes under the player to see from which node the + player is sneaking from, if any. If the node from under + the player has been removed, the player falls. + */ + f32 position_y_mod = 0.05 * BS; + if (m_sneak_node_bb_ymax > 0) + position_y_mod = m_sneak_node_bb_ymax - position_y_mod; + v3s16 current_node = floatToInt(position - v3f(0, position_y_mod, 0), BS); + if (m_sneak_node_exists && + nodemgr->get(map->getNodeNoEx(m_old_node_below)).name == "air" && + m_old_node_below_type != "air") { + // Old node appears to have been removed; that is, + // it wasn't air before but now it is + m_need_to_get_new_sneak_node = false; + m_sneak_node_exists = false; + } else if (nodemgr->get(map->getNodeNoEx(current_node)).name != "air") { + // We are on something, so make sure to recalculate the sneak + // node. + m_need_to_get_new_sneak_node = true; + } + + if (m_need_to_get_new_sneak_node && physics_override_sneak) { + m_sneak_node_bb_ymax = 0; + v3s16 pos_i_bottom = floatToInt(position - v3f(0, position_y_mod, 0), BS); + v2f player_p2df(position.X, position.Z); + f32 min_distance_f = 100000.0 * BS; + // If already seeking from some node, compare to it. + v3s16 new_sneak_node = m_sneak_node; + for (s16 x= -1; x <= 1; x++) + for (s16 z= -1; z <= 1; z++) { + v3s16 p = pos_i_bottom + v3s16(x, 0, z); + v3f pf = intToFloat(p, BS); + v2f node_p2df(pf.X, pf.Z); + f32 distance_f = player_p2df.getDistanceFrom(node_p2df); + f32 max_axis_distance_f = MYMAX( + std::fabs(player_p2df.X - node_p2df.X), + std::fabs(player_p2df.Y - node_p2df.Y)); + + if (distance_f > min_distance_f || + max_axis_distance_f > 0.5 * BS + sneak_max + 0.1 * BS) + continue; + + // The node to be sneaked on has to be walkable + node = map->getNodeNoEx(p, &is_valid_position); + if (!is_valid_position || !nodemgr->get(node).walkable) + continue; + // And the node above it has to be nonwalkable + node = map->getNodeNoEx(p + v3s16(0, 1, 0), &is_valid_position); + if (!is_valid_position || nodemgr->get(node).walkable) + continue; + // If not 'sneak_glitch' the node 2 nodes above it has to be nonwalkable + if (!physics_override_sneak_glitch) { + node =map->getNodeNoEx(p + v3s16(0, 2, 0), &is_valid_position); + if (!is_valid_position || nodemgr->get(node).walkable) + continue; + } + + min_distance_f = distance_f; + new_sneak_node = p; + } + + bool sneak_node_found = (min_distance_f < 100000.0 * BS * 0.9); + + m_sneak_node = new_sneak_node; + m_sneak_node_exists = sneak_node_found; + + if (sneak_node_found) { + f32 cb_max = 0; + MapNode n = map->getNodeNoEx(m_sneak_node); + std::vector nodeboxes; + n.getCollisionBoxes(nodemgr, &nodeboxes); + for (const auto &box : nodeboxes) { + if (box.MaxEdge.Y > cb_max) + cb_max = box.MaxEdge.Y; + } + m_sneak_node_bb_ymax = cb_max; + } + + /* + If sneaking, the player's collision box can be in air, so + this has to be set explicitly + */ + if (sneak_node_found && control.sneak) + touching_ground = true; + } + + /* + Set new position but keep sneak node set + */ + bool sneak_node_exists = m_sneak_node_exists; + setPosition(position); + m_sneak_node_exists = sneak_node_exists; + + /* + Report collisions + */ + // Dont report if flying + if (collision_info && !(player_settings.free_move && fly_allowed)) { + for (const auto &info : result.collisions) { + collision_info->push_back(info); + } + } + + if (!result.standing_on_object && !touching_ground_was && touching_ground) { + m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_REGAIN_GROUND)); + // Set camera impact value to be used for view bobbing + camera_impact = getSpeed().Y * -1; + } + + { + camera_barely_in_ceiling = false; + v3s16 camera_np = floatToInt(getEyePosition(), BS); + MapNode n = map->getNodeNoEx(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 + */ + m_old_node_below = floatToInt(position - v3f(0, BS / 2, 0), BS); + m_old_node_below_type = nodemgr->get(map->getNodeNoEx(m_old_node_below)).name; + + /* + Check properties of the node on which the player is standing + */ + const ContentFeatures &f = nodemgr->get(map->getNodeNoEx(getStandingNodePos())); + // Determine if jumping is possible + m_can_jump = touching_ground && !in_liquid; + if (itemgroup_get(f.groups, "disable_jump")) + m_can_jump = false; + // Jump key pressed while jumping off from a bouncy block + if (m_can_jump && control.jump && itemgroup_get(f.groups, "bouncy") && + m_speed.Y >= -0.5 * BS) { + float jumpspeed = movement_speed_jump * physics_override_jump; + if (m_speed.Y > 1) { + // Reduce boost when speed already is high + m_speed.Y += jumpspeed / (1 + (m_speed.Y / 16 )); + } else { + m_speed.Y += jumpspeed; + } + setSpeed(m_speed); + m_can_jump = false; + } + + // Autojump + handleAutojump(dtime, env, result, initial_position, initial_speed, pos_max_d); +} + +float LocalPlayer::getSlipFactor(Environment *env, const v3f &speedH) +{ + // Slip on slippery nodes + const NodeDefManager *nodemgr = env->getGameDef()->ndef(); + Map *map = &env->getMap(); + const ContentFeatures &f = nodemgr->get(map->getNodeNoEx( + getStandingNodePos())); + int slippery = 0; + if (f.walkable) + slippery = itemgroup_get(f.groups, "slippery"); + + if (slippery >= 1) { + if (speedH == v3f(0.0f)) { + slippery = slippery * 2; + } + return core::clamp(1.0f / (slippery + 1), 0.001f, 1.0f); + } + return 1.0f; +} + +void LocalPlayer::handleAutojump(f32 dtime, Environment *env, + const collisionMoveResult &result, const v3f &initial_position, + const v3f &initial_speed, f32 pos_max_d) +{ + PlayerSettings &player_settings = getPlayerSettings(); + if (!player_settings.autojump) + return; + + if (m_autojump) { + // release autojump after a given time + m_autojump_time -= dtime; + if (m_autojump_time <= 0.0f) + m_autojump = false; + return; + } + + bool control_forward = control.up || + (!control.up && !control.down && + control.forw_move_joystick_axis < -0.05); + bool could_autojump = + m_can_jump && !control.jump && !control.sneak && control_forward; + if (!could_autojump) + return; + + bool horizontal_collision = false; + for (const auto &colinfo : result.collisions) { + if (colinfo.type == COLLISION_NODE && colinfo.plane != 1) { + horizontal_collision = true; + break; // one is enough + } + } + + // must be running against something to trigger autojumping + if (!horizontal_collision) + return; + + // check for nodes above + v3f headpos_min = m_position + m_collisionbox.MinEdge * 0.99f; + v3f headpos_max = m_position + m_collisionbox.MaxEdge * 0.99f; + headpos_min.Y = headpos_max.Y; // top face of collision box + v3s16 ceilpos_min = floatToInt(headpos_min, BS) + v3s16(0, 1, 0); + v3s16 ceilpos_max = floatToInt(headpos_max, BS) + v3s16(0, 1, 0); + const NodeDefManager *ndef = env->getGameDef()->ndef(); + bool is_position_valid; + for (s16 z = ceilpos_min.Z; z <= ceilpos_max.Z; z++) { + for (s16 x = ceilpos_min.X; x <= ceilpos_max.X; x++) { + MapNode n = env->getMap().getNodeNoEx(v3s16(x, ceilpos_max.Y, z), &is_position_valid); + + if (!is_position_valid) + break; // won't collide with the void outside + if (n.getContent() == CONTENT_IGNORE) + return; // players collide with ignore blocks -> same as walkable + const ContentFeatures &f = ndef->get(n); + if (f.walkable) + return; // would bump head, don't jump + } + } + + float jump_height = 1.1f; // TODO: better than a magic number + v3f jump_pos = initial_position + v3f(0.0f, jump_height * BS, 0.0f); + v3f jump_speed = initial_speed; + + // try at peak of jump, zero step height + collisionMoveResult jump_result = collisionMoveSimple(env, m_client, pos_max_d, + m_collisionbox, 0.0f, dtime, &jump_pos, &jump_speed, + v3f(0, 0, 0)); + + // see if we can get a little bit farther horizontally if we had + // jumped + v3f run_delta = m_position - initial_position; + run_delta.Y = 0.0f; + v3f jump_delta = jump_pos - initial_position; + jump_delta.Y = 0.0f; + if (jump_delta.getLengthSQ() > run_delta.getLengthSQ() * 1.01f) { + m_autojump = true; + m_autojump_time = 0.1f; + } +} diff --git a/src/client/localplayer.h b/src/client/localplayer.h new file mode 100644 index 000000000..7148bc4de --- /dev/null +++ b/src/client/localplayer.h @@ -0,0 +1,198 @@ +/* +Minetest +Copyright (C) 2010-2013 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include "player.h" +#include "environment.h" +#include "constants.h" +#include "settings.h" +#include + +class Client; +class Environment; +class GenericCAO; +class ClientActiveObject; +class ClientEnvironment; +class IGameDef; +struct collisionMoveResult; + +enum LocalPlayerAnimations +{ + NO_ANIM, + WALK_ANIM, + DIG_ANIM, + WD_ANIM +}; // no local animation, walking, digging, both + +class LocalPlayer : public Player +{ +public: + LocalPlayer(Client *client, const char *name); + virtual ~LocalPlayer() = default; + + ClientActiveObject *parent = nullptr; + + // Initialize hp to 0, so that no hearts will be shown if server + // doesn't support health points + u16 hp = 0; + bool isAttached = false; + bool touching_ground = false; + // This oscillates so that the player jumps a bit above the surface + bool in_liquid = false; + // This is more stable and defines the maximum speed of the player + bool in_liquid_stable = false; + // Gets the viscosity of water to calculate friction + u8 liquid_viscosity = 0; + bool is_climbing = false; + bool swimming_vertical = false; + + float physics_override_speed = 1.0f; + float physics_override_jump = 1.0f; + float physics_override_gravity = 1.0f; + bool physics_override_sneak = true; + bool physics_override_sneak_glitch = false; + // Temporary option for old move code + bool physics_override_new_move = true; + + v3f overridePosition; + + void move(f32 dtime, Environment *env, f32 pos_max_d); + void move(f32 dtime, Environment *env, f32 pos_max_d, + std::vector *collision_info); + // Temporary option for old move code + void old_move(f32 dtime, Environment *env, f32 pos_max_d, + std::vector *collision_info); + + void applyControl(float dtime, Environment *env); + + v3s16 getStandingNodePos(); + v3s16 getFootstepNodePos(); + + // Used to check if anything changed and prevent sending packets if not + v3f last_position; + v3f last_speed; + float last_pitch = 0.0f; + float last_yaw = 0.0f; + unsigned int last_keyPressed = 0; + u8 last_camera_fov = 0; + u8 last_wanted_range = 0; + + float camera_impact = 0.0f; + + bool makes_footstep_sound = true; + + int last_animation = NO_ANIM; + float last_animation_speed; + + std::string hotbar_image = ""; + std::string hotbar_selected_image = ""; + + video::SColor light_color = video::SColor(255, 255, 255, 255); + + float hurt_tilt_timer = 0.0f; + float hurt_tilt_strength = 0.0f; + + GenericCAO *getCAO() const { return m_cao; } + + void setCAO(GenericCAO *toset) + { + assert(!m_cao); // Pre-condition + m_cao = toset; + } + + u32 maxHudId() const { return hud.size(); } + + u16 getBreath() const { return m_breath; } + void setBreath(u16 breath) { m_breath = breath; } + + v3s16 getLightPosition() const; + + void setYaw(f32 yaw) { m_yaw = yaw; } + f32 getYaw() const { return m_yaw; } + + void setPitch(f32 pitch) { m_pitch = pitch; } + f32 getPitch() const { return m_pitch; } + + inline void setPosition(const v3f &position) + { + m_position = position; + m_sneak_node_exists = false; + } + + v3f getPosition() const { return m_position; } + v3f getEyePosition() const { return m_position + getEyeOffset(); } + v3f getEyeOffset() const; + void setEyeHeight(float eye_height) { m_eye_height = eye_height; } + + void setCollisionbox(const aabb3f &box) { m_collisionbox = box; } + + float getZoomFOV() const { return m_zoom_fov; } + void setZoomFOV(float zoom_fov) { m_zoom_fov = zoom_fov; } + + bool getAutojump() const { return m_autojump; } + +private: + void accelerateHorizontal(const v3f &target_speed, const f32 max_increase); + void accelerateVertical(const v3f &target_speed, const f32 max_increase); + bool updateSneakNode(Map *map, const v3f &position, const v3f &sneak_max); + float getSlipFactor(Environment *env, const v3f &speedH); + void handleAutojump(f32 dtime, Environment *env, + const collisionMoveResult &result, + const v3f &position_before_move, const v3f &speed_before_move, + f32 pos_max_d); + + v3f m_position; + v3s16 m_standing_node; + + v3s16 m_sneak_node = v3s16(32767, 32767, 32767); + // Stores the top bounding box of m_sneak_node + aabb3f m_sneak_node_bb_top = aabb3f(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f); + // Whether the player is allowed to sneak + bool m_sneak_node_exists = false; + // Whether a "sneak ladder" structure is detected at the players pos + // see detectSneakLadder() in the .cpp for more info (always false if disabled) + bool m_sneak_ladder_detected = false; + + // ***** Variables for temporary option of the old move code ***** + // Stores the max player uplift by m_sneak_node + f32 m_sneak_node_bb_ymax = 0.0f; + // Whether recalculation of m_sneak_node and its top bbox is needed + bool m_need_to_get_new_sneak_node = true; + // Node below player, used to determine whether it has been removed, + // and its old type + v3s16 m_old_node_below = v3s16(32767, 32767, 32767); + std::string m_old_node_below_type = "air"; + // ***** End of variables for temporary option ***** + + bool m_can_jump = false; + 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; + float m_zoom_fov = 0.0f; + bool m_autojump = false; + float m_autojump_time = 0.0f; + + GenericCAO *m_cao = nullptr; + Client *m_client; +}; diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp new file mode 100644 index 000000000..ed8a073de --- /dev/null +++ b/src/client/mapblock_mesh.cpp @@ -0,0 +1,1389 @@ +/* +Minetest +Copyright (C) 2010-2013 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "mapblock_mesh.h" +#include "client.h" +#include "mapblock.h" +#include "map.h" +#include "profiler.h" +#include "shader.h" +#include "mesh.h" +#include "minimap.h" +#include "content_mapblock.h" +#include "util/directiontables.h" +#include "client/meshgen/collector.h" +#include "client/renderingengine.h" +#include + +/* + MeshMakeData +*/ + +MeshMakeData::MeshMakeData(Client *client, bool use_shaders, + bool use_tangent_vertices): + m_client(client), + m_use_shaders(use_shaders), + m_use_tangent_vertices(use_tangent_vertices) +{} + +void MeshMakeData::fillBlockDataBegin(const v3s16 &blockpos) +{ + m_blockpos = blockpos; + + v3s16 blockpos_nodes = m_blockpos*MAP_BLOCKSIZE; + + m_vmanip.clear(); + VoxelArea voxel_area(blockpos_nodes - v3s16(1,1,1) * MAP_BLOCKSIZE, + blockpos_nodes + v3s16(1,1,1) * MAP_BLOCKSIZE*2-v3s16(1,1,1)); + m_vmanip.addArea(voxel_area); +} + +void MeshMakeData::fillBlockData(const v3s16 &block_offset, MapNode *data) +{ + v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE); + VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1)); + + v3s16 bp = m_blockpos + block_offset; + v3s16 blockpos_nodes = bp * MAP_BLOCKSIZE; + m_vmanip.copyFrom(data, data_area, v3s16(0,0,0), blockpos_nodes, data_size); +} + +void MeshMakeData::fill(MapBlock *block) +{ + fillBlockDataBegin(block->getPos()); + + fillBlockData(v3s16(0,0,0), block->getData()); + + // Get map for reading neighbor blocks + Map *map = block->getParent(); + + for (const v3s16 &dir : g_26dirs) { + v3s16 bp = m_blockpos + dir; + MapBlock *b = map->getBlockNoCreateNoEx(bp); + if(b) + fillBlockData(dir, b->getData()); + } +} + +void MeshMakeData::fillSingleNode(MapNode *node) +{ + m_blockpos = v3s16(0,0,0); + + v3s16 blockpos_nodes = v3s16(0,0,0); + VoxelArea area(blockpos_nodes-v3s16(1,1,1)*MAP_BLOCKSIZE, + blockpos_nodes+v3s16(1,1,1)*MAP_BLOCKSIZE*2-v3s16(1,1,1)); + s32 volume = area.getVolume(); + s32 our_node_index = area.index(1,1,1); + + // Allocate this block + neighbors + m_vmanip.clear(); + m_vmanip.addArea(area); + + // Fill in data + MapNode *data = new MapNode[volume]; + for(s32 i = 0; i < volume; i++) + { + if (i == our_node_index) + data[i] = *node; + else + data[i] = MapNode(CONTENT_AIR, LIGHT_MAX, 0); + } + m_vmanip.copyFrom(data, area, area.MinEdge, area.MinEdge, area.getExtent()); + delete[] data; +} + +void MeshMakeData::setCrack(int crack_level, v3s16 crack_pos) +{ + if (crack_level >= 0) + m_crack_pos_relative = crack_pos - m_blockpos*MAP_BLOCKSIZE; +} + +void MeshMakeData::setSmoothLighting(bool smooth_lighting) +{ + m_smooth_lighting = smooth_lighting; +} + +/* + Light and vertex color functions +*/ + +/* + Calculate non-smooth lighting at interior of node. + Single light bank. +*/ +static u8 getInteriorLight(enum LightBank bank, MapNode n, s32 increment, + const NodeDefManager *ndef) +{ + u8 light = n.getLight(bank, ndef); + if (light > 0) + light = rangelim(light + increment, 0, LIGHT_SUN); + return decode_light(light); +} + +/* + Calculate non-smooth lighting at interior of node. + Both light banks. +*/ +u16 getInteriorLight(MapNode n, s32 increment, const NodeDefManager *ndef) +{ + u16 day = getInteriorLight(LIGHTBANK_DAY, n, increment, ndef); + u16 night = getInteriorLight(LIGHTBANK_NIGHT, n, increment, ndef); + return day | (night << 8); +} + +/* + Calculate non-smooth lighting at face of node. + Single light bank. +*/ +static u8 getFaceLight(enum LightBank bank, MapNode n, MapNode n2, + v3s16 face_dir, const NodeDefManager *ndef) +{ + u8 light; + u8 l1 = n.getLight(bank, ndef); + u8 l2 = n2.getLight(bank, ndef); + if(l1 > l2) + light = l1; + else + light = l2; + + // Boost light level for light sources + u8 light_source = MYMAX(ndef->get(n).light_source, + ndef->get(n2).light_source); + if(light_source > light) + light = light_source; + + return decode_light(light); +} + +/* + Calculate non-smooth lighting at face of node. + Both light banks. +*/ +u16 getFaceLight(MapNode n, MapNode n2, const v3s16 &face_dir, + const NodeDefManager *ndef) +{ + u16 day = getFaceLight(LIGHTBANK_DAY, n, n2, face_dir, ndef); + u16 night = getFaceLight(LIGHTBANK_NIGHT, n, n2, face_dir, ndef); + return day | (night << 8); +} + +/* + Calculate smooth lighting at the XYZ- corner of p. + Both light banks +*/ +static u16 getSmoothLightCombined(const v3s16 &p, + const std::array &dirs, MeshMakeData *data) +{ + const NodeDefManager *ndef = data->m_client->ndef(); + + u16 ambient_occlusion = 0; + u16 light_count = 0; + u8 light_source_max = 0; + u16 light_day = 0; + u16 light_night = 0; + bool direct_sunlight = false; + + auto add_node = [&] (u8 i, bool obstructed = false) -> bool { + if (obstructed) { + ambient_occlusion++; + return false; + } + MapNode n = data->m_vmanip.getNodeNoExNoEmerge(p + dirs[i]); + if (n.getContent() == CONTENT_IGNORE) + return true; + const ContentFeatures &f = ndef->get(n); + if (f.light_source > light_source_max) + light_source_max = f.light_source; + // Check f.solidness because fast-style leaves look better this way + if (f.param_type == CPT_LIGHT && f.solidness != 2) { + u8 light_level_day = n.getLightNoChecks(LIGHTBANK_DAY, &f); + u8 light_level_night = n.getLightNoChecks(LIGHTBANK_NIGHT, &f); + if (light_level_day == LIGHT_SUN) + direct_sunlight = true; + light_day += decode_light(light_level_day); + light_night += decode_light(light_level_night); + light_count++; + } else { + ambient_occlusion++; + } + return f.light_propagates; + }; + + std::array obstructed = {{ 1, 1, 1, 1 }}; + add_node(0); + bool opaque1 = !add_node(1); + bool opaque2 = !add_node(2); + bool opaque3 = !add_node(3); + obstructed[0] = opaque1 && opaque2; + obstructed[1] = opaque1 && opaque3; + obstructed[2] = opaque2 && opaque3; + for (u8 k = 0; k < 3; ++k) + if (add_node(k + 4, obstructed[k])) + obstructed[3] = false; + if (add_node(7, obstructed[3])) { // wrap light around nodes + ambient_occlusion -= 3; + for (u8 k = 0; k < 3; ++k) + add_node(k + 4, !obstructed[k]); + } + + if (light_count == 0) { + light_day = light_night = 0; + } else { + light_day /= light_count; + light_night /= light_count; + } + + // boost direct sunlight, if any + if (direct_sunlight) + light_day = 0xFF; + + // Boost brightness around light sources + bool skip_ambient_occlusion_day = false; + if (decode_light(light_source_max) >= light_day) { + light_day = decode_light(light_source_max); + skip_ambient_occlusion_day = true; + } + + bool skip_ambient_occlusion_night = false; + if(decode_light(light_source_max) >= light_night) { + light_night = decode_light(light_source_max); + skip_ambient_occlusion_night = true; + } + + if (ambient_occlusion > 4) { + static thread_local const float ao_gamma = rangelim( + g_settings->getFloat("ambient_occlusion_gamma"), 0.25, 4.0); + + // Table of gamma space multiply factors. + static thread_local const float light_amount[3] = { + powf(0.75, 1.0 / ao_gamma), + powf(0.5, 1.0 / ao_gamma), + powf(0.25, 1.0 / ao_gamma) + }; + + //calculate table index for gamma space multiplier + ambient_occlusion -= 5; + + if (!skip_ambient_occlusion_day) + light_day = rangelim(core::round32( + light_day * light_amount[ambient_occlusion]), 0, 255); + if (!skip_ambient_occlusion_night) + light_night = rangelim(core::round32( + light_night * light_amount[ambient_occlusion]), 0, 255); + } + + return light_day | (light_night << 8); +} + +/* + Calculate smooth lighting at the given corner of p. + Both light banks. + Node at p is solid, and thus the lighting is face-dependent. +*/ +u16 getSmoothLightSolid(const v3s16 &p, const v3s16 &face_dir, const v3s16 &corner, MeshMakeData *data) +{ + return getSmoothLightTransparent(p + face_dir, corner - 2 * face_dir, data); +} + +/* + Calculate smooth lighting at the given corner of p. + Both light banks. + Node at p is not solid, and the lighting is not face-dependent. +*/ +u16 getSmoothLightTransparent(const v3s16 &p, const v3s16 &corner, MeshMakeData *data) +{ + const std::array dirs = {{ + // Always shine light + v3s16(0,0,0), + v3s16(corner.X,0,0), + v3s16(0,corner.Y,0), + v3s16(0,0,corner.Z), + + // Can be obstructed + v3s16(corner.X,corner.Y,0), + v3s16(corner.X,0,corner.Z), + v3s16(0,corner.Y,corner.Z), + v3s16(corner.X,corner.Y,corner.Z) + }}; + return getSmoothLightCombined(p, dirs, data); +} + +void get_sunlight_color(video::SColorf *sunlight, u32 daynight_ratio){ + f32 rg = daynight_ratio / 1000.0f - 0.04f; + f32 b = (0.98f * daynight_ratio) / 1000.0f + 0.078f; + sunlight->r = rg; + sunlight->g = rg; + sunlight->b = b; +} + +void final_color_blend(video::SColor *result, + u16 light, u32 daynight_ratio) +{ + video::SColorf dayLight; + get_sunlight_color(&dayLight, daynight_ratio); + final_color_blend(result, + encode_light(light, 0), dayLight); +} + +void final_color_blend(video::SColor *result, + const video::SColor &data, const video::SColorf &dayLight) +{ + static const video::SColorf artificialColor(1.04f, 1.04f, 1.04f); + + video::SColorf c(data); + f32 n = 1 - c.a; + + f32 r = c.r * (c.a * dayLight.r + n * artificialColor.r) * 2.0f; + f32 g = c.g * (c.a * dayLight.g + n * artificialColor.g) * 2.0f; + f32 b = c.b * (c.a * dayLight.b + n * artificialColor.b) * 2.0f; + + // Emphase blue a bit in darker places + // Each entry of this array represents a range of 8 blue levels + static const u8 emphase_blue_when_dark[32] = { + 1, 4, 6, 6, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + + b += emphase_blue_when_dark[irr::core::clamp((s32) ((r + g + b) / 3 * 255), + 0, 255) / 8] / 255.0f; + + result->setRed(core::clamp((s32) (r * 255.0f), 0, 255)); + result->setGreen(core::clamp((s32) (g * 255.0f), 0, 255)); + result->setBlue(core::clamp((s32) (b * 255.0f), 0, 255)); +} + +/* + Mesh generation helpers +*/ + +/* + vertex_dirs: v3s16[4] +*/ +static void getNodeVertexDirs(const v3s16 &dir, v3s16 *vertex_dirs) +{ + /* + If looked from outside the node towards the face, the corners are: + 0: bottom-right + 1: bottom-left + 2: top-left + 3: top-right + */ + if (dir == v3s16(0, 0, 1)) { + // If looking towards z+, this is the face that is behind + // the center point, facing towards z+. + vertex_dirs[0] = v3s16(-1,-1, 1); + vertex_dirs[1] = v3s16( 1,-1, 1); + vertex_dirs[2] = v3s16( 1, 1, 1); + vertex_dirs[3] = v3s16(-1, 1, 1); + } else if (dir == v3s16(0, 0, -1)) { + // faces towards Z- + vertex_dirs[0] = v3s16( 1,-1,-1); + vertex_dirs[1] = v3s16(-1,-1,-1); + vertex_dirs[2] = v3s16(-1, 1,-1); + vertex_dirs[3] = v3s16( 1, 1,-1); + } else if (dir == v3s16(1, 0, 0)) { + // faces towards X+ + vertex_dirs[0] = v3s16( 1,-1, 1); + vertex_dirs[1] = v3s16( 1,-1,-1); + vertex_dirs[2] = v3s16( 1, 1,-1); + vertex_dirs[3] = v3s16( 1, 1, 1); + } else if (dir == v3s16(-1, 0, 0)) { + // faces towards X- + vertex_dirs[0] = v3s16(-1,-1,-1); + vertex_dirs[1] = v3s16(-1,-1, 1); + vertex_dirs[2] = v3s16(-1, 1, 1); + vertex_dirs[3] = v3s16(-1, 1,-1); + } else if (dir == v3s16(0, 1, 0)) { + // faces towards Y+ (assume Z- as "down" in texture) + vertex_dirs[0] = v3s16( 1, 1,-1); + vertex_dirs[1] = v3s16(-1, 1,-1); + vertex_dirs[2] = v3s16(-1, 1, 1); + vertex_dirs[3] = v3s16( 1, 1, 1); + } else if (dir == v3s16(0, -1, 0)) { + // faces towards Y- (assume Z+ as "down" in texture) + vertex_dirs[0] = v3s16( 1,-1, 1); + vertex_dirs[1] = v3s16(-1,-1, 1); + vertex_dirs[2] = v3s16(-1,-1,-1); + vertex_dirs[3] = v3s16( 1,-1,-1); + } +} + +static void getNodeTextureCoords(v3f base, const v3f &scale, const v3s16 &dir, float *u, float *v) +{ + if (dir.X > 0 || dir.Y > 0 || dir.Z < 0) + base -= scale; + if (dir == v3s16(0,0,1)) { + *u = -base.X - 1; + *v = -base.Y - 1; + } else if (dir == v3s16(0,0,-1)) { + *u = base.X + 1; + *v = -base.Y - 2; + } else if (dir == v3s16(1,0,0)) { + *u = base.Z + 1; + *v = -base.Y - 2; + } else if (dir == v3s16(-1,0,0)) { + *u = -base.Z - 1; + *v = -base.Y - 1; + } else if (dir == v3s16(0,1,0)) { + *u = base.X + 1; + *v = -base.Z - 2; + } else if (dir == v3s16(0,-1,0)) { + *u = base.X; + *v = base.Z; + } +} + +struct FastFace +{ + TileSpec tile; + video::S3DVertex vertices[4]; // Precalculated vertices + /*! + * The face is divided into two triangles. If this is true, + * vertices 0 and 2 are connected, othervise vertices 1 and 3 + * are connected. + */ + bool vertex_0_2_connected; +}; + +static void makeFastFace(const TileSpec &tile, u16 li0, u16 li1, u16 li2, u16 li3, + const v3f &tp, const v3f &p, const v3s16 &dir, const v3f &scale, std::vector &dest) +{ + // Position is at the center of the cube. + v3f pos = p * BS; + + float x0 = 0.0f; + float y0 = 0.0f; + float w = 1.0f; + float h = 1.0f; + + v3f vertex_pos[4]; + v3s16 vertex_dirs[4]; + getNodeVertexDirs(dir, vertex_dirs); + if (tile.world_aligned) + getNodeTextureCoords(tp, scale, dir, &x0, &y0); + + v3s16 t; + u16 t1; + switch (tile.rotation) { + case 0: + break; + case 1: //R90 + t = vertex_dirs[0]; + vertex_dirs[0] = vertex_dirs[3]; + vertex_dirs[3] = vertex_dirs[2]; + vertex_dirs[2] = vertex_dirs[1]; + vertex_dirs[1] = t; + t1 = li0; + li0 = li3; + li3 = li2; + li2 = li1; + li1 = t1; + break; + case 2: //R180 + t = vertex_dirs[0]; + vertex_dirs[0] = vertex_dirs[2]; + vertex_dirs[2] = t; + t = vertex_dirs[1]; + vertex_dirs[1] = vertex_dirs[3]; + vertex_dirs[3] = t; + t1 = li0; + li0 = li2; + li2 = t1; + t1 = li1; + li1 = li3; + li3 = t1; + break; + case 3: //R270 + t = vertex_dirs[0]; + vertex_dirs[0] = vertex_dirs[1]; + vertex_dirs[1] = vertex_dirs[2]; + vertex_dirs[2] = vertex_dirs[3]; + vertex_dirs[3] = t; + t1 = li0; + li0 = li1; + li1 = li2; + li2 = li3; + li3 = t1; + break; + case 4: //FXR90 + t = vertex_dirs[0]; + vertex_dirs[0] = vertex_dirs[3]; + vertex_dirs[3] = vertex_dirs[2]; + vertex_dirs[2] = vertex_dirs[1]; + vertex_dirs[1] = t; + t1 = li0; + li0 = li3; + li3 = li2; + li2 = li1; + li1 = t1; + y0 += h; + h *= -1; + break; + case 5: //FXR270 + t = vertex_dirs[0]; + vertex_dirs[0] = vertex_dirs[1]; + vertex_dirs[1] = vertex_dirs[2]; + vertex_dirs[2] = vertex_dirs[3]; + vertex_dirs[3] = t; + t1 = li0; + li0 = li1; + li1 = li2; + li2 = li3; + li3 = t1; + y0 += h; + h *= -1; + break; + case 6: //FYR90 + t = vertex_dirs[0]; + vertex_dirs[0] = vertex_dirs[3]; + vertex_dirs[3] = vertex_dirs[2]; + vertex_dirs[2] = vertex_dirs[1]; + vertex_dirs[1] = t; + t1 = li0; + li0 = li3; + li3 = li2; + li2 = li1; + li1 = t1; + x0 += w; + w *= -1; + break; + case 7: //FYR270 + t = vertex_dirs[0]; + vertex_dirs[0] = vertex_dirs[1]; + vertex_dirs[1] = vertex_dirs[2]; + vertex_dirs[2] = vertex_dirs[3]; + vertex_dirs[3] = t; + t1 = li0; + li0 = li1; + li1 = li2; + li2 = li3; + li3 = t1; + x0 += w; + w *= -1; + break; + case 8: //FX + y0 += h; + h *= -1; + break; + case 9: //FY + x0 += w; + w *= -1; + break; + default: + break; + } + + for (u16 i = 0; i < 4; i++) { + vertex_pos[i] = v3f( + BS / 2 * vertex_dirs[i].X, + BS / 2 * vertex_dirs[i].Y, + BS / 2 * vertex_dirs[i].Z + ); + } + + for (v3f &vpos : vertex_pos) { + vpos.X *= scale.X; + vpos.Y *= scale.Y; + vpos.Z *= scale.Z; + vpos += pos; + } + + f32 abs_scale = 1.0f; + if (scale.X < 0.999f || scale.X > 1.001f) abs_scale = scale.X; + else if (scale.Y < 0.999f || scale.Y > 1.001f) abs_scale = scale.Y; + else if (scale.Z < 0.999f || scale.Z > 1.001f) abs_scale = scale.Z; + + v3f normal(dir.X, dir.Y, dir.Z); + + u16 li[4] = { li0, li1, li2, li3 }; + u16 day[4]; + u16 night[4]; + + for (u8 i = 0; i < 4; i++) { + day[i] = li[i] >> 8; + night[i] = li[i] & 0xFF; + } + + bool vertex_0_2_connected = abs(day[0] - day[2]) + abs(night[0] - night[2]) + < abs(day[1] - day[3]) + abs(night[1] - night[3]); + + v2f32 f[4] = { + core::vector2d(x0 + w * abs_scale, y0 + h), + core::vector2d(x0, y0 + h), + core::vector2d(x0, y0), + core::vector2d(x0 + w * abs_scale, y0) }; + + // equivalent to dest.push_back(FastFace()) but faster + dest.emplace_back(); + FastFace& face = *dest.rbegin(); + + for (u8 i = 0; i < 4; i++) { + video::SColor c = encode_light(li[i], tile.emissive_light); + if (!tile.emissive_light) + applyFacesShading(c, normal); + + face.vertices[i] = video::S3DVertex(vertex_pos[i], normal, c, f[i]); + } + + /* + Revert triangles for nicer looking gradient if the + brightness of vertices 1 and 3 differ less than + the brightness of vertices 0 and 2. + */ + face.vertex_0_2_connected = vertex_0_2_connected; + face.tile = tile; +} + +/* + Nodes make a face if contents differ and solidness differs. + Return value: + 0: No face + 1: Face uses m1's content + 2: Face uses m2's content + equivalent: Whether the blocks share the same face (eg. water and glass) + + TODO: Add 3: Both faces drawn with backface culling, remove equivalent +*/ +static u8 face_contents(content_t m1, content_t m2, bool *equivalent, + const NodeDefManager *ndef) +{ + *equivalent = false; + + if (m1 == m2 || m1 == CONTENT_IGNORE || m2 == CONTENT_IGNORE) + return 0; + + const ContentFeatures &f1 = ndef->get(m1); + const ContentFeatures &f2 = ndef->get(m2); + + // Contents don't differ for different forms of same liquid + if (f1.sameLiquid(f2)) + return 0; + + u8 c1 = f1.solidness; + u8 c2 = f2.solidness; + + if (c1 == c2) + return 0; + + if (c1 == 0) + c1 = f1.visual_solidness; + else if (c2 == 0) + c2 = f2.visual_solidness; + + if (c1 == c2) { + *equivalent = true; + // If same solidness, liquid takes precense + if (f1.isLiquid()) + return 1; + if (f2.isLiquid()) + return 2; + } + + if (c1 > c2) + return 1; + + return 2; +} + +/* + Gets nth node tile (0 <= n <= 5). +*/ +void getNodeTileN(MapNode mn, const v3s16 &p, u8 tileindex, MeshMakeData *data, TileSpec &tile) +{ + const NodeDefManager *ndef = data->m_client->ndef(); + const ContentFeatures &f = ndef->get(mn); + tile = f.tiles[tileindex]; + bool has_crack = p == data->m_crack_pos_relative; + for (TileLayer &layer : tile.layers) { + if (layer.texture_id == 0) + continue; + if (!layer.has_color) + mn.getColor(f, &(layer.color)); + // Apply temporary crack + if (has_crack) + layer.material_flags |= MATERIAL_FLAG_CRACK; + } +} + +/* + Gets node tile given a face direction. +*/ +void getNodeTile(MapNode mn, const v3s16 &p, const v3s16 &dir, MeshMakeData *data, TileSpec &tile) +{ + const NodeDefManager *ndef = data->m_client->ndef(); + + // Direction must be (1,0,0), (-1,0,0), (0,1,0), (0,-1,0), + // (0,0,1), (0,0,-1) or (0,0,0) + assert(dir.X * dir.X + dir.Y * dir.Y + dir.Z * dir.Z <= 1); + + // Convert direction to single integer for table lookup + // 0 = (0,0,0) + // 1 = (1,0,0) + // 2 = (0,1,0) + // 3 = (0,0,1) + // 4 = invalid, treat as (0,0,0) + // 5 = (0,0,-1) + // 6 = (0,-1,0) + // 7 = (-1,0,0) + u8 dir_i = ((dir.X + 2 * dir.Y + 3 * dir.Z) & 7) * 2; + + // Get rotation for things like chests + u8 facedir = mn.getFaceDir(ndef); + + static const u16 dir_to_tile[24 * 16] = + { + // 0 +X +Y +Z -Z -Y -X -> value=tile,rotation + 0,0, 2,0 , 0,0 , 4,0 , 0,0, 5,0 , 1,0 , 3,0 , // rotate around y+ 0 - 3 + 0,0, 4,0 , 0,3 , 3,0 , 0,0, 2,0 , 1,1 , 5,0 , + 0,0, 3,0 , 0,2 , 5,0 , 0,0, 4,0 , 1,2 , 2,0 , + 0,0, 5,0 , 0,1 , 2,0 , 0,0, 3,0 , 1,3 , 4,0 , + + 0,0, 2,3 , 5,0 , 0,2 , 0,0, 1,0 , 4,2 , 3,1 , // rotate around z+ 4 - 7 + 0,0, 4,3 , 2,0 , 0,1 , 0,0, 1,1 , 3,2 , 5,1 , + 0,0, 3,3 , 4,0 , 0,0 , 0,0, 1,2 , 5,2 , 2,1 , + 0,0, 5,3 , 3,0 , 0,3 , 0,0, 1,3 , 2,2 , 4,1 , + + 0,0, 2,1 , 4,2 , 1,2 , 0,0, 0,0 , 5,0 , 3,3 , // rotate around z- 8 - 11 + 0,0, 4,1 , 3,2 , 1,3 , 0,0, 0,3 , 2,0 , 5,3 , + 0,0, 3,1 , 5,2 , 1,0 , 0,0, 0,2 , 4,0 , 2,3 , + 0,0, 5,1 , 2,2 , 1,1 , 0,0, 0,1 , 3,0 , 4,3 , + + 0,0, 0,3 , 3,3 , 4,1 , 0,0, 5,3 , 2,3 , 1,3 , // rotate around x+ 12 - 15 + 0,0, 0,2 , 5,3 , 3,1 , 0,0, 2,3 , 4,3 , 1,0 , + 0,0, 0,1 , 2,3 , 5,1 , 0,0, 4,3 , 3,3 , 1,1 , + 0,0, 0,0 , 4,3 , 2,1 , 0,0, 3,3 , 5,3 , 1,2 , + + 0,0, 1,1 , 2,1 , 4,3 , 0,0, 5,1 , 3,1 , 0,1 , // rotate around x- 16 - 19 + 0,0, 1,2 , 4,1 , 3,3 , 0,0, 2,1 , 5,1 , 0,0 , + 0,0, 1,3 , 3,1 , 5,3 , 0,0, 4,1 , 2,1 , 0,3 , + 0,0, 1,0 , 5,1 , 2,3 , 0,0, 3,1 , 4,1 , 0,2 , + + 0,0, 3,2 , 1,2 , 4,2 , 0,0, 5,2 , 0,2 , 2,2 , // rotate around y- 20 - 23 + 0,0, 5,2 , 1,3 , 3,2 , 0,0, 2,2 , 0,1 , 4,2 , + 0,0, 2,2 , 1,0 , 5,2 , 0,0, 4,2 , 0,0 , 3,2 , + 0,0, 4,2 , 1,1 , 2,2 , 0,0, 3,2 , 0,3 , 5,2 + + }; + u16 tile_index = facedir * 16 + dir_i; + getNodeTileN(mn, p, dir_to_tile[tile_index], data, tile); + tile.rotation = tile.world_aligned ? 0 : dir_to_tile[tile_index + 1]; +} + +static void getTileInfo( + // Input: + MeshMakeData *data, + const v3s16 &p, + const v3s16 &face_dir, + // Output: + bool &makes_face, + v3s16 &p_corrected, + v3s16 &face_dir_corrected, + u16 *lights, + TileSpec &tile + ) +{ + VoxelManipulator &vmanip = data->m_vmanip; + const NodeDefManager *ndef = data->m_client->ndef(); + v3s16 blockpos_nodes = data->m_blockpos * MAP_BLOCKSIZE; + + const MapNode &n0 = vmanip.getNodeRefUnsafe(blockpos_nodes + p); + + // Don't even try to get n1 if n0 is already CONTENT_IGNORE + if (n0.getContent() == CONTENT_IGNORE) { + makes_face = false; + return; + } + + const MapNode &n1 = vmanip.getNodeRefUnsafeCheckFlags(blockpos_nodes + p + face_dir); + + if (n1.getContent() == CONTENT_IGNORE) { + makes_face = false; + return; + } + + // This is hackish + bool equivalent = false; + u8 mf = face_contents(n0.getContent(), n1.getContent(), + &equivalent, ndef); + + if (mf == 0) { + makes_face = false; + return; + } + + makes_face = true; + + MapNode n = n0; + + if (mf == 1) { + p_corrected = p; + face_dir_corrected = face_dir; + } else { + n = n1; + p_corrected = p + face_dir; + face_dir_corrected = -face_dir; + } + + getNodeTile(n, p_corrected, face_dir_corrected, data, tile); + const ContentFeatures &f = ndef->get(n); + tile.emissive_light = f.light_source; + + // eg. water and glass + if (equivalent) { + for (TileLayer &layer : tile.layers) + layer.material_flags |= MATERIAL_FLAG_BACKFACE_CULLING; + } + + if (!data->m_smooth_lighting) { + lights[0] = lights[1] = lights[2] = lights[3] = + getFaceLight(n0, n1, face_dir, ndef); + } else { + v3s16 vertex_dirs[4]; + getNodeVertexDirs(face_dir_corrected, vertex_dirs); + + v3s16 light_p = blockpos_nodes + p_corrected; + for (u16 i = 0; i < 4; i++) + lights[i] = getSmoothLightSolid(light_p, face_dir_corrected, vertex_dirs[i], data); + } +} + +/* + startpos: + translate_dir: unit vector with only one of x, y or z + face_dir: unit vector with only one of x, y or z +*/ +static void updateFastFaceRow( + MeshMakeData *data, + const v3s16 &&startpos, + v3s16 translate_dir, + const v3f &&translate_dir_f, + const v3s16 &&face_dir, + std::vector &dest) +{ + v3s16 p = startpos; + + u16 continuous_tiles_count = 1; + + bool makes_face = false; + v3s16 p_corrected; + v3s16 face_dir_corrected; + u16 lights[4] = {0, 0, 0, 0}; + TileSpec tile; + getTileInfo(data, p, face_dir, + makes_face, p_corrected, face_dir_corrected, + lights, tile); + + // Unroll this variable which has a significant build cost + TileSpec next_tile; + for (u16 j = 0; j < MAP_BLOCKSIZE; j++) { + // If tiling can be done, this is set to false in the next step + bool next_is_different = true; + + v3s16 p_next; + + bool next_makes_face = false; + v3s16 next_p_corrected; + v3s16 next_face_dir_corrected; + u16 next_lights[4] = {0, 0, 0, 0}; + + // If at last position, there is nothing to compare to and + // the face must be drawn anyway + if (j != MAP_BLOCKSIZE - 1) { + p_next = p + translate_dir; + + getTileInfo(data, p_next, face_dir, + next_makes_face, next_p_corrected, + next_face_dir_corrected, next_lights, + next_tile); + + if (next_makes_face == makes_face + && next_p_corrected == p_corrected + translate_dir + && next_face_dir_corrected == face_dir_corrected + && memcmp(next_lights, lights, ARRLEN(lights) * sizeof(u16)) == 0 + && next_tile.isTileable(tile)) { + next_is_different = false; + continuous_tiles_count++; + } + } + if (next_is_different) { + /* + Create a face if there should be one + */ + if (makes_face) { + // Floating point conversion of the position vector + v3f pf(p_corrected.X, p_corrected.Y, p_corrected.Z); + // Center point of face (kind of) + v3f sp = pf - ((f32)continuous_tiles_count * 0.5f - 0.5f) + * translate_dir_f; + v3f scale(1, 1, 1); + + if (translate_dir.X != 0) + scale.X = continuous_tiles_count; + if (translate_dir.Y != 0) + scale.Y = continuous_tiles_count; + if (translate_dir.Z != 0) + scale.Z = continuous_tiles_count; + + makeFastFace(tile, lights[0], lights[1], lights[2], lights[3], + pf, sp, face_dir_corrected, scale, dest); + + g_profiler->avg("Meshgen: faces drawn by tiling", 0); + for (int i = 1; i < continuous_tiles_count; i++) + g_profiler->avg("Meshgen: faces drawn by tiling", 1); + } + + continuous_tiles_count = 1; + } + + makes_face = next_makes_face; + p_corrected = next_p_corrected; + face_dir_corrected = next_face_dir_corrected; + std::memcpy(lights, next_lights, ARRLEN(lights) * sizeof(u16)); + if (next_is_different) + tile = next_tile; + p = p_next; + } +} + +static void updateAllFastFaceRows(MeshMakeData *data, + std::vector &dest) +{ + /* + Go through every y,z and get top(y+) faces in rows of x+ + */ + for (s16 y = 0; y < MAP_BLOCKSIZE; y++) + for (s16 z = 0; z < MAP_BLOCKSIZE; z++) + updateFastFaceRow(data, + v3s16(0, y, z), + v3s16(1, 0, 0), //dir + v3f (1, 0, 0), + v3s16(0, 1, 0), //face dir + dest); + + /* + Go through every x,y and get right(x+) faces in rows of z+ + */ + for (s16 x = 0; x < MAP_BLOCKSIZE; x++) + for (s16 y = 0; y < MAP_BLOCKSIZE; y++) + updateFastFaceRow(data, + v3s16(x, y, 0), + v3s16(0, 0, 1), //dir + v3f (0, 0, 1), + v3s16(1, 0, 0), //face dir + dest); + + /* + Go through every y,z and get back(z+) faces in rows of x+ + */ + for (s16 z = 0; z < MAP_BLOCKSIZE; z++) + for (s16 y = 0; y < MAP_BLOCKSIZE; y++) + updateFastFaceRow(data, + v3s16(0, y, z), + v3s16(1, 0, 0), //dir + v3f (1, 0, 0), + v3s16(0, 0, 1), //face dir + dest); +} + +static void applyTileColor(PreMeshBuffer &pmb) +{ + video::SColor tc = pmb.layer.color; + if (tc == video::SColor(0xFFFFFFFF)) + return; + for (video::S3DVertex &vertex : pmb.vertices) { + video::SColor *c = &vertex.Color; + c->set(c->getAlpha(), + c->getRed() * tc.getRed() / 255, + c->getGreen() * tc.getGreen() / 255, + c->getBlue() * tc.getBlue() / 255); + } +} + +/* + MapBlockMesh +*/ + +MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): + m_minimap_mapblock(NULL), + m_tsrc(data->m_client->getTextureSource()), + m_shdrsrc(data->m_client->getShaderSource()), + m_animation_force_timer(0), // force initial animation + m_last_crack(-1), + m_last_daynight_ratio((u32) -1) +{ + for (auto &m : m_mesh) + m = new scene::SMesh(); + m_enable_shaders = data->m_use_shaders; + m_use_tangent_vertices = data->m_use_tangent_vertices; + m_enable_vbo = g_settings->getBool("enable_vbo"); + + if (g_settings->getBool("enable_minimap")) { + m_minimap_mapblock = new MinimapMapblock; + m_minimap_mapblock->getMinimapNodes( + &data->m_vmanip, data->m_blockpos * MAP_BLOCKSIZE); + } + + // 4-21ms for MAP_BLOCKSIZE=16 (NOTE: probably outdated) + // 24-155ms for MAP_BLOCKSIZE=32 (NOTE: probably outdated) + //TimeTaker timer1("MapBlockMesh()"); + + std::vector fastfaces_new; + fastfaces_new.reserve(512); + + /* + We are including the faces of the trailing edges of the block. + This means that when something changes, the caller must + also update the meshes of the blocks at the leading edges. + + NOTE: This is the slowest part of this method. + */ + { + // 4-23ms for MAP_BLOCKSIZE=16 (NOTE: probably outdated) + //TimeTaker timer2("updateAllFastFaceRows()"); + updateAllFastFaceRows(data, fastfaces_new); + } + // End of slow part + + /* + Convert FastFaces to MeshCollector + */ + + MeshCollector collector; + + { + // avg 0ms (100ms spikes when loading textures the first time) + // (NOTE: probably outdated) + //TimeTaker timer2("MeshCollector building"); + + for (const FastFace &f : fastfaces_new) { + static const u16 indices[] = {0, 1, 2, 2, 3, 0}; + static const u16 indices_alternate[] = {0, 1, 3, 2, 3, 1}; + const u16 *indices_p = + f.vertex_0_2_connected ? indices : indices_alternate; + collector.append(f.tile, f.vertices, 4, indices_p, 6); + } + } + + /* + Add special graphics: + - torches + - flowing water + - fences + - whatever + */ + + { + MapblockMeshGenerator generator(data, &collector); + generator.generate(); + } + + /* + Convert MeshCollector to SMesh + */ + + for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) { + for(u32 i = 0; i < collector.prebuffers[layer].size(); i++) + { + PreMeshBuffer &p = collector.prebuffers[layer][i]; + + applyTileColor(p); + + // Generate animation data + // - Cracks + if (p.layer.material_flags & MATERIAL_FLAG_CRACK) { + // Find the texture name plus ^[crack:N: + std::ostringstream os(std::ios::binary); + os << m_tsrc->getTextureName(p.layer.texture_id) << "^[crack"; + if (p.layer.material_flags & MATERIAL_FLAG_CRACK_OVERLAY) + os << "o"; // use ^[cracko + u8 tiles = p.layer.scale; + if (tiles > 1) + os << ":" << (u32)tiles; + os << ":" << (u32)p.layer.animation_frame_count << ":"; + m_crack_materials.insert(std::make_pair( + std::pair(layer, i), os.str())); + // Replace tile texture with the cracked one + p.layer.texture = m_tsrc->getTextureForMesh( + os.str() + "0", + &p.layer.texture_id); + } + // - Texture animation + if (p.layer.material_flags & MATERIAL_FLAG_ANIMATION) { + // Add to MapBlockMesh in order to animate these tiles + m_animation_tiles[std::pair(layer, i)] = p.layer; + m_animation_frames[std::pair(layer, i)] = 0; + if (g_settings->getBool( + "desynchronize_mapblock_texture_animation")) { + // Get starting position from noise + m_animation_frame_offsets[std::pair(layer, i)] = + 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(layer, i)] = 0; + } + // Replace tile texture with the first animation frame + p.layer.texture = (*p.layer.frames)[0].texture; + } + + if (!m_enable_shaders) { + // Extract colors for day-night animation + // Dummy sunlight to handle non-sunlit areas + video::SColorf sunlight; + get_sunlight_color(&sunlight, 0); + 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(layer, i)][j] = copy; + + // The sunlight ratio has been stored, + // delete alpha (for the final rendering). + vc->setAlpha(255); + } + } + + // Create material + video::SMaterial material; + material.setFlag(video::EMF_LIGHTING, false); + material.setFlag(video::EMF_BACK_FACE_CULLING, true); + material.setFlag(video::EMF_BILINEAR_FILTER, false); + material.setFlag(video::EMF_FOG_ENABLE, true); + material.setTexture(0, p.layer.texture); + + if (m_enable_shaders) { + material.MaterialType = m_shdrsrc->getShaderInfo( + p.layer.shader_id).material; + p.layer.applyMaterialOptionsWithShaders(material); + if (p.layer.normal_texture) + material.setTexture(1, p.layer.normal_texture); + material.setTexture(2, p.layer.flags_texture); + } else { + p.layer.applyMaterialOptions(material); + } + + scene::SMesh *mesh = (scene::SMesh *)m_mesh[layer]; + + // Create meshbuffer, add to mesh + if (m_use_tangent_vertices) { + scene::SMeshBufferTangents *buf = + new scene::SMeshBufferTangents(); + buf->Material = material; + buf->Vertices.reallocate(p.vertices.size()); + buf->Indices.reallocate(p.indices.size()); + for (const video::S3DVertex &v: p.vertices) + buf->Vertices.push_back(video::S3DVertexTangents(v.Pos, v.Color, v.TCoords)); + for (u16 i: p.indices) + buf->Indices.push_back(i); + buf->recalculateBoundingBox(); + mesh->addMeshBuffer(buf); + buf->drop(); + } else { + scene::SMeshBuffer *buf = new scene::SMeshBuffer(); + buf->Material = material; + buf->append(&p.vertices[0], p.vertices.size(), + &p.indices[0], p.indices.size()); + mesh->addMeshBuffer(buf); + buf->drop(); + } + } + + /* + Do some stuff to the mesh + */ + m_camera_offset = camera_offset; + translateMesh(m_mesh[layer], + intToFloat(data->m_blockpos * MAP_BLOCKSIZE - camera_offset, BS)); + + if (m_use_tangent_vertices) { + scene::IMeshManipulator* meshmanip = + RenderingEngine::get_scene_manager()->getMeshManipulator(); + meshmanip->recalculateTangents(m_mesh[layer], true, false, false); + } + + if (m_mesh[layer]) { +#if 0 + // Usually 1-700 faces and 1-7 materials + std::cout << "Updated MapBlock has " << fastfaces_new.size() + << " faces and uses " << m_mesh[layer]->getMeshBufferCount() + << " materials (meshbuffers)" << std::endl; +#endif + + // Use VBO for mesh (this just would set this for ever buffer) + if (m_enable_vbo) + m_mesh[layer]->setHardwareMappingHint(scene::EHM_STATIC); + } + } + + //std::cout<<"added "<getMeshBufferCount(); i++) { + scene::IMeshBuffer *buf = m->getMeshBuffer(i); + RenderingEngine::get_video_driver()->removeHardwareBuffer(buf); + } + m->drop(); + m = NULL; + } + delete m_minimap_mapblock; +} + +bool MapBlockMesh::animate(bool faraway, float time, int crack, + u32 daynight_ratio) +{ + if (!m_has_animation) { + m_animation_force_timer = 100000; + return false; + } + + m_animation_force_timer = myrand_range(5, 100); + + // Cracks + if (crack != m_last_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; + u32 new_texture_id = 0; + video::ITexture *new_texture = + m_tsrc->getTextureForMesh(os.str(), &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; + tile.texture = new_texture; + tile.texture_id = new_texture_id; + // force animation update + m_animation_frames[crack_material.first] = -1; + } + } + + m_last_crack = crack; + } + + // Texture animation + for (auto &animation_tile : m_animation_tiles) { + const TileLayer &tile = animation_tile.second; + // 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; + // If frame doesn't change, skip + if (frame == m_animation_frames[animation_tile.first]) + continue; + + m_animation_frames[animation_tile.first] = frame; + + scene::IMeshBuffer *buf = m_mesh[animation_tile.first.first]-> + getMeshBuffer(animation_tile.first.second); + + const FrameSpec &animation_frame = (*tile.frames)[frame]; + buf->getMaterial().setTexture(0, animation_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); + } + } + + // Day-night transition + if (!m_enable_shaders && (daynight_ratio != m_last_daynight_ratio)) { + // Force reload mesh to VBO + if (m_enable_vbo) + for (scene::IMesh *m : m_mesh) + m->setDirty(); + video::SColorf day_color; + get_sunlight_color(&day_color, daynight_ratio); + + for (auto &daynight_diff : m_daynight_diffs) { + scene::IMeshBuffer *buf = m_mesh[daynight_diff.first.first]-> + getMeshBuffer(daynight_diff.first.second); + video::S3DVertex *vertices = (video::S3DVertex *)buf->getVertices(); + for (const auto &j : daynight_diff.second) + final_color_blend(&(vertices[j.first].Color), j.second, + day_color); + } + m_last_daynight_ratio = daynight_ratio; + } + + return true; +} + +void MapBlockMesh::updateCameraOffset(v3s16 camera_offset) +{ + if (camera_offset != m_camera_offset) { + for (scene::IMesh *layer : m_mesh) { + translateMesh(layer, + intToFloat(m_camera_offset - camera_offset, BS)); + if (m_enable_vbo) + layer->setDirty(); + } + m_camera_offset = camera_offset; + } +} + +video::SColor encode_light(u16 light, u8 emissive_light) +{ + // Get components + u32 day = (light & 0xff); + u32 night = (light >> 8); + // Add emissive light + night += emissive_light * 2.5f; + if (night > 255) + night = 255; + // Since we don't know if the day light is sunlight or + // artificial light, assume it is artificial when the night + // light bank is also lit. + if (day < night) + day = 0; + else + day = day - night; + u32 sum = day + night; + // Ratio of sunlight: + u32 r; + if (sum > 0) + r = day * 255 / sum; + else + r = 0; + // Average light: + float b = (day + night) / 2; + return video::SColor(r, b, b, b); +} diff --git a/src/client/mapblock_mesh.h b/src/client/mapblock_mesh.h new file mode 100644 index 000000000..6af23a656 --- /dev/null +++ b/src/client/mapblock_mesh.h @@ -0,0 +1,228 @@ +/* +Minetest +Copyright (C) 2010-2013 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include "irrlichttypes_extrabloated.h" +#include "client/tile.h" +#include "voxel.h" +#include +#include + +class Client; +class IShaderSource; + +/* + Mesh making stuff +*/ + + +class MapBlock; +struct MinimapMapblock; + +struct MeshMakeData +{ + VoxelManipulator m_vmanip; + v3s16 m_blockpos = v3s16(-1337,-1337,-1337); + v3s16 m_crack_pos_relative = v3s16(-1337,-1337,-1337); + bool m_smooth_lighting = false; + + Client *m_client; + bool m_use_shaders; + bool m_use_tangent_vertices; + + MeshMakeData(Client *client, bool use_shaders, + bool use_tangent_vertices = false); + + /* + Copy block data manually (to allow optimizations by the caller) + */ + void fillBlockDataBegin(const v3s16 &blockpos); + void fillBlockData(const v3s16 &block_offset, MapNode *data); + + /* + Copy central data directly from block, and other data from + parent of block. + */ + void fill(MapBlock *block); + + /* + Set up with only a single node at (1,1,1) + */ + void fillSingleNode(MapNode *node); + + /* + Set the (node) position of a crack + */ + void setCrack(int crack_level, v3s16 crack_pos); + + /* + Enable or disable smooth lighting + */ + void setSmoothLighting(bool smooth_lighting); +}; + +/* + Holds a mesh for a mapblock. + + Besides the SMesh*, this contains information used for animating + the vertex positions, colors and texture coordinates of the mesh. + For example: + - cracks [implemented] + - day/night transitions [implemented] + - animated flowing liquids [not implemented] + - animating vertex positions for e.g. axles [not implemented] +*/ +class MapBlockMesh +{ +public: + // Builds the mesh given + MapBlockMesh(MeshMakeData *data, v3s16 camera_offset); + ~MapBlockMesh(); + + // Main animation function, parameters: + // faraway: whether the block is far away from the camera (~50 nodes) + // time: the global animation time, 0 .. 60 (repeats every minute) + // daynight_ratio: 0 .. 1000 + // crack: -1 .. CRACK_ANIMATION_LENGTH-1 (-1 for off) + // Returns true if anything has been changed. + bool animate(bool faraway, float time, int crack, u32 daynight_ratio); + + scene::IMesh *getMesh() + { + return m_mesh[0]; + } + + scene::IMesh *getMesh(u8 layer) + { + return m_mesh[layer]; + } + + MinimapMapblock *moveMinimapMapblock() + { + MinimapMapblock *p = m_minimap_mapblock; + m_minimap_mapblock = NULL; + return p; + } + + bool isAnimationForced() const + { + return m_animation_force_timer == 0; + } + + void decreaseAnimationForceTimer() + { + if(m_animation_force_timer > 0) + m_animation_force_timer--; + } + + void updateCameraOffset(v3s16 camera_offset); + +private: + scene::IMesh *m_mesh[MAX_TILE_LAYERS]; + MinimapMapblock *m_minimap_mapblock; + ITextureSource *m_tsrc; + IShaderSource *m_shdrsrc; + + bool m_enable_shaders; + bool m_use_tangent_vertices; + bool m_enable_vbo; + + // Must animate() be called before rendering? + bool m_has_animation; + int m_animation_force_timer; + + // Animation info: cracks + // Last crack value passed to animate() + int m_last_crack; + // Maps mesh and mesh buffer (i.e. material) indices to base texture names + std::map, std::string> m_crack_materials; + + // Animation info: texture animationi + // Maps mesh and mesh buffer indices to TileSpecs + // Keys are pairs of (mesh index, buffer index in the mesh) + std::map, TileLayer> m_animation_tiles; + std::map, int> m_animation_frames; // last animation frame + std::map, int> m_animation_frame_offsets; + + // Animation info: day/night transitions + // Last daynight_ratio value passed to animate() + u32 m_last_daynight_ratio; + // For each mesh and mesh buffer, stores pre-baked colors + // of sunlit vertices + // Keys are pairs of (mesh index, buffer index in the mesh) + std::map, std::map > m_daynight_diffs; + + // Camera offset info -> do we have to translate the mesh? + v3s16 m_camera_offset; +}; + +/*! + * Encodes light of a node. + * The result is not the final color, but a + * half-baked vertex color. + * You have to multiply the resulting color + * with the node's color. + * + * \param light the first 8 bits are day light, + * the last 8 bits are night light + * \param emissive_light amount of light the surface emits, + * from 0 to LIGHT_SUN. + */ +video::SColor encode_light(u16 light, u8 emissive_light); + +// Compute light at node +u16 getInteriorLight(MapNode n, s32 increment, const NodeDefManager *ndef); +u16 getFaceLight(MapNode n, MapNode n2, const v3s16 &face_dir, + const NodeDefManager *ndef); +u16 getSmoothLightSolid(const v3s16 &p, const v3s16 &face_dir, const v3s16 &corner, MeshMakeData *data); +u16 getSmoothLightTransparent(const v3s16 &p, const v3s16 &corner, MeshMakeData *data); + +/*! + * Returns the sunlight's color from the current + * day-night ratio. + */ +void get_sunlight_color(video::SColorf *sunlight, u32 daynight_ratio); + +/*! + * Gives the final SColor shown on screen. + * + * \param result output color + * \param light first 8 bits are day light, second 8 bits are + * night light + */ +void final_color_blend(video::SColor *result, + u16 light, u32 daynight_ratio); + +/*! + * Gives the final SColor shown on screen. + * + * \param result output color + * \param data the half-baked vertex color + * \param dayLight color of the sunlight + */ +void final_color_blend(video::SColor *result, + const video::SColor &data, const video::SColorf &dayLight); + +// Retrieves the TileSpec of a face of a node +// Adds MATERIAL_FLAG_CRACK if the node is cracked +// TileSpec should be passed as reference due to the underlying TileFrame and its vector +// TileFrame vector copy cost very much to client +void getNodeTileN(MapNode mn, const v3s16 &p, u8 tileindex, MeshMakeData *data, TileSpec &tile); +void getNodeTile(MapNode mn, const v3s16 &p, const v3s16 &dir, MeshMakeData *data, TileSpec &tile); diff --git a/src/client/mesh.cpp b/src/client/mesh.cpp new file mode 100644 index 000000000..7fc7531f2 --- /dev/null +++ b/src/client/mesh.cpp @@ -0,0 +1,1135 @@ +/* +Minetest +Copyright (C) 2010-2013 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "mesh.h" +#include "debug.h" +#include "log.h" +#include "irrMap.h" +#include +#include +#include +#include + +// In Irrlicht 1.8 the signature of ITexture::lock was changed from +// (bool, u32) to (E_TEXTURE_LOCK_MODE, u32). +#if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 7 +#define MY_ETLM_READ_ONLY true +#else +#define MY_ETLM_READ_ONLY video::ETLM_READ_ONLY +#endif + +inline static void applyShadeFactor(video::SColor& color, float factor) +{ + color.setRed(core::clamp(core::round32(color.getRed()*factor), 0, 255)); + color.setGreen(core::clamp(core::round32(color.getGreen()*factor), 0, 255)); + color.setBlue(core::clamp(core::round32(color.getBlue()*factor), 0, 255)); +} + +void applyFacesShading(video::SColor &color, const v3f &normal) +{ + /* + Some drawtypes have normals set to (0, 0, 0), this must result in + maximum brightness: shade factor 1.0. + Shade factors for aligned cube faces are: + +Y 1.000000 sqrt(1.0) + -Y 0.447213 sqrt(0.2) + +-X 0.670820 sqrt(0.45) + +-Z 0.836660 sqrt(0.7) + */ + float x2 = normal.X * normal.X; + float y2 = normal.Y * normal.Y; + float z2 = normal.Z * normal.Z; + if (normal.Y < 0) + applyShadeFactor(color, 0.670820f * x2 + 0.447213f * y2 + 0.836660f * z2); + else if ((x2 > 1e-3) || (z2 > 1e-3)) + applyShadeFactor(color, 0.670820f * x2 + 1.000000f * y2 + 0.836660f * z2); +} + +scene::IAnimatedMesh* createCubeMesh(v3f scale) +{ + video::SColor c(255,255,255,255); + video::S3DVertex vertices[24] = + { + // Up + video::S3DVertex(-0.5,+0.5,-0.5, 0,1,0, c, 0,1), + video::S3DVertex(-0.5,+0.5,+0.5, 0,1,0, c, 0,0), + video::S3DVertex(+0.5,+0.5,+0.5, 0,1,0, c, 1,0), + video::S3DVertex(+0.5,+0.5,-0.5, 0,1,0, c, 1,1), + // Down + video::S3DVertex(-0.5,-0.5,-0.5, 0,-1,0, c, 0,0), + video::S3DVertex(+0.5,-0.5,-0.5, 0,-1,0, c, 1,0), + video::S3DVertex(+0.5,-0.5,+0.5, 0,-1,0, c, 1,1), + video::S3DVertex(-0.5,-0.5,+0.5, 0,-1,0, c, 0,1), + // Right + video::S3DVertex(+0.5,-0.5,-0.5, 1,0,0, c, 0,1), + video::S3DVertex(+0.5,+0.5,-0.5, 1,0,0, c, 0,0), + video::S3DVertex(+0.5,+0.5,+0.5, 1,0,0, c, 1,0), + video::S3DVertex(+0.5,-0.5,+0.5, 1,0,0, c, 1,1), + // Left + video::S3DVertex(-0.5,-0.5,-0.5, -1,0,0, c, 1,1), + video::S3DVertex(-0.5,-0.5,+0.5, -1,0,0, c, 0,1), + video::S3DVertex(-0.5,+0.5,+0.5, -1,0,0, c, 0,0), + video::S3DVertex(-0.5,+0.5,-0.5, -1,0,0, c, 1,0), + // Back + video::S3DVertex(-0.5,-0.5,+0.5, 0,0,1, c, 1,1), + video::S3DVertex(+0.5,-0.5,+0.5, 0,0,1, c, 0,1), + video::S3DVertex(+0.5,+0.5,+0.5, 0,0,1, c, 0,0), + video::S3DVertex(-0.5,+0.5,+0.5, 0,0,1, c, 1,0), + // Front + video::S3DVertex(-0.5,-0.5,-0.5, 0,0,-1, c, 0,1), + video::S3DVertex(-0.5,+0.5,-0.5, 0,0,-1, c, 0,0), + video::S3DVertex(+0.5,+0.5,-0.5, 0,0,-1, c, 1,0), + video::S3DVertex(+0.5,-0.5,-0.5, 0,0,-1, c, 1,1), + }; + + u16 indices[6] = {0,1,2,2,3,0}; + + scene::SMesh *mesh = new scene::SMesh(); + for (u32 i=0; i<6; ++i) + { + scene::IMeshBuffer *buf = new scene::SMeshBuffer(); + buf->append(vertices + 4 * i, 4, indices, 6); + // Set default material + buf->getMaterial().setFlag(video::EMF_LIGHTING, false); + buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false); + buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; + // Add mesh buffer to mesh + mesh->addMeshBuffer(buf); + buf->drop(); + } + + scene::SAnimatedMesh *anim_mesh = new scene::SAnimatedMesh(mesh); + mesh->drop(); + scaleMesh(anim_mesh, scale); // also recalculates bounding box + return anim_mesh; +} + +void scaleMesh(scene::IMesh *mesh, v3f scale) +{ + if (mesh == NULL) + return; + + aabb3f bbox; + bbox.reset(0, 0, 0); + + u32 mc = mesh->getMeshBufferCount(); + for (u32 j = 0; j < mc; j++) { + scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); + const u32 stride = getVertexPitchFromType(buf->getVertexType()); + u32 vertex_count = buf->getVertexCount(); + u8 *vertices = (u8 *)buf->getVertices(); + for (u32 i = 0; i < vertex_count; i++) + ((video::S3DVertex *)(vertices + i * stride))->Pos *= scale; + + buf->recalculateBoundingBox(); + + // calculate total bounding box + if (j == 0) + bbox = buf->getBoundingBox(); + else + bbox.addInternalBox(buf->getBoundingBox()); + } + mesh->setBoundingBox(bbox); +} + +void translateMesh(scene::IMesh *mesh, v3f vec) +{ + if (mesh == NULL) + return; + + aabb3f bbox; + bbox.reset(0, 0, 0); + + u32 mc = mesh->getMeshBufferCount(); + for (u32 j = 0; j < mc; j++) { + scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); + const u32 stride = getVertexPitchFromType(buf->getVertexType()); + u32 vertex_count = buf->getVertexCount(); + u8 *vertices = (u8 *)buf->getVertices(); + for (u32 i = 0; i < vertex_count; i++) + ((video::S3DVertex *)(vertices + i * stride))->Pos += vec; + + buf->recalculateBoundingBox(); + + // calculate total bounding box + if (j == 0) + bbox = buf->getBoundingBox(); + else + bbox.addInternalBox(buf->getBoundingBox()); + } + mesh->setBoundingBox(bbox); +} + +void setMeshBufferColor(scene::IMeshBuffer *buf, const video::SColor &color) +{ + const u32 stride = getVertexPitchFromType(buf->getVertexType()); + u32 vertex_count = buf->getVertexCount(); + u8 *vertices = (u8 *) buf->getVertices(); + for (u32 i = 0; i < vertex_count; i++) + ((video::S3DVertex *) (vertices + i * stride))->Color = color; +} + +void setAnimatedMeshColor(scene::IAnimatedMeshSceneNode *node, const video::SColor &color) +{ + for (u32 i = 0; i < node->getMaterialCount(); ++i) { + node->getMaterial(i).EmissiveColor = color; + } +} + +void setMeshColor(scene::IMesh *mesh, const video::SColor &color) +{ + if (mesh == NULL) + return; + + u32 mc = mesh->getMeshBufferCount(); + for (u32 j = 0; j < mc; j++) + setMeshBufferColor(mesh->getMeshBuffer(j), color); +} + +void colorizeMeshBuffer(scene::IMeshBuffer *buf, const video::SColor *buffercolor) +{ + const u32 stride = getVertexPitchFromType(buf->getVertexType()); + u32 vertex_count = buf->getVertexCount(); + u8 *vertices = (u8 *) buf->getVertices(); + for (u32 i = 0; i < vertex_count; i++) { + video::S3DVertex *vertex = (video::S3DVertex *) (vertices + i * stride); + video::SColor *vc = &(vertex->Color); + // Reset color + *vc = *buffercolor; + // Apply shading + applyFacesShading(*vc, vertex->Normal); + } +} + +void setMeshColorByNormalXYZ(scene::IMesh *mesh, + const video::SColor &colorX, + const video::SColor &colorY, + const video::SColor &colorZ) +{ + if (mesh == NULL) + return; + + u16 mc = mesh->getMeshBufferCount(); + for (u16 j = 0; j < mc; j++) { + scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); + const u32 stride = getVertexPitchFromType(buf->getVertexType()); + u32 vertex_count = buf->getVertexCount(); + u8 *vertices = (u8 *)buf->getVertices(); + for (u32 i = 0; i < vertex_count; i++) { + video::S3DVertex *vertex = (video::S3DVertex *)(vertices + i * stride); + f32 x = fabs(vertex->Normal.X); + f32 y = fabs(vertex->Normal.Y); + f32 z = fabs(vertex->Normal.Z); + if (x >= y && x >= z) + vertex->Color = colorX; + else if (y >= z) + vertex->Color = colorY; + else + vertex->Color = colorZ; + } + } +} + +void setMeshColorByNormal(scene::IMesh *mesh, const v3f &normal, + const video::SColor &color) +{ + if (!mesh) + return; + + u16 mc = mesh->getMeshBufferCount(); + for (u16 j = 0; j < mc; j++) { + scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); + const u32 stride = getVertexPitchFromType(buf->getVertexType()); + u32 vertex_count = buf->getVertexCount(); + u8 *vertices = (u8 *)buf->getVertices(); + for (u32 i = 0; i < vertex_count; i++) { + video::S3DVertex *vertex = (video::S3DVertex *)(vertices + i * stride); + if (normal == vertex->Normal) { + vertex->Color = color; + } + } + } +} + +void rotateMeshXYby(scene::IMesh *mesh, f64 degrees) +{ + u16 mc = mesh->getMeshBufferCount(); + for (u16 j = 0; j < mc; j++) { + scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); + const u32 stride = getVertexPitchFromType(buf->getVertexType()); + u32 vertex_count = buf->getVertexCount(); + u8 *vertices = (u8 *)buf->getVertices(); + for (u32 i = 0; i < vertex_count; i++) + ((video::S3DVertex *)(vertices + i * stride))->Pos.rotateXYBy(degrees); + } +} + +void rotateMeshXZby(scene::IMesh *mesh, f64 degrees) +{ + u16 mc = mesh->getMeshBufferCount(); + for (u16 j = 0; j < mc; j++) { + scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); + const u32 stride = getVertexPitchFromType(buf->getVertexType()); + u32 vertex_count = buf->getVertexCount(); + u8 *vertices = (u8 *)buf->getVertices(); + for (u32 i = 0; i < vertex_count; i++) + ((video::S3DVertex *)(vertices + i * stride))->Pos.rotateXZBy(degrees); + } +} + +void rotateMeshYZby(scene::IMesh *mesh, f64 degrees) +{ + u16 mc = mesh->getMeshBufferCount(); + for (u16 j = 0; j < mc; j++) { + scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); + const u32 stride = getVertexPitchFromType(buf->getVertexType()); + u32 vertex_count = buf->getVertexCount(); + u8 *vertices = (u8 *)buf->getVertices(); + for (u32 i = 0; i < vertex_count; i++) + ((video::S3DVertex *)(vertices + i * stride))->Pos.rotateYZBy(degrees); + } +} + +void rotateMeshBy6dFacedir(scene::IMesh *mesh, int facedir) +{ + int axisdir = facedir >> 2; + facedir &= 0x03; + + u16 mc = mesh->getMeshBufferCount(); + for (u16 j = 0; j < mc; j++) { + scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); + const u32 stride = getVertexPitchFromType(buf->getVertexType()); + u32 vertex_count = buf->getVertexCount(); + u8 *vertices = (u8 *)buf->getVertices(); + for (u32 i = 0; i < vertex_count; i++) { + video::S3DVertex *vertex = (video::S3DVertex *)(vertices + i * stride); + switch (axisdir) { + case 0: + if (facedir == 1) + vertex->Pos.rotateXZBy(-90); + else if (facedir == 2) + vertex->Pos.rotateXZBy(180); + else if (facedir == 3) + vertex->Pos.rotateXZBy(90); + break; + case 1: // z+ + vertex->Pos.rotateYZBy(90); + if (facedir == 1) + vertex->Pos.rotateXYBy(90); + else if (facedir == 2) + vertex->Pos.rotateXYBy(180); + else if (facedir == 3) + vertex->Pos.rotateXYBy(-90); + break; + case 2: //z- + vertex->Pos.rotateYZBy(-90); + if (facedir == 1) + vertex->Pos.rotateXYBy(-90); + else if (facedir == 2) + vertex->Pos.rotateXYBy(180); + else if (facedir == 3) + vertex->Pos.rotateXYBy(90); + break; + case 3: //x+ + vertex->Pos.rotateXYBy(-90); + if (facedir == 1) + vertex->Pos.rotateYZBy(90); + else if (facedir == 2) + vertex->Pos.rotateYZBy(180); + else if (facedir == 3) + vertex->Pos.rotateYZBy(-90); + break; + case 4: //x- + vertex->Pos.rotateXYBy(90); + if (facedir == 1) + vertex->Pos.rotateYZBy(-90); + else if (facedir == 2) + vertex->Pos.rotateYZBy(180); + else if (facedir == 3) + vertex->Pos.rotateYZBy(90); + break; + case 5: + vertex->Pos.rotateXYBy(-180); + if (facedir == 1) + vertex->Pos.rotateXZBy(90); + else if (facedir == 2) + vertex->Pos.rotateXZBy(180); + else if (facedir == 3) + vertex->Pos.rotateXZBy(-90); + break; + default: + break; + } + } + } +} + +void recalculateBoundingBox(scene::IMesh *src_mesh) +{ + aabb3f bbox; + bbox.reset(0,0,0); + for (u16 j = 0; j < src_mesh->getMeshBufferCount(); j++) { + scene::IMeshBuffer *buf = src_mesh->getMeshBuffer(j); + buf->recalculateBoundingBox(); + if (j == 0) + bbox = buf->getBoundingBox(); + else + bbox.addInternalBox(buf->getBoundingBox()); + } + src_mesh->setBoundingBox(bbox); +} + +scene::IMeshBuffer* cloneMeshBuffer(scene::IMeshBuffer *mesh_buffer) +{ + switch (mesh_buffer->getVertexType()) { + case video::EVT_STANDARD: { + video::S3DVertex *v = (video::S3DVertex *) mesh_buffer->getVertices(); + u16 *indices = mesh_buffer->getIndices(); + scene::SMeshBuffer *cloned_buffer = new scene::SMeshBuffer(); + cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices, + mesh_buffer->getIndexCount()); + return cloned_buffer; + } + case video::EVT_2TCOORDS: { + video::S3DVertex2TCoords *v = + (video::S3DVertex2TCoords *) mesh_buffer->getVertices(); + u16 *indices = mesh_buffer->getIndices(); + scene::SMeshBufferTangents *cloned_buffer = + new scene::SMeshBufferTangents(); + cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices, + mesh_buffer->getIndexCount()); + return cloned_buffer; + } + case video::EVT_TANGENTS: { + video::S3DVertexTangents *v = + (video::S3DVertexTangents *) mesh_buffer->getVertices(); + u16 *indices = mesh_buffer->getIndices(); + scene::SMeshBufferTangents *cloned_buffer = + new scene::SMeshBufferTangents(); + cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices, + mesh_buffer->getIndexCount()); + return cloned_buffer; + } + } + // This should not happen. + sanity_check(false); + return NULL; +} + +scene::SMesh* cloneMesh(scene::IMesh *src_mesh) +{ + scene::SMesh* dst_mesh = new scene::SMesh(); + for (u16 j = 0; j < src_mesh->getMeshBufferCount(); j++) { + scene::IMeshBuffer *temp_buf = cloneMeshBuffer( + src_mesh->getMeshBuffer(j)); + dst_mesh->addMeshBuffer(temp_buf); + temp_buf->drop(); + + } + return dst_mesh; +} + +scene::IMesh* convertNodeboxesToMesh(const std::vector &boxes, + const f32 *uv_coords, float expand) +{ + scene::SMesh* dst_mesh = new scene::SMesh(); + + for (u16 j = 0; j < 6; j++) + { + scene::IMeshBuffer *buf = new scene::SMeshBuffer(); + buf->getMaterial().setFlag(video::EMF_LIGHTING, false); + buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false); + dst_mesh->addMeshBuffer(buf); + buf->drop(); + } + + video::SColor c(255,255,255,255); + + for (aabb3f box : boxes) { + box.repair(); + + box.MinEdge.X -= expand; + box.MinEdge.Y -= expand; + box.MinEdge.Z -= expand; + box.MaxEdge.X += expand; + box.MaxEdge.Y += expand; + box.MaxEdge.Z += expand; + + // Compute texture UV coords + f32 tx1 = (box.MinEdge.X / BS) + 0.5; + f32 ty1 = (box.MinEdge.Y / BS) + 0.5; + f32 tz1 = (box.MinEdge.Z / BS) + 0.5; + f32 tx2 = (box.MaxEdge.X / BS) + 0.5; + f32 ty2 = (box.MaxEdge.Y / BS) + 0.5; + f32 tz2 = (box.MaxEdge.Z / BS) + 0.5; + + f32 txc_default[24] = { + // up + tx1, 1 - tz2, tx2, 1 - tz1, + // down + tx1, tz1, tx2, tz2, + // right + tz1, 1 - ty2, tz2, 1 - ty1, + // left + 1 - tz2, 1 - ty2, 1 - tz1, 1 - ty1, + // back + 1 - tx2, 1 - ty2, 1 - tx1, 1 - ty1, + // front + tx1, 1 - ty2, tx2, 1 - ty1, + }; + + // use default texture UV mapping if not provided + const f32 *txc = uv_coords ? uv_coords : txc_default; + + v3f min = box.MinEdge; + v3f max = box.MaxEdge; + + video::S3DVertex vertices[24] = + { + // up + video::S3DVertex(min.X,max.Y,max.Z, 0,1,0, c, txc[0],txc[1]), + video::S3DVertex(max.X,max.Y,max.Z, 0,1,0, c, txc[2],txc[1]), + video::S3DVertex(max.X,max.Y,min.Z, 0,1,0, c, txc[2],txc[3]), + video::S3DVertex(min.X,max.Y,min.Z, 0,1,0, c, txc[0],txc[3]), + // down + video::S3DVertex(min.X,min.Y,min.Z, 0,-1,0, c, txc[4],txc[5]), + video::S3DVertex(max.X,min.Y,min.Z, 0,-1,0, c, txc[6],txc[5]), + video::S3DVertex(max.X,min.Y,max.Z, 0,-1,0, c, txc[6],txc[7]), + video::S3DVertex(min.X,min.Y,max.Z, 0,-1,0, c, txc[4],txc[7]), + // right + video::S3DVertex(max.X,max.Y,min.Z, 1,0,0, c, txc[ 8],txc[9]), + video::S3DVertex(max.X,max.Y,max.Z, 1,0,0, c, txc[10],txc[9]), + video::S3DVertex(max.X,min.Y,max.Z, 1,0,0, c, txc[10],txc[11]), + video::S3DVertex(max.X,min.Y,min.Z, 1,0,0, c, txc[ 8],txc[11]), + // left + video::S3DVertex(min.X,max.Y,max.Z, -1,0,0, c, txc[12],txc[13]), + video::S3DVertex(min.X,max.Y,min.Z, -1,0,0, c, txc[14],txc[13]), + video::S3DVertex(min.X,min.Y,min.Z, -1,0,0, c, txc[14],txc[15]), + video::S3DVertex(min.X,min.Y,max.Z, -1,0,0, c, txc[12],txc[15]), + // back + video::S3DVertex(max.X,max.Y,max.Z, 0,0,1, c, txc[16],txc[17]), + video::S3DVertex(min.X,max.Y,max.Z, 0,0,1, c, txc[18],txc[17]), + video::S3DVertex(min.X,min.Y,max.Z, 0,0,1, c, txc[18],txc[19]), + video::S3DVertex(max.X,min.Y,max.Z, 0,0,1, c, txc[16],txc[19]), + // front + video::S3DVertex(min.X,max.Y,min.Z, 0,0,-1, c, txc[20],txc[21]), + video::S3DVertex(max.X,max.Y,min.Z, 0,0,-1, c, txc[22],txc[21]), + video::S3DVertex(max.X,min.Y,min.Z, 0,0,-1, c, txc[22],txc[23]), + video::S3DVertex(min.X,min.Y,min.Z, 0,0,-1, c, txc[20],txc[23]), + }; + + u16 indices[] = {0,1,2,2,3,0}; + + for(u16 j = 0; j < 24; j += 4) + { + scene::IMeshBuffer *buf = dst_mesh->getMeshBuffer(j / 4); + buf->append(vertices + j, 4, indices, 6); + } + } + return dst_mesh; +} + +struct vcache +{ + core::array tris; + float score; + s16 cachepos; + u16 NumActiveTris; +}; + +struct tcache +{ + u16 ind[3]; + float score; + bool drawn; +}; + +const u16 cachesize = 32; + +float FindVertexScore(vcache *v) +{ + const float CacheDecayPower = 1.5f; + const float LastTriScore = 0.75f; + const float ValenceBoostScale = 2.0f; + const float ValenceBoostPower = 0.5f; + const float MaxSizeVertexCache = 32.0f; + + if (v->NumActiveTris == 0) + { + // No tri needs this vertex! + return -1.0f; + } + + float Score = 0.0f; + int CachePosition = v->cachepos; + if (CachePosition < 0) + { + // Vertex is not in FIFO cache - no score. + } + else + { + if (CachePosition < 3) + { + // This vertex was used in the last triangle, + // so it has a fixed score. + Score = LastTriScore; + } + else + { + // Points for being high in the cache. + const float Scaler = 1.0f / (MaxSizeVertexCache - 3); + Score = 1.0f - (CachePosition - 3) * Scaler; + Score = powf(Score, CacheDecayPower); + } + } + + // Bonus points for having a low number of tris still to + // use the vert, so we get rid of lone verts quickly. + float ValenceBoost = powf(v->NumActiveTris, + -ValenceBoostPower); + Score += ValenceBoostScale * ValenceBoost; + + return Score; +} + +/* + A specialized LRU cache for the Forsyth algorithm. +*/ + +class f_lru +{ + +public: + f_lru(vcache *v, tcache *t): vc(v), tc(t) + { + for (int &i : cache) { + i = -1; + } + } + + // Adds this vertex index and returns the highest-scoring triangle index + u32 add(u16 vert, bool updatetris = false) + { + bool found = false; + + // Mark existing pos as empty + for (u16 i = 0; i < cachesize; i++) + { + if (cache[i] == vert) + { + // Move everything down + for (u16 j = i; j; j--) + { + cache[j] = cache[j - 1]; + } + + found = true; + break; + } + } + + if (!found) + { + if (cache[cachesize-1] != -1) + vc[cache[cachesize-1]].cachepos = -1; + + // Move everything down + for (u16 i = cachesize - 1; i; i--) + { + cache[i] = cache[i - 1]; + } + } + + cache[0] = vert; + + u32 highest = 0; + float hiscore = 0; + + if (updatetris) + { + // Update cache positions + for (u16 i = 0; i < cachesize; i++) + { + if (cache[i] == -1) + break; + + vc[cache[i]].cachepos = i; + vc[cache[i]].score = FindVertexScore(&vc[cache[i]]); + } + + // Update triangle scores + for (int i : cache) { + if (i == -1) + break; + + const u16 trisize = vc[i].tris.size(); + for (u16 t = 0; t < trisize; t++) + { + tcache *tri = &tc[vc[i].tris[t]]; + + tri->score = + vc[tri->ind[0]].score + + vc[tri->ind[1]].score + + vc[tri->ind[2]].score; + + if (tri->score > hiscore) + { + hiscore = tri->score; + highest = vc[i].tris[t]; + } + } + } + } + + return highest; + } + +private: + s32 cache[cachesize]; + vcache *vc; + tcache *tc; +}; + +/** +Vertex cache optimization according to the Forsyth paper: +http://home.comcast.net/~tom_forsyth/papers/fast_vert_cache_opt.html + +The function is thread-safe (read: you can optimize several meshes in different threads) + +\param mesh Source mesh for the operation. */ +scene::IMesh* createForsythOptimizedMesh(const scene::IMesh *mesh) +{ + if (!mesh) + return 0; + + scene::SMesh *newmesh = new scene::SMesh(); + newmesh->BoundingBox = mesh->getBoundingBox(); + + const u32 mbcount = mesh->getMeshBufferCount(); + + for (u32 b = 0; b < mbcount; ++b) + { + const scene::IMeshBuffer *mb = mesh->getMeshBuffer(b); + + if (mb->getIndexType() != video::EIT_16BIT) + { + //os::Printer::log("Cannot optimize a mesh with 32bit indices", ELL_ERROR); + newmesh->drop(); + return 0; + } + + const u32 icount = mb->getIndexCount(); + const u32 tcount = icount / 3; + const u32 vcount = mb->getVertexCount(); + const u16 *ind = mb->getIndices(); + + vcache *vc = new vcache[vcount]; + tcache *tc = new tcache[tcount]; + + f_lru lru(vc, tc); + + // init + for (u16 i = 0; i < vcount; i++) + { + vc[i].score = 0; + vc[i].cachepos = -1; + vc[i].NumActiveTris = 0; + } + + // First pass: count how many times a vert is used + for (u32 i = 0; i < icount; i += 3) + { + vc[ind[i]].NumActiveTris++; + vc[ind[i + 1]].NumActiveTris++; + vc[ind[i + 2]].NumActiveTris++; + + const u32 tri_ind = i/3; + tc[tri_ind].ind[0] = ind[i]; + tc[tri_ind].ind[1] = ind[i + 1]; + tc[tri_ind].ind[2] = ind[i + 2]; + } + + // Second pass: list of each triangle + for (u32 i = 0; i < tcount; i++) + { + vc[tc[i].ind[0]].tris.push_back(i); + vc[tc[i].ind[1]].tris.push_back(i); + vc[tc[i].ind[2]].tris.push_back(i); + + tc[i].drawn = false; + } + + // Give initial scores + for (u16 i = 0; i < vcount; i++) + { + vc[i].score = FindVertexScore(&vc[i]); + } + for (u32 i = 0; i < tcount; i++) + { + tc[i].score = + vc[tc[i].ind[0]].score + + vc[tc[i].ind[1]].score + + vc[tc[i].ind[2]].score; + } + + switch(mb->getVertexType()) + { + case video::EVT_STANDARD: + { + video::S3DVertex *v = (video::S3DVertex *) mb->getVertices(); + + scene::SMeshBuffer *buf = new scene::SMeshBuffer(); + buf->Material = mb->getMaterial(); + + buf->Vertices.reallocate(vcount); + buf->Indices.reallocate(icount); + + core::map sind; // search index for fast operation + typedef core::map::Node snode; + + // Main algorithm + u32 highest = 0; + u32 drawcalls = 0; + for (;;) + { + if (tc[highest].drawn) + { + bool found = false; + float hiscore = 0; + for (u32 t = 0; t < tcount; t++) + { + if (!tc[t].drawn) + { + if (tc[t].score > hiscore) + { + highest = t; + hiscore = tc[t].score; + found = true; + } + } + } + if (!found) + break; + } + + // Output the best triangle + u16 newind = buf->Vertices.size(); + + snode *s = sind.find(v[tc[highest].ind[0]]); + + if (!s) + { + buf->Vertices.push_back(v[tc[highest].ind[0]]); + buf->Indices.push_back(newind); + sind.insert(v[tc[highest].ind[0]], newind); + newind++; + } + else + { + buf->Indices.push_back(s->getValue()); + } + + s = sind.find(v[tc[highest].ind[1]]); + + if (!s) + { + buf->Vertices.push_back(v[tc[highest].ind[1]]); + buf->Indices.push_back(newind); + sind.insert(v[tc[highest].ind[1]], newind); + newind++; + } + else + { + buf->Indices.push_back(s->getValue()); + } + + s = sind.find(v[tc[highest].ind[2]]); + + if (!s) + { + buf->Vertices.push_back(v[tc[highest].ind[2]]); + buf->Indices.push_back(newind); + sind.insert(v[tc[highest].ind[2]], newind); + } + else + { + buf->Indices.push_back(s->getValue()); + } + + vc[tc[highest].ind[0]].NumActiveTris--; + vc[tc[highest].ind[1]].NumActiveTris--; + vc[tc[highest].ind[2]].NumActiveTris--; + + tc[highest].drawn = true; + + for (u16 j : tc[highest].ind) { + vcache *vert = &vc[j]; + for (u16 t = 0; t < vert->tris.size(); t++) + { + if (highest == vert->tris[t]) + { + vert->tris.erase(t); + break; + } + } + } + + lru.add(tc[highest].ind[0]); + lru.add(tc[highest].ind[1]); + highest = lru.add(tc[highest].ind[2], true); + drawcalls++; + } + + buf->setBoundingBox(mb->getBoundingBox()); + newmesh->addMeshBuffer(buf); + buf->drop(); + } + break; + case video::EVT_2TCOORDS: + { + video::S3DVertex2TCoords *v = (video::S3DVertex2TCoords *) mb->getVertices(); + + scene::SMeshBufferLightMap *buf = new scene::SMeshBufferLightMap(); + buf->Material = mb->getMaterial(); + + buf->Vertices.reallocate(vcount); + buf->Indices.reallocate(icount); + + core::map sind; // search index for fast operation + typedef core::map::Node snode; + + // Main algorithm + u32 highest = 0; + u32 drawcalls = 0; + for (;;) + { + if (tc[highest].drawn) + { + bool found = false; + float hiscore = 0; + for (u32 t = 0; t < tcount; t++) + { + if (!tc[t].drawn) + { + if (tc[t].score > hiscore) + { + highest = t; + hiscore = tc[t].score; + found = true; + } + } + } + if (!found) + break; + } + + // Output the best triangle + u16 newind = buf->Vertices.size(); + + snode *s = sind.find(v[tc[highest].ind[0]]); + + if (!s) + { + buf->Vertices.push_back(v[tc[highest].ind[0]]); + buf->Indices.push_back(newind); + sind.insert(v[tc[highest].ind[0]], newind); + newind++; + } + else + { + buf->Indices.push_back(s->getValue()); + } + + s = sind.find(v[tc[highest].ind[1]]); + + if (!s) + { + buf->Vertices.push_back(v[tc[highest].ind[1]]); + buf->Indices.push_back(newind); + sind.insert(v[tc[highest].ind[1]], newind); + newind++; + } + else + { + buf->Indices.push_back(s->getValue()); + } + + s = sind.find(v[tc[highest].ind[2]]); + + if (!s) + { + buf->Vertices.push_back(v[tc[highest].ind[2]]); + buf->Indices.push_back(newind); + sind.insert(v[tc[highest].ind[2]], newind); + } + else + { + buf->Indices.push_back(s->getValue()); + } + + vc[tc[highest].ind[0]].NumActiveTris--; + vc[tc[highest].ind[1]].NumActiveTris--; + vc[tc[highest].ind[2]].NumActiveTris--; + + tc[highest].drawn = true; + + for (u16 j : tc[highest].ind) { + vcache *vert = &vc[j]; + for (u16 t = 0; t < vert->tris.size(); t++) + { + if (highest == vert->tris[t]) + { + vert->tris.erase(t); + break; + } + } + } + + lru.add(tc[highest].ind[0]); + lru.add(tc[highest].ind[1]); + highest = lru.add(tc[highest].ind[2]); + drawcalls++; + } + + buf->setBoundingBox(mb->getBoundingBox()); + newmesh->addMeshBuffer(buf); + buf->drop(); + + } + break; + case video::EVT_TANGENTS: + { + video::S3DVertexTangents *v = (video::S3DVertexTangents *) mb->getVertices(); + + scene::SMeshBufferTangents *buf = new scene::SMeshBufferTangents(); + buf->Material = mb->getMaterial(); + + buf->Vertices.reallocate(vcount); + buf->Indices.reallocate(icount); + + core::map sind; // search index for fast operation + typedef core::map::Node snode; + + // Main algorithm + u32 highest = 0; + u32 drawcalls = 0; + for (;;) + { + if (tc[highest].drawn) + { + bool found = false; + float hiscore = 0; + for (u32 t = 0; t < tcount; t++) + { + if (!tc[t].drawn) + { + if (tc[t].score > hiscore) + { + highest = t; + hiscore = tc[t].score; + found = true; + } + } + } + if (!found) + break; + } + + // Output the best triangle + u16 newind = buf->Vertices.size(); + + snode *s = sind.find(v[tc[highest].ind[0]]); + + if (!s) + { + buf->Vertices.push_back(v[tc[highest].ind[0]]); + buf->Indices.push_back(newind); + sind.insert(v[tc[highest].ind[0]], newind); + newind++; + } + else + { + buf->Indices.push_back(s->getValue()); + } + + s = sind.find(v[tc[highest].ind[1]]); + + if (!s) + { + buf->Vertices.push_back(v[tc[highest].ind[1]]); + buf->Indices.push_back(newind); + sind.insert(v[tc[highest].ind[1]], newind); + newind++; + } + else + { + buf->Indices.push_back(s->getValue()); + } + + s = sind.find(v[tc[highest].ind[2]]); + + if (!s) + { + buf->Vertices.push_back(v[tc[highest].ind[2]]); + buf->Indices.push_back(newind); + sind.insert(v[tc[highest].ind[2]], newind); + } + else + { + buf->Indices.push_back(s->getValue()); + } + + vc[tc[highest].ind[0]].NumActiveTris--; + vc[tc[highest].ind[1]].NumActiveTris--; + vc[tc[highest].ind[2]].NumActiveTris--; + + tc[highest].drawn = true; + + for (u16 j : tc[highest].ind) { + vcache *vert = &vc[j]; + for (u16 t = 0; t < vert->tris.size(); t++) + { + if (highest == vert->tris[t]) + { + vert->tris.erase(t); + break; + } + } + } + + lru.add(tc[highest].ind[0]); + lru.add(tc[highest].ind[1]); + highest = lru.add(tc[highest].ind[2]); + drawcalls++; + } + + buf->setBoundingBox(mb->getBoundingBox()); + newmesh->addMeshBuffer(buf); + buf->drop(); + } + break; + } + + delete [] vc; + delete [] tc; + + } // for each meshbuffer + + return newmesh; +} diff --git a/src/client/mesh.h b/src/client/mesh.h new file mode 100644 index 000000000..0c4094de2 --- /dev/null +++ b/src/client/mesh.h @@ -0,0 +1,129 @@ +/* +Minetest +Copyright (C) 2010-2013 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include "irrlichttypes_extrabloated.h" +#include "nodedef.h" + +/*! + * Applies shading to a color based on the surface's + * normal vector. + */ +void applyFacesShading(video::SColor &color, const v3f &normal); + +/* + Create a new cube mesh. + Vertices are at (+-scale.X/2, +-scale.Y/2, +-scale.Z/2). + + The resulting mesh has 6 materials (up, down, right, left, back, front) + which must be defined by the caller. +*/ +scene::IAnimatedMesh* createCubeMesh(v3f scale); + +/* + Multiplies each vertex coordinate by the specified scaling factors + (componentwise vector multiplication). +*/ +void scaleMesh(scene::IMesh *mesh, v3f scale); + +/* + Translate each vertex coordinate by the specified vector. +*/ +void translateMesh(scene::IMesh *mesh, v3f vec); + +/*! + * Sets a constant color for all vertices in the mesh buffer. + */ +void setMeshBufferColor(scene::IMeshBuffer *buf, const video::SColor &color); + +/* + Set a constant color for all vertices in the mesh +*/ +void setMeshColor(scene::IMesh *mesh, const video::SColor &color); + +/* + Set a constant color for an animated mesh +*/ +void setAnimatedMeshColor(scene::IAnimatedMeshSceneNode *node, const video::SColor &color); + +/*! + * Overwrites the color of a mesh buffer. + * The color is darkened based on the normal vector of the vertices. + */ +void colorizeMeshBuffer(scene::IMeshBuffer *buf, const video::SColor *buffercolor); + +/* + Set the color of all vertices in the mesh. + For each vertex, determine the largest absolute entry in + the normal vector, and choose one of colorX, colorY or + colorZ accordingly. +*/ +void setMeshColorByNormalXYZ(scene::IMesh *mesh, + const video::SColor &colorX, + const video::SColor &colorY, + const video::SColor &colorZ); + +void setMeshColorByNormal(scene::IMesh *mesh, const v3f &normal, + const video::SColor &color); + +/* + Rotate the mesh by 6d facedir value. + Method only for meshnodes, not suitable for entities. +*/ +void rotateMeshBy6dFacedir(scene::IMesh *mesh, int facedir); + +/* + Rotate the mesh around the axis and given angle in degrees. +*/ +void rotateMeshXYby (scene::IMesh *mesh, f64 degrees); +void rotateMeshXZby (scene::IMesh *mesh, f64 degrees); +void rotateMeshYZby (scene::IMesh *mesh, f64 degrees); + +/* + * Clone the mesh buffer. + * The returned pointer should be dropped. + */ +scene::IMeshBuffer* cloneMeshBuffer(scene::IMeshBuffer *mesh_buffer); + +/* + Clone the mesh. +*/ +scene::SMesh* cloneMesh(scene::IMesh *src_mesh); + +/* + Convert nodeboxes to mesh. Each tile goes into a different buffer. + boxes - set of nodeboxes to be converted into cuboids + uv_coords[24] - table of texture uv coords for each cuboid face + expand - factor by which cuboids will be resized +*/ +scene::IMesh* convertNodeboxesToMesh(const std::vector &boxes, + const f32 *uv_coords = NULL, float expand = 0); + +/* + Update bounding box for a mesh. +*/ +void recalculateBoundingBox(scene::IMesh *src_mesh); + +/* + Vertex cache optimization according to the Forsyth paper: + http://home.comcast.net/~tom_forsyth/papers/fast_vert_cache_opt.html + Ported from irrlicht 1.8 +*/ +scene::IMesh* createForsythOptimizedMesh(const scene::IMesh *mesh); diff --git a/src/client/mesh_generator_thread.cpp b/src/client/mesh_generator_thread.cpp new file mode 100644 index 000000000..be4bcc1f4 --- /dev/null +++ b/src/client/mesh_generator_thread.cpp @@ -0,0 +1,308 @@ +/* +Minetest +Copyright (C) 2013, 2017 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "mesh_generator_thread.h" +#include "settings.h" +#include "profiler.h" +#include "client.h" +#include "mapblock.h" +#include "map.h" + +/* + CachedMapBlockData +*/ + +CachedMapBlockData::~CachedMapBlockData() +{ + assert(refcount_from_queue == 0); + + delete[] data; +} + +/* + QueuedMeshUpdate +*/ + +QueuedMeshUpdate::~QueuedMeshUpdate() +{ + delete data; +} + +/* + MeshUpdateQueue +*/ + +MeshUpdateQueue::MeshUpdateQueue(Client *client): + m_client(client) +{ + m_cache_enable_shaders = g_settings->getBool("enable_shaders"); + m_cache_use_tangent_vertices = m_cache_enable_shaders && ( + g_settings->getBool("enable_bumpmapping") || + g_settings->getBool("enable_parallax_occlusion")); + m_cache_smooth_lighting = g_settings->getBool("smooth_lighting"); + m_meshgen_block_cache_size = g_settings->getS32("meshgen_block_cache_size"); +} + +MeshUpdateQueue::~MeshUpdateQueue() +{ + MutexAutoLock lock(m_mutex); + + for (auto &i : m_cache) { + delete i.second; + } + + for (QueuedMeshUpdate *q : m_queue) { + delete q; + } +} + +void MeshUpdateQueue::addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool urgent) +{ + MutexAutoLock lock(m_mutex); + + cleanupCache(); + + /* + Cache the block data (force-update the center block, don't update the + neighbors but get them if they aren't already cached) + */ + std::vector cached_blocks; + size_t cache_hit_counter = 0; + cached_blocks.reserve(3*3*3); + v3s16 dp; + for (dp.X = -1; dp.X <= 1; dp.X++) + for (dp.Y = -1; dp.Y <= 1; dp.Y++) + for (dp.Z = -1; dp.Z <= 1; dp.Z++) { + v3s16 p1 = p + dp; + CachedMapBlockData *cached_block; + if (dp == v3s16(0, 0, 0)) + cached_block = cacheBlock(map, p1, FORCE_UPDATE); + else + cached_block = cacheBlock(map, p1, SKIP_UPDATE_IF_ALREADY_CACHED, + &cache_hit_counter); + cached_blocks.push_back(cached_block); + } + g_profiler->avg("MeshUpdateQueue MapBlock cache hit %", + 100.0f * cache_hit_counter / cached_blocks.size()); + + /* + Mark the block as urgent if requested + */ + if (urgent) + m_urgents.insert(p); + + /* + Find if block is already in queue. + If it is, update the data and quit. + */ + for (QueuedMeshUpdate *q : m_queue) { + if (q->p == p) { + // NOTE: We are not adding a new position to the queue, thus + // refcount_from_queue stays the same. + if(ack_block_to_server) + q->ack_block_to_server = true; + q->crack_level = m_client->getCrackLevel(); + q->crack_pos = m_client->getCrackPos(); + return; + } + } + + /* + Add the block + */ + QueuedMeshUpdate *q = new QueuedMeshUpdate; + q->p = p; + q->ack_block_to_server = ack_block_to_server; + q->crack_level = m_client->getCrackLevel(); + q->crack_pos = m_client->getCrackPos(); + m_queue.push_back(q); + + // This queue entry is a new reference to the cached blocks + for (CachedMapBlockData *cached_block : cached_blocks) { + cached_block->refcount_from_queue++; + } +} + +// Returned pointer must be deleted +// Returns NULL if queue is empty +QueuedMeshUpdate *MeshUpdateQueue::pop() +{ + MutexAutoLock lock(m_mutex); + + bool must_be_urgent = !m_urgents.empty(); + for (std::vector::iterator i = m_queue.begin(); + i != m_queue.end(); ++i) { + QueuedMeshUpdate *q = *i; + if(must_be_urgent && m_urgents.count(q->p) == 0) + continue; + m_queue.erase(i); + m_urgents.erase(q->p); + fillDataFromMapBlockCache(q); + return q; + } + return NULL; +} + +CachedMapBlockData* MeshUpdateQueue::cacheBlock(Map *map, v3s16 p, UpdateMode mode, + size_t *cache_hit_counter) +{ + std::map::iterator it = + m_cache.find(p); + if (it != m_cache.end()) { + // Already in cache + CachedMapBlockData *cached_block = it->second; + if (mode == SKIP_UPDATE_IF_ALREADY_CACHED) { + if (cache_hit_counter) + (*cache_hit_counter)++; + return cached_block; + } + MapBlock *b = map->getBlockNoCreateNoEx(p); + if (b) { + if (cached_block->data == NULL) + cached_block->data = + new MapNode[MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE]; + memcpy(cached_block->data, b->getData(), + MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE * sizeof(MapNode)); + } else { + delete[] cached_block->data; + cached_block->data = NULL; + } + return cached_block; + } + + // Not yet in cache + CachedMapBlockData *cached_block = new CachedMapBlockData(); + m_cache[p] = cached_block; + MapBlock *b = map->getBlockNoCreateNoEx(p); + if (b) { + cached_block->data = + new MapNode[MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE]; + memcpy(cached_block->data, b->getData(), + MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE * sizeof(MapNode)); + } + return cached_block; +} + +CachedMapBlockData* MeshUpdateQueue::getCachedBlock(const v3s16 &p) +{ + std::map::iterator it = m_cache.find(p); + if (it != m_cache.end()) { + return it->second; + } + return NULL; +} + +void MeshUpdateQueue::fillDataFromMapBlockCache(QueuedMeshUpdate *q) +{ + MeshMakeData *data = new MeshMakeData(m_client, m_cache_enable_shaders, + m_cache_use_tangent_vertices); + q->data = data; + + data->fillBlockDataBegin(q->p); + + std::time_t t_now = std::time(0); + + // Collect data for 3*3*3 blocks from cache + v3s16 dp; + for (dp.X = -1; dp.X <= 1; dp.X++) + for (dp.Y = -1; dp.Y <= 1; dp.Y++) + for (dp.Z = -1; dp.Z <= 1; dp.Z++) { + v3s16 p = q->p + dp; + CachedMapBlockData *cached_block = getCachedBlock(p); + if (cached_block) { + cached_block->refcount_from_queue--; + cached_block->last_used_timestamp = t_now; + if (cached_block->data) + data->fillBlockData(dp, cached_block->data); + } + } + + data->setCrack(q->crack_level, q->crack_pos); + data->setSmoothLighting(m_cache_smooth_lighting); +} + +void MeshUpdateQueue::cleanupCache() +{ + const int mapblock_kB = MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE * + sizeof(MapNode) / 1000; + g_profiler->avg("MeshUpdateQueue MapBlock cache size kB", + mapblock_kB * m_cache.size()); + + // 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; + const int cache_soft_max_size = m_meshgen_block_cache_size * 1000 / mapblock_kB; + int cache_seconds = MYMAX(2, cache_seconds_max - + m_cache.size() / (cache_soft_max_size / cache_seconds_max)); + + int t_now = time(0); + + for (std::map::iterator 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++); + delete cached_block; + } else { + ++it; + } + } +} + +/* + MeshUpdateThread +*/ + +MeshUpdateThread::MeshUpdateThread(Client *client): + UpdateThread("Mesh"), + m_queue_in(client) +{ + m_generation_interval = g_settings->getU16("mesh_generation_interval"); + m_generation_interval = rangelim(m_generation_interval, 0, 50); +} + +void MeshUpdateThread::updateBlock(Map *map, v3s16 p, bool ack_block_to_server, + bool urgent) +{ + // Allow the MeshUpdateQueue to do whatever it wants + m_queue_in.addBlock(map, p, ack_block_to_server, urgent); + deferUpdate(); +} + +void MeshUpdateThread::doUpdate() +{ + QueuedMeshUpdate *q; + while ((q = m_queue_in.pop())) { + if (m_generation_interval) + sleep_ms(m_generation_interval); + ScopeProfiler sp(g_profiler, "Client: Mesh making"); + + MapBlockMesh *mesh_new = new MapBlockMesh(q->data, m_camera_offset); + + MeshUpdateResult r; + r.p = q->p; + r.mesh = mesh_new; + r.ack_block_to_server = q->ack_block_to_server; + + m_queue_out.push_back(r); + + delete q; + } +} diff --git a/src/client/mesh_generator_thread.h b/src/client/mesh_generator_thread.h new file mode 100644 index 000000000..9a42852a3 --- /dev/null +++ b/src/client/mesh_generator_thread.h @@ -0,0 +1,131 @@ +/* +Minetest +Copyright (C) 2013, 2017 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include +#include +#include "mapblock_mesh.h" +#include "threading/mutex_auto_lock.h" +#include "util/thread.h" + +struct CachedMapBlockData +{ + v3s16 p = v3s16(-1337, -1337, -1337); + MapNode *data = nullptr; // A copy of the MapBlock's data member + int refcount_from_queue = 0; + std::time_t last_used_timestamp = std::time(0); + + CachedMapBlockData() = default; + ~CachedMapBlockData(); +}; + +struct QueuedMeshUpdate +{ + v3s16 p = v3s16(-1337, -1337, -1337); + bool ack_block_to_server = false; + bool urgent = false; + int crack_level = -1; + v3s16 crack_pos; + MeshMakeData *data = nullptr; // This is generated in MeshUpdateQueue::pop() + + QueuedMeshUpdate() = default; + ~QueuedMeshUpdate(); +}; + +/* + A thread-safe queue of mesh update tasks and a cache of MapBlock data +*/ +class MeshUpdateQueue +{ + enum UpdateMode + { + FORCE_UPDATE, + SKIP_UPDATE_IF_ALREADY_CACHED, + }; + +public: + MeshUpdateQueue(Client *client); + + ~MeshUpdateQueue(); + + // Caches the block at p and its neighbors (if needed) and queues a mesh + // update for the block at p + void addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool urgent); + + // Returned pointer must be deleted + // Returns NULL if queue is empty + QueuedMeshUpdate *pop(); + + u32 size() + { + MutexAutoLock lock(m_mutex); + return m_queue.size(); + } + +private: + Client *m_client; + std::vector m_queue; + std::set m_urgents; + std::map m_cache; + std::mutex m_mutex; + + // TODO: Add callback to update these when g_settings changes + bool m_cache_enable_shaders; + bool m_cache_use_tangent_vertices; + bool m_cache_smooth_lighting; + int m_meshgen_block_cache_size; + + CachedMapBlockData *cacheBlock(Map *map, v3s16 p, UpdateMode mode, + size_t *cache_hit_counter = NULL); + CachedMapBlockData *getCachedBlock(const v3s16 &p); + void fillDataFromMapBlockCache(QueuedMeshUpdate *q); + void cleanupCache(); +}; + +struct MeshUpdateResult +{ + v3s16 p = v3s16(-1338, -1338, -1338); + MapBlockMesh *mesh = nullptr; + bool ack_block_to_server = false; + + MeshUpdateResult() = default; +}; + +class MeshUpdateThread : public UpdateThread +{ +public: + MeshUpdateThread(Client *client); + + // Caches the block at p and its neighbors (if needed) and queues a mesh + // update for the block at p + void updateBlock(Map *map, v3s16 p, bool ack_block_to_server, bool urgent); + + v3s16 m_camera_offset; + MutexedQueue m_queue_out; + +private: + MeshUpdateQueue m_queue_in; + + // TODO: Add callback to update these when g_settings changes + int m_generation_interval; + +protected: + virtual void doUpdate(); +}; diff --git a/src/client/meshgen/collector.cpp b/src/client/meshgen/collector.cpp index c317a0772..25457c868 100644 --- a/src/client/meshgen/collector.cpp +++ b/src/client/meshgen/collector.cpp @@ -20,7 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "collector.h" #include #include "log.h" -#include "mesh.h" +#include "client/mesh.h" void MeshCollector::append(const TileSpec &tile, const video::S3DVertex *vertices, u32 numVertices, const u16 *indices, u32 numIndices) diff --git a/src/client/minimap.cpp b/src/client/minimap.cpp new file mode 100644 index 000000000..4d83c088a --- /dev/null +++ b/src/client/minimap.cpp @@ -0,0 +1,624 @@ +/* +Minetest +Copyright (C) 2010-2015 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "minimap.h" +#include +#include "client.h" +#include "clientmap.h" +#include "settings.h" +#include "shader.h" +#include "mapblock.h" +#include "client/renderingengine.h" + + +//// +//// MinimapUpdateThread +//// + +MinimapUpdateThread::~MinimapUpdateThread() +{ + for (auto &it : m_blocks_cache) { + delete it.second; + } + + for (auto &q : m_update_queue) { + delete q.data; + } +} + +bool MinimapUpdateThread::pushBlockUpdate(v3s16 pos, MinimapMapblock *data) +{ + MutexAutoLock lock(m_queue_mutex); + + // Find if block is already in queue. + // If it is, update the data and quit. + for (QueuedMinimapUpdate &q : m_update_queue) { + if (q.pos == pos) { + delete q.data; + q.data = data; + return false; + } + } + + // Add the block + QueuedMinimapUpdate q; + q.pos = pos; + q.data = data; + m_update_queue.push_back(q); + + return true; +} + +bool MinimapUpdateThread::popBlockUpdate(QueuedMinimapUpdate *update) +{ + MutexAutoLock lock(m_queue_mutex); + + if (m_update_queue.empty()) + return false; + + *update = m_update_queue.front(); + m_update_queue.pop_front(); + + return true; +} + +void MinimapUpdateThread::enqueueBlock(v3s16 pos, MinimapMapblock *data) +{ + pushBlockUpdate(pos, data); + deferUpdate(); +} + + +void MinimapUpdateThread::doUpdate() +{ + QueuedMinimapUpdate update; + + while (popBlockUpdate(&update)) { + if (update.data) { + // Swap two values in the map using single lookup + std::pair::iterator, bool> + result = m_blocks_cache.insert(std::make_pair(update.pos, update.data)); + if (!result.second) { + delete result.first->second; + result.first->second = update.data; + } + } else { + std::map::iterator it; + it = m_blocks_cache.find(update.pos); + if (it != m_blocks_cache.end()) { + delete it->second; + m_blocks_cache.erase(it); + } + } + } + + if (data->map_invalidated && data->mode != MINIMAP_MODE_OFF) { + getMap(data->pos, data->map_size, data->scan_height); + data->map_invalidated = false; + } +} + +void MinimapUpdateThread::getMap(v3s16 pos, s16 size, s16 height) +{ + v3s16 pos_min(pos.X - size / 2, pos.Y - height / 2, pos.Z - size / 2); + v3s16 pos_max(pos_min.X + size - 1, pos.Y + height / 2, pos_min.Z + size - 1); + v3s16 blockpos_min = getNodeBlockPos(pos_min); + v3s16 blockpos_max = getNodeBlockPos(pos_max); + +// clear the map + for (int z = 0; z < size; z++) + for (int x = 0; x < size; x++) { + MinimapPixel &mmpixel = data->minimap_scan[x + z * size]; + mmpixel.air_count = 0; + mmpixel.height = 0; + mmpixel.n = MapNode(CONTENT_AIR); + } + +// draw the map + v3s16 blockpos; + for (blockpos.Z = blockpos_min.Z; blockpos.Z <= blockpos_max.Z; ++blockpos.Z) + for (blockpos.Y = blockpos_min.Y; blockpos.Y <= blockpos_max.Y; ++blockpos.Y) + for (blockpos.X = blockpos_min.X; blockpos.X <= blockpos_max.X; ++blockpos.X) { + std::map::const_iterator pblock = + m_blocks_cache.find(blockpos); + if (pblock == m_blocks_cache.end()) + continue; + const MinimapMapblock &block = *pblock->second; + + v3s16 block_node_min(blockpos * MAP_BLOCKSIZE); + v3s16 block_node_max(block_node_min + MAP_BLOCKSIZE - 1); + // clip + v3s16 range_min = componentwise_max(block_node_min, pos_min); + v3s16 range_max = componentwise_min(block_node_max, pos_max); + + v3s16 pos; + pos.Y = range_min.Y; + for (pos.Z = range_min.Z; pos.Z <= range_max.Z; ++pos.Z) + for (pos.X = range_min.X; pos.X <= range_max.X; ++pos.X) { + v3s16 inblock_pos = pos - block_node_min; + const MinimapPixel &in_pixel = + block.data[inblock_pos.Z * MAP_BLOCKSIZE + inblock_pos.X]; + + v3s16 inmap_pos = pos - pos_min; + MinimapPixel &out_pixel = + data->minimap_scan[inmap_pos.X + inmap_pos.Z * size]; + + out_pixel.air_count += in_pixel.air_count; + if (in_pixel.n.param0 != CONTENT_AIR) { + out_pixel.n = in_pixel.n; + out_pixel.height = inmap_pos.Y + in_pixel.height; + } + } + } +} + +//// +//// Mapper +//// + +Minimap::Minimap(Client *client) +{ + this->client = client; + this->driver = RenderingEngine::get_video_driver(); + this->m_tsrc = client->getTextureSource(); + this->m_shdrsrc = client->getShaderSource(); + this->m_ndef = client->getNodeDefManager(); + + m_angle = 0.f; + + // Initialize static settings + m_enable_shaders = g_settings->getBool("enable_shaders"); + m_surface_mode_scan_height = + g_settings->getBool("minimap_double_scan_height") ? 256 : 128; + + // Initialize minimap data + data = new MinimapData; + data->mode = MINIMAP_MODE_OFF; + data->is_radar = false; + data->map_invalidated = true; + data->texture = NULL; + data->heightmap_texture = NULL; + data->minimap_shape_round = g_settings->getBool("minimap_shape_round"); + + // Get round minimap textures + data->minimap_mask_round = driver->createImage( + m_tsrc->getTexture("minimap_mask_round.png"), + core::position2d(0, 0), + core::dimension2d(MINIMAP_MAX_SX, MINIMAP_MAX_SY)); + data->minimap_overlay_round = m_tsrc->getTexture("minimap_overlay_round.png"); + + // Get square minimap textures + data->minimap_mask_square = driver->createImage( + m_tsrc->getTexture("minimap_mask_square.png"), + core::position2d(0, 0), + core::dimension2d(MINIMAP_MAX_SX, MINIMAP_MAX_SY)); + data->minimap_overlay_square = m_tsrc->getTexture("minimap_overlay_square.png"); + + // Create player marker texture + data->player_marker = m_tsrc->getTexture("player_marker.png"); + // Create object marker texture + data->object_marker_red = m_tsrc->getTexture("object_marker_red.png"); + + // Create mesh buffer for minimap + m_meshbuffer = getMinimapMeshBuffer(); + + // Initialize and start thread + m_minimap_update_thread = new MinimapUpdateThread(); + m_minimap_update_thread->data = data; + m_minimap_update_thread->start(); +} + +Minimap::~Minimap() +{ + m_minimap_update_thread->stop(); + m_minimap_update_thread->wait(); + + m_meshbuffer->drop(); + + data->minimap_mask_round->drop(); + data->minimap_mask_square->drop(); + + driver->removeTexture(data->texture); + driver->removeTexture(data->heightmap_texture); + driver->removeTexture(data->minimap_overlay_round); + driver->removeTexture(data->minimap_overlay_square); + driver->removeTexture(data->object_marker_red); + + delete data; + delete m_minimap_update_thread; +} + +void Minimap::addBlock(v3s16 pos, MinimapMapblock *data) +{ + m_minimap_update_thread->enqueueBlock(pos, data); +} + +void Minimap::toggleMinimapShape() +{ + MutexAutoLock lock(m_mutex); + + data->minimap_shape_round = !data->minimap_shape_round; + g_settings->setBool("minimap_shape_round", data->minimap_shape_round); + m_minimap_update_thread->deferUpdate(); +} + +void Minimap::setMinimapShape(MinimapShape shape) +{ + MutexAutoLock lock(m_mutex); + + if (shape == MINIMAP_SHAPE_SQUARE) + data->minimap_shape_round = false; + else if (shape == MINIMAP_SHAPE_ROUND) + data->minimap_shape_round = true; + + g_settings->setBool("minimap_shape_round", data->minimap_shape_round); + m_minimap_update_thread->deferUpdate(); +} + +MinimapShape Minimap::getMinimapShape() +{ + if (data->minimap_shape_round) { + return MINIMAP_SHAPE_ROUND; + } + + return MINIMAP_SHAPE_SQUARE; +} + +void Minimap::setMinimapMode(MinimapMode mode) +{ + static const MinimapModeDef modedefs[MINIMAP_MODE_COUNT] = { + {false, 0, 0}, + {false, m_surface_mode_scan_height, 256}, + {false, m_surface_mode_scan_height, 128}, + {false, m_surface_mode_scan_height, 64}, + {true, 32, 128}, + {true, 32, 64}, + {true, 32, 32} + }; + + if (mode >= MINIMAP_MODE_COUNT) + return; + + MutexAutoLock lock(m_mutex); + + data->is_radar = modedefs[mode].is_radar; + data->scan_height = modedefs[mode].scan_height; + data->map_size = modedefs[mode].map_size; + data->mode = mode; + + m_minimap_update_thread->deferUpdate(); +} + +void Minimap::setPos(v3s16 pos) +{ + bool do_update = false; + + { + MutexAutoLock lock(m_mutex); + + if (pos != data->old_pos) { + data->old_pos = data->pos; + data->pos = pos; + do_update = true; + } + } + + if (do_update) + m_minimap_update_thread->deferUpdate(); +} + +void Minimap::setAngle(f32 angle) +{ + m_angle = angle; +} + +void Minimap::blitMinimapPixelsToImageRadar(video::IImage *map_image) +{ + video::SColor c(240, 0, 0, 0); + for (s16 x = 0; x < data->map_size; x++) + for (s16 z = 0; z < data->map_size; z++) { + MinimapPixel *mmpixel = &data->minimap_scan[x + z * data->map_size]; + + if (mmpixel->air_count > 0) + c.setGreen(core::clamp(core::round32(32 + mmpixel->air_count * 8), 0, 255)); + else + c.setGreen(0); + + map_image->setPixel(x, data->map_size - z - 1, c); + } +} + +void Minimap::blitMinimapPixelsToImageSurface( + video::IImage *map_image, video::IImage *heightmap_image) +{ + // This variable creation/destruction has a 1% cost on rendering minimap + video::SColor tilecolor; + for (s16 x = 0; x < data->map_size; x++) + for (s16 z = 0; z < data->map_size; z++) { + MinimapPixel *mmpixel = &data->minimap_scan[x + z * data->map_size]; + + const ContentFeatures &f = m_ndef->get(mmpixel->n); + const TileDef *tile = &f.tiledef[0]; + + // Color of the 0th tile (mostly this is the topmost) + if(tile->has_color) + tilecolor = tile->color; + else + mmpixel->n.getColor(f, &tilecolor); + + tilecolor.setRed(tilecolor.getRed() * f.minimap_color.getRed() / 255); + tilecolor.setGreen(tilecolor.getGreen() * f.minimap_color.getGreen() / 255); + tilecolor.setBlue(tilecolor.getBlue() * f.minimap_color.getBlue() / 255); + tilecolor.setAlpha(240); + + map_image->setPixel(x, data->map_size - z - 1, tilecolor); + + u32 h = mmpixel->height; + heightmap_image->setPixel(x,data->map_size - z - 1, + video::SColor(255, h, h, h)); + } +} + +video::ITexture *Minimap::getMinimapTexture() +{ + // update minimap textures when new scan is ready + if (data->map_invalidated) + return data->texture; + + // create minimap and heightmap images in memory + core::dimension2d dim(data->map_size, data->map_size); + video::IImage *map_image = driver->createImage(video::ECF_A8R8G8B8, dim); + video::IImage *heightmap_image = driver->createImage(video::ECF_A8R8G8B8, dim); + video::IImage *minimap_image = driver->createImage(video::ECF_A8R8G8B8, + core::dimension2d(MINIMAP_MAX_SX, MINIMAP_MAX_SY)); + + // Blit MinimapPixels to images + if (data->is_radar) + blitMinimapPixelsToImageRadar(map_image); + else + blitMinimapPixelsToImageSurface(map_image, heightmap_image); + + map_image->copyToScaling(minimap_image); + map_image->drop(); + + video::IImage *minimap_mask = data->minimap_shape_round ? + data->minimap_mask_round : data->minimap_mask_square; + + if (minimap_mask) { + for (s16 y = 0; y < MINIMAP_MAX_SY; y++) + for (s16 x = 0; x < MINIMAP_MAX_SX; x++) { + const video::SColor &mask_col = minimap_mask->getPixel(x, y); + if (!mask_col.getAlpha()) + minimap_image->setPixel(x, y, video::SColor(0,0,0,0)); + } + } + + if (data->texture) + driver->removeTexture(data->texture); + if (data->heightmap_texture) + driver->removeTexture(data->heightmap_texture); + + data->texture = driver->addTexture("minimap__", minimap_image); + data->heightmap_texture = + driver->addTexture("minimap_heightmap__", heightmap_image); + minimap_image->drop(); + heightmap_image->drop(); + + data->map_invalidated = true; + + return data->texture; +} + +v3f Minimap::getYawVec() +{ + if (data->minimap_shape_round) { + return v3f( + std::cos(m_angle * core::DEGTORAD), + std::sin(m_angle * core::DEGTORAD), + 1.0); + } + + return v3f(1.0, 0.0, 1.0); +} + +scene::SMeshBuffer *Minimap::getMinimapMeshBuffer() +{ + scene::SMeshBuffer *buf = new scene::SMeshBuffer(); + buf->Vertices.set_used(4); + buf->Indices.set_used(6); + static const video::SColor c(255, 255, 255, 255); + + buf->Vertices[0] = video::S3DVertex(-1, -1, 0, 0, 0, 1, c, 0, 1); + buf->Vertices[1] = video::S3DVertex(-1, 1, 0, 0, 0, 1, c, 0, 0); + buf->Vertices[2] = video::S3DVertex( 1, 1, 0, 0, 0, 1, c, 1, 0); + buf->Vertices[3] = video::S3DVertex( 1, -1, 0, 0, 0, 1, c, 1, 1); + + buf->Indices[0] = 0; + buf->Indices[1] = 1; + buf->Indices[2] = 2; + buf->Indices[3] = 2; + buf->Indices[4] = 3; + buf->Indices[5] = 0; + + return buf; +} + +void Minimap::drawMinimap() +{ + video::ITexture *minimap_texture = getMinimapTexture(); + if (!minimap_texture) + return; + + updateActiveMarkers(); + v2u32 screensize = RenderingEngine::get_instance()->getWindowSize(); + const u32 size = 0.25 * screensize.Y; + + core::rect oldViewPort = driver->getViewPort(); + core::matrix4 oldProjMat = driver->getTransform(video::ETS_PROJECTION); + core::matrix4 oldViewMat = driver->getTransform(video::ETS_VIEW); + + driver->setViewPort(core::rect( + screensize.X - size - 10, 10, + screensize.X - 10, size + 10)); + driver->setTransform(video::ETS_PROJECTION, core::matrix4()); + driver->setTransform(video::ETS_VIEW, core::matrix4()); + + core::matrix4 matrix; + matrix.makeIdentity(); + + video::SMaterial &material = m_meshbuffer->getMaterial(); + material.setFlag(video::EMF_TRILINEAR_FILTER, true); + material.Lighting = false; + material.TextureLayer[0].Texture = minimap_texture; + material.TextureLayer[1].Texture = data->heightmap_texture; + + if (m_enable_shaders && !data->is_radar) { + u16 sid = m_shdrsrc->getShader("minimap_shader", 1, 1); + material.MaterialType = m_shdrsrc->getShaderInfo(sid).material; + } else { + material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; + } + + if (data->minimap_shape_round) + matrix.setRotationDegrees(core::vector3df(0, 0, 360 - m_angle)); + + // Draw minimap + driver->setTransform(video::ETS_WORLD, matrix); + driver->setMaterial(material); + driver->drawMeshBuffer(m_meshbuffer); + + // Draw overlay + video::ITexture *minimap_overlay = data->minimap_shape_round ? + data->minimap_overlay_round : data->minimap_overlay_square; + material.TextureLayer[0].Texture = minimap_overlay; + material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; + driver->setMaterial(material); + driver->drawMeshBuffer(m_meshbuffer); + + // Draw player marker on minimap + if (data->minimap_shape_round) { + matrix.setRotationDegrees(core::vector3df(0, 0, 0)); + } else { + matrix.setRotationDegrees(core::vector3df(0, 0, m_angle)); + } + + material.TextureLayer[0].Texture = data->player_marker; + driver->setTransform(video::ETS_WORLD, matrix); + driver->setMaterial(material); + driver->drawMeshBuffer(m_meshbuffer); + + // Reset transformations + driver->setTransform(video::ETS_VIEW, oldViewMat); + driver->setTransform(video::ETS_PROJECTION, oldProjMat); + driver->setViewPort(oldViewPort); + + // Draw player markers + v2s32 s_pos(screensize.X - size - 10, 10); + core::dimension2di imgsize(data->object_marker_red->getOriginalSize()); + core::rect img_rect(0, 0, imgsize.Width, imgsize.Height); + static const video::SColor col(255, 255, 255, 255); + static const video::SColor c[4] = {col, col, col, col}; + f32 sin_angle = std::sin(m_angle * core::DEGTORAD); + f32 cos_angle = std::cos(m_angle * core::DEGTORAD); + s32 marker_size2 = 0.025 * (float)size; + for (std::list::const_iterator + i = m_active_markers.begin(); + i != m_active_markers.end(); ++i) { + v2f posf = *i; + if (data->minimap_shape_round) { + f32 t1 = posf.X * cos_angle - posf.Y * sin_angle; + f32 t2 = posf.X * sin_angle + posf.Y * cos_angle; + posf.X = t1; + posf.Y = t2; + } + posf.X = (posf.X + 0.5) * (float)size; + posf.Y = (posf.Y + 0.5) * (float)size; + core::rect dest_rect( + s_pos.X + posf.X - marker_size2, + s_pos.Y + posf.Y - marker_size2, + s_pos.X + posf.X + marker_size2, + s_pos.Y + posf.Y + marker_size2); + driver->draw2DImage(data->object_marker_red, dest_rect, + img_rect, &dest_rect, &c[0], true); + } +} + +void Minimap::updateActiveMarkers() +{ + video::IImage *minimap_mask = data->minimap_shape_round ? + data->minimap_mask_round : data->minimap_mask_square; + + const std::list &nametags = client->getCamera()->getNametags(); + + m_active_markers.clear(); + + for (Nametag *nametag : nametags) { + v3s16 pos = floatToInt(nametag->parent_node->getPosition() + + intToFloat(client->getCamera()->getOffset(), BS), BS); + pos -= data->pos - v3s16(data->map_size / 2, + data->scan_height / 2, + data->map_size / 2); + if (pos.X < 0 || pos.X > data->map_size || + pos.Y < 0 || pos.Y > data->scan_height || + pos.Z < 0 || pos.Z > data->map_size) { + continue; + } + pos.X = ((float)pos.X / data->map_size) * MINIMAP_MAX_SX; + pos.Z = ((float)pos.Z / data->map_size) * MINIMAP_MAX_SY; + const video::SColor &mask_col = minimap_mask->getPixel(pos.X, pos.Z); + if (!mask_col.getAlpha()) { + continue; + } + + m_active_markers.emplace_back(((float)pos.X / (float)MINIMAP_MAX_SX) - 0.5, + (1.0 - (float)pos.Z / (float)MINIMAP_MAX_SY) - 0.5); + } +} + +//// +//// MinimapMapblock +//// + +void MinimapMapblock::getMinimapNodes(VoxelManipulator *vmanip, const v3s16 &pos) +{ + + for (s16 x = 0; x < MAP_BLOCKSIZE; x++) + for (s16 z = 0; z < MAP_BLOCKSIZE; z++) { + s16 air_count = 0; + bool surface_found = false; + MinimapPixel *mmpixel = &data[z * MAP_BLOCKSIZE + x]; + + for (s16 y = MAP_BLOCKSIZE -1; y >= 0; y--) { + v3s16 p(x, y, z); + MapNode n = vmanip->getNodeNoEx(pos + p); + if (!surface_found && n.getContent() != CONTENT_AIR) { + mmpixel->height = y; + mmpixel->n = n; + surface_found = true; + } else if (n.getContent() == CONTENT_AIR) { + air_count++; + } + } + + if (!surface_found) + mmpixel->n = MapNode(CONTENT_AIR); + + mmpixel->air_count = air_count; + } +} diff --git a/src/client/minimap.h b/src/client/minimap.h new file mode 100644 index 000000000..258d5330d --- /dev/null +++ b/src/client/minimap.h @@ -0,0 +1,163 @@ +/* +Minetest +Copyright (C) 2010-2015 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include "irrlichttypes_extrabloated.h" +#include "util/thread.h" +#include "voxel.h" +#include +#include +#include + +class Client; +class ITextureSource; +class IShaderSource; + +#define MINIMAP_MAX_SX 512 +#define MINIMAP_MAX_SY 512 + +enum MinimapMode { + MINIMAP_MODE_OFF, + MINIMAP_MODE_SURFACEx1, + MINIMAP_MODE_SURFACEx2, + MINIMAP_MODE_SURFACEx4, + MINIMAP_MODE_RADARx1, + MINIMAP_MODE_RADARx2, + MINIMAP_MODE_RADARx4, + MINIMAP_MODE_COUNT, +}; + +enum MinimapShape { + MINIMAP_SHAPE_SQUARE, + MINIMAP_SHAPE_ROUND, +}; + +struct MinimapModeDef { + bool is_radar; + u16 scan_height; + u16 map_size; +}; + +struct MinimapPixel { + //! The topmost node that the minimap displays. + MapNode n; + u16 height; + u16 air_count; +}; + +struct MinimapMapblock { + void getMinimapNodes(VoxelManipulator *vmanip, const v3s16 &pos); + + MinimapPixel data[MAP_BLOCKSIZE * MAP_BLOCKSIZE]; +}; + +struct MinimapData { + bool is_radar; + MinimapMode mode; + v3s16 pos; + v3s16 old_pos; + u16 scan_height; + u16 map_size; + MinimapPixel minimap_scan[MINIMAP_MAX_SX * MINIMAP_MAX_SY]; + bool map_invalidated; + bool minimap_shape_round; + video::IImage *minimap_mask_round = nullptr; + video::IImage *minimap_mask_square = nullptr; + video::ITexture *texture = nullptr; + video::ITexture *heightmap_texture = nullptr; + video::ITexture *minimap_overlay_round = nullptr; + video::ITexture *minimap_overlay_square = nullptr; + video::ITexture *player_marker = nullptr; + video::ITexture *object_marker_red = nullptr; +}; + +struct QueuedMinimapUpdate { + v3s16 pos; + MinimapMapblock *data = nullptr; +}; + +class MinimapUpdateThread : public UpdateThread { +public: + MinimapUpdateThread() : UpdateThread("Minimap") {} + virtual ~MinimapUpdateThread(); + + void getMap(v3s16 pos, s16 size, s16 height); + void enqueueBlock(v3s16 pos, MinimapMapblock *data); + bool pushBlockUpdate(v3s16 pos, MinimapMapblock *data); + bool popBlockUpdate(QueuedMinimapUpdate *update); + + MinimapData *data = nullptr; + +protected: + virtual void doUpdate(); + +private: + std::mutex m_queue_mutex; + std::deque m_update_queue; + std::map m_blocks_cache; +}; + +class Minimap { +public: + Minimap(Client *client); + ~Minimap(); + + void addBlock(v3s16 pos, MinimapMapblock *data); + + v3f getYawVec(); + + void setPos(v3s16 pos); + v3s16 getPos() const { return data->pos; } + void setAngle(f32 angle); + f32 getAngle() const { return m_angle; } + void setMinimapMode(MinimapMode mode); + MinimapMode getMinimapMode() const { return data->mode; } + void toggleMinimapShape(); + void setMinimapShape(MinimapShape shape); + MinimapShape getMinimapShape(); + + + video::ITexture *getMinimapTexture(); + + void blitMinimapPixelsToImageRadar(video::IImage *map_image); + void blitMinimapPixelsToImageSurface(video::IImage *map_image, + video::IImage *heightmap_image); + + scene::SMeshBuffer *getMinimapMeshBuffer(); + + void updateActiveMarkers(); + void drawMinimap(); + + video::IVideoDriver *driver; + Client* client; + MinimapData *data; + +private: + ITextureSource *m_tsrc; + IShaderSource *m_shdrsrc; + const NodeDefManager *m_ndef; + MinimapUpdateThread *m_minimap_update_thread; + scene::SMeshBuffer *m_meshbuffer; + bool m_enable_shaders; + u16 m_surface_mode_scan_height; + f32 m_angle; + std::mutex m_mutex; + std::list m_active_markers; +}; diff --git a/src/client/particles.cpp b/src/client/particles.cpp new file mode 100644 index 000000000..25cfa081e --- /dev/null +++ b/src/client/particles.cpp @@ -0,0 +1,684 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "particles.h" +#include +#include "client.h" +#include "collision.h" +#include "client/clientevent.h" +#include "client/renderingengine.h" +#include "util/numeric.h" +#include "light.h" +#include "environment.h" +#include "clientmap.h" +#include "mapnode.h" +#include "nodedef.h" +#include "client.h" +#include "settings.h" + +/* + Utility +*/ + +v3f random_v3f(v3f min, v3f max) +{ + return v3f( rand()/(float)RAND_MAX*(max.X-min.X)+min.X, + rand()/(float)RAND_MAX*(max.Y-min.Y)+min.Y, + rand()/(float)RAND_MAX*(max.Z-min.Z)+min.Z); +} + +Particle::Particle( + IGameDef *gamedef, + LocalPlayer *player, + ClientEnvironment *env, + v3f pos, + v3f velocity, + v3f acceleration, + float expirationtime, + float size, + bool collisiondetection, + bool collision_removal, + bool object_collision, + bool vertical, + video::ITexture *texture, + v2f texpos, + v2f texsize, + const struct TileAnimationParams &anim, + u8 glow, + video::SColor color +): + scene::ISceneNode(RenderingEngine::get_scene_manager()->getRootSceneNode(), + RenderingEngine::get_scene_manager()) +{ + // Misc + m_gamedef = gamedef; + m_env = env; + + // 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); + m_texpos = texpos; + m_texsize = texsize; + m_animation = anim; + + // Color + m_base_color = color; + m_color = color; + + // Particle related + m_pos = pos; + m_velocity = velocity; + m_acceleration = acceleration; + m_expiration = expirationtime; + m_player = player; + m_size = size; + m_collisiondetection = collisiondetection; + m_collision_removal = collision_removal; + m_object_collision = object_collision; + m_vertical = vertical; + m_glow = glow; + + // Irrlicht stuff + m_collisionbox = aabb3f + (-size/2,-size/2,-size/2,size/2,size/2,size/2); + this->setAutomaticCulling(scene::EAC_OFF); + + // Init lighting + updateLight(); + + // Init model + updateVertices(); +} + +void Particle::OnRegisterSceneNode() +{ + if (IsVisible) + SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT_EFFECT); + + ISceneNode::OnRegisterSceneNode(); +} + +void Particle::render() +{ + video::IVideoDriver* driver = SceneManager->getVideoDriver(); + driver->setMaterial(m_material); + driver->setTransform(video::ETS_WORLD, AbsoluteTransformation); + + u16 indices[] = {0,1,2, 2,3,0}; + driver->drawVertexPrimitiveList(m_vertices, 4, + indices, 2, video::EVT_STANDARD, + scene::EPT_TRIANGLES, video::EIT_16BIT); +} + +void Particle::step(float dtime) +{ + m_time += dtime; + if (m_collisiondetection) { + aabb3f box = m_collisionbox; + v3f p_pos = m_pos * BS; + v3f p_velocity = m_velocity * BS; + 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; + } else { + m_pos = p_pos / BS; + m_velocity = p_velocity / BS; + } + } else { + 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; + m_animation.determineParams( + m_material.getTexture(0)->getSize(), + &frame_count, &frame_length_i, NULL); + float frame_length = frame_length_i / 1000.0; + while (m_animation_time > frame_length) { + m_animation_frame++; + m_animation_time -= frame_length; + } + } + + // Update lighting + updateLight(); + + // Update model + updateVertices(); +} + +void Particle::updateLight() +{ + u8 light = 0; + bool pos_ok; + + v3s16 p = v3s16( + floor(m_pos.X+0.5), + floor(m_pos.Y+0.5), + floor(m_pos.Z+0.5) + ); + MapNode n = m_env->getClientMap().getNodeNoEx(p, &pos_ok); + if (pos_ok) + light = n.getLightBlend(m_env->getDayNightRatio(), m_gamedef->ndef()); + else + light = blend_light(m_env->getDayNightRatio(), LIGHT_SUN, 0); + + u8 m_light = decode_light(light + m_glow); + m_color.set(255, + m_light * m_base_color.getRed() / 255, + m_light * m_base_color.getGreen() / 255, + m_light * m_base_color.getBlue() / 255); +} + +void Particle::updateVertices() +{ + f32 tx0, tx1, ty0, ty1; + + if (m_animation.type != TAT_NONE) { + const v2u32 texsize = m_material.getTexture(0)->getSize(); + v2f texcoord, framesize_f; + v2u32 framesize; + texcoord = m_animation.getTextureCoords(texsize, m_animation_frame); + m_animation.determineParams(texsize, NULL, NULL, &framesize); + framesize_f = v2f(framesize.X / (float) texsize.X, framesize.Y / (float) texsize.Y); + + tx0 = m_texpos.X + texcoord.X; + tx1 = m_texpos.X + texcoord.X + framesize_f.X * m_texsize.X; + ty0 = m_texpos.Y + texcoord.Y; + ty1 = m_texpos.Y + texcoord.Y + framesize_f.Y * m_texsize.Y; + } else { + tx0 = m_texpos.X; + tx1 = m_texpos.X + m_texsize.X; + ty0 = m_texpos.Y; + ty1 = m_texpos.Y + m_texsize.Y; + } + + m_vertices[0] = video::S3DVertex(-m_size / 2, -m_size / 2, + 0, 0, 0, 0, m_color, tx0, ty1); + m_vertices[1] = video::S3DVertex(m_size / 2, -m_size / 2, + 0, 0, 0, 0, m_color, tx1, ty1); + m_vertices[2] = video::S3DVertex(m_size / 2, m_size / 2, + 0, 0, 0, 0, m_color, tx1, ty0); + m_vertices[3] = video::S3DVertex(-m_size / 2, m_size / 2, + 0, 0, 0, 0, m_color, tx0, ty0); + + v3s16 camera_offset = m_env->getCameraOffset(); + for (video::S3DVertex &vertex : m_vertices) { + if (m_vertical) { + v3f ppos = m_player->getPosition()/BS; + vertex.Pos.rotateXZBy(std::atan2(ppos.Z - m_pos.Z, ppos.X - m_pos.X) / + core::DEGTORAD + 90); + } else { + vertex.Pos.rotateYZBy(m_player->getPitch()); + vertex.Pos.rotateXZBy(m_player->getYaw()); + } + m_box.addInternalPoint(vertex.Pos); + vertex.Pos += m_pos*BS - intToFloat(camera_offset, BS); + } +} + +/* + ParticleSpawner +*/ + +ParticleSpawner::ParticleSpawner( + IGameDef *gamedef, + LocalPlayer *player, + u16 amount, + float time, + v3f minpos, v3f maxpos, + v3f minvel, v3f maxvel, + v3f minacc, v3f maxacc, + float minexptime, float maxexptime, + float minsize, float maxsize, + bool collisiondetection, + bool collision_removal, + bool object_collision, + u16 attached_id, + bool vertical, + video::ITexture *texture, + u32 id, + const struct TileAnimationParams &anim, + u8 glow, + ParticleManager *p_manager +): + m_particlemanager(p_manager) +{ + m_gamedef = gamedef; + m_player = player; + m_amount = amount; + m_spawntime = time; + m_minpos = minpos; + m_maxpos = maxpos; + m_minvel = minvel; + m_maxvel = maxvel; + m_minacc = minacc; + m_maxacc = maxacc; + m_minexptime = minexptime; + m_maxexptime = maxexptime; + m_minsize = minsize; + m_maxsize = maxsize; + m_collisiondetection = collisiondetection; + m_collision_removal = collision_removal; + m_object_collision = object_collision; + m_attached_id = attached_id; + m_vertical = vertical; + m_texture = texture; + m_time = 0; + m_animation = anim; + m_glow = glow; + + for (u16 i = 0; i<=m_amount; i++) + { + float spawntime = (float)rand()/(float)RAND_MAX*m_spawntime; + m_spawntimes.push_back(spawntime); + } +} + +void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius, + bool is_attached, const v3f &attached_pos, float attached_yaw) +{ + v3f ppos = m_player->getPosition() / BS; + v3f pos = random_v3f(m_minpos, m_maxpos); + + // Need to apply this first or the following check + // will be wrong for attached spawners + if (is_attached) { + pos.rotateXZBy(attached_yaw); + pos += attached_pos; + } + + if (pos.getDistanceFrom(ppos) > radius) + return; + + v3f vel = random_v3f(m_minvel, m_maxvel); + v3f acc = random_v3f(m_minacc, m_maxacc); + + if (is_attached) { + // Apply attachment yaw + vel.rotateXZBy(attached_yaw); + acc.rotateXZBy(attached_yaw); + } + + float exptime = rand() / (float)RAND_MAX + * (m_maxexptime - m_minexptime) + + m_minexptime; + float size = rand() / (float)RAND_MAX + * (m_maxsize - m_minsize) + + m_minsize; + + m_particlemanager->addParticle(new Particle( + m_gamedef, + m_player, + env, + pos, + vel, + acc, + exptime, + size, + m_collisiondetection, + m_collision_removal, + m_object_collision, + m_vertical, + m_texture, + v2f(0.0, 0.0), + v2f(1.0, 1.0), + m_animation, + m_glow + )); +} + +void ParticleSpawner::step(float dtime, ClientEnvironment* env) +{ + m_time += dtime; + + static thread_local const float radius = + g_settings->getS16("max_block_send_distance") * MAP_BLOCKSIZE; + + bool unloaded = false; + bool is_attached = false; + v3f attached_pos = v3f(0,0,0); + float attached_yaw = 0; + if (m_attached_id != 0) { + if (ClientActiveObject *attached = env->getActiveObject(m_attached_id)) { + attached_pos = attached->getPosition() / BS; + attached_yaw = attached->getYaw(); + is_attached = true; + } else { + unloaded = true; + } + } + + if (m_spawntime != 0) { + // Spawner exists for a predefined timespan + for (std::vector::iterator i = m_spawntimes.begin(); + i != m_spawntimes.end();) { + if ((*i) <= m_time && m_amount > 0) { + m_amount--; + + // Pretend to, but don't actually spawn a particle if it is + // attached to an unloaded object or distant from player. + if (!unloaded) + spawnParticle(env, radius, is_attached, attached_pos, attached_yaw); + + i = m_spawntimes.erase(i); + } else { + ++i; + } + } + } else { + // Spawner exists for an infinity timespan, spawn on a per-second base + + // Skip this step if attached to an unloaded object + if (unloaded) + return; + + for (int i = 0; i <= m_amount; i++) { + if (rand() / (float)RAND_MAX < dtime) + spawnParticle(env, radius, is_attached, attached_pos, attached_yaw); + } + } +} + + +ParticleManager::ParticleManager(ClientEnvironment* env) : + m_env(env) +{} + +ParticleManager::~ParticleManager() +{ + clearAll(); +} + +void ParticleManager::step(float dtime) +{ + stepParticles (dtime); + stepSpawners (dtime); +} + +void ParticleManager::stepSpawners (float dtime) +{ + MutexAutoLock lock(m_spawner_list_lock); + for (std::map::iterator i = + m_particle_spawners.begin(); + i != m_particle_spawners.end();) + { + if (i->second->get_expired()) + { + delete i->second; + m_particle_spawners.erase(i++); + } + else + { + i->second->step(dtime, m_env); + ++i; + } + } +} + +void ParticleManager::stepParticles (float dtime) +{ + MutexAutoLock lock(m_particle_list_lock); + for(std::vector::iterator i = m_particles.begin(); + i != m_particles.end();) + { + if ((*i)->get_expired()) + { + (*i)->remove(); + delete *i; + i = m_particles.erase(i); + } + else + { + (*i)->step(dtime); + ++i; + } + } +} + +void ParticleManager::clearAll () +{ + MutexAutoLock lock(m_spawner_list_lock); + MutexAutoLock lock2(m_particle_list_lock); + for(std::map::iterator i = + m_particle_spawners.begin(); + i != m_particle_spawners.end();) + { + delete i->second; + m_particle_spawners.erase(i++); + } + + for(std::vector::iterator i = + m_particles.begin(); + i != m_particles.end();) + { + (*i)->remove(); + delete *i; + i = m_particles.erase(i); + } +} + +void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client, + LocalPlayer *player) +{ + switch (event->type) { + case CE_DELETE_PARTICLESPAWNER: { + MutexAutoLock lock(m_spawner_list_lock); + if (m_particle_spawners.find(event->delete_particlespawner.id) != + m_particle_spawners.end()) { + delete m_particle_spawners.find(event->delete_particlespawner.id)->second; + m_particle_spawners.erase(event->delete_particlespawner.id); + } + // no allocated memory in delete event + break; + } + case CE_ADD_PARTICLESPAWNER: { + { + MutexAutoLock lock(m_spawner_list_lock); + if (m_particle_spawners.find(event->add_particlespawner.id) != + m_particle_spawners.end()) { + delete m_particle_spawners.find(event->add_particlespawner.id)->second; + m_particle_spawners.erase(event->add_particlespawner.id); + } + } + + video::ITexture *texture = + client->tsrc()->getTextureForMesh(*(event->add_particlespawner.texture)); + + ParticleSpawner *toadd = new ParticleSpawner(client, player, + event->add_particlespawner.amount, + event->add_particlespawner.spawntime, + *event->add_particlespawner.minpos, + *event->add_particlespawner.maxpos, + *event->add_particlespawner.minvel, + *event->add_particlespawner.maxvel, + *event->add_particlespawner.minacc, + *event->add_particlespawner.maxacc, + event->add_particlespawner.minexptime, + event->add_particlespawner.maxexptime, + event->add_particlespawner.minsize, + event->add_particlespawner.maxsize, + event->add_particlespawner.collisiondetection, + event->add_particlespawner.collision_removal, + event->add_particlespawner.object_collision, + event->add_particlespawner.attached_id, + event->add_particlespawner.vertical, + texture, + event->add_particlespawner.id, + event->add_particlespawner.animation, + event->add_particlespawner.glow, + this); + + /* delete allocated content of event */ + delete event->add_particlespawner.minpos; + delete event->add_particlespawner.maxpos; + delete event->add_particlespawner.minvel; + delete event->add_particlespawner.maxvel; + delete event->add_particlespawner.minacc; + delete event->add_particlespawner.texture; + delete event->add_particlespawner.maxacc; + + { + MutexAutoLock lock(m_spawner_list_lock); + m_particle_spawners.insert( + std::pair( + event->add_particlespawner.id, + toadd)); + } + break; + } + case CE_SPAWN_PARTICLE: { + video::ITexture *texture = + client->tsrc()->getTextureForMesh(*(event->spawn_particle.texture)); + + Particle *toadd = new Particle(client, player, m_env, + *event->spawn_particle.pos, + *event->spawn_particle.vel, + *event->spawn_particle.acc, + event->spawn_particle.expirationtime, + event->spawn_particle.size, + event->spawn_particle.collisiondetection, + event->spawn_particle.collision_removal, + event->spawn_particle.object_collision, + event->spawn_particle.vertical, + texture, + v2f(0.0, 0.0), + v2f(1.0, 1.0), + event->spawn_particle.animation, + event->spawn_particle.glow); + + addParticle(toadd); + + delete event->spawn_particle.pos; + delete event->spawn_particle.vel; + delete event->spawn_particle.acc; + delete event->spawn_particle.texture; + + break; + } + default: break; + } +} + +// The final burst of particles when a node is finally dug, *not* particles +// spawned during the digging of a node. + +void ParticleManager::addDiggingParticles(IGameDef* gamedef, + LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f) +{ + // No particles for "airlike" nodes + if (f.drawtype == NDT_AIRLIKE) + return; + + for (u16 j = 0; j < 16; j++) { + addNodeParticle(gamedef, player, pos, n, f); + } +} + +// During the digging of a node particles are spawned individually by this +// function, called from Game::handleDigging() in game.cpp. + +void ParticleManager::addNodeParticle(IGameDef* gamedef, + LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f) +{ + // No particles for "airlike" nodes + if (f.drawtype == NDT_AIRLIKE) + return; + + // Texture + u8 texid = myrand_range(0, 5); + const TileLayer &tile = f.tiles[texid].layers[0]; + video::ITexture *texture; + struct TileAnimationParams anim; + anim.type = TAT_NONE; + + // Only use first frame of animated texture + if (tile.material_flags & MATERIAL_FLAG_ANIMATION) + texture = (*tile.frames)[0].texture; + else + texture = tile.texture; + + float size = (rand() % 8) / 64.0f; + float visual_size = BS * size; + if (tile.scale) + size /= tile.scale; + v2f texsize(size * 2.0f, size * 2.0f); + v2f texpos; + texpos.X = (rand() % 64) / 64.0f - texsize.X; + texpos.Y = (rand() % 64) / 64.0f - texsize.Y; + + // Physics + v3f velocity( + (rand() % 150) / 50.0f - 1.5f, + (rand() % 150) / 50.0f, + (rand() % 150) / 50.0f - 1.5f + ); + v3f acceleration( + 0.0f, + -player->movement_gravity * player->physics_override_gravity / BS, + 0.0f + ); + v3f particlepos = 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 + ); + + video::SColor color; + if (tile.has_color) + color = tile.color; + else + n.getColor(f, &color); + + Particle* toadd = new Particle( + gamedef, + player, + m_env, + particlepos, + velocity, + acceleration, + (rand() % 100) / 100.0f, // expiration time + visual_size, + true, + false, + false, + false, + texture, + texpos, + texsize, + anim, + 0, + color); + + addParticle(toadd); +} + +void ParticleManager::addParticle(Particle* toadd) +{ + MutexAutoLock lock(m_particle_list_lock); + m_particles.push_back(toadd); +} diff --git a/src/client/particles.h b/src/client/particles.h new file mode 100644 index 000000000..3392e7e95 --- /dev/null +++ b/src/client/particles.h @@ -0,0 +1,223 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include +#include "irrlichttypes_extrabloated.h" +#include "client/tile.h" +#include "localplayer.h" +#include "tileanimation.h" + +struct ClientEvent; +class ParticleManager; +class ClientEnvironment; +struct MapNode; +struct ContentFeatures; + +class Particle : public scene::ISceneNode +{ + public: + Particle( + IGameDef* gamedef, + LocalPlayer *player, + ClientEnvironment *env, + v3f pos, + v3f velocity, + v3f acceleration, + float expirationtime, + float size, + bool collisiondetection, + bool collision_removal, + bool object_collision, + bool vertical, + video::ITexture *texture, + v2f texpos, + v2f texsize, + const struct TileAnimationParams &anim, + u8 glow, + video::SColor color = video::SColor(0xFFFFFFFF) + ); + ~Particle() = default; + + virtual const aabb3f &getBoundingBox() const + { + return m_box; + } + + virtual u32 getMaterialCount() const + { + return 1; + } + + virtual video::SMaterial& getMaterial(u32 i) + { + return m_material; + } + + virtual void OnRegisterSceneNode(); + virtual void render(); + + void step(float dtime); + + bool get_expired () + { return m_expiration < m_time; } + +private: + void updateLight(); + void updateVertices(); + + video::S3DVertex m_vertices[4]; + float m_time = 0.0f; + float m_expiration; + + ClientEnvironment *m_env; + IGameDef *m_gamedef; + aabb3f m_box; + aabb3f m_collisionbox; + video::SMaterial m_material; + v2f m_texpos; + v2f m_texsize; + v3f m_pos; + v3f m_velocity; + v3f m_acceleration; + LocalPlayer *m_player; + float m_size; + //! Color without lighting + video::SColor m_base_color; + //! Final rendered color + video::SColor m_color; + bool m_collisiondetection; + bool m_collision_removal; + bool m_object_collision; + bool m_vertical; + v3s16 m_camera_offset; + struct TileAnimationParams m_animation; + float m_animation_time = 0.0f; + int m_animation_frame = 0; + u8 m_glow; +}; + +class ParticleSpawner +{ +public: + ParticleSpawner(IGameDef* gamedef, + LocalPlayer *player, + u16 amount, + float time, + v3f minp, v3f maxp, + v3f minvel, v3f maxvel, + v3f minacc, v3f maxacc, + float minexptime, float maxexptime, + float minsize, float maxsize, + bool collisiondetection, + bool collision_removal, + bool object_collision, + u16 attached_id, + bool vertical, + video::ITexture *texture, + u32 id, + const struct TileAnimationParams &anim, u8 glow, + ParticleManager* p_manager); + + ~ParticleSpawner() = default; + + void step(float dtime, ClientEnvironment *env); + + bool get_expired () + { return (m_amount <= 0) && m_spawntime != 0; } + +private: + void spawnParticle(ClientEnvironment *env, float radius, + bool is_attached, const v3f &attached_pos, + float attached_yaw); + + ParticleManager *m_particlemanager; + float m_time; + IGameDef *m_gamedef; + LocalPlayer *m_player; + u16 m_amount; + float m_spawntime; + v3f m_minpos; + v3f m_maxpos; + v3f m_minvel; + v3f m_maxvel; + v3f m_minacc; + v3f m_maxacc; + float m_minexptime; + float m_maxexptime; + float m_minsize; + float m_maxsize; + video::ITexture *m_texture; + std::vector m_spawntimes; + bool m_collisiondetection; + bool m_collision_removal; + bool m_object_collision; + bool m_vertical; + u16 m_attached_id; + struct TileAnimationParams m_animation; + u8 m_glow; +}; + +/** + * Class doing particle as well as their spawners handling + */ +class ParticleManager +{ +friend class ParticleSpawner; +public: + ParticleManager(ClientEnvironment* env); + ~ParticleManager(); + + void step (float dtime); + + void handleParticleEvent(ClientEvent *event, Client *client, + LocalPlayer *player); + + void addDiggingParticles(IGameDef *gamedef, LocalPlayer *player, v3s16 pos, + const MapNode &n, const ContentFeatures &f); + + void addNodeParticle(IGameDef *gamedef, LocalPlayer *player, v3s16 pos, + const MapNode &n, const ContentFeatures &f); + + u32 getSpawnerId() const + { + for (u32 id = 0;; ++id) { // look for unused particlespawner id + if (m_particle_spawners.find(id) == m_particle_spawners.end()) + return id; + } + } + +protected: + void addParticle(Particle* toadd); + +private: + + void stepParticles (float dtime); + void stepSpawners (float dtime); + + void clearAll (); + + std::vector m_particles; + std::map m_particle_spawners; + + ClientEnvironment* m_env; + std::mutex m_particle_list_lock; + std::mutex m_spawner_list_lock; +}; diff --git a/src/client/render/core.cpp b/src/client/render/core.cpp index bd280bc73..8c70b36c6 100644 --- a/src/client/render/core.cpp +++ b/src/client/render/core.cpp @@ -19,11 +19,11 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "core.h" -#include "camera.h" -#include "client.h" -#include "clientmap.h" -#include "hud.h" -#include "minimap.h" +#include "client/camera.h" +#include "client/client.h" +#include "client/clientmap.h" +#include "client/hud.h" +#include "client/minimap.h" RenderingCore::RenderingCore(IrrlichtDevice *_device, Client *_client, Hud *_hud) : device(_device), driver(device->getVideoDriver()), smgr(device->getSceneManager()), diff --git a/src/client/render/interlaced.cpp b/src/client/render/interlaced.cpp index 87d75de30..2aadadc17 100644 --- a/src/client/render/interlaced.cpp +++ b/src/client/render/interlaced.cpp @@ -19,8 +19,8 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "interlaced.h" -#include "client.h" -#include "shader.h" +#include "client/client.h" +#include "client/shader.h" #include "client/tile.h" RenderingCoreInterlaced::RenderingCoreInterlaced( diff --git a/src/client/render/stereo.cpp b/src/client/render/stereo.cpp index 4a5761bb7..967b5a78f 100644 --- a/src/client/render/stereo.cpp +++ b/src/client/render/stereo.cpp @@ -19,7 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "stereo.h" -#include "camera.h" +#include "client/camera.h" #include "constants.h" #include "settings.h" diff --git a/src/client/shader.cpp b/src/client/shader.cpp new file mode 100644 index 000000000..3b49a36ba --- /dev/null +++ b/src/client/shader.cpp @@ -0,0 +1,873 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola +Copyright (C) 2013 Kahrl + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include +#include +#include "shader.h" +#include "irrlichttypes_extrabloated.h" +#include "debug.h" +#include "filesys.h" +#include "util/container.h" +#include "util/thread.h" +#include "settings.h" +#include +#include +#include +#include +#include +#include "client/renderingengine.h" +#include "EShaderTypes.h" +#include "log.h" +#include "gamedef.h" +#include "client/tile.h" + +/* + A cache from shader name to shader path +*/ +MutexedMap g_shadername_to_path_cache; + +/* + Gets the path to a shader by first checking if the file + name_of_shader/filename + exists in shader_path and if not, using the data path. + + If not found, returns "". + + Utilizes a thread-safe cache. +*/ +std::string getShaderPath(const std::string &name_of_shader, + const std::string &filename) +{ + std::string combined = name_of_shader + DIR_DELIM + filename; + std::string fullpath; + /* + Check from cache + */ + bool incache = g_shadername_to_path_cache.get(combined, &fullpath); + if(incache) + return fullpath; + + /* + Check from shader_path + */ + std::string shader_path = g_settings->get("shader_path"); + if (!shader_path.empty()) { + std::string testpath = shader_path + DIR_DELIM + combined; + if(fs::PathExists(testpath)) + fullpath = testpath; + } + + /* + Check from default data directory + */ + if (fullpath.empty()) { + std::string rel_path = std::string("client") + DIR_DELIM + + "shaders" + DIR_DELIM + + name_of_shader + DIR_DELIM + + filename; + std::string testpath = porting::path_share + DIR_DELIM + rel_path; + if(fs::PathExists(testpath)) + fullpath = testpath; + } + + // Add to cache (also an empty result is cached) + g_shadername_to_path_cache.set(combined, fullpath); + + // Finally return it + return fullpath; +} + +/* + SourceShaderCache: A cache used for storing source shaders. +*/ + +class SourceShaderCache +{ +public: + void insert(const std::string &name_of_shader, const std::string &filename, + const std::string &program, bool prefer_local) + { + std::string combined = name_of_shader + DIR_DELIM + filename; + // Try to use local shader instead if asked to + if(prefer_local){ + std::string path = getShaderPath(name_of_shader, filename); + if(!path.empty()){ + std::string p = readFile(path); + if (!p.empty()) { + m_programs[combined] = p; + return; + } + } + } + m_programs[combined] = program; + } + + std::string get(const std::string &name_of_shader, + const std::string &filename) + { + std::string combined = name_of_shader + DIR_DELIM + filename; + StringMap::iterator n = m_programs.find(combined); + if (n != m_programs.end()) + return n->second; + return ""; + } + + // Primarily fetches from cache, secondarily tries to read from filesystem + std::string getOrLoad(const std::string &name_of_shader, + const std::string &filename) + { + std::string combined = name_of_shader + DIR_DELIM + filename; + StringMap::iterator n = m_programs.find(combined); + if (n != m_programs.end()) + return n->second; + std::string path = getShaderPath(name_of_shader, filename); + if (path.empty()) { + infostream << "SourceShaderCache::getOrLoad(): No path found for \"" + << combined << "\"" << std::endl; + return ""; + } + infostream << "SourceShaderCache::getOrLoad(): Loading path \"" + << path << "\"" << std::endl; + std::string p = readFile(path); + if (!p.empty()) { + m_programs[combined] = p; + return p; + } + return ""; + } +private: + StringMap m_programs; + + std::string readFile(const std::string &path) + { + std::ifstream is(path.c_str(), std::ios::binary); + if(!is.is_open()) + return ""; + std::ostringstream tmp_os; + tmp_os << is.rdbuf(); + return tmp_os.str(); + } +}; + + +/* + ShaderCallback: Sets constants that can be used in shaders +*/ + +class ShaderCallback : public video::IShaderConstantSetCallBack +{ + std::vector m_setters; + +public: + ShaderCallback(const std::vector &factories) + { + for (IShaderConstantSetterFactory *factory : factories) + m_setters.push_back(factory->create()); + } + + ~ShaderCallback() + { + for (IShaderConstantSetter *setter : m_setters) + delete setter; + } + + virtual void OnSetConstants(video::IMaterialRendererServices *services, s32 userData) + { + video::IVideoDriver *driver = services->getVideoDriver(); + sanity_check(driver != NULL); + + bool is_highlevel = userData; + + for (IShaderConstantSetter *setter : m_setters) + setter->onSetConstants(services, is_highlevel); + } +}; + + +/* + MainShaderConstantSetter: Set basic constants required for almost everything +*/ + +class MainShaderConstantSetter : public IShaderConstantSetter +{ + CachedVertexShaderSetting m_world_view_proj; + CachedVertexShaderSetting m_world; + +public: + MainShaderConstantSetter() : + m_world_view_proj("mWorldViewProj"), + m_world("mWorld") + {} + ~MainShaderConstantSetter() = default; + + virtual void onSetConstants(video::IMaterialRendererServices *services, + bool is_highlevel) + { + video::IVideoDriver *driver = services->getVideoDriver(); + sanity_check(driver); + + // Set clip matrix + core::matrix4 worldViewProj; + worldViewProj = driver->getTransform(video::ETS_PROJECTION); + worldViewProj *= driver->getTransform(video::ETS_VIEW); + worldViewProj *= driver->getTransform(video::ETS_WORLD); + if (is_highlevel) + m_world_view_proj.set(*reinterpret_cast(worldViewProj.pointer()), services); + else + services->setVertexShaderConstant(worldViewProj.pointer(), 0, 4); + + // Set world matrix + core::matrix4 world = driver->getTransform(video::ETS_WORLD); + if (is_highlevel) + m_world.set(*reinterpret_cast(world.pointer()), services); + else + services->setVertexShaderConstant(world.pointer(), 4, 4); + + } +}; + + +class MainShaderConstantSetterFactory : public IShaderConstantSetterFactory +{ +public: + virtual IShaderConstantSetter* create() + { return new MainShaderConstantSetter(); } +}; + + +/* + ShaderSource +*/ + +class ShaderSource : public IWritableShaderSource +{ +public: + ShaderSource(); + ~ShaderSource(); + + /* + - If shader material specified by name is found from cache, + return the cached id. + - Otherwise generate the shader material, add to cache and return id. + + The id 0 points to a null shader. Its material is EMT_SOLID. + */ + u32 getShaderIdDirect(const std::string &name, + const u8 material_type, const u8 drawtype); + + /* + If shader specified by the name pointed by the id doesn't + exist, create it, then return id. + + Can be called from any thread. If called from some other thread + and not found in cache, the call is queued to the main thread + for processing. + */ + + u32 getShader(const std::string &name, + const u8 material_type, const u8 drawtype); + + ShaderInfo getShaderInfo(u32 id); + + // Processes queued shader requests from other threads. + // Shall be called from the main thread. + void processQueue(); + + // Insert a shader program into the cache without touching the + // filesystem. Shall be called from the main thread. + void insertSourceShader(const std::string &name_of_shader, + const std::string &filename, const std::string &program); + + // Rebuild shaders from the current set of source shaders + // Shall be called from the main thread. + void rebuildShaders(); + + void addShaderConstantSetterFactory(IShaderConstantSetterFactory *setter) + { + m_setter_factories.push_back(setter); + } + +private: + + // The id of the thread that is allowed to use irrlicht directly + std::thread::id m_main_thread; + + // Cache of source shaders + // This should be only accessed from the main thread + SourceShaderCache m_sourcecache; + + // A shader id is index in this array. + // The first position contains a dummy shader. + std::vector m_shaderinfo_cache; + // The former container is behind this mutex + std::mutex m_shaderinfo_cache_mutex; + + // Queued shader fetches (to be processed by the main thread) + RequestQueue m_get_shader_queue; + + // Global constant setter factories + std::vector m_setter_factories; + + // Shader callbacks + std::vector m_callbacks; +}; + +IWritableShaderSource *createShaderSource() +{ + return new ShaderSource(); +} + +/* + Generate shader given the shader name. +*/ +ShaderInfo generate_shader(const std::string &name, + u8 material_type, u8 drawtype, std::vector &callbacks, + const std::vector &setter_factories, + SourceShaderCache *sourcecache); + +/* + Load shader programs +*/ +void load_shaders(const std::string &name, SourceShaderCache *sourcecache, + video::E_DRIVER_TYPE drivertype, bool enable_shaders, + std::string &vertex_program, std::string &pixel_program, + std::string &geometry_program, bool &is_highlevel); + +ShaderSource::ShaderSource() +{ + m_main_thread = std::this_thread::get_id(); + + // Add a dummy ShaderInfo as the first index, named "" + m_shaderinfo_cache.emplace_back(); + + // Add main global constant setter + addShaderConstantSetterFactory(new MainShaderConstantSetterFactory()); +} + +ShaderSource::~ShaderSource() +{ + for (ShaderCallback *callback : m_callbacks) { + delete callback; + } + for (IShaderConstantSetterFactory *setter_factorie : m_setter_factories) { + delete setter_factorie; + } +} + +u32 ShaderSource::getShader(const std::string &name, + const u8 material_type, const u8 drawtype) +{ + /* + Get shader + */ + + if (std::this_thread::get_id() == m_main_thread) { + return getShaderIdDirect(name, material_type, drawtype); + } + + /*errorstream<<"getShader(): Queued: name=\""< result_queue; + + // Throw a request in + m_get_shader_queue.add(name, 0, 0, &result_queue); + + /* infostream<<"Waiting for shader from main thread, name=\"" + < + result = result_queue.pop_frontNoEx(); + + if (result.key == name) { + return result.item; + } + + errorstream << "Got shader with invalid name: " << result.key << std::endl; + } + + infostream << "getShader(): Failed" << std::endl; + + return 0; +} + +/* + This method generates all the shaders +*/ +u32 ShaderSource::getShaderIdDirect(const std::string &name, + const u8 material_type, const u8 drawtype) +{ + //infostream<<"getShaderIdDirect(): name=\""<name == name && info->material_type == material_type && + info->drawtype == drawtype) + return i; + } + + /* + Calling only allowed from main thread + */ + if (std::this_thread::get_id() != m_main_thread) { + errorstream<<"ShaderSource::getShaderIdDirect() " + "called not from main thread"<= m_shaderinfo_cache.size()) + return ShaderInfo(); + + return m_shaderinfo_cache[id]; +} + +void ShaderSource::processQueue() +{ + + +} + +void ShaderSource::insertSourceShader(const std::string &name_of_shader, + const std::string &filename, const std::string &program) +{ + /*infostream<<"ShaderSource::insertSourceShader(): " + "name_of_shader=\""<name.empty()) { + *info = generate_shader(info->name, info->material_type, + info->drawtype, m_callbacks, + m_setter_factories, &m_sourcecache); + } + } +} + + +ShaderInfo generate_shader(const std::string &name, u8 material_type, u8 drawtype, + std::vector &callbacks, + const std::vector &setter_factories, + SourceShaderCache *sourcecache) +{ + ShaderInfo shaderinfo; + shaderinfo.name = name; + shaderinfo.material_type = material_type; + shaderinfo.drawtype = drawtype; + shaderinfo.material = video::EMT_SOLID; + switch (material_type) { + case TILE_MATERIAL_OPAQUE: + case TILE_MATERIAL_LIQUID_OPAQUE: + shaderinfo.base_material = video::EMT_SOLID; + break; + case TILE_MATERIAL_ALPHA: + case TILE_MATERIAL_LIQUID_TRANSPARENT: + shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL; + break; + case TILE_MATERIAL_BASIC: + case TILE_MATERIAL_WAVING_LEAVES: + case TILE_MATERIAL_WAVING_PLANTS: + shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; + break; + } + + bool enable_shaders = g_settings->getBool("enable_shaders"); + if (!enable_shaders) + return shaderinfo; + + video::IVideoDriver *driver = RenderingEngine::get_video_driver(); + + video::IGPUProgrammingServices *gpu = driver->getGPUProgrammingServices(); + if(!gpu){ + errorstream<<"generate_shader(): " + "failed to generate \""<getDriverType(), + enable_shaders, vertex_program, pixel_program, + geometry_program, is_highlevel); + // Check hardware/driver support + if (!vertex_program.empty() && + !driver->queryFeature(video::EVDF_VERTEX_SHADER_1_1) && + !driver->queryFeature(video::EVDF_ARB_VERTEX_PROGRAM_1)){ + infostream<<"generate_shader(): vertex shaders disabled " + "because of missing driver/hardware support." + <queryFeature(video::EVDF_PIXEL_SHADER_1_1) && + !driver->queryFeature(video::EVDF_ARB_FRAGMENT_PROGRAM_1)){ + infostream<<"generate_shader(): pixel shaders disabled " + "because of missing driver/hardware support." + <queryFeature(video::EVDF_GEOMETRY_SHADER)){ + infostream<<"generate_shader(): geometry shaders disabled " + "because of missing driver/hardware support." + <getBool("generate_normalmaps")) { + shaders_header += "#define GENERATE_NORMALMAPS 1\n"; + } else { + shaders_header += "#define GENERATE_NORMALMAPS 0\n"; + } + shaders_header += "#define NORMALMAPS_STRENGTH "; + shaders_header += ftos(g_settings->getFloat("normalmaps_strength")); + shaders_header += "\n"; + float sample_step; + int smooth = (int)g_settings->getFloat("normalmaps_smooth"); + switch (smooth){ + case 0: + sample_step = 0.0078125; // 1.0 / 128.0 + break; + case 1: + sample_step = 0.00390625; // 1.0 / 256.0 + break; + case 2: + sample_step = 0.001953125; // 1.0 / 512.0 + break; + default: + sample_step = 0.0078125; + break; + } + shaders_header += "#define SAMPLE_STEP "; + shaders_header += ftos(sample_step); + shaders_header += "\n"; + + if (g_settings->getBool("enable_bumpmapping")) + shaders_header += "#define ENABLE_BUMPMAPPING\n"; + + if (g_settings->getBool("enable_parallax_occlusion")){ + int mode = g_settings->getFloat("parallax_occlusion_mode"); + float scale = g_settings->getFloat("parallax_occlusion_scale"); + float bias = g_settings->getFloat("parallax_occlusion_bias"); + int iterations = g_settings->getFloat("parallax_occlusion_iterations"); + shaders_header += "#define ENABLE_PARALLAX_OCCLUSION\n"; + shaders_header += "#define PARALLAX_OCCLUSION_MODE "; + shaders_header += itos(mode); + shaders_header += "\n"; + shaders_header += "#define PARALLAX_OCCLUSION_SCALE "; + shaders_header += ftos(scale); + shaders_header += "\n"; + shaders_header += "#define PARALLAX_OCCLUSION_BIAS "; + shaders_header += ftos(bias); + shaders_header += "\n"; + shaders_header += "#define PARALLAX_OCCLUSION_ITERATIONS "; + shaders_header += itos(iterations); + shaders_header += "\n"; + } + + shaders_header += "#define USE_NORMALMAPS "; + if (g_settings->getBool("enable_bumpmapping") || g_settings->getBool("enable_parallax_occlusion")) + shaders_header += "1\n"; + else + shaders_header += "0\n"; + + if (g_settings->getBool("enable_waving_water")){ + shaders_header += "#define ENABLE_WAVING_WATER 1\n"; + shaders_header += "#define WATER_WAVE_HEIGHT "; + shaders_header += ftos(g_settings->getFloat("water_wave_height")); + shaders_header += "\n"; + shaders_header += "#define WATER_WAVE_LENGTH "; + shaders_header += ftos(g_settings->getFloat("water_wave_length")); + shaders_header += "\n"; + shaders_header += "#define WATER_WAVE_SPEED "; + shaders_header += ftos(g_settings->getFloat("water_wave_speed")); + shaders_header += "\n"; + } else{ + shaders_header += "#define ENABLE_WAVING_WATER 0\n"; + } + + shaders_header += "#define ENABLE_WAVING_LEAVES "; + if (g_settings->getBool("enable_waving_leaves")) + shaders_header += "1\n"; + else + shaders_header += "0\n"; + + shaders_header += "#define ENABLE_WAVING_PLANTS "; + if (g_settings->getBool("enable_waving_plants")) + shaders_header += "1\n"; + else + shaders_header += "0\n"; + + if (g_settings->getBool("tone_mapping")) + shaders_header += "#define ENABLE_TONE_MAPPING\n"; + + shaders_header += "#define FOG_START "; + shaders_header += ftos(rangelim(g_settings->getFloat("fog_start"), 0.0f, 0.99f)); + shaders_header += "\n"; + + // Call addHighLevelShaderMaterial() or addShaderMaterial() + const c8* vertex_program_ptr = 0; + const c8* pixel_program_ptr = 0; + const c8* geometry_program_ptr = 0; + if (!vertex_program.empty()) { + vertex_program = shaders_header + vertex_program; + vertex_program_ptr = vertex_program.c_str(); + } + if (!pixel_program.empty()) { + pixel_program = shaders_header + pixel_program; + pixel_program_ptr = pixel_program.c_str(); + } + if (!geometry_program.empty()) { + geometry_program = shaders_header + geometry_program; + geometry_program_ptr = geometry_program.c_str(); + } + ShaderCallback *cb = new ShaderCallback(setter_factories); + s32 shadermat = -1; + if(is_highlevel){ + infostream<<"Compiling high level shaders for "<addHighLevelShaderMaterial( + vertex_program_ptr, // Vertex shader program + "vertexMain", // Vertex shader entry point + video::EVST_VS_1_1, // Vertex shader version + pixel_program_ptr, // Pixel shader program + "pixelMain", // Pixel shader entry point + video::EPST_PS_1_2, // Pixel shader version + geometry_program_ptr, // Geometry shader program + "geometryMain", // Geometry shader entry point + video::EGST_GS_4_0, // Geometry shader version + scene::EPT_TRIANGLES, // Geometry shader input + scene::EPT_TRIANGLE_STRIP, // Geometry shader output + 0, // Support maximum number of vertices + cb, // Set-constant callback + shaderinfo.base_material, // Base material + 1 // Userdata passed to callback + ); + if(shadermat == -1){ + errorstream<<"generate_shader(): " + "failed to generate \""<addShaderMaterial( + vertex_program_ptr, // Vertex shader program + pixel_program_ptr, // Pixel shader program + cb, // Set-constant callback + shaderinfo.base_material, // Base material + 0 // Userdata passed to callback + ); + + if(shadermat == -1){ + errorstream<<"generate_shader(): " + "failed to generate \""<getMaterialRenderer(shadermat)->grab(); + + // Apply the newly created material type + shaderinfo.material = (video::E_MATERIAL_TYPE) shadermat; + return shaderinfo; +} + +void load_shaders(const std::string &name, SourceShaderCache *sourcecache, + video::E_DRIVER_TYPE drivertype, bool enable_shaders, + std::string &vertex_program, std::string &pixel_program, + std::string &geometry_program, bool &is_highlevel) +{ + vertex_program = ""; + pixel_program = ""; + geometry_program = ""; + is_highlevel = false; + + if(enable_shaders){ + // Look for high level shaders + if(drivertype == video::EDT_DIRECT3D9){ + // Direct3D 9: HLSL + // (All shaders in one file) + vertex_program = sourcecache->getOrLoad(name, "d3d9.hlsl"); + pixel_program = vertex_program; + geometry_program = vertex_program; + } + else if(drivertype == video::EDT_OPENGL){ + // OpenGL: GLSL + vertex_program = sourcecache->getOrLoad(name, "opengl_vertex.glsl"); + pixel_program = sourcecache->getOrLoad(name, "opengl_fragment.glsl"); + geometry_program = sourcecache->getOrLoad(name, "opengl_geometry.glsl"); + } + if (!vertex_program.empty() || !pixel_program.empty() || !geometry_program.empty()){ + is_highlevel = true; + return; + } + } + +} + +void dumpShaderProgram(std::ostream &output_stream, + const std::string &program_type, const std::string &program) +{ + output_stream << program_type << " shader program:" << std::endl << + "----------------------------------" << std::endl; + size_t pos = 0; + size_t prev = 0; + s16 line = 1; + while ((pos = program.find('\n', prev)) != std::string::npos) { + output_stream << line++ << ": "<< program.substr(prev, pos - prev) << + std::endl; + prev = pos + 1; + } + output_stream << line << ": " << program.substr(prev) << std::endl << + "End of " << program_type << " shader program." << std::endl << + " " << std::endl; +} diff --git a/src/client/shader.h b/src/client/shader.h new file mode 100644 index 000000000..583c776f4 --- /dev/null +++ b/src/client/shader.h @@ -0,0 +1,156 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola +Copyright (C) 2013 Kahrl + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include +#include "irrlichttypes_bloated.h" +#include + +class IGameDef; + +/* + shader.{h,cpp}: Shader handling stuff. +*/ + +/* + Gets the path to a shader by first checking if the file + name_of_shader/filename + exists in shader_path and if not, using the data path. + + If not found, returns "". + + Utilizes a thread-safe cache. +*/ +std::string getShaderPath(const std::string &name_of_shader, + const std::string &filename); + +struct ShaderInfo { + std::string name = ""; + video::E_MATERIAL_TYPE base_material = video::EMT_SOLID; + video::E_MATERIAL_TYPE material = video::EMT_SOLID; + u8 drawtype = 0; + u8 material_type = 0; + + ShaderInfo() = default; + virtual ~ShaderInfo() = default; +}; + +/* + Setter of constants for shaders +*/ + +namespace irr { namespace video { + class IMaterialRendererServices; +} } + + +class IShaderConstantSetter { +public: + virtual ~IShaderConstantSetter() = default; + virtual void onSetConstants(video::IMaterialRendererServices *services, + bool is_highlevel) = 0; +}; + + +class IShaderConstantSetterFactory { +public: + virtual ~IShaderConstantSetterFactory() = default; + virtual IShaderConstantSetter* create() = 0; +}; + + +template +class CachedShaderSetting { + const char *m_name; + T m_sent[count]; + bool has_been_set = false; + bool is_pixel; +protected: + CachedShaderSetting(const char *name, bool is_pixel) : + m_name(name), is_pixel(is_pixel) + {} +public: + void set(const T value[count], video::IMaterialRendererServices *services) + { + if (has_been_set && std::equal(m_sent, m_sent + count, value)) + return; + if (is_pixel) + services->setPixelShaderConstant(m_name, value, count); + else + services->setVertexShaderConstant(m_name, value, count); + std::copy(value, value + count, m_sent); + has_been_set = true; + } +}; + +template +class CachedPixelShaderSetting : public CachedShaderSetting { +public: + CachedPixelShaderSetting(const char *name) : + CachedShaderSetting(name, true){} +}; + +template +class CachedVertexShaderSetting : public CachedShaderSetting { +public: + CachedVertexShaderSetting(const char *name) : + CachedShaderSetting(name, false){} +}; + + +/* + ShaderSource creates and caches shaders. +*/ + +class IShaderSource { +public: + IShaderSource() = default; + virtual ~IShaderSource() = default; + + virtual u32 getShaderIdDirect(const std::string &name, + const u8 material_type, const u8 drawtype){return 0;} + virtual ShaderInfo getShaderInfo(u32 id){return ShaderInfo();} + virtual u32 getShader(const std::string &name, + const u8 material_type, const u8 drawtype){return 0;} +}; + +class IWritableShaderSource : public IShaderSource { +public: + IWritableShaderSource() = default; + virtual ~IWritableShaderSource() = default; + + virtual u32 getShaderIdDirect(const std::string &name, + const u8 material_type, const u8 drawtype){return 0;} + virtual ShaderInfo getShaderInfo(u32 id){return ShaderInfo();} + virtual u32 getShader(const std::string &name, + const u8 material_type, const u8 drawtype){return 0;} + + virtual void processQueue()=0; + virtual void insertSourceShader(const std::string &name_of_shader, + const std::string &filename, const std::string &program)=0; + virtual void rebuildShaders()=0; + virtual void addShaderConstantSetterFactory(IShaderConstantSetterFactory *setter) = 0; +}; + +IWritableShaderSource *createShaderSource(); + +void dumpShaderProgram(std::ostream &output_stream, + const std::string &program_type, const std::string &program); diff --git a/src/client/sky.cpp b/src/client/sky.cpp new file mode 100644 index 000000000..faf12ba92 --- /dev/null +++ b/src/client/sky.cpp @@ -0,0 +1,755 @@ +/* +Minetest +Copyright (C) 2010-2013 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "sky.h" +#include "IVideoDriver.h" +#include "ISceneManager.h" +#include "ICameraSceneNode.h" +#include "S3DVertex.h" +#include "client/tile.h" +#include "noise.h" // easeCurve +#include "profiler.h" +#include "util/numeric.h" +#include +#include "client/renderingengine.h" +#include "settings.h" +#include "camera.h" // CameraModes + + +Sky::Sky(s32 id, ITextureSource *tsrc): + scene::ISceneNode(RenderingEngine::get_scene_manager()->getRootSceneNode(), + RenderingEngine::get_scene_manager(), id) +{ + setAutomaticCulling(scene::EAC_OFF); + m_box.MaxEdge.set(0, 0, 0); + m_box.MinEdge.set(0, 0, 0); + + // Create material + + video::SMaterial mat; + mat.Lighting = false; +#ifdef __ANDROID__ + mat.ZBuffer = video::ECFN_DISABLED; +#else + mat.ZBuffer = video::ECFN_NEVER; +#endif + mat.ZWriteEnable = false; + mat.AntiAliasing = 0; + mat.TextureLayer[0].TextureWrapU = video::ETC_CLAMP_TO_EDGE; + mat.TextureLayer[0].TextureWrapV = video::ETC_CLAMP_TO_EDGE; + mat.BackfaceCulling = false; + + m_materials[0] = mat; + + m_materials[1] = mat; + //m_materials[1].MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA; + m_materials[1].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; + + m_materials[2] = mat; + m_materials[2].setTexture(0, tsrc->getTextureForMesh("sunrisebg.png")); + m_materials[2].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; + //m_materials[2].MaterialType = video::EMT_TRANSPARENT_ADD_COLOR; + + m_sun_texture = tsrc->isKnownSourceImage("sun.png") ? + tsrc->getTextureForMesh("sun.png") : NULL; + m_moon_texture = tsrc->isKnownSourceImage("moon.png") ? + tsrc->getTextureForMesh("moon.png") : NULL; + m_sun_tonemap = tsrc->isKnownSourceImage("sun_tonemap.png") ? + tsrc->getTexture("sun_tonemap.png") : NULL; + m_moon_tonemap = tsrc->isKnownSourceImage("moon_tonemap.png") ? + tsrc->getTexture("moon_tonemap.png") : NULL; + + if (m_sun_texture) { + m_materials[3] = mat; + m_materials[3].setTexture(0, m_sun_texture); + m_materials[3].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; + if (m_sun_tonemap) + m_materials[3].Lighting = true; + } + + if (m_moon_texture) { + m_materials[4] = mat; + m_materials[4].setTexture(0, m_moon_texture); + m_materials[4].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; + if (m_moon_tonemap) + m_materials[4].Lighting = true; + } + + for (v3f &star : m_stars) { + star = v3f( + myrand_range(-10000, 10000), + myrand_range(-10000, 10000), + myrand_range(-10000, 10000) + ); + star.normalize(); + } + + m_directional_colored_fog = g_settings->getBool("directional_colored_fog"); +} + + +void Sky::OnRegisterSceneNode() +{ + if (IsVisible) + SceneManager->registerNodeForRendering(this, scene::ESNRP_SKY_BOX); + + scene::ISceneNode::OnRegisterSceneNode(); +} + + +void Sky::render() +{ + if (!m_visible) + return; + + video::IVideoDriver* driver = SceneManager->getVideoDriver(); + scene::ICameraSceneNode* camera = SceneManager->getActiveCamera(); + + if (!camera || !driver) + return; + + ScopeProfiler sp(g_profiler, "Sky::render()", SPT_AVG); + + // Draw perspective skybox + + core::matrix4 translate(AbsoluteTransformation); + translate.setTranslation(camera->getAbsolutePosition()); + + // Draw the sky box between the near and far clip plane + const f32 viewDistance = (camera->getNearValue() + camera->getFarValue()) * 0.5f; + core::matrix4 scale; + scale.setScale(core::vector3df(viewDistance, viewDistance, viewDistance)); + + driver->setTransform(video::ETS_WORLD, translate * scale); + + if (m_sunlight_seen) { + float sunsize = 0.07; + video::SColorf suncolor_f(1, 1, 0, 1); + //suncolor_f.r = 1; + //suncolor_f.g = MYMAX(0.3, MYMIN(1.0, 0.7 + m_time_brightness * 0.5)); + //suncolor_f.b = MYMAX(0.0, m_brightness * 0.95); + video::SColorf suncolor2_f(1, 1, 1, 1); + // The values below were probably meant to be suncolor2_f instead of a + // reassignment of suncolor_f. However, the resulting colour was chosen + // and is our long-running classic colour. So preserve, but comment-out + // the unnecessary first assignments above. + suncolor_f.r = 1; + suncolor_f.g = MYMAX(0.3, MYMIN(1.0, 0.85 + m_time_brightness * 0.5)); + suncolor_f.b = MYMAX(0.0, m_brightness); + + float moonsize = 0.04; + video::SColorf mooncolor_f(0.50, 0.57, 0.65, 1); + video::SColorf mooncolor2_f(0.85, 0.875, 0.9, 1); + + float nightlength = 0.415; + float wn = nightlength / 2; + float wicked_time_of_day = 0; + if (m_time_of_day > wn && m_time_of_day < 1.0 - wn) + wicked_time_of_day = (m_time_of_day - wn) / (1.0 - wn * 2) * 0.5 + 0.25; + else if (m_time_of_day < 0.5) + wicked_time_of_day = m_time_of_day / wn * 0.25; + else + wicked_time_of_day = 1.0 - ((1.0 - m_time_of_day) / wn * 0.25); + /*std::cerr<<"time_of_day="< " + <<"wicked_time_of_day="<lock(); + video::SColor* texel = (video::SColor *)(texels + (u32)offset * 4); + video::SColor texel_color (255, texel->getRed(), + texel->getGreen(), texel->getBlue()); + m_sun_tonemap->unlock(); + m_materials[3].EmissiveColor = texel_color; + } + + if (m_moon_tonemap) { + u8 * texels = (u8 *)m_moon_tonemap->lock(); + video::SColor* texel = (video::SColor *)(texels + (u32)offset * 4); + video::SColor texel_color (255, texel->getRed(), + texel->getGreen(), texel->getBlue()); + m_moon_tonemap->unlock(); + m_materials[4].EmissiveColor = texel_color; + } + + const f32 t = 1.0f; + const f32 o = 0.0f; + static const u16 indices[4] = {0, 1, 2, 3}; + video::S3DVertex vertices[4]; + + driver->setMaterial(m_materials[1]); + + video::SColor cloudyfogcolor = m_bgcolor; + + // Draw far cloudy fog thing blended with skycolor + for (u32 j = 0; j < 4; j++) { + video::SColor c = cloudyfogcolor.getInterpolated(m_skycolor, 0.45); + vertices[0] = video::S3DVertex(-1, 0.08, -1, 0, 0, 1, c, t, t); + vertices[1] = video::S3DVertex( 1, 0.08, -1, 0, 0, 1, c, o, t); + vertices[2] = video::S3DVertex( 1, 0.12, -1, 0, 0, 1, c, o, o); + vertices[3] = video::S3DVertex(-1, 0.12, -1, 0, 0, 1, c, t, o); + for (video::S3DVertex &vertex : vertices) { + if (j == 0) + // Don't switch + {} + else if (j == 1) + // Switch from -Z (south) to +X (east) + vertex.Pos.rotateXZBy(90); + else if (j == 2) + // Switch from -Z (south) to -X (west) + vertex.Pos.rotateXZBy(-90); + else + // Switch from -Z (south) to +Z (north) + vertex.Pos.rotateXZBy(-180); + } + driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2); + } + + // Draw far cloudy fog thing + for (u32 j = 0; j < 4; j++) { + video::SColor c = cloudyfogcolor; + vertices[0] = video::S3DVertex(-1, -1.0, -1, 0, 0, 1, c, t, t); + vertices[1] = video::S3DVertex( 1, -1.0, -1, 0, 0, 1, c, o, t); + vertices[2] = video::S3DVertex( 1, 0.08, -1, 0, 0, 1, c, o, o); + vertices[3] = video::S3DVertex(-1, 0.08, -1, 0, 0, 1, c, t, o); + for (video::S3DVertex &vertex : vertices) { + if (j == 0) + // Don't switch + {} + else if (j == 1) + // Switch from -Z (south) to +X (east) + vertex.Pos.rotateXZBy(90); + else if (j == 2) + // Switch from -Z (south) to -X (west) + vertex.Pos.rotateXZBy(-90); + else + // Switch from -Z (south) to +Z (north) + vertex.Pos.rotateXZBy(-180); + } + driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2); + } + + // Draw bottom far cloudy fog thing + video::SColor c = cloudyfogcolor; + vertices[0] = video::S3DVertex(-1, -1.0, -1, 0, 1, 0, c, t, t); + vertices[1] = video::S3DVertex( 1, -1.0, -1, 0, 1, 0, c, o, t); + vertices[2] = video::S3DVertex( 1, -1.0, 1, 0, 1, 0, c, o, o); + vertices[3] = video::S3DVertex(-1, -1.0, 1, 0, 1, 0, c, t, o); + driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2); + + // If sun, moon and stars are (temporarily) disabled, abort here + if (!m_bodies_visible) + return; + + driver->setMaterial(m_materials[2]); + + // Draw sunrise/sunset horizon glow texture (textures/base/pack/sunrisebg.png) + { + float mid1 = 0.25; + float mid = wicked_time_of_day < 0.5 ? mid1 : (1.0 - mid1); + float a_ = 1.0f - std::fabs(wicked_time_of_day - mid) * 35.0f; + float a = easeCurve(MYMAX(0, MYMIN(1, a_))); + //std::cerr<<"a_="< CAMERA_MODE_THIRD) + dir_factor = -1; + f32 pointcolor_blend = wrapDegrees_0_360(yaw * dir_factor + 90); + if (pointcolor_blend > 180) + pointcolor_blend = 360 - pointcolor_blend; + pointcolor_blend /= 180; + // Bound view angle to determine where transition starts and ends + pointcolor_blend = rangelim(1 - pointcolor_blend * 1.375, 0, 1 / 1.375) * + 1.375; + // Combine the colors when looking up or down, otherwise turning looks weird + pointcolor_blend += (0.5 - pointcolor_blend) * + (1 - MYMIN((90 - std::fabs(pitch)) / 90 * 1.5, 1)); + // Invert direction to match where the sun and moon are rising + if (m_time_of_day > 0.5) + pointcolor_blend = 1 - pointcolor_blend; + // Horizon colors of sun and moon + f32 pointcolor_light = rangelim(m_time_brightness * 3, 0.2, 1); + + video::SColorf pointcolor_sun_f(1, 1, 1, 1); + if (m_sun_tonemap) { + pointcolor_sun_f.r = pointcolor_light * + (float)m_materials[3].EmissiveColor.getRed() / 255; + pointcolor_sun_f.b = pointcolor_light * + (float)m_materials[3].EmissiveColor.getBlue() / 255; + pointcolor_sun_f.g = pointcolor_light * + (float)m_materials[3].EmissiveColor.getGreen() / 255; + } else { + pointcolor_sun_f.r = pointcolor_light * 1; + pointcolor_sun_f.b = pointcolor_light * + (0.25 + (rangelim(m_time_brightness, 0.25, 0.75) - 0.25) * 2 * 0.75); + pointcolor_sun_f.g = pointcolor_light * (pointcolor_sun_f.b * 0.375 + + (rangelim(m_time_brightness, 0.05, 0.15) - 0.05) * 10 * 0.625); + } + + video::SColorf pointcolor_moon_f(0.5 * pointcolor_light, + 0.6 * pointcolor_light, 0.8 * pointcolor_light, 1); + if (m_moon_tonemap) { + pointcolor_moon_f.r = pointcolor_light * + (float)m_materials[4].EmissiveColor.getRed() / 255; + pointcolor_moon_f.b = pointcolor_light * + (float)m_materials[4].EmissiveColor.getBlue() / 255; + pointcolor_moon_f.g = pointcolor_light * + (float)m_materials[4].EmissiveColor.getGreen() / 255; + } + + video::SColor pointcolor_sun = pointcolor_sun_f.toSColor(); + video::SColor pointcolor_moon = pointcolor_moon_f.toSColor(); + // Calculate the blend color + pointcolor = m_mix_scolor(pointcolor_moon, pointcolor_sun, pointcolor_blend); + } + m_bgcolor = m_mix_scolor(m_bgcolor, pointcolor, m_horizon_blend() * 0.5); + m_skycolor = m_mix_scolor(m_skycolor, pointcolor, m_horizon_blend() * 0.25); + } + + float cloud_direct_brightness = 0.0f; + if (sunlight_seen) { + if (!m_directional_colored_fog) { + cloud_direct_brightness = time_brightness; + // Boost cloud brightness relative to sky, at dawn, dusk and at night + if (time_brightness < 0.7f) + cloud_direct_brightness *= 1.3f; + } else { + cloud_direct_brightness = std::fmin(m_horizon_blend() * 0.15f + + m_time_brightness, 1.0f); + // Set the same minimum cloud brightness at night + if (time_brightness < 0.5f) + cloud_direct_brightness = std::fmax(cloud_direct_brightness, + time_brightness * 1.3f); + } + } else { + cloud_direct_brightness = direct_brightness; + } + + m_cloud_brightness = m_cloud_brightness * cloud_color_change_fraction + + cloud_direct_brightness * (1.0 - cloud_color_change_fraction); + m_cloudcolor_f = video::SColorf( + m_cloudcolor_bright_f.r * m_cloud_brightness, + m_cloudcolor_bright_f.g * m_cloud_brightness, + m_cloudcolor_bright_f.b * m_cloud_brightness, + 1.0 + ); + if (m_directional_colored_fog) { + m_cloudcolor_f = m_mix_scolorf(m_cloudcolor_f, + video::SColorf(pointcolor), m_horizon_blend() * 0.25); + } +} diff --git a/src/client/sky.h b/src/client/sky.h new file mode 100644 index 000000000..b66a4990f --- /dev/null +++ b/src/client/sky.h @@ -0,0 +1,148 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include +#include "camera.h" +#include "irrlichttypes_extrabloated.h" + +#pragma once + +#define SKY_MATERIAL_COUNT 5 +#define SKY_STAR_COUNT 200 + +class ITextureSource; + +// Skybox, rendered with zbuffer turned off, before all other nodes. +class Sky : public scene::ISceneNode +{ +public: + //! constructor + Sky(s32 id, ITextureSource *tsrc); + + virtual void OnRegisterSceneNode(); + + //! renders the node. + virtual void render(); + + virtual const aabb3f &getBoundingBox() const { return m_box; } + + // Used by Irrlicht for optimizing rendering + virtual video::SMaterial &getMaterial(u32 i) { return m_materials[i]; } + + // Used by Irrlicht for optimizing rendering + virtual u32 getMaterialCount() const { return SKY_MATERIAL_COUNT; } + + void update(float m_time_of_day, float time_brightness, float direct_brightness, + bool sunlight_seen, CameraMode cam_mode, float yaw, float pitch); + + float getBrightness() { return m_brightness; } + + const video::SColor &getBgColor() const + { + return m_visible ? m_bgcolor : m_fallback_bg_color; + } + + const video::SColor &getSkyColor() const + { + return m_visible ? m_skycolor : m_fallback_bg_color; + } + + bool getCloudsVisible() const { return m_clouds_visible && m_clouds_enabled; } + const video::SColorf &getCloudColor() const { return m_cloudcolor_f; } + + void setVisible(bool visible) { m_visible = visible; } + // Set only from set_sky API + void setCloudsEnabled(bool clouds_enabled) { m_clouds_enabled = clouds_enabled; } + void setFallbackBgColor(const video::SColor &fallback_bg_color) + { + m_fallback_bg_color = fallback_bg_color; + } + void overrideColors(const video::SColor &bgcolor, const video::SColor &skycolor) + { + m_bgcolor = bgcolor; + m_skycolor = skycolor; + } + void setBodiesVisible(bool visible) { m_bodies_visible = visible; } + +private: + aabb3f m_box; + video::SMaterial m_materials[SKY_MATERIAL_COUNT]; + + // How much sun & moon transition should affect horizon color + float m_horizon_blend() + { + if (!m_sunlight_seen) + return 0; + float x = m_time_of_day >= 0.5 ? (1 - m_time_of_day) * 2 + : m_time_of_day * 2; + + if (x <= 0.3) + return 0; + if (x <= 0.4) // when the sun and moon are aligned + return (x - 0.3) * 10; + if (x <= 0.5) + return (0.5 - x) * 10; + return 0; + } + + // Mix two colors by a given amount + video::SColor m_mix_scolor(video::SColor col1, video::SColor col2, f32 factor) + { + video::SColor result = video::SColor( + col1.getAlpha() * (1 - factor) + col2.getAlpha() * factor, + col1.getRed() * (1 - factor) + col2.getRed() * factor, + col1.getGreen() * (1 - factor) + col2.getGreen() * factor, + col1.getBlue() * (1 - factor) + col2.getBlue() * factor); + return result; + } + video::SColorf m_mix_scolorf(video::SColorf col1, video::SColorf col2, f32 factor) + { + video::SColorf result = + video::SColorf(col1.r * (1 - factor) + col2.r * factor, + col1.g * (1 - factor) + col2.g * factor, + col1.b * (1 - factor) + col2.b * factor, + col1.a * (1 - factor) + col2.a * factor); + return result; + } + + bool m_visible = true; + // Used when m_visible=false + video::SColor m_fallback_bg_color = video::SColor(255, 255, 255, 255); + bool m_first_update = true; + float m_time_of_day; + float m_time_brightness; + bool m_sunlight_seen; + float m_brightness = 0.5f; + float m_cloud_brightness = 0.5f; + bool m_clouds_visible; // Whether clouds are disabled due to player underground + bool m_clouds_enabled = true; // Initialised to true, reset only by set_sky API + bool m_directional_colored_fog; + bool m_bodies_visible = true; // sun, moon, stars + video::SColorf m_bgcolor_bright_f = video::SColorf(1.0f, 1.0f, 1.0f, 1.0f); + video::SColorf m_skycolor_bright_f = video::SColorf(1.0f, 1.0f, 1.0f, 1.0f); + video::SColorf m_cloudcolor_bright_f = video::SColorf(1.0f, 1.0f, 1.0f, 1.0f); + video::SColor m_bgcolor; + video::SColor m_skycolor; + video::SColorf m_cloudcolor_f; + v3f m_stars[SKY_STAR_COUNT]; + video::ITexture *m_sun_texture; + video::ITexture *m_moon_texture; + video::ITexture *m_sun_tonemap; + video::ITexture *m_moon_tonemap; +}; diff --git a/src/client/wieldmesh.cpp b/src/client/wieldmesh.cpp new file mode 100644 index 000000000..7791a5a92 --- /dev/null +++ b/src/client/wieldmesh.cpp @@ -0,0 +1,685 @@ +/* +Minetest +Copyright (C) 2010-2014 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "wieldmesh.h" +#include "settings.h" +#include "shader.h" +#include "inventory.h" +#include "client.h" +#include "itemdef.h" +#include "nodedef.h" +#include "mesh.h" +#include "content_mapblock.h" +#include "mapblock_mesh.h" +#include "client/meshgen/collector.h" +#include "client/tile.h" +#include "log.h" +#include "util/numeric.h" +#include +#include + +#define WIELD_SCALE_FACTOR 30.0 +#define WIELD_SCALE_FACTOR_EXTRUDED 40.0 + +#define MIN_EXTRUSION_MESH_RESOLUTION 16 +#define MAX_EXTRUSION_MESH_RESOLUTION 512 + +static scene::IMesh *createExtrusionMesh(int resolution_x, int resolution_y) +{ + const f32 r = 0.5; + + scene::IMeshBuffer *buf = new scene::SMeshBuffer(); + video::SColor c(255,255,255,255); + v3f scale(1.0, 1.0, 0.1); + + // Front and back + { + video::S3DVertex vertices[8] = { + // z- + video::S3DVertex(-r,+r,-r, 0,0,-1, c, 0,0), + video::S3DVertex(+r,+r,-r, 0,0,-1, c, 1,0), + video::S3DVertex(+r,-r,-r, 0,0,-1, c, 1,1), + video::S3DVertex(-r,-r,-r, 0,0,-1, c, 0,1), + // z+ + video::S3DVertex(-r,+r,+r, 0,0,+1, c, 0,0), + video::S3DVertex(-r,-r,+r, 0,0,+1, c, 0,1), + video::S3DVertex(+r,-r,+r, 0,0,+1, c, 1,1), + video::S3DVertex(+r,+r,+r, 0,0,+1, c, 1,0), + }; + u16 indices[12] = {0,1,2,2,3,0,4,5,6,6,7,4}; + buf->append(vertices, 8, indices, 12); + } + + f32 pixelsize_x = 1 / (f32) resolution_x; + f32 pixelsize_y = 1 / (f32) resolution_y; + + for (int i = 0; i < resolution_x; ++i) { + f32 pixelpos_x = i * pixelsize_x - 0.5; + f32 x0 = pixelpos_x; + f32 x1 = pixelpos_x + pixelsize_x; + f32 tex0 = (i + 0.1) * pixelsize_x; + f32 tex1 = (i + 0.9) * pixelsize_x; + video::S3DVertex vertices[8] = { + // x- + video::S3DVertex(x0,-r,-r, -1,0,0, c, tex0,1), + video::S3DVertex(x0,-r,+r, -1,0,0, c, tex1,1), + video::S3DVertex(x0,+r,+r, -1,0,0, c, tex1,0), + video::S3DVertex(x0,+r,-r, -1,0,0, c, tex0,0), + // x+ + video::S3DVertex(x1,-r,-r, +1,0,0, c, tex0,1), + video::S3DVertex(x1,+r,-r, +1,0,0, c, tex0,0), + video::S3DVertex(x1,+r,+r, +1,0,0, c, tex1,0), + video::S3DVertex(x1,-r,+r, +1,0,0, c, tex1,1), + }; + u16 indices[12] = {0,1,2,2,3,0,4,5,6,6,7,4}; + buf->append(vertices, 8, indices, 12); + } + for (int i = 0; i < resolution_y; ++i) { + f32 pixelpos_y = i * pixelsize_y - 0.5; + f32 y0 = -pixelpos_y - pixelsize_y; + f32 y1 = -pixelpos_y; + f32 tex0 = (i + 0.1) * pixelsize_y; + f32 tex1 = (i + 0.9) * pixelsize_y; + video::S3DVertex vertices[8] = { + // y- + video::S3DVertex(-r,y0,-r, 0,-1,0, c, 0,tex0), + video::S3DVertex(+r,y0,-r, 0,-1,0, c, 1,tex0), + video::S3DVertex(+r,y0,+r, 0,-1,0, c, 1,tex1), + video::S3DVertex(-r,y0,+r, 0,-1,0, c, 0,tex1), + // y+ + video::S3DVertex(-r,y1,-r, 0,+1,0, c, 0,tex0), + video::S3DVertex(-r,y1,+r, 0,+1,0, c, 0,tex1), + video::S3DVertex(+r,y1,+r, 0,+1,0, c, 1,tex1), + video::S3DVertex(+r,y1,-r, 0,+1,0, c, 1,tex0), + }; + u16 indices[12] = {0,1,2,2,3,0,4,5,6,6,7,4}; + buf->append(vertices, 8, indices, 12); + } + + // Create mesh object + scene::SMesh *mesh = new scene::SMesh(); + mesh->addMeshBuffer(buf); + buf->drop(); + scaleMesh(mesh, scale); // also recalculates bounding box + return mesh; +} + +/* + Caches extrusion meshes so that only one of them per resolution + is needed. Also caches one cube (for convenience). + + E.g. there is a single extrusion mesh that is used for all + 16x16 px images, another for all 256x256 px images, and so on. + + WARNING: Not thread safe. This should not be a problem since + rendering related classes (such as WieldMeshSceneNode) will be + used from the rendering thread only. +*/ +class ExtrusionMeshCache: public IReferenceCounted +{ +public: + // Constructor + ExtrusionMeshCache() + { + for (int resolution = MIN_EXTRUSION_MESH_RESOLUTION; + resolution <= MAX_EXTRUSION_MESH_RESOLUTION; + resolution *= 2) { + m_extrusion_meshes[resolution] = + createExtrusionMesh(resolution, resolution); + } + m_cube = createCubeMesh(v3f(1.0, 1.0, 1.0)); + } + // Destructor + virtual ~ExtrusionMeshCache() + { + for (auto &extrusion_meshe : m_extrusion_meshes) { + extrusion_meshe.second->drop(); + } + m_cube->drop(); + } + // Get closest extrusion mesh for given image dimensions + // Caller must drop the returned pointer + scene::IMesh* create(core::dimension2d dim) + { + // handle non-power of two textures inefficiently without cache + if (!is_power_of_two(dim.Width) || !is_power_of_two(dim.Height)) { + return createExtrusionMesh(dim.Width, dim.Height); + } + + int maxdim = MYMAX(dim.Width, dim.Height); + + std::map::iterator + it = m_extrusion_meshes.lower_bound(maxdim); + + if (it == m_extrusion_meshes.end()) { + // no viable resolution found; use largest one + it = m_extrusion_meshes.find(MAX_EXTRUSION_MESH_RESOLUTION); + sanity_check(it != m_extrusion_meshes.end()); + } + + scene::IMesh *mesh = it->second; + mesh->grab(); + return mesh; + } + // Returns a 1x1x1 cube mesh with one meshbuffer (material) per face + // Caller must drop the returned pointer + scene::IMesh* createCube() + { + m_cube->grab(); + return m_cube; + } + +private: + std::map m_extrusion_meshes; + scene::IMesh *m_cube; +}; + +ExtrusionMeshCache *g_extrusion_mesh_cache = NULL; + + +WieldMeshSceneNode::WieldMeshSceneNode(scene::ISceneManager *mgr, s32 id, bool lighting): + scene::ISceneNode(mgr->getRootSceneNode(), mgr, id), + m_material_type(video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF), + m_lighting(lighting) +{ + m_enable_shaders = g_settings->getBool("enable_shaders"); + m_anisotropic_filter = g_settings->getBool("anisotropic_filter"); + m_bilinear_filter = g_settings->getBool("bilinear_filter"); + m_trilinear_filter = g_settings->getBool("trilinear_filter"); + + // If this is the first wield mesh scene node, create a cache + // for extrusion meshes (and a cube mesh), otherwise reuse it + if (!g_extrusion_mesh_cache) + g_extrusion_mesh_cache = new ExtrusionMeshCache(); + else + g_extrusion_mesh_cache->grab(); + + // Disable bounding box culling for this scene node + // since we won't calculate the bounding box. + setAutomaticCulling(scene::EAC_OFF); + + // Create the child scene node + scene::IMesh *dummymesh = g_extrusion_mesh_cache->createCube(); + m_meshnode = SceneManager->addMeshSceneNode(dummymesh, this, -1); + m_meshnode->setReadOnlyMaterials(false); + m_meshnode->setVisible(false); + dummymesh->drop(); // m_meshnode grabbed it +} + +WieldMeshSceneNode::~WieldMeshSceneNode() +{ + sanity_check(g_extrusion_mesh_cache); + if (g_extrusion_mesh_cache->drop()) + g_extrusion_mesh_cache = nullptr; +} + +void WieldMeshSceneNode::setCube(const ContentFeatures &f, + v3f wield_scale) +{ + scene::IMesh *cubemesh = g_extrusion_mesh_cache->createCube(); + scene::SMesh *copy = cloneMesh(cubemesh); + cubemesh->drop(); + postProcessNodeMesh(copy, f, false, true, &m_material_type, &m_colors, true); + changeToMesh(copy); + copy->drop(); + m_meshnode->setScale(wield_scale * WIELD_SCALE_FACTOR); +} + +void WieldMeshSceneNode::setExtruded(const std::string &imagename, + const std::string &overlay_name, v3f wield_scale, ITextureSource *tsrc, + u8 num_frames) +{ + video::ITexture *texture = tsrc->getTexture(imagename); + if (!texture) { + changeToMesh(nullptr); + return; + } + video::ITexture *overlay_texture = + overlay_name.empty() ? NULL : tsrc->getTexture(overlay_name); + + core::dimension2d dim = texture->getSize(); + // Detect animation texture and pull off top frame instead of using entire thing + if (num_frames > 1) { + u32 frame_height = dim.Height / num_frames; + dim = core::dimension2d(dim.Width, frame_height); + } + scene::IMesh *original = g_extrusion_mesh_cache->create(dim); + scene::SMesh *mesh = cloneMesh(original); + original->drop(); + //set texture + mesh->getMeshBuffer(0)->getMaterial().setTexture(0, + tsrc->getTexture(imagename)); + if (overlay_texture) { + scene::IMeshBuffer *copy = cloneMeshBuffer(mesh->getMeshBuffer(0)); + copy->getMaterial().setTexture(0, overlay_texture); + mesh->addMeshBuffer(copy); + copy->drop(); + } + changeToMesh(mesh); + mesh->drop(); + + m_meshnode->setScale(wield_scale * WIELD_SCALE_FACTOR_EXTRUDED); + + // Customize materials + for (u32 layer = 0; layer < m_meshnode->getMaterialCount(); layer++) { + video::SMaterial &material = m_meshnode->getMaterial(layer); + material.TextureLayer[0].TextureWrapU = video::ETC_CLAMP_TO_EDGE; + material.TextureLayer[0].TextureWrapV = video::ETC_CLAMP_TO_EDGE; + material.MaterialType = m_material_type; + material.setFlag(video::EMF_BACK_FACE_CULLING, true); + // Enable bi/trilinear filtering only for high resolution textures + if (dim.Width > 32) { + material.setFlag(video::EMF_BILINEAR_FILTER, m_bilinear_filter); + material.setFlag(video::EMF_TRILINEAR_FILTER, m_trilinear_filter); + } else { + material.setFlag(video::EMF_BILINEAR_FILTER, false); + material.setFlag(video::EMF_TRILINEAR_FILTER, false); + } + material.setFlag(video::EMF_ANISOTROPIC_FILTER, m_anisotropic_filter); + // mipmaps cause "thin black line" artifacts +#if (IRRLICHT_VERSION_MAJOR >= 1 && IRRLICHT_VERSION_MINOR >= 8) || IRRLICHT_VERSION_MAJOR >= 2 + material.setFlag(video::EMF_USE_MIP_MAPS, false); +#endif + if (m_enable_shaders) { + material.setTexture(2, tsrc->getShaderFlagsTexture(false)); + } + } +} + +scene::SMesh *createSpecialNodeMesh(Client *client, content_t id, std::vector *colors) +{ + MeshMakeData mesh_make_data(client, false, false); + MeshCollector collector; + mesh_make_data.setSmoothLighting(false); + MapblockMeshGenerator gen(&mesh_make_data, &collector); + gen.renderSingle(id); + colors->clear(); + scene::SMesh *mesh = new scene::SMesh(); + for (auto &prebuffers : collector.prebuffers) + for (PreMeshBuffer &p : prebuffers) { + if (p.layer.material_flags & MATERIAL_FLAG_ANIMATION) { + const FrameSpec &frame = (*p.layer.frames)[0]; + p.layer.texture = frame.texture; + p.layer.normal_texture = frame.normal_texture; + } + for (video::S3DVertex &v : p.vertices) + v.Color.setAlpha(255); + scene::SMeshBuffer *buf = new scene::SMeshBuffer(); + buf->Material.setTexture(0, p.layer.texture); + p.layer.applyMaterialOptions(buf->Material); + mesh->addMeshBuffer(buf); + buf->append(&p.vertices[0], p.vertices.size(), + &p.indices[0], p.indices.size()); + buf->drop(); + colors->push_back( + ItemPartColor(p.layer.has_color, p.layer.color)); + } + return mesh; +} + +void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client) +{ + ITextureSource *tsrc = client->getTextureSource(); + IItemDefManager *idef = client->getItemDefManager(); + IShaderSource *shdrsrc = client->getShaderSource(); + const NodeDefManager *ndef = client->getNodeDefManager(); + const ItemDefinition &def = item.getDefinition(idef); + const ContentFeatures &f = ndef->get(def.name); + content_t id = ndef->getId(def.name); + + scene::SMesh *mesh = nullptr; + + if (m_enable_shaders) { + u32 shader_id = shdrsrc->getShader("wielded_shader", TILE_MATERIAL_BASIC, NDT_NORMAL); + m_material_type = shdrsrc->getShaderInfo(shader_id).material; + } + + // Color-related + m_colors.clear(); + m_base_color = idef->getItemstackColor(item, client); + + // If wield_image is defined, it overrides everything else + if (!def.wield_image.empty()) { + setExtruded(def.wield_image, def.wield_overlay, def.wield_scale, tsrc, + 1); + m_colors.emplace_back(); + // overlay is white, if present + m_colors.emplace_back(true, video::SColor(0xFFFFFFFF)); + return; + } + + // Handle nodes + // See also CItemDefManager::createClientCached() + if (def.type == ITEM_NODE) { + if (f.mesh_ptr[0]) { + // e.g. mesh nodes and nodeboxes + mesh = cloneMesh(f.mesh_ptr[0]); + postProcessNodeMesh(mesh, f, m_enable_shaders, true, + &m_material_type, &m_colors); + changeToMesh(mesh); + mesh->drop(); + // mesh is pre-scaled by BS * f->visual_scale + m_meshnode->setScale( + def.wield_scale * WIELD_SCALE_FACTOR + / (BS * f.visual_scale)); + } else { + switch (f.drawtype) { + case NDT_AIRLIKE: { + changeToMesh(nullptr); + break; + } + case NDT_PLANTLIKE: { + setExtruded(tsrc->getTextureName(f.tiles[0].layers[0].texture_id), + tsrc->getTextureName(f.tiles[0].layers[1].texture_id), + def.wield_scale, tsrc, + f.tiles[0].layers[0].animation_frame_count); + // Add color + const TileLayer &l0 = f.tiles[0].layers[0]; + m_colors.emplace_back(l0.has_color, l0.color); + const TileLayer &l1 = f.tiles[0].layers[1]; + m_colors.emplace_back(l1.has_color, l1.color); + break; + } + case NDT_PLANTLIKE_ROOTED: { + setExtruded(tsrc->getTextureName(f.special_tiles[0].layers[0].texture_id), + "", def.wield_scale, tsrc, + f.special_tiles[0].layers[0].animation_frame_count); + // Add color + const TileLayer &l0 = f.special_tiles[0].layers[0]; + m_colors.emplace_back(l0.has_color, l0.color); + break; + } + case NDT_NORMAL: + case NDT_ALLFACES: + case NDT_LIQUID: + case NDT_FLOWINGLIQUID: { + setCube(f, def.wield_scale); + break; + } + default: { + mesh = createSpecialNodeMesh(client, id, &m_colors); + changeToMesh(mesh); + mesh->drop(); + m_meshnode->setScale( + def.wield_scale * WIELD_SCALE_FACTOR + / (BS * f.visual_scale)); + } + } + } + u32 material_count = m_meshnode->getMaterialCount(); + for (u32 i = 0; i < material_count; ++i) { + video::SMaterial &material = m_meshnode->getMaterial(i); + material.MaterialType = m_material_type; + material.setFlag(video::EMF_BACK_FACE_CULLING, true); + material.setFlag(video::EMF_BILINEAR_FILTER, m_bilinear_filter); + material.setFlag(video::EMF_TRILINEAR_FILTER, m_trilinear_filter); + } + return; + } + else if (!def.inventory_image.empty()) { + setExtruded(def.inventory_image, def.inventory_overlay, def.wield_scale, + tsrc, 1); + m_colors.emplace_back(); + // overlay is white, if present + m_colors.emplace_back(true, video::SColor(0xFFFFFFFF)); + return; + } + + // no wield mesh found + changeToMesh(nullptr); +} + +void WieldMeshSceneNode::setColor(video::SColor c) +{ + assert(!m_lighting); + scene::IMesh *mesh = m_meshnode->getMesh(); + if (!mesh) + return; + + u8 red = c.getRed(); + u8 green = c.getGreen(); + u8 blue = c.getBlue(); + u32 mc = mesh->getMeshBufferCount(); + for (u32 j = 0; j < mc; j++) { + video::SColor bc(m_base_color); + if ((m_colors.size() > j) && (m_colors[j].override_base)) + bc = m_colors[j].color; + video::SColor buffercolor(255, + bc.getRed() * red / 255, + bc.getGreen() * green / 255, + bc.getBlue() * blue / 255); + scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); + colorizeMeshBuffer(buf, &buffercolor); + } +} + +void WieldMeshSceneNode::render() +{ + // note: if this method is changed to actually do something, + // you probably should implement OnRegisterSceneNode as well +} + +void WieldMeshSceneNode::changeToMesh(scene::IMesh *mesh) +{ + if (!mesh) { + scene::IMesh *dummymesh = g_extrusion_mesh_cache->createCube(); + m_meshnode->setVisible(false); + m_meshnode->setMesh(dummymesh); + dummymesh->drop(); // m_meshnode grabbed it + } else { + m_meshnode->setMesh(mesh); + } + + m_meshnode->setMaterialFlag(video::EMF_LIGHTING, m_lighting); + // need to normalize normals when lighting is enabled (because of setScale()) + m_meshnode->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, m_lighting); + m_meshnode->setVisible(true); +} + +void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result) +{ + ITextureSource *tsrc = client->getTextureSource(); + IItemDefManager *idef = client->getItemDefManager(); + const NodeDefManager *ndef = client->getNodeDefManager(); + const ItemDefinition &def = item.getDefinition(idef); + const ContentFeatures &f = ndef->get(def.name); + content_t id = ndef->getId(def.name); + + FATAL_ERROR_IF(!g_extrusion_mesh_cache, "Extrusion mesh cache is not yet initialized"); + + scene::SMesh *mesh = nullptr; + + // Shading is on by default + result->needs_shading = true; + + // If inventory_image is defined, it overrides everything else + if (!def.inventory_image.empty()) { + mesh = getExtrudedMesh(tsrc, def.inventory_image, + def.inventory_overlay); + result->buffer_colors.emplace_back(); + // overlay is white, if present + result->buffer_colors.emplace_back(true, video::SColor(0xFFFFFFFF)); + // Items with inventory images do not need shading + result->needs_shading = false; + } else if (def.type == ITEM_NODE) { + if (f.mesh_ptr[0]) { + mesh = cloneMesh(f.mesh_ptr[0]); + scaleMesh(mesh, v3f(0.12, 0.12, 0.12)); + postProcessNodeMesh(mesh, f, false, false, nullptr, + &result->buffer_colors); + } else { + switch (f.drawtype) { + case NDT_PLANTLIKE: { + mesh = getExtrudedMesh(tsrc, + tsrc->getTextureName(f.tiles[0].layers[0].texture_id), + tsrc->getTextureName(f.tiles[0].layers[1].texture_id)); + // Add color + const TileLayer &l0 = f.tiles[0].layers[0]; + result->buffer_colors.emplace_back(l0.has_color, l0.color); + const TileLayer &l1 = f.tiles[0].layers[1]; + result->buffer_colors.emplace_back(l1.has_color, l1.color); + break; + } + case NDT_PLANTLIKE_ROOTED: { + mesh = getExtrudedMesh(tsrc, + tsrc->getTextureName(f.special_tiles[0].layers[0].texture_id), ""); + // Add color + const TileLayer &l0 = f.special_tiles[0].layers[0]; + result->buffer_colors.emplace_back(l0.has_color, l0.color); + break; + } + case NDT_NORMAL: + case NDT_ALLFACES: + case NDT_LIQUID: + case NDT_FLOWINGLIQUID: { + scene::IMesh *cube = g_extrusion_mesh_cache->createCube(); + mesh = cloneMesh(cube); + cube->drop(); + scaleMesh(mesh, v3f(1.2, 1.2, 1.2)); + // add overlays + postProcessNodeMesh(mesh, f, false, false, nullptr, + &result->buffer_colors); + break; + } + default: { + mesh = createSpecialNodeMesh(client, id, &result->buffer_colors); + scaleMesh(mesh, v3f(0.12, 0.12, 0.12)); + } + } + } + + u32 mc = mesh->getMeshBufferCount(); + for (u32 i = 0; i < mc; ++i) { + scene::IMeshBuffer *buf = mesh->getMeshBuffer(i); + video::SMaterial &material = buf->getMaterial(); + material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; + material.setFlag(video::EMF_BILINEAR_FILTER, false); + material.setFlag(video::EMF_TRILINEAR_FILTER, false); + material.setFlag(video::EMF_BACK_FACE_CULLING, true); + material.setFlag(video::EMF_LIGHTING, false); + } + + rotateMeshXZby(mesh, -45); + rotateMeshYZby(mesh, -30); + } + result->mesh = mesh; +} + + + +scene::SMesh *getExtrudedMesh(ITextureSource *tsrc, + const std::string &imagename, const std::string &overlay_name) +{ + // check textures + video::ITexture *texture = tsrc->getTextureForMesh(imagename); + if (!texture) { + return NULL; + } + video::ITexture *overlay_texture = + (overlay_name.empty()) ? NULL : tsrc->getTexture(overlay_name); + + // get mesh + core::dimension2d dim = texture->getSize(); + scene::IMesh *original = g_extrusion_mesh_cache->create(dim); + scene::SMesh *mesh = cloneMesh(original); + original->drop(); + + //set texture + mesh->getMeshBuffer(0)->getMaterial().setTexture(0, + tsrc->getTexture(imagename)); + if (overlay_texture) { + scene::IMeshBuffer *copy = cloneMeshBuffer(mesh->getMeshBuffer(0)); + copy->getMaterial().setTexture(0, overlay_texture); + mesh->addMeshBuffer(copy); + copy->drop(); + } + // Customize materials + for (u32 layer = 0; layer < mesh->getMeshBufferCount(); layer++) { + video::SMaterial &material = mesh->getMeshBuffer(layer)->getMaterial(); + material.TextureLayer[0].TextureWrapU = video::ETC_CLAMP_TO_EDGE; + material.TextureLayer[0].TextureWrapV = video::ETC_CLAMP_TO_EDGE; + material.setFlag(video::EMF_BILINEAR_FILTER, false); + material.setFlag(video::EMF_TRILINEAR_FILTER, false); + material.setFlag(video::EMF_BACK_FACE_CULLING, true); + material.setFlag(video::EMF_LIGHTING, false); + material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; + } + scaleMesh(mesh, v3f(2.0, 2.0, 2.0)); + + return mesh; +} + +void postProcessNodeMesh(scene::SMesh *mesh, const ContentFeatures &f, + bool use_shaders, bool set_material, const video::E_MATERIAL_TYPE *mattype, + std::vector *colors, bool apply_scale) +{ + u32 mc = mesh->getMeshBufferCount(); + // Allocate colors for existing buffers + colors->clear(); + for (u32 i = 0; i < mc; ++i) + colors->push_back(ItemPartColor()); + + for (u32 i = 0; i < mc; ++i) { + const TileSpec *tile = &(f.tiles[i]); + scene::IMeshBuffer *buf = mesh->getMeshBuffer(i); + for (int layernum = 0; layernum < MAX_TILE_LAYERS; layernum++) { + const TileLayer *layer = &tile->layers[layernum]; + if (layer->texture_id == 0) + continue; + if (layernum != 0) { + scene::IMeshBuffer *copy = cloneMeshBuffer(buf); + copy->getMaterial() = buf->getMaterial(); + mesh->addMeshBuffer(copy); + copy->drop(); + buf = copy; + colors->push_back( + ItemPartColor(layer->has_color, layer->color)); + } else { + (*colors)[i] = ItemPartColor(layer->has_color, layer->color); + } + video::SMaterial &material = buf->getMaterial(); + if (set_material) + layer->applyMaterialOptions(material); + if (mattype) { + material.MaterialType = *mattype; + } + if (layer->animation_frame_count > 1) { + const FrameSpec &animation_frame = (*layer->frames)[0]; + material.setTexture(0, animation_frame.texture); + } else { + material.setTexture(0, layer->texture); + } + if (use_shaders) { + if (layer->normal_texture) { + if (layer->animation_frame_count > 1) { + const FrameSpec &animation_frame = (*layer->frames)[0]; + material.setTexture(1, animation_frame.normal_texture); + } else + material.setTexture(1, layer->normal_texture); + } + material.setTexture(2, layer->flags_texture); + } + if (apply_scale && tile->world_aligned) { + u32 n = buf->getVertexCount(); + for (u32 k = 0; k != n; ++k) + buf->getTCoords(k) /= layer->scale; + } + } + } +} diff --git a/src/client/wieldmesh.h b/src/client/wieldmesh.h new file mode 100644 index 000000000..0908d3ac2 --- /dev/null +++ b/src/client/wieldmesh.h @@ -0,0 +1,140 @@ +/* +Minetest +Copyright (C) 2010-2014 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include +#include +#include "irrlichttypes_extrabloated.h" + +struct ItemStack; +class Client; +class ITextureSource; +struct ContentFeatures; + +/*! + * Holds color information of an item mesh's buffer. + */ +struct ItemPartColor +{ + /*! + * If this is false, the global base color of the item + * will be used instead of the specific color of the + * buffer. + */ + bool override_base = false; + /*! + * The color of the buffer. + */ + video::SColor color = 0; + + ItemPartColor() = default; + + ItemPartColor(bool override, video::SColor color) : + override_base(override), color(color) + { + } +}; + +struct ItemMesh +{ + scene::IMesh *mesh = nullptr; + /*! + * Stores the color of each mesh buffer. + */ + std::vector buffer_colors; + /*! + * If false, all faces of the item should have the same brightness. + * Disables shading based on normal vectors. + */ + bool needs_shading = true; + + ItemMesh() = default; +}; + +/* + Wield item scene node, renders the wield mesh of some item +*/ +class WieldMeshSceneNode : public scene::ISceneNode +{ +public: + WieldMeshSceneNode(scene::ISceneManager *mgr, s32 id = -1, bool lighting = false); + virtual ~WieldMeshSceneNode(); + + void setCube(const ContentFeatures &f, v3f wield_scale); + void setExtruded(const std::string &imagename, const std::string &overlay_image, + v3f wield_scale, ITextureSource *tsrc, u8 num_frames); + void setItem(const ItemStack &item, Client *client); + + // Sets the vertex color of the wield mesh. + // Must only be used if the constructor was called with lighting = false + void setColor(video::SColor color); + + scene::IMesh *getMesh() { return m_meshnode->getMesh(); } + + virtual void render(); + + virtual const aabb3f &getBoundingBox() const { return m_bounding_box; } + +private: + void changeToMesh(scene::IMesh *mesh); + + // Child scene node with the current wield mesh + scene::IMeshSceneNode *m_meshnode = nullptr; + video::E_MATERIAL_TYPE m_material_type; + + // True if EMF_LIGHTING should be enabled. + bool m_lighting; + + bool m_enable_shaders; + bool m_anisotropic_filter; + bool m_bilinear_filter; + bool m_trilinear_filter; + /*! + * Stores the colors of the mesh's mesh buffers. + * This does not include lighting. + */ + std::vector m_colors; + /*! + * The base color of this mesh. This is the default + * for all mesh buffers. + */ + video::SColor m_base_color; + + // Bounding box culling is disabled for this type of scene node, + // so this variable is just required so we can implement + // getBoundingBox() and is set to an empty box. + aabb3f m_bounding_box; +}; + +void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result); + +scene::SMesh *getExtrudedMesh(ITextureSource *tsrc, const std::string &imagename, + const std::string &overlay_name); + +/*! + * Applies overlays, textures and optionally materials to the given mesh and + * extracts tile colors for colorization. + * \param mattype overrides the buffer's material type, but can also + * be NULL to leave the original material. + * \param colors returns the colors of the mesh buffers in the mesh. + */ +void postProcessNodeMesh(scene::SMesh *mesh, const ContentFeatures &f, bool use_shaders, + bool set_material, const video::E_MATERIAL_TYPE *mattype, + std::vector *colors, bool apply_scale = false); diff --git a/src/clientenvironment.cpp b/src/clientenvironment.cpp deleted file mode 100644 index e2f24aaa3..000000000 --- a/src/clientenvironment.cpp +++ /dev/null @@ -1,540 +0,0 @@ -/* -Minetest -Copyright (C) 2010-2017 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include "util/serialize.h" -#include "util/pointedthing.h" -#include "client.h" -#include "clientenvironment.h" -#include "clientsimpleobject.h" -#include "clientmap.h" -#include "scripting_client.h" -#include "mapblock_mesh.h" -#include "event.h" -#include "collision.h" -#include "nodedef.h" -#include "profiler.h" -#include "raycast.h" -#include "voxelalgorithms.h" -#include "settings.h" -#include "content_cao.h" -#include -#include "client/renderingengine.h" - -/* - ClientEnvironment -*/ - -ClientEnvironment::ClientEnvironment(ClientMap *map, - ITextureSource *texturesource, Client *client): - Environment(client), - m_map(map), - m_texturesource(texturesource), - m_client(client) -{ - char zero = 0; - memset(attachement_parent_ids, zero, sizeof(attachement_parent_ids)); -} - -ClientEnvironment::~ClientEnvironment() -{ - // delete active objects - for (auto &active_object : m_active_objects) { - delete active_object.second; - } - - for (auto &simple_object : m_simple_objects) { - delete simple_object; - } - - // Drop/delete map - m_map->drop(); - - delete m_local_player; -} - -Map & ClientEnvironment::getMap() -{ - return *m_map; -} - -ClientMap & ClientEnvironment::getClientMap() -{ - return *m_map; -} - -void ClientEnvironment::setLocalPlayer(LocalPlayer *player) -{ - /* - It is a failure if already is a local player - */ - FATAL_ERROR_IF(m_local_player != NULL, - "Local player already allocated"); - - m_local_player = player; -} - -void ClientEnvironment::step(float dtime) -{ - /* Step time of day */ - stepTimeOfDay(dtime); - - // Get some settings - bool fly_allowed = m_client->checkLocalPrivilege("fly"); - bool free_move = fly_allowed && g_settings->getBool("free_move"); - - // Get local player - LocalPlayer *lplayer = getLocalPlayer(); - assert(lplayer); - // collision info queue - std::vector player_collisions; - - /* - Get the speed the player is going - */ - bool is_climbing = lplayer->is_climbing; - - f32 player_speed = lplayer->getSpeed().getLength(); - - /* - Maximum position increment - */ - //f32 position_max_increment = 0.05*BS; - f32 position_max_increment = 0.1*BS; - - // Maximum time increment (for collision detection etc) - // time = distance / speed - f32 dtime_max_increment = 1; - if(player_speed > 0.001) - dtime_max_increment = position_max_increment / player_speed; - - // Maximum time increment is 10ms or lower - if(dtime_max_increment > 0.01) - dtime_max_increment = 0.01; - - // Don't allow overly huge dtime - if(dtime > 0.5) - dtime = 0.5; - - f32 dtime_downcount = dtime; - - /* - Stuff that has a maximum time increment - */ - - u32 loopcount = 0; - do - { - loopcount++; - - f32 dtime_part; - if(dtime_downcount > dtime_max_increment) - { - dtime_part = dtime_max_increment; - dtime_downcount -= dtime_part; - } - else - { - dtime_part = dtime_downcount; - /* - Setting this to 0 (no -=dtime_part) disables an infinite loop - when dtime_part is so small that dtime_downcount -= dtime_part - does nothing - */ - dtime_downcount = 0; - } - - /* - Handle local player - */ - - { - // Apply physics - if (!free_move && !is_climbing) { - // Gravity - v3f speed = lplayer->getSpeed(); - if (!lplayer->in_liquid) - speed.Y -= lplayer->movement_gravity * - lplayer->physics_override_gravity * dtime_part * 2.0f; - - // Liquid floating / sinking - if (lplayer->in_liquid && !lplayer->swimming_vertical) - speed.Y -= lplayer->movement_liquid_sink * dtime_part * 2.0f; - - // Liquid resistance - if (lplayer->in_liquid_stable || lplayer->in_liquid) { - // How much the node's viscosity blocks movement, ranges - // between 0 and 1. Should match the scale at which viscosity - // increase affects other liquid attributes. - static const f32 viscosity_factor = 0.3f; - - v3f d_wanted = -speed / lplayer->movement_liquid_fluidity; - f32 dl = d_wanted.getLength(); - if (dl > lplayer->movement_liquid_fluidity_smooth) - dl = lplayer->movement_liquid_fluidity_smooth; - - dl *= (lplayer->liquid_viscosity * viscosity_factor) + - (1 - viscosity_factor); - v3f d = d_wanted.normalize() * (dl * dtime_part * 100.0f); - speed += d; - } - - lplayer->setSpeed(speed); - } - - /* - Move the lplayer. - This also does collision detection. - */ - lplayer->move(dtime_part, this, position_max_increment, - &player_collisions); - } - } while (dtime_downcount > 0.001); - - bool player_immortal = lplayer->getCAO() && lplayer->getCAO()->isImmortal(); - - for (const CollisionInfo &info : player_collisions) { - v3f speed_diff = info.new_speed - info.old_speed;; - // Handle only fall damage - // (because otherwise walking against something in fast_move kills you) - if (speed_diff.Y < 0 || info.old_speed.Y >= 0) - continue; - // Get rid of other components - speed_diff.X = 0; - speed_diff.Z = 0; - f32 pre_factor = 1; // 1 hp per node/s - f32 tolerance = BS*14; // 5 without damage - f32 post_factor = 1; // 1 hp per node/s - if (info.type == COLLISION_NODE) { - const ContentFeatures &f = m_client->ndef()-> - get(m_map->getNodeNoEx(info.node_p)); - // Determine fall damage multiplier - int addp = itemgroup_get(f.groups, "fall_damage_add_percent"); - pre_factor = 1.0f + (float)addp / 100.0f; - } - float speed = pre_factor * speed_diff.getLength(); - if (speed > tolerance && !player_immortal) { - f32 damage_f = (speed - tolerance) / BS * post_factor; - u8 damage = (u8)MYMIN(damage_f + 0.5, 255); - if (damage != 0) { - damageLocalPlayer(damage, true); - m_client->getEventManager()->put( - new SimpleTriggerEvent(MtEvent::PLAYER_FALLING_DAMAGE)); - } - } - } - - if (m_client->modsLoaded()) - m_script->environment_step(dtime); - - // Update lighting on local player (used for wield item) - u32 day_night_ratio = getDayNightRatio(); - { - // Get node at head - - // On InvalidPositionException, use this as default - // (day: LIGHT_SUN, night: 0) - MapNode node_at_lplayer(CONTENT_AIR, 0x0f, 0); - - v3s16 p = lplayer->getLightPosition(); - node_at_lplayer = m_map->getNodeNoEx(p); - - u16 light = getInteriorLight(node_at_lplayer, 0, m_client->ndef()); - final_color_blend(&lplayer->light_color, light, day_night_ratio); - } - - /* - Step active objects and update lighting of them - */ - - g_profiler->avg("CEnv: num of objects", m_active_objects.size()); - bool update_lighting = m_active_object_light_update_interval.step(dtime, 0.21); - for (auto &ao_it : m_active_objects) { - ClientActiveObject* obj = ao_it.second; - // Step object - obj->step(dtime, this); - - if (update_lighting) { - // Update lighting - u8 light = 0; - bool pos_ok; - - // Get node at head - v3s16 p = obj->getLightPosition(); - MapNode n = m_map->getNodeNoEx(p, &pos_ok); - if (pos_ok) - light = n.getLightBlend(day_night_ratio, m_client->ndef()); - else - light = blend_light(day_night_ratio, LIGHT_SUN, 0); - - obj->updateLight(light); - } - } - - /* - Step and handle simple objects - */ - g_profiler->avg("CEnv: num of simple objects", m_simple_objects.size()); - for (auto i = m_simple_objects.begin(); i != m_simple_objects.end();) { - auto cur = i; - ClientSimpleObject *simple = *cur; - - simple->step(dtime); - if(simple->m_to_be_removed) { - delete simple; - i = m_simple_objects.erase(cur); - } - else { - ++i; - } - } -} - -void ClientEnvironment::addSimpleObject(ClientSimpleObject *simple) -{ - m_simple_objects.push_back(simple); -} - -GenericCAO* ClientEnvironment::getGenericCAO(u16 id) -{ - ClientActiveObject *obj = getActiveObject(id); - if (obj && obj->getType() == ACTIVEOBJECT_TYPE_GENERIC) - return (GenericCAO*) obj; - - return NULL; -} - -ClientActiveObject* ClientEnvironment::getActiveObject(u16 id) -{ - auto n = m_active_objects.find(id); - if (n == m_active_objects.end()) - return NULL; - return n->second; -} - -bool isFreeClientActiveObjectId(const u16 id, - ClientActiveObjectMap &objects) -{ - return id != 0 && objects.find(id) == objects.end(); - -} - -u16 getFreeClientActiveObjectId(ClientActiveObjectMap &objects) -{ - //try to reuse id's as late as possible - static u16 last_used_id = 0; - u16 startid = last_used_id; - for(;;) { - last_used_id ++; - if (isFreeClientActiveObjectId(last_used_id, objects)) - return last_used_id; - - if (last_used_id == startid) - return 0; - } -} - -u16 ClientEnvironment::addActiveObject(ClientActiveObject *object) -{ - assert(object); // Pre-condition - if(object->getId() == 0) - { - u16 new_id = getFreeClientActiveObjectId(m_active_objects); - if(new_id == 0) - { - infostream<<"ClientEnvironment::addActiveObject(): " - <<"no free ids available"<setId(new_id); - } - if (!isFreeClientActiveObjectId(object->getId(), m_active_objects)) { - infostream<<"ClientEnvironment::addActiveObject(): " - <<"id is not free ("<getId()<<")"<getId()] = object; - object->addToScene(m_texturesource); - { // Update lighting immediately - u8 light = 0; - bool pos_ok; - - // Get node at head - v3s16 p = object->getLightPosition(); - MapNode n = m_map->getNodeNoEx(p, &pos_ok); - if (pos_ok) - light = n.getLightBlend(getDayNightRatio(), m_client->ndef()); - else - light = blend_light(getDayNightRatio(), LIGHT_SUN, 0); - - object->updateLight(light); - } - return object->getId(); -} - -void ClientEnvironment::addActiveObject(u16 id, u8 type, - const std::string &init_data) -{ - ClientActiveObject* obj = - ClientActiveObject::create((ActiveObjectType) type, m_client, this); - if(obj == NULL) - { - infostream<<"ClientEnvironment::addActiveObject(): " - <<"id="<setId(id); - - try - { - obj->initialize(init_data); - } - catch(SerializationError &e) - { - errorstream<<"ClientEnvironment::addActiveObject():" - <<" id="<removeFromScene(true); - delete obj; - m_active_objects.erase(id); -} - -void ClientEnvironment::processActiveObjectMessage(u16 id, const std::string &data) -{ - ClientActiveObject *obj = getActiveObject(id); - if (obj == NULL) { - infostream << "ClientEnvironment::processActiveObjectMessage():" - << " got message for id=" << id << ", which doesn't exist." - << std::endl; - return; - } - - try { - obj->processMessage(data); - } catch (SerializationError &e) { - errorstream<<"ClientEnvironment::processActiveObjectMessage():" - << " id=" << id << " type=" << obj->getType() - << " SerializationError in processMessage(): " << e.what() - << std::endl; - } -} - -/* - Callbacks for activeobjects -*/ - -void ClientEnvironment::damageLocalPlayer(u8 damage, bool handle_hp) -{ - LocalPlayer *lplayer = getLocalPlayer(); - assert(lplayer); - - if (handle_hp) { - if (lplayer->hp > damage) - lplayer->hp -= damage; - else - lplayer->hp = 0; - } - - ClientEnvEvent event; - event.type = CEE_PLAYER_DAMAGE; - event.player_damage.amount = damage; - event.player_damage.send_to_server = handle_hp; - m_client_event_queue.push(event); -} - -/* - Client likes to call these -*/ - -void ClientEnvironment::getActiveObjects(v3f origin, f32 max_d, - std::vector &dest) -{ - for (auto &ao_it : m_active_objects) { - ClientActiveObject* obj = ao_it.second; - - f32 d = (obj->getPosition() - origin).getLength(); - - if (d > max_d) - continue; - - dest.emplace_back(obj, d); - } -} - -ClientEnvEvent ClientEnvironment::getClientEnvEvent() -{ - FATAL_ERROR_IF(m_client_event_queue.empty(), - "ClientEnvironment::getClientEnvEvent(): queue is empty"); - - ClientEnvEvent event = m_client_event_queue.front(); - m_client_event_queue.pop(); - return event; -} - -void ClientEnvironment::getSelectedActiveObjects( - const core::line3d &shootline_on_map, - std::vector &objects) -{ - std::vector allObjects; - getActiveObjects(shootline_on_map.start, - shootline_on_map.getLength() + 10.0f, allObjects); - const v3f line_vector = shootline_on_map.getVector(); - - for (const auto &allObject : allObjects) { - ClientActiveObject *obj = allObject.obj; - aabb3f selection_box; - if (!obj->getSelectionBox(&selection_box)) - continue; - - const v3f &pos = obj->getPosition(); - aabb3f offsetted_box(selection_box.MinEdge + pos, - selection_box.MaxEdge + pos); - - v3f current_intersection; - v3s16 current_normal; - if (boxLineCollision(offsetted_box, shootline_on_map.start, line_vector, - ¤t_intersection, ¤t_normal)) { - objects.emplace_back((s16) obj->getId(), current_intersection, current_normal, - (current_intersection - shootline_on_map.start).getLengthSQ()); - } - } -} diff --git a/src/clientenvironment.h b/src/clientenvironment.h deleted file mode 100644 index 606070e3a..000000000 --- a/src/clientenvironment.h +++ /dev/null @@ -1,151 +0,0 @@ -/* -Minetest -Copyright (C) 2010-2017 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#pragma once - -#include "environment.h" -#include -#include "clientobject.h" -#include "util/numeric.h" - -class ClientSimpleObject; -class ClientMap; -class ClientScripting; -class ClientActiveObject; -class GenericCAO; -class LocalPlayer; - -/* - The client-side environment. - - This is not thread-safe. - Must be called from main (irrlicht) thread (uses the SceneManager) - Client uses an environment mutex. -*/ - -enum ClientEnvEventType -{ - CEE_NONE, - CEE_PLAYER_DAMAGE -}; - -struct ClientEnvEvent -{ - ClientEnvEventType type; - union { - //struct{ - //} none; - struct{ - u8 amount; - bool send_to_server; - } player_damage; - }; -}; - -typedef std::unordered_map ClientActiveObjectMap; -class ClientEnvironment : public Environment -{ -public: - ClientEnvironment(ClientMap *map, ITextureSource *texturesource, Client *client); - ~ClientEnvironment(); - - Map & getMap(); - ClientMap & getClientMap(); - - Client *getGameDef() { return m_client; } - void setScript(ClientScripting *script) { m_script = script; } - - void step(f32 dtime); - - virtual void setLocalPlayer(LocalPlayer *player); - LocalPlayer *getLocalPlayer() const { return m_local_player; } - - /* - ClientSimpleObjects - */ - - void addSimpleObject(ClientSimpleObject *simple); - - /* - ActiveObjects - */ - - GenericCAO* getGenericCAO(u16 id); - ClientActiveObject* getActiveObject(u16 id); - - /* - Adds an active object to the environment. - Environment handles deletion of object. - Object may be deleted by environment immediately. - If id of object is 0, assigns a free id to it. - Returns the id of the object. - Returns 0 if not added and thus deleted. - */ - u16 addActiveObject(ClientActiveObject *object); - - void addActiveObject(u16 id, u8 type, const std::string &init_data); - void removeActiveObject(u16 id); - - void processActiveObjectMessage(u16 id, const std::string &data); - - /* - Callbacks for activeobjects - */ - - void damageLocalPlayer(u8 damage, bool handle_hp=true); - - /* - Client likes to call these - */ - - // Get all nearby objects - void getActiveObjects(v3f origin, f32 max_d, - std::vector &dest); - - bool hasClientEnvEvents() const { return !m_client_event_queue.empty(); } - - // Get event from queue. If queue is empty, it triggers an assertion failure. - ClientEnvEvent getClientEnvEvent(); - - virtual void getSelectedActiveObjects( - const core::line3d &shootline_on_map, - std::vector &objects - ); - - u16 attachement_parent_ids[USHRT_MAX + 1]; - - const std::list &getPlayerNames() { return m_player_names; } - void addPlayerName(const std::string &name) { m_player_names.push_back(name); } - void removePlayerName(const std::string &name) { m_player_names.remove(name); } - void updateCameraOffset(const v3s16 &camera_offset) - { m_camera_offset = camera_offset; } - v3s16 getCameraOffset() const { return m_camera_offset; } -private: - ClientMap *m_map; - LocalPlayer *m_local_player = nullptr; - ITextureSource *m_texturesource; - Client *m_client; - ClientScripting *m_script = nullptr; - ClientActiveObjectMap m_active_objects; - std::vector m_simple_objects; - std::queue m_client_event_queue; - IntervalLimiter m_active_object_light_update_interval; - std::list m_player_names; - v3s16 m_camera_offset; -}; diff --git a/src/clientmap.cpp b/src/clientmap.cpp deleted file mode 100644 index 969c55539..000000000 --- a/src/clientmap.cpp +++ /dev/null @@ -1,671 +0,0 @@ -/* -Minetest -Copyright (C) 2010-2013 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include "clientmap.h" -#include "client.h" -#include "mapblock_mesh.h" -#include -#include -#include "mapsector.h" -#include "mapblock.h" -#include "profiler.h" -#include "settings.h" -#include "camera.h" // CameraModes -#include "util/basic_macros.h" -#include -#include "client/renderingengine.h" - -ClientMap::ClientMap( - Client *client, - MapDrawControl &control, - s32 id -): - Map(dout_client, client), - scene::ISceneNode(RenderingEngine::get_scene_manager()->getRootSceneNode(), - RenderingEngine::get_scene_manager(), id), - m_client(client), - m_control(control) -{ - m_box = aabb3f(-BS*1000000,-BS*1000000,-BS*1000000, - BS*1000000,BS*1000000,BS*1000000); - - /* TODO: Add a callback function so these can be updated when a setting - * changes. At this point in time it doesn't matter (e.g. /set - * is documented to change server settings only) - * - * TODO: Local caching of settings is not optimal and should at some stage - * be updated to use a global settings object for getting thse values - * (as opposed to the this local caching). This can be addressed in - * a later release. - */ - 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"); - -} - -MapSector * ClientMap::emergeSector(v2s16 p2d) -{ - // Check that it doesn't exist already - try { - return getSectorNoGenerate(p2d); - } catch(InvalidPositionException &e) { - } - - // Create a sector - MapSector *sector = new MapSector(this, p2d, m_gamedef); - m_sectors[p2d] = sector; - - return sector; -} - -void ClientMap::OnRegisterSceneNode() -{ - if(IsVisible) - { - SceneManager->registerNodeForRendering(this, scene::ESNRP_SOLID); - SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT); - } - - ISceneNode::OnRegisterSceneNode(); -} - -void ClientMap::getBlocksInViewRange(v3s16 cam_pos_nodes, - v3s16 *p_blocks_min, v3s16 *p_blocks_max) -{ - v3s16 box_nodes_d = m_control.wanted_range * v3s16(1, 1, 1); - // Define p_nodes_min/max as v3s32 because 'cam_pos_nodes -/+ box_nodes_d' - // can exceed the range of v3s16 when a large view range is used near the - // world edges. - v3s32 p_nodes_min( - cam_pos_nodes.X - box_nodes_d.X, - cam_pos_nodes.Y - box_nodes_d.Y, - cam_pos_nodes.Z - box_nodes_d.Z); - v3s32 p_nodes_max( - cam_pos_nodes.X + box_nodes_d.X, - cam_pos_nodes.Y + box_nodes_d.Y, - cam_pos_nodes.Z + box_nodes_d.Z); - // Take a fair amount as we will be dropping more out later - // Umm... these additions are a bit strange but they are needed. - *p_blocks_min = v3s16( - p_nodes_min.X / MAP_BLOCKSIZE - 3, - p_nodes_min.Y / MAP_BLOCKSIZE - 3, - p_nodes_min.Z / MAP_BLOCKSIZE - 3); - *p_blocks_max = v3s16( - p_nodes_max.X / MAP_BLOCKSIZE + 1, - p_nodes_max.Y / MAP_BLOCKSIZE + 1, - p_nodes_max.Z / MAP_BLOCKSIZE + 1); -} - -void ClientMap::updateDrawList() -{ - ScopeProfiler sp(g_profiler, "CM::updateDrawList()", SPT_AVG); - g_profiler->add("CM::updateDrawList() count", 1); - - for (auto &i : m_drawlist) { - MapBlock *block = i.second; - block->refDrop(); - } - m_drawlist.clear(); - - v3f camera_position = m_camera_position; - v3f camera_direction = m_camera_direction; - f32 camera_fov = m_camera_fov; - - // Use a higher fov to accomodate faster camera movements. - // Blocks are cropped better when they are drawn. - // Or maybe they aren't? Well whatever. - camera_fov *= 1.2; - - v3s16 cam_pos_nodes = floatToInt(camera_position, BS); - v3s16 p_blocks_min; - v3s16 p_blocks_max; - getBlocksInViewRange(cam_pos_nodes, &p_blocks_min, &p_blocks_max); - - // Number of blocks in rendering range - u32 blocks_in_range = 0; - // Number of blocks occlusion culled - u32 blocks_occlusion_culled = 0; - // Number of blocks in rendering range but don't have a mesh - u32 blocks_in_range_without_mesh = 0; - // Blocks that had mesh that would have been drawn according to - // rendering range (if max blocks limit didn't kick in) - u32 blocks_would_have_drawn = 0; - // Blocks that were drawn and had a mesh - u32 blocks_drawn = 0; - // Blocks which had a corresponding meshbuffer for this pass - //u32 blocks_had_pass_meshbuf = 0; - // Blocks from which stuff was actually drawn - //u32 blocks_without_stuff = 0; - // Distance to farthest drawn block - float farthest_drawn = 0; - - // No occlusion culling when free_move is on and camera is - // inside ground - bool occlusion_culling_enabled = true; - if (g_settings->getBool("free_move")) { - MapNode n = getNodeNoEx(cam_pos_nodes); - if (n.getContent() == CONTENT_IGNORE || - m_nodedef->get(n).solidness == 2) - occlusion_culling_enabled = false; - } - - for (const auto §or_it : m_sectors) { - MapSector *sector = sector_it.second; - v2s16 sp = sector->getPos(); - - if (!m_control.range_all) { - if (sp.X < p_blocks_min.X || sp.X > p_blocks_max.X || - sp.Y < p_blocks_min.Z || sp.Y > p_blocks_max.Z) - continue; - } - - MapBlockVect sectorblocks; - sector->getBlocks(sectorblocks); - - /* - Loop through blocks in sector - */ - - u32 sector_blocks_drawn = 0; - - for (auto block : sectorblocks) { - /* - Compare block position to camera position, skip - if not seen on display - */ - - if (block->mesh) - block->mesh->updateCameraOffset(m_camera_offset); - - float range = 100000 * BS; - if (!m_control.range_all) - range = m_control.wanted_range * BS; - - float d = 0.0; - if (!isBlockInSight(block->getPos(), camera_position, - camera_direction, camera_fov, range, &d)) - continue; - - blocks_in_range++; - - /* - Ignore if mesh doesn't exist - */ - if (!block->mesh) { - blocks_in_range_without_mesh++; - continue; - } - - /* - Occlusion culling - */ - if (occlusion_culling_enabled && isBlockOccluded(block, cam_pos_nodes)) { - blocks_occlusion_culled++; - continue; - } - - // This block is in range. Reset usage timer. - block->resetUsageTimer(); - - // Limit block count in case of a sudden increase - blocks_would_have_drawn++; - if (blocks_drawn >= m_control.wanted_max_blocks && - !m_control.range_all && - d > m_control.wanted_range * BS) - continue; - - // Add to set - block->refGrab(); - m_drawlist[block->getPos()] = block; - - sector_blocks_drawn++; - blocks_drawn++; - if (d / BS > farthest_drawn) - farthest_drawn = d / BS; - - } // foreach sectorblocks - - if (sector_blocks_drawn != 0) - m_last_drawn_sectors.insert(sp); - } - - g_profiler->avg("CM: blocks in range", blocks_in_range); - g_profiler->avg("CM: blocks occlusion culled", blocks_occlusion_culled); - if (blocks_in_range != 0) - g_profiler->avg("CM: blocks in range without mesh (frac)", - (float)blocks_in_range_without_mesh / blocks_in_range); - g_profiler->avg("CM: blocks drawn", blocks_drawn); - g_profiler->avg("CM: farthest drawn", farthest_drawn); - g_profiler->avg("CM: wanted max blocks", m_control.wanted_max_blocks); -} - -struct MeshBufList -{ - video::SMaterial m; - std::vector bufs; -}; - -struct MeshBufListList -{ - /*! - * Stores the mesh buffers of the world. - * The array index is the material's layer. - * The vector part groups vertices by material. - */ - std::vector lists[MAX_TILE_LAYERS]; - - void clear() - { - for (auto &list : lists) - list.clear(); - } - - void add(scene::IMeshBuffer *buf, u8 layer) - { - // Append to the correct layer - std::vector &list = lists[layer]; - const video::SMaterial &m = buf->getMaterial(); - for (MeshBufList &l : list) { - // comparing a full material is quite expensive so we don't do it if - // not even first texture is equal - if (l.m.TextureLayer[0].Texture != m.TextureLayer[0].Texture) - continue; - - if (l.m == m) { - l.bufs.push_back(buf); - return; - } - } - MeshBufList l; - l.m = m; - l.bufs.push_back(buf); - list.push_back(l); - } -}; - -void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) -{ - bool is_transparent_pass = pass == scene::ESNRP_TRANSPARENT; - - std::string prefix; - if (pass == scene::ESNRP_SOLID) - prefix = "CM: solid: "; - else - prefix = "CM: transparent: "; - - /* - This is called two times per frame, reset on the non-transparent one - */ - if (pass == scene::ESNRP_SOLID) - m_last_drawn_sectors.clear(); - - /* - Get time for measuring timeout. - - Measuring time is very useful for long delays when the - machine is swapping a lot. - */ - std::time_t time1 = time(0); - - /* - Get animation parameters - */ - float animation_time = m_client->getAnimationTime(); - int crack = m_client->getCrackLevel(); - u32 daynight_ratio = m_client->getEnv().getDayNightRatio(); - - v3f camera_position = m_camera_position; - v3f camera_direction = m_camera_direction; - f32 camera_fov = m_camera_fov; - - /* - Get all blocks and draw all visible ones - */ - - u32 vertex_count = 0; - u32 meshbuffer_count = 0; - - // For limiting number of mesh animations per frame - u32 mesh_animate_count = 0; - u32 mesh_animate_count_far = 0; - - // Blocks that were drawn and had a mesh - u32 blocks_drawn = 0; - // Blocks which had a corresponding meshbuffer for this pass - u32 blocks_had_pass_meshbuf = 0; - // Blocks from which stuff was actually drawn - u32 blocks_without_stuff = 0; - - /* - Draw the selected MapBlocks - */ - - { - ScopeProfiler sp(g_profiler, prefix + "drawing blocks", SPT_AVG); - - MeshBufListList drawbufs; - - for (auto &i : m_drawlist) { - MapBlock *block = i.second; - - // If the mesh of the block happened to get deleted, ignore it - if (!block->mesh) - continue; - - float d = 0.0; - if (!isBlockInSight(block->getPos(), camera_position, - camera_direction, camera_fov, 100000 * BS, &d)) - continue; - - // Mesh animation - if (pass == scene::ESNRP_SOLID) { - //MutexAutoLock lock(block->mesh_mutex); - MapBlockMesh *mapBlockMesh = block->mesh; - assert(mapBlockMesh); - // Pretty random but this should work somewhat nicely - bool faraway = d >= BS * 50; - //bool faraway = d >= m_control.wanted_range * BS; - if (mapBlockMesh->isAnimationForced() || !faraway || - mesh_animate_count_far < (m_control.range_all ? 200 : 50)) { - bool animated = mapBlockMesh->animate(faraway, animation_time, - crack, daynight_ratio); - if (animated) - mesh_animate_count++; - if (animated && faraway) - mesh_animate_count_far++; - } else { - mapBlockMesh->decreaseAnimationForceTimer(); - } - } - - /* - Get the meshbuffers of the block - */ - { - //MutexAutoLock lock(block->mesh_mutex); - - MapBlockMesh *mapBlockMesh = block->mesh; - assert(mapBlockMesh); - - for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) { - scene::IMesh *mesh = mapBlockMesh->getMesh(layer); - assert(mesh); - - u32 c = mesh->getMeshBufferCount(); - for (u32 i = 0; i < c; i++) { - scene::IMeshBuffer *buf = mesh->getMeshBuffer(i); - - video::SMaterial& material = buf->getMaterial(); - video::IMaterialRenderer* rnd = - driver->getMaterialRenderer(material.MaterialType); - bool transparent = (rnd && rnd->isTransparent()); - if (transparent == is_transparent_pass) { - if (buf->getVertexCount() == 0) - errorstream << "Block [" << analyze_block(block) - << "] contains an empty meshbuf" << std::endl; - - material.setFlag(video::EMF_TRILINEAR_FILTER, - m_cache_trilinear_filter); - material.setFlag(video::EMF_BILINEAR_FILTER, - m_cache_bilinear_filter); - material.setFlag(video::EMF_ANISOTROPIC_FILTER, - m_cache_anistropic_filter); - material.setFlag(video::EMF_WIREFRAME, - m_control.show_wireframe); - - drawbufs.add(buf, layer); - } - } - } - } - } - - // Render all layers in order - for (auto &lists : drawbufs.lists) { - int timecheck_counter = 0; - for (MeshBufList &list : lists) { - timecheck_counter++; - if (timecheck_counter > 50) { - timecheck_counter = 0; - std::time_t time2 = time(0); - if (time2 > time1 + 4) { - infostream << "ClientMap::renderMap(): " - "Rendering takes ages, returning." - << std::endl; - return; - } - } - - driver->setMaterial(list.m); - - for (scene::IMeshBuffer *buf : list.bufs) { - driver->drawMeshBuffer(buf); - vertex_count += buf->getVertexCount(); - meshbuffer_count++; - } - } - } - } // ScopeProfiler - - // Log only on solid pass because values are the same - if (pass == scene::ESNRP_SOLID) { - g_profiler->avg("CM: animated meshes", mesh_animate_count); - g_profiler->avg("CM: animated meshes (far)", mesh_animate_count_far); - } - - g_profiler->avg(prefix + "vertices drawn", vertex_count); - if (blocks_had_pass_meshbuf != 0) - g_profiler->avg(prefix + "meshbuffers per block", - (float)meshbuffer_count / (float)blocks_had_pass_meshbuf); - if (blocks_drawn != 0) - g_profiler->avg(prefix + "empty blocks (frac)", - (float)blocks_without_stuff / blocks_drawn); -} - -static bool getVisibleBrightness(Map *map, const v3f &p0, v3f dir, float step, - float step_multiplier, float start_distance, float end_distance, - const NodeDefManager *ndef, u32 daylight_factor, float sunlight_min_d, - int *result, bool *sunlight_seen) -{ - int brightness_sum = 0; - int brightness_count = 0; - float distance = start_distance; - dir.normalize(); - v3f pf = p0; - pf += dir * distance; - int noncount = 0; - bool nonlight_seen = false; - bool allow_allowing_non_sunlight_propagates = false; - bool allow_non_sunlight_propagates = false; - // Check content nearly at camera position - { - v3s16 p = floatToInt(p0 /*+ dir * 3*BS*/, BS); - MapNode n = map->getNodeNoEx(p); - if(ndef->get(n).param_type == CPT_LIGHT && - !ndef->get(n).sunlight_propagates) - allow_allowing_non_sunlight_propagates = true; - } - // If would start at CONTENT_IGNORE, start closer - { - v3s16 p = floatToInt(pf, BS); - MapNode n = map->getNodeNoEx(p); - if(n.getContent() == CONTENT_IGNORE){ - float newd = 2*BS; - pf = p0 + dir * 2*newd; - distance = newd; - sunlight_min_d = 0; - } - } - for (int i=0; distance < end_distance; i++) { - pf += dir * step; - distance += step; - step *= step_multiplier; - - v3s16 p = floatToInt(pf, BS); - MapNode n = map->getNodeNoEx(p); - if (allow_allowing_non_sunlight_propagates && i == 0 && - ndef->get(n).param_type == CPT_LIGHT && - !ndef->get(n).sunlight_propagates) { - allow_non_sunlight_propagates = true; - } - - if (ndef->get(n).param_type != CPT_LIGHT || - (!ndef->get(n).sunlight_propagates && - !allow_non_sunlight_propagates)){ - nonlight_seen = true; - noncount++; - if(noncount >= 4) - break; - continue; - } - - if (distance >= sunlight_min_d && !*sunlight_seen && !nonlight_seen) - if (n.getLight(LIGHTBANK_DAY, ndef) == LIGHT_SUN) - *sunlight_seen = true; - noncount = 0; - brightness_sum += decode_light(n.getLightBlend(daylight_factor, ndef)); - brightness_count++; - } - *result = 0; - if(brightness_count == 0) - return false; - *result = brightness_sum / brightness_count; - /*std::cerr<<"Sampled "<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) - { - post_effect_color = video::SColor(255, 0, 0, 0); - } - if (post_effect_color.getAlpha() != 0) - { - // Draw a full-screen rectangle - video::IVideoDriver* driver = SceneManager->getVideoDriver(); - v2u32 ss = driver->getScreenSize(); - core::rect rect(0,0, ss.X, ss.Y); - driver->draw2DRectangle(post_effect_color, rect); - } -} - -void ClientMap::PrintInfo(std::ostream &out) -{ - out<<"ClientMap: "; -} - - diff --git a/src/clientmap.h b/src/clientmap.h deleted file mode 100644 index 8402bb00d..000000000 --- a/src/clientmap.h +++ /dev/null @@ -1,138 +0,0 @@ -/* -Minetest -Copyright (C) 2010-2013 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#pragma once - -#include "irrlichttypes_extrabloated.h" -#include "map.h" -#include "camera.h" -#include -#include - -struct MapDrawControl -{ - // Overrides limits by drawing everything - bool range_all = false; - // Wanted drawing range - float wanted_range = 0.0f; - // Maximum number of blocks to draw - u32 wanted_max_blocks = 0; - // show a wire frame for debugging - bool show_wireframe = false; -}; - -class Client; -class ITextureSource; - -/* - ClientMap - - This is the only map class that is able to render itself on screen. -*/ - -class ClientMap : public Map, public scene::ISceneNode -{ -public: - ClientMap( - Client *client, - MapDrawControl &control, - s32 id - ); - - virtual ~ClientMap() = default; - - s32 mapType() const - { - return MAPTYPE_CLIENT; - } - - void drop() - { - ISceneNode::drop(); - } - - void updateCamera(const v3f &pos, const v3f &dir, f32 fov, const v3s16 &offset) - { - m_camera_position = pos; - m_camera_direction = dir; - m_camera_fov = fov; - m_camera_offset = offset; - } - - /* - Forcefully get a sector from somewhere - */ - MapSector * emergeSector(v2s16 p); - - //void deSerializeSector(v2s16 p2d, std::istream &is); - - /* - ISceneNode methods - */ - - virtual void OnRegisterSceneNode(); - - virtual void render() - { - video::IVideoDriver* driver = SceneManager->getVideoDriver(); - driver->setTransform(video::ETS_WORLD, AbsoluteTransformation); - renderMap(driver, SceneManager->getSceneNodeRenderPass()); - } - - virtual const aabb3f &getBoundingBox() const - { - return m_box; - } - - void getBlocksInViewRange(v3s16 cam_pos_nodes, - v3s16 *p_blocks_min, v3s16 *p_blocks_max); - void updateDrawList(); - void renderMap(video::IVideoDriver* driver, s32 pass); - - int getBackgroundBrightness(float max_d, u32 daylight_factor, - int oldvalue, bool *sunlight_seen_result); - - void renderPostFx(CameraMode cam_mode); - - // For debug printing - virtual void PrintInfo(std::ostream &out); - - const MapDrawControl & getControl() const { return m_control; } - f32 getCameraFov() const { return m_camera_fov; } -private: - Client *m_client; - - aabb3f m_box = aabb3f(-BS * 1000000, -BS * 1000000, -BS * 1000000, - BS * 1000000, BS * 1000000, BS * 1000000); - - MapDrawControl &m_control; - - v3f m_camera_position = v3f(0,0,0); - v3f m_camera_direction = v3f(0,0,1); - f32 m_camera_fov = M_PI; - v3s16 m_camera_offset; - - std::map m_drawlist; - - std::set m_last_drawn_sectors; - - bool m_cache_trilinear_filter; - bool m_cache_bilinear_filter; - bool m_cache_anistropic_filter; -}; diff --git a/src/clientmedia.cpp b/src/clientmedia.cpp deleted file mode 100644 index 97931ee68..000000000 --- a/src/clientmedia.cpp +++ /dev/null @@ -1,639 +0,0 @@ -/* -Minetest -Copyright (C) 2013 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include "clientmedia.h" -#include "httpfetch.h" -#include "client.h" -#include "filecache.h" -#include "filesys.h" -#include "log.h" -#include "porting.h" -#include "settings.h" -#include "util/hex.h" -#include "util/serialize.h" -#include "util/sha1.h" -#include "util/string.h" - -static std::string getMediaCacheDir() -{ - return porting::path_cache + DIR_DELIM + "media"; -} - -/* - ClientMediaDownloader -*/ - -ClientMediaDownloader::ClientMediaDownloader(): - m_media_cache(getMediaCacheDir()), - m_httpfetch_caller(HTTPFETCH_DISCARD) -{ -} - -ClientMediaDownloader::~ClientMediaDownloader() -{ - if (m_httpfetch_caller != HTTPFETCH_DISCARD) - httpfetch_caller_free(m_httpfetch_caller); - - for (auto &file_it : m_files) - delete file_it.second; - - for (auto &remote : m_remotes) - delete remote; -} - -void ClientMediaDownloader::addFile(const std::string &name, const std::string &sha1) -{ - assert(!m_initial_step_done); // pre-condition - - // if name was already announced, ignore the new announcement - if (m_files.count(name) != 0) { - errorstream << "Client: ignoring duplicate media announcement " - << "sent by server: \"" << name << "\"" - << std::endl; - return; - } - - // if name is empty or contains illegal characters, ignore the file - if (name.empty() || !string_allowed(name, TEXTURENAME_ALLOWED_CHARS)) { - errorstream << "Client: ignoring illegal file name " - << "sent by server: \"" << name << "\"" - << std::endl; - return; - } - - // length of sha1 must be exactly 20 (160 bits), else ignore the file - if (sha1.size() != 20) { - errorstream << "Client: ignoring illegal SHA1 sent by server: " - << hex_encode(sha1) << " \"" << name << "\"" - << std::endl; - return; - } - - FileStatus *filestatus = new FileStatus(); - filestatus->received = false; - filestatus->sha1 = sha1; - filestatus->current_remote = -1; - m_files.insert(std::make_pair(name, filestatus)); -} - -void ClientMediaDownloader::addRemoteServer(const std::string &baseurl) -{ - assert(!m_initial_step_done); // pre-condition - - #ifdef USE_CURL - - if (g_settings->getBool("enable_remote_media_server")) { - infostream << "Client: Adding remote server \"" - << baseurl << "\" for media download" << std::endl; - - RemoteServerStatus *remote = new RemoteServerStatus(); - remote->baseurl = baseurl; - remote->active_count = 0; - remote->request_by_filename = false; - m_remotes.push_back(remote); - } - - #else - - infostream << "Client: Ignoring remote server \"" - << baseurl << "\" because cURL support is not compiled in" - << std::endl; - - #endif -} - -void ClientMediaDownloader::step(Client *client) -{ - if (!m_initial_step_done) { - initialStep(client); - m_initial_step_done = true; - } - - // Remote media: check for completion of fetches - if (m_httpfetch_active) { - bool fetched_something = false; - HTTPFetchResult fetch_result; - - while (httpfetch_async_get(m_httpfetch_caller, fetch_result)) { - m_httpfetch_active--; - fetched_something = true; - - // Is this a hashset (index.mth) or a media file? - if (fetch_result.request_id < m_remotes.size()) - remoteHashSetReceived(fetch_result); - else - remoteMediaReceived(fetch_result, client); - } - - if (fetched_something) - startRemoteMediaTransfers(); - - // Did all remote transfers end and no new ones can be started? - // If so, request still missing files from the minetest server - // (Or report that we have all files.) - if (m_httpfetch_active == 0) { - if (m_uncached_received_count < m_uncached_count) { - infostream << "Client: Failed to remote-fetch " - << (m_uncached_count-m_uncached_received_count) - << " files. Requesting them" - << " the usual way." << std::endl; - } - startConventionalTransfers(client); - } - } -} - -void ClientMediaDownloader::initialStep(Client *client) -{ - // Check media cache - m_uncached_count = m_files.size(); - for (auto &file_it : m_files) { - std::string name = file_it.first; - FileStatus *filestatus = file_it.second; - const std::string &sha1 = filestatus->sha1; - - std::ostringstream tmp_os(std::ios_base::binary); - bool found_in_cache = m_media_cache.load(hex_encode(sha1), tmp_os); - - // If found in cache, try to load it from there - if (found_in_cache) { - bool success = checkAndLoad(name, sha1, - tmp_os.str(), true, client); - if (success) { - filestatus->received = true; - m_uncached_count--; - } - } - } - - assert(m_uncached_received_count == 0); - - // Create the media cache dir if we are likely to write to it - if (m_uncached_count != 0) { - bool did = fs::CreateAllDirs(getMediaCacheDir()); - if (!did) { - errorstream << "Client: " - << "Could not create media cache directory: " - << getMediaCacheDir() - << std::endl; - } - } - - // If we found all files in the cache, report this fact to the server. - // If the server reported no remote servers, immediately start - // conventional transfers. Note: if cURL support is not compiled in, - // m_remotes is always empty, so "!USE_CURL" is redundant but may - // reduce the size of the compiled code - if (!USE_CURL || m_uncached_count == 0 || m_remotes.empty()) { - startConventionalTransfers(client); - } - else { - // Otherwise start off by requesting each server's sha1 set - - // This is the first time we use httpfetch, so alloc a caller ID - m_httpfetch_caller = httpfetch_caller_alloc(); - m_httpfetch_timeout = g_settings->getS32("curl_timeout"); - - // Set the active fetch limit to curl_parallel_limit or 84, - // whichever is greater. This gives us some leeway so that - // inefficiencies in communicating with the httpfetch thread - // don't slow down fetches too much. (We still want some limit - // so that when the first remote server returns its hash set, - // not all files are requested from that server immediately.) - // One such inefficiency is that ClientMediaDownloader::step() - // is only called a couple times per second, while httpfetch - // might return responses much faster than that. - // Note that httpfetch strictly enforces curl_parallel_limit - // but at no inter-thread communication cost. This however - // doesn't help with the aforementioned inefficiencies. - // The signifance of 84 is that it is 2*6*9 in base 13. - m_httpfetch_active_limit = g_settings->getS32("curl_parallel_limit"); - m_httpfetch_active_limit = MYMAX(m_httpfetch_active_limit, 84); - - // Write a list of hashes that we need. This will be POSTed - // to the server using Content-Type: application/octet-stream - std::string required_hash_set = serializeRequiredHashSet(); - - // minor fixme: this loop ignores m_httpfetch_active_limit - - // another minor fixme, unlikely to matter in normal usage: - // these index.mth fetches do (however) count against - // m_httpfetch_active_limit when starting actual media file - // requests, so if there are lots of remote servers that are - // not responding, those will stall new media file transfers. - - for (u32 i = 0; i < m_remotes.size(); ++i) { - assert(m_httpfetch_next_id == i); - - RemoteServerStatus *remote = m_remotes[i]; - actionstream << "Client: Contacting remote server \"" - << remote->baseurl << "\"" << std::endl; - - HTTPFetchRequest fetch_request; - fetch_request.url = - remote->baseurl + MTHASHSET_FILE_NAME; - fetch_request.caller = m_httpfetch_caller; - fetch_request.request_id = m_httpfetch_next_id; // == i - fetch_request.timeout = m_httpfetch_timeout; - fetch_request.connect_timeout = m_httpfetch_timeout; - fetch_request.post_data = required_hash_set; - fetch_request.extra_headers.emplace_back( - "Content-Type: application/octet-stream"); - httpfetch_async(fetch_request); - - m_httpfetch_active++; - m_httpfetch_next_id++; - m_outstanding_hash_sets++; - } - } -} - -void ClientMediaDownloader::remoteHashSetReceived( - const HTTPFetchResult &fetch_result) -{ - u32 remote_id = fetch_result.request_id; - assert(remote_id < m_remotes.size()); - RemoteServerStatus *remote = m_remotes[remote_id]; - - m_outstanding_hash_sets--; - - if (fetch_result.succeeded) { - try { - // Server sent a list of file hashes that are - // available on it, try to parse the list - - std::set sha1_set; - deSerializeHashSet(fetch_result.data, sha1_set); - - // Parsing succeeded: For every file that is - // available on this server, add this server - // to the available_remotes array - - for(std::map::iterator - it = m_files.upper_bound(m_name_bound); - it != m_files.end(); ++it) { - FileStatus *f = it->second; - if (!f->received && sha1_set.count(f->sha1)) - f->available_remotes.push_back(remote_id); - } - } - catch (SerializationError &e) { - infostream << "Client: Remote server \"" - << remote->baseurl << "\" sent invalid hash set: " - << e.what() << std::endl; - } - } - - // For compatibility: If index.mth is not found, assume that the - // server contains files named like the original files (not their sha1) - - // Do NOT check for any particular response code (e.g. 404) here, - // because different servers respond differently - - if (!fetch_result.succeeded && !fetch_result.timeout) { - infostream << "Client: Enabling compatibility mode for remote " - << "server \"" << remote->baseurl << "\"" << std::endl; - remote->request_by_filename = true; - - // Assume every file is available on this server - - for(std::map::iterator - it = m_files.upper_bound(m_name_bound); - it != m_files.end(); ++it) { - FileStatus *f = it->second; - if (!f->received) - f->available_remotes.push_back(remote_id); - } - } -} - -void ClientMediaDownloader::remoteMediaReceived( - const HTTPFetchResult &fetch_result, - Client *client) -{ - // Some remote server sent us a file. - // -> decrement number of active fetches - // -> mark file as received if fetch succeeded - // -> try to load media - - std::string name; - { - std::unordered_map::iterator it = - m_remote_file_transfers.find(fetch_result.request_id); - assert(it != m_remote_file_transfers.end()); - name = it->second; - m_remote_file_transfers.erase(it); - } - - sanity_check(m_files.count(name) != 0); - - FileStatus *filestatus = m_files[name]; - sanity_check(!filestatus->received); - sanity_check(filestatus->current_remote >= 0); - - RemoteServerStatus *remote = m_remotes[filestatus->current_remote]; - - filestatus->current_remote = -1; - remote->active_count--; - - // If fetch succeeded, try to load media file - - if (fetch_result.succeeded) { - bool success = checkAndLoad(name, filestatus->sha1, - fetch_result.data, false, client); - if (success) { - filestatus->received = true; - assert(m_uncached_received_count < m_uncached_count); - m_uncached_received_count++; - } - } -} - -s32 ClientMediaDownloader::selectRemoteServer(FileStatus *filestatus) -{ - // Pre-conditions - assert(filestatus != NULL); - assert(!filestatus->received); - assert(filestatus->current_remote < 0); - - if (filestatus->available_remotes.empty()) - return -1; - - // Of all servers that claim to provide the file (and haven't - // been unsuccessfully tried before), find the one with the - // smallest number of currently active transfers - - s32 best = 0; - s32 best_remote_id = filestatus->available_remotes[best]; - s32 best_active_count = m_remotes[best_remote_id]->active_count; - - for (u32 i = 1; i < filestatus->available_remotes.size(); ++i) { - s32 remote_id = filestatus->available_remotes[i]; - s32 active_count = m_remotes[remote_id]->active_count; - if (active_count < best_active_count) { - best = i; - best_remote_id = remote_id; - best_active_count = active_count; - } - } - - filestatus->available_remotes.erase( - filestatus->available_remotes.begin() + best); - - return best_remote_id; - -} - -void ClientMediaDownloader::startRemoteMediaTransfers() -{ - bool changing_name_bound = true; - - for (std::map::iterator - files_iter = m_files.upper_bound(m_name_bound); - files_iter != m_files.end(); ++files_iter) { - - // Abort if active fetch limit is exceeded - if (m_httpfetch_active >= m_httpfetch_active_limit) - break; - - const std::string &name = files_iter->first; - FileStatus *filestatus = files_iter->second; - - if (!filestatus->received && filestatus->current_remote < 0) { - // File has not been received yet and is not currently - // being transferred. Choose a server for it. - s32 remote_id = selectRemoteServer(filestatus); - if (remote_id >= 0) { - // Found a server, so start fetching - RemoteServerStatus *remote = - m_remotes[remote_id]; - - std::string url = remote->baseurl + - (remote->request_by_filename ? name : - hex_encode(filestatus->sha1)); - verbosestream << "Client: " - << "Requesting remote media file " - << "\"" << name << "\" " - << "\"" << url << "\"" << std::endl; - - HTTPFetchRequest fetch_request; - fetch_request.url = url; - fetch_request.caller = m_httpfetch_caller; - fetch_request.request_id = m_httpfetch_next_id; - fetch_request.timeout = 0; // no data timeout! - fetch_request.connect_timeout = - m_httpfetch_timeout; - httpfetch_async(fetch_request); - - m_remote_file_transfers.insert(std::make_pair( - m_httpfetch_next_id, - name)); - - filestatus->current_remote = remote_id; - remote->active_count++; - m_httpfetch_active++; - m_httpfetch_next_id++; - } - } - - if (filestatus->received || - (filestatus->current_remote < 0 && - !m_outstanding_hash_sets)) { - // If we arrive here, we conclusively know that we - // won't fetch this file from a remote server in the - // future. So update the name bound if possible. - if (changing_name_bound) - m_name_bound = name; - } - else - changing_name_bound = false; - } - -} - -void ClientMediaDownloader::startConventionalTransfers(Client *client) -{ - assert(m_httpfetch_active == 0); // pre-condition - - if (m_uncached_received_count != m_uncached_count) { - // Some media files have not been received yet, use the - // conventional slow method (minetest protocol) to get them - std::vector file_requests; - for (auto &file : m_files) { - if (!file.second->received) - file_requests.push_back(file.first); - } - assert((s32) file_requests.size() == - m_uncached_count - m_uncached_received_count); - client->request_media(file_requests); - } -} - -void ClientMediaDownloader::conventionalTransferDone( - const std::string &name, - const std::string &data, - Client *client) -{ - // Check that file was announced - std::map::iterator - file_iter = m_files.find(name); - if (file_iter == m_files.end()) { - errorstream << "Client: server sent media file that was" - << "not announced, ignoring it: \"" << name << "\"" - << std::endl; - return; - } - FileStatus *filestatus = file_iter->second; - assert(filestatus != NULL); - - // Check that file hasn't already been received - if (filestatus->received) { - errorstream << "Client: server sent media file that we already" - << "received, ignoring it: \"" << name << "\"" - << std::endl; - return; - } - - // Mark file as received, regardless of whether loading it works and - // whether the checksum matches (because at this point there is no - // other server that could send a replacement) - filestatus->received = true; - assert(m_uncached_received_count < m_uncached_count); - m_uncached_received_count++; - - // Check that received file matches announced checksum - // If so, load it - checkAndLoad(name, filestatus->sha1, data, false, client); -} - -bool ClientMediaDownloader::checkAndLoad( - const std::string &name, const std::string &sha1, - const std::string &data, bool is_from_cache, Client *client) -{ - const char *cached_or_received = is_from_cache ? "cached" : "received"; - const char *cached_or_received_uc = is_from_cache ? "Cached" : "Received"; - std::string sha1_hex = hex_encode(sha1); - - // Compute actual checksum of data - std::string data_sha1; - { - SHA1 data_sha1_calculator; - data_sha1_calculator.addBytes(data.c_str(), data.size()); - unsigned char *data_tmpdigest = data_sha1_calculator.getDigest(); - data_sha1.assign((char*) data_tmpdigest, 20); - free(data_tmpdigest); - } - - // Check that received file matches announced checksum - if (data_sha1 != sha1) { - std::string data_sha1_hex = hex_encode(data_sha1); - infostream << "Client: " - << cached_or_received_uc << " media file " - << sha1_hex << " \"" << name << "\" " - << "mismatches actual checksum " << data_sha1_hex - << std::endl; - return false; - } - - // Checksum is ok, try loading the file - bool success = client->loadMedia(data, name); - if (!success) { - infostream << "Client: " - << "Failed to load " << cached_or_received << " media: " - << sha1_hex << " \"" << name << "\"" - << std::endl; - return false; - } - - verbosestream << "Client: " - << "Loaded " << cached_or_received << " media: " - << sha1_hex << " \"" << name << "\"" - << std::endl; - - // Update cache (unless we just loaded the file from the cache) - if (!is_from_cache) - m_media_cache.update(sha1_hex, data); - - return true; -} - - -/* - Minetest Hashset File Format - - All values are stored in big-endian byte order. - [u32] signature: 'MTHS' - [u16] version: 1 - For each hash in set: - [u8*20] SHA1 hash - - Version changes: - 1 - Initial version -*/ - -std::string ClientMediaDownloader::serializeRequiredHashSet() -{ - std::ostringstream os(std::ios::binary); - - writeU32(os, MTHASHSET_FILE_SIGNATURE); // signature - writeU16(os, 1); // version - - // Write list of hashes of files that have not been - // received (found in cache) yet - for (std::map::iterator - it = m_files.begin(); - it != m_files.end(); ++it) { - if (!it->second->received) { - FATAL_ERROR_IF(it->second->sha1.size() != 20, "Invalid SHA1 size"); - os << it->second->sha1; - } - } - - return os.str(); -} - -void ClientMediaDownloader::deSerializeHashSet(const std::string &data, - std::set &result) -{ - if (data.size() < 6 || data.size() % 20 != 6) { - throw SerializationError( - "ClientMediaDownloader::deSerializeHashSet: " - "invalid hash set file size"); - } - - const u8 *data_cstr = (const u8*) data.c_str(); - - u32 signature = readU32(&data_cstr[0]); - if (signature != MTHASHSET_FILE_SIGNATURE) { - throw SerializationError( - "ClientMediaDownloader::deSerializeHashSet: " - "invalid hash set file signature"); - } - - u16 version = readU16(&data_cstr[4]); - if (version != 1) { - throw SerializationError( - "ClientMediaDownloader::deSerializeHashSet: " - "unsupported hash set file version"); - } - - for (u32 pos = 6; pos < data.size(); pos += 20) { - result.insert(data.substr(pos, 20)); - } -} diff --git a/src/clientmedia.h b/src/clientmedia.h deleted file mode 100644 index b08b83e4d..000000000 --- a/src/clientmedia.h +++ /dev/null @@ -1,148 +0,0 @@ -/* -Minetest -Copyright (C) 2013 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#pragma once - -#include "irrlichttypes.h" -#include "filecache.h" -#include -#include -#include -#include -#include - -class Client; -struct HTTPFetchResult; - -#define MTHASHSET_FILE_SIGNATURE 0x4d544853 // 'MTHS' -#define MTHASHSET_FILE_NAME "index.mth" - -class ClientMediaDownloader -{ -public: - ClientMediaDownloader(); - ~ClientMediaDownloader(); - - float getProgress() const { - if (m_uncached_count >= 1) - return 1.0f * m_uncached_received_count / - m_uncached_count; - - return 0.0f; - } - - bool isStarted() const { - return m_initial_step_done; - } - - // If this returns true, the downloader is done and can be deleted - bool isDone() const { - return m_initial_step_done && - m_uncached_received_count == m_uncached_count; - } - - // Add a file to the list of required file (but don't fetch it yet) - void addFile(const std::string &name, const std::string &sha1); - - // Add a remote server to the list; ignored if not built with cURL - void addRemoteServer(const std::string &baseurl); - - // Steps the media downloader: - // - May load media into client by calling client->loadMedia() - // - May check media cache for files - // - May add files to media cache - // - May start remote transfers by calling httpfetch_async - // - May check for completion of current remote transfers - // - May start conventional transfers by calling client->request_media() - // - May inform server that all media has been loaded - // by calling client->received_media() - // After step has been called once, don't call addFile/addRemoteServer. - void step(Client *client); - - // Must be called for each file received through TOCLIENT_MEDIA - void conventionalTransferDone( - const std::string &name, - const std::string &data, - Client *client); - -private: - struct FileStatus { - bool received; - std::string sha1; - s32 current_remote; - std::vector available_remotes; - }; - - struct RemoteServerStatus { - std::string baseurl; - s32 active_count; - bool request_by_filename; - }; - - void initialStep(Client *client); - void remoteHashSetReceived(const HTTPFetchResult &fetch_result); - void remoteMediaReceived(const HTTPFetchResult &fetch_result, - Client *client); - s32 selectRemoteServer(FileStatus *filestatus); - void startRemoteMediaTransfers(); - void startConventionalTransfers(Client *client); - - bool checkAndLoad(const std::string &name, const std::string &sha1, - const std::string &data, bool is_from_cache, - Client *client); - - std::string serializeRequiredHashSet(); - static void deSerializeHashSet(const std::string &data, - std::set &result); - - // Maps filename to file status - std::map m_files; - - // Array of remote media servers - std::vector m_remotes; - - // Filesystem-based media cache - FileCache m_media_cache; - - // Has an attempt been made to load media files from the file cache? - // Have hash sets been requested from remote servers? - bool m_initial_step_done = false; - - // Total number of media files to load - s32 m_uncached_count = 0; - - // Number of media files that have been received - s32 m_uncached_received_count = 0; - - // Status of remote transfers - unsigned long m_httpfetch_caller; - unsigned long m_httpfetch_next_id = 0; - long m_httpfetch_timeout = 0; - s32 m_httpfetch_active = 0; - s32 m_httpfetch_active_limit = 0; - s32 m_outstanding_hash_sets = 0; - std::unordered_map m_remote_file_transfers; - - // All files up to this name have either been received from a - // remote server or failed on all remote servers, so those files - // don't need to be looked at again - // (use m_files.upper_bound(m_name_bound) to get an iterator) - std::string m_name_bound = ""; - -}; diff --git a/src/clientobject.cpp b/src/clientobject.cpp deleted file mode 100644 index f4b69201b..000000000 --- a/src/clientobject.cpp +++ /dev/null @@ -1,66 +0,0 @@ -/* -Minetest -Copyright (C) 2010-2013 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include "clientobject.h" -#include "debug.h" -#include "porting.h" - -/* - ClientActiveObject -*/ - -ClientActiveObject::ClientActiveObject(u16 id, Client *client, - ClientEnvironment *env): - ActiveObject(id), - m_client(client), - m_env(env) -{ -} - -ClientActiveObject::~ClientActiveObject() -{ - removeFromScene(true); -} - -ClientActiveObject* ClientActiveObject::create(ActiveObjectType type, - Client *client, ClientEnvironment *env) -{ - // Find factory function - auto n = m_types.find(type); - if (n == m_types.end()) { - // If factory is not found, just return. - warningstream << "ClientActiveObject: No factory for type=" - << (int)type << std::endl; - return NULL; - } - - Factory f = n->second; - ClientActiveObject *object = (*f)(client, env); - return object; -} - -void ClientActiveObject::registerType(u16 type, Factory f) -{ - auto n = m_types.find(type); - if (n != m_types.end()) - return; - m_types[type] = f; -} - - diff --git a/src/clientobject.h b/src/clientobject.h deleted file mode 100644 index 9377d1e67..000000000 --- a/src/clientobject.h +++ /dev/null @@ -1,108 +0,0 @@ -/* -Minetest -Copyright (C) 2010-2013 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#pragma once - -#include "irrlichttypes_extrabloated.h" -#include "activeobject.h" -#include - -class ClientEnvironment; -class ITextureSource; -class Client; -class IGameDef; -class LocalPlayer; -struct ItemStack; -class WieldMeshSceneNode; - -class ClientActiveObject : public ActiveObject -{ -public: - ClientActiveObject(u16 id, Client *client, ClientEnvironment *env); - virtual ~ClientActiveObject(); - - virtual void addToScene(ITextureSource *tsrc) {}; - virtual void removeFromScene(bool permanent) {} - // 0 <= light_at_pos <= LIGHT_SUN - virtual void updateLight(u8 light_at_pos){} - virtual void updateLightNoCheck(u8 light_at_pos){} - virtual v3s16 getLightPosition(){return v3s16(0,0,0);} - virtual bool getCollisionBox(aabb3f *toset) const { return false; } - virtual bool getSelectionBox(aabb3f *toset) const { return false; } - virtual bool collideWithObjects() const { return false; } - virtual v3f getPosition(){ return v3f(0,0,0); } - virtual float getYaw() const { return 0; } - virtual scene::ISceneNode *getSceneNode() { return NULL; } - virtual scene::IAnimatedMeshSceneNode *getAnimatedMeshSceneNode() { return NULL; } - virtual bool isLocalPlayer() const {return false;} - virtual ClientActiveObject *getParent() const { return nullptr; }; - virtual void setAttachments() {} - virtual bool doShowSelectionBox(){return true;} - - // Step object in time - virtual void step(float dtime, ClientEnvironment *env){} - - // Process a message sent by the server side object - virtual void processMessage(const std::string &data){} - - virtual std::string infoText() {return "";} - virtual std::string debugInfoText() {return "";} - - /* - This takes the return value of - ServerActiveObject::getClientInitializationData - */ - virtual void initialize(const std::string &data){} - - // Create a certain type of ClientActiveObject - static ClientActiveObject* create(ActiveObjectType type, Client *client, - ClientEnvironment *env); - - // If returns true, punch will not be sent to the server - virtual bool directReportPunch(v3f dir, const ItemStack *punchitem=NULL, - float time_from_last_punch=1000000) - { return false; } - -protected: - // Used for creating objects based on type - typedef ClientActiveObject* (*Factory)(Client *client, ClientEnvironment *env); - static void registerType(u16 type, Factory f); - Client *m_client; - ClientEnvironment *m_env; -private: - // Used for creating objects based on type - static std::unordered_map m_types; -}; - -struct DistanceSortedActiveObject -{ - ClientActiveObject *obj; - f32 d; - - DistanceSortedActiveObject(ClientActiveObject *a_obj, f32 a_d) - { - obj = a_obj; - d = a_d; - } - - bool operator < (const DistanceSortedActiveObject &other) const - { - return d < other.d; - } -}; diff --git a/src/clouds.cpp b/src/clouds.cpp deleted file mode 100644 index 13051f32c..000000000 --- a/src/clouds.cpp +++ /dev/null @@ -1,386 +0,0 @@ -/* -Minetest -Copyright (C) 2010-2013 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include "client/renderingengine.h" -#include "clouds.h" -#include "noise.h" -#include "constants.h" -#include "debug.h" -#include "profiler.h" -#include "settings.h" -#include - - -// Menu clouds are created later -class Clouds; -Clouds *g_menuclouds = NULL; -irr::scene::ISceneManager *g_menucloudsmgr = NULL; - -// Constant for now -static constexpr const float cloud_size = BS * 64.0f; - -static void cloud_3d_setting_changed(const std::string &settingname, void *data) -{ - ((Clouds *)data)->readSettings(); -} - -Clouds::Clouds(scene::ISceneManager* mgr, - s32 id, - u32 seed -): - scene::ISceneNode(mgr->getRootSceneNode(), mgr, id), - m_seed(seed) -{ - m_material.setFlag(video::EMF_LIGHTING, false); - //m_material.setFlag(video::EMF_BACK_FACE_CULLING, false); - m_material.setFlag(video::EMF_BACK_FACE_CULLING, true); - m_material.setFlag(video::EMF_BILINEAR_FILTER, false); - m_material.setFlag(video::EMF_FOG_ENABLE, true); - m_material.setFlag(video::EMF_ANTI_ALIASING, true); - //m_material.MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA; - m_material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; - - m_params.height = 120; - m_params.density = 0.4f; - m_params.thickness = 16.0f; - m_params.color_bright = video::SColor(229, 240, 240, 255); - m_params.color_ambient = video::SColor(255, 0, 0, 0); - m_params.speed = v2f(0.0f, -2.0f); - - readSettings(); - g_settings->registerChangedCallback("enable_3d_clouds", - &cloud_3d_setting_changed, this); - - updateBox(); -} - -Clouds::~Clouds() -{ - g_settings->deregisterChangedCallback("enable_3d_clouds", - &cloud_3d_setting_changed, this); -} - -void Clouds::OnRegisterSceneNode() -{ - if(IsVisible) - { - SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT); - //SceneManager->registerNodeForRendering(this, scene::ESNRP_SOLID); - } - - ISceneNode::OnRegisterSceneNode(); -} - -void Clouds::render() -{ - - if (m_params.density <= 0.0f) - return; // no need to do anything - - video::IVideoDriver* driver = SceneManager->getVideoDriver(); - - if(SceneManager->getSceneNodeRenderPass() != scene::ESNRP_TRANSPARENT) - //if(SceneManager->getSceneNodeRenderPass() != scene::ESNRP_SOLID) - return; - - ScopeProfiler sp(g_profiler, "Rendering of clouds, avg", SPT_AVG); - - int num_faces_to_draw = m_enable_3d ? 6 : 1; - - m_material.setFlag(video::EMF_BACK_FACE_CULLING, m_enable_3d); - - driver->setTransform(video::ETS_WORLD, AbsoluteTransformation); - driver->setMaterial(m_material); - - /* - Clouds move from Z+ towards Z- - */ - - const float cloud_full_radius = cloud_size * m_cloud_radius_i; - - v2f camera_pos_2d(m_camera_pos.X, m_camera_pos.Z); - // Position of cloud noise origin from the camera - v2f cloud_origin_from_camera_f = m_origin - camera_pos_2d; - // The center point of drawing in the noise - v2f center_of_drawing_in_noise_f = -cloud_origin_from_camera_f; - // The integer center point of drawing in the noise - v2s16 center_of_drawing_in_noise_i( - std::floor(center_of_drawing_in_noise_f.X / cloud_size), - std::floor(center_of_drawing_in_noise_f.Y / cloud_size) - ); - - // The world position of the integer center point of drawing in the noise - v2f world_center_of_drawing_in_noise_f = v2f( - center_of_drawing_in_noise_i.X * cloud_size, - center_of_drawing_in_noise_i.Y * cloud_size - ) + m_origin; - - /*video::SColor c_top(128,b*240,b*240,b*255); - video::SColor c_side_1(128,b*230,b*230,b*255); - video::SColor c_side_2(128,b*220,b*220,b*245); - video::SColor c_bottom(128,b*205,b*205,b*230);*/ - video::SColorf c_top_f(m_color); - video::SColorf c_side_1_f(m_color); - video::SColorf c_side_2_f(m_color); - video::SColorf c_bottom_f(m_color); - c_side_1_f.r *= 0.95; - c_side_1_f.g *= 0.95; - c_side_1_f.b *= 0.95; - c_side_2_f.r *= 0.90; - c_side_2_f.g *= 0.90; - c_side_2_f.b *= 0.90; - c_bottom_f.r *= 0.80; - c_bottom_f.g *= 0.80; - c_bottom_f.b *= 0.80; - video::SColor c_top = c_top_f.toSColor(); - video::SColor c_side_1 = c_side_1_f.toSColor(); - video::SColor c_side_2 = c_side_2_f.toSColor(); - video::SColor c_bottom = c_bottom_f.toSColor(); - - // Get fog parameters for setting them back later - video::SColor fog_color(0,0,0,0); - video::E_FOG_TYPE fog_type = video::EFT_FOG_LINEAR; - f32 fog_start = 0; - f32 fog_end = 0; - f32 fog_density = 0; - bool fog_pixelfog = false; - bool fog_rangefog = false; - driver->getFog(fog_color, fog_type, fog_start, fog_end, fog_density, - fog_pixelfog, fog_rangefog); - - // Set our own fog - driver->setFog(fog_color, fog_type, cloud_full_radius * 0.5, - cloud_full_radius*1.2, fog_density, fog_pixelfog, fog_rangefog); - - // Read noise - - bool *grid = new bool[m_cloud_radius_i * 2 * m_cloud_radius_i * 2]; - - - for(s16 zi = -m_cloud_radius_i; zi < m_cloud_radius_i; zi++) { - u32 si = (zi + m_cloud_radius_i) * m_cloud_radius_i * 2 + m_cloud_radius_i; - - for (s16 xi = -m_cloud_radius_i; xi < m_cloud_radius_i; xi++) { - u32 i = si + xi; - - grid[i] = gridFilled( - xi + center_of_drawing_in_noise_i.X, - zi + center_of_drawing_in_noise_i.Y - ); - } - } - -#define GETINDEX(x, z, radius) (((z)+(radius))*(radius)*2 + (x)+(radius)) -#define INAREA(x, z, radius) \ - ((x) >= -(radius) && (x) < (radius) && (z) >= -(radius) && (z) < (radius)) - - for (s16 zi0= -m_cloud_radius_i; zi0 < m_cloud_radius_i; zi0++) - for (s16 xi0= -m_cloud_radius_i; xi0 < m_cloud_radius_i; xi0++) - { - s16 zi = zi0; - s16 xi = xi0; - // Draw from front to back (needed for transparency) - /*if(zi <= 0) - zi = -m_cloud_radius_i - zi; - if(xi <= 0) - xi = -m_cloud_radius_i - xi;*/ - // Draw from back to front - if(zi >= 0) - zi = m_cloud_radius_i - zi - 1; - if(xi >= 0) - xi = m_cloud_radius_i - xi - 1; - - u32 i = GETINDEX(xi, zi, m_cloud_radius_i); - - if (!grid[i]) - continue; - - v2f p0 = v2f(xi,zi)*cloud_size + world_center_of_drawing_in_noise_f; - - video::S3DVertex v[4] = { - video::S3DVertex(0,0,0, 0,0,0, c_top, 0, 1), - video::S3DVertex(0,0,0, 0,0,0, c_top, 1, 1), - video::S3DVertex(0,0,0, 0,0,0, c_top, 1, 0), - video::S3DVertex(0,0,0, 0,0,0, c_top, 0, 0) - }; - - /*if(zi <= 0 && xi <= 0){ - v[0].Color.setBlue(255); - v[1].Color.setBlue(255); - v[2].Color.setBlue(255); - v[3].Color.setBlue(255); - }*/ - - f32 rx = cloud_size / 2.0f; - // if clouds are flat, the top layer should be at the given height - f32 ry = m_enable_3d ? m_params.thickness * BS : 0.0f; - f32 rz = cloud_size / 2; - - for(int i=0; idrawVertexPrimitiveList(v, 4, indices, 2, - video::EVT_STANDARD, scene::EPT_TRIANGLES, video::EIT_16BIT); - } - } - - delete[] grid; - - // Restore fog settings - driver->setFog(fog_color, fog_type, fog_start, fog_end, fog_density, - fog_pixelfog, fog_rangefog); -} - -void Clouds::step(float dtime) -{ - m_origin = m_origin + dtime * BS * m_params.speed; -} - -void Clouds::update(const v3f &camera_p, const video::SColorf &color_diffuse) -{ - m_camera_pos = camera_p; - m_color.r = MYMIN(MYMAX(color_diffuse.r * m_params.color_bright.getRed(), - m_params.color_ambient.getRed()), 255) / 255.0f; - m_color.g = MYMIN(MYMAX(color_diffuse.g * m_params.color_bright.getGreen(), - m_params.color_ambient.getGreen()), 255) / 255.0f; - m_color.b = MYMIN(MYMAX(color_diffuse.b * m_params.color_bright.getBlue(), - m_params.color_ambient.getBlue()), 255) / 255.0f; - m_color.a = m_params.color_bright.getAlpha() / 255.0f; - - // is the camera inside the cloud mesh? - m_camera_inside_cloud = false; // default - if (m_enable_3d) { - float camera_height = camera_p.Y; - if (camera_height >= m_box.MinEdge.Y && - camera_height <= m_box.MaxEdge.Y) { - v2f camera_in_noise; - camera_in_noise.X = floor((camera_p.X - m_origin.X) / cloud_size + 0.5); - camera_in_noise.Y = floor((camera_p.Z - m_origin.Y) / cloud_size + 0.5); - bool filled = gridFilled(camera_in_noise.X, camera_in_noise.Y); - m_camera_inside_cloud = filled; - } - } -} - -void Clouds::readSettings() -{ - m_cloud_radius_i = g_settings->getU16("cloud_radius"); - m_enable_3d = g_settings->getBool("enable_3d_clouds"); -} - -bool Clouds::gridFilled(int x, int y) const -{ - float cloud_size_noise = cloud_size / (BS * 200.f); - float noise = noise2d_perlin( - (float)x * cloud_size_noise, - (float)y * cloud_size_noise, - m_seed, 3, 0.5); - // normalize to 0..1 (given 3 octaves) - static constexpr const float noise_bound = 1.0f + 0.5f + 0.25f; - float density = noise / noise_bound * 0.5f + 0.5f; - return (density < m_params.density); -} diff --git a/src/clouds.h b/src/clouds.h deleted file mode 100644 index a4d810faa..000000000 --- a/src/clouds.h +++ /dev/null @@ -1,144 +0,0 @@ -/* -Minetest -Copyright (C) 2010-2013 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#pragma once - -#include "irrlichttypes_extrabloated.h" -#include -#include "constants.h" -#include "cloudparams.h" - -// Menu clouds -class Clouds; -extern Clouds *g_menuclouds; - -// Scene manager used for menu clouds -namespace irr{namespace scene{class ISceneManager;}} -extern irr::scene::ISceneManager *g_menucloudsmgr; - -class Clouds : public scene::ISceneNode -{ -public: - Clouds(scene::ISceneManager* mgr, - s32 id, - u32 seed - ); - - ~Clouds(); - - /* - ISceneNode methods - */ - - virtual void OnRegisterSceneNode(); - - virtual void render(); - - virtual const aabb3f &getBoundingBox() const - { - return m_box; - } - - virtual u32 getMaterialCount() const - { - return 1; - } - - virtual video::SMaterial& getMaterial(u32 i) - { - return m_material; - } - - /* - Other stuff - */ - - void step(float dtime); - - void update(const v3f &camera_p, const video::SColorf &color); - - void updateCameraOffset(const v3s16 &camera_offset) - { - m_camera_offset = camera_offset; - updateBox(); - } - - void readSettings(); - - void setDensity(float density) - { - m_params.density = density; - // currently does not need bounding - } - - void setColorBright(const video::SColor &color_bright) - { - m_params.color_bright = color_bright; - } - - void setColorAmbient(const video::SColor &color_ambient) - { - m_params.color_ambient = color_ambient; - } - - void setHeight(float height) - { - m_params.height = height; // add bounding when necessary - updateBox(); - } - - void setSpeed(v2f speed) - { - m_params.speed = speed; - } - - void setThickness(float thickness) - { - m_params.thickness = thickness; - updateBox(); - } - - bool isCameraInsideCloud() const { return m_camera_inside_cloud; } - - const video::SColor getColor() const { return m_color.toSColor(); } - -private: - void updateBox() - { - float height_bs = m_params.height * BS; - float thickness_bs = m_params.thickness * BS; - m_box = aabb3f(-BS * 1000000.0f, height_bs - BS * m_camera_offset.Y, -BS * 1000000.0f, - BS * 1000000.0f, height_bs + thickness_bs - BS * m_camera_offset.Y, BS * 1000000.0f); - } - - bool gridFilled(int x, int y) const; - - video::SMaterial m_material; - aabb3f m_box; - u16 m_cloud_radius_i; - bool m_enable_3d; - u32 m_seed; - v3f m_camera_pos; - v2f m_origin; - v3s16 m_camera_offset; - video::SColorf m_color = video::SColorf(1.0f, 1.0f, 1.0f, 1.0f); - CloudParams m_params; - bool m_camera_inside_cloud = false; - -}; diff --git a/src/collision.cpp b/src/collision.cpp index 9626221a0..a07899e65 100644 --- a/src/collision.cpp +++ b/src/collision.cpp @@ -24,7 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "nodedef.h" #include "gamedef.h" #ifndef SERVER -#include "clientenvironment.h" +#include "client/clientenvironment.h" #endif #include "serverenvironment.h" #include "serverobject.h" diff --git a/src/content_cao.cpp b/src/content_cao.cpp deleted file mode 100644 index db59ae5c5..000000000 --- a/src/content_cao.cpp +++ /dev/null @@ -1,1611 +0,0 @@ -/* -Minetest -Copyright (C) 2010-2013 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include -#include -#include -#include -#include -#include "content_cao.h" -#include "util/numeric.h" // For IntervalLimiter -#include "util/serialize.h" -#include "util/basic_macros.h" -#include "client/sound.h" -#include "client/tile.h" -#include "environment.h" -#include "collision.h" -#include "settings.h" -#include "serialization.h" // For decompressZlib -#include "clientobject.h" -#include "mesh.h" -#include "itemdef.h" -#include "tool.h" -#include "content_cso.h" -#include "sound.h" -#include "nodedef.h" -#include "localplayer.h" -#include "map.h" -#include "camera.h" // CameraModes -#include "client.h" -#include "wieldmesh.h" -#include -#include -#include "client/renderingengine.h" - -class Settings; -struct ToolCapabilities; - -std::unordered_map ClientActiveObject::m_types; - -template -void SmoothTranslator::init(T current) -{ - val_old = current; - val_current = current; - val_target = current; - anim_time = 0; - anim_time_counter = 0; - aim_is_end = true; -} - -template -void SmoothTranslator::update(T new_target, bool is_end_position, float update_interval) -{ - aim_is_end = is_end_position; - val_old = val_current; - val_target = new_target; - if (update_interval > 0) { - anim_time = update_interval; - } else { - if (anim_time < 0.001 || anim_time > 1.0) - anim_time = anim_time_counter; - else - anim_time = anim_time * 0.9 + anim_time_counter * 0.1; - } - anim_time_counter = 0; -} - -template -void SmoothTranslator::translate(f32 dtime) -{ - anim_time_counter = anim_time_counter + dtime; - T val_diff = val_target - val_old; - f32 moveratio = 1.0; - if (anim_time > 0.001) - moveratio = anim_time_counter / anim_time; - f32 move_end = aim_is_end ? 1.0 : 1.5; - - // Move a bit less than should, to avoid oscillation - moveratio = std::min(moveratio * 0.8f, move_end); - val_current = val_old + val_diff * moveratio; -} - -void SmoothTranslatorWrapped::translate(f32 dtime) -{ - anim_time_counter = anim_time_counter + dtime; - f32 val_diff = std::abs(val_target - val_old); - if (val_diff > 180.f) - val_diff = 360.f - val_diff; - - f32 moveratio = 1.0; - if (anim_time > 0.001) - moveratio = anim_time_counter / anim_time; - f32 move_end = aim_is_end ? 1.0 : 1.5; - - // Move a bit less than should, to avoid oscillation - moveratio = std::min(moveratio * 0.8f, move_end); - wrappedApproachShortest(val_current, val_target, - val_diff * moveratio, 360.f); -} - -void SmoothTranslatorWrappedv3f::translate(f32 dtime) -{ - anim_time_counter = anim_time_counter + dtime; - - v3f val_diff_v3f; - val_diff_v3f.X = std::abs(val_target.X - val_old.X); - val_diff_v3f.Y = std::abs(val_target.Y - val_old.Y); - val_diff_v3f.Z = std::abs(val_target.Z - val_old.Z); - - if (val_diff_v3f.X > 180.f) - val_diff_v3f.X = 360.f - val_diff_v3f.X; - - if (val_diff_v3f.Y > 180.f) - val_diff_v3f.Y = 360.f - val_diff_v3f.Y; - - if (val_diff_v3f.Z > 180.f) - val_diff_v3f.Z = 360.f - val_diff_v3f.Z; - - f32 moveratio = 1.0; - if (anim_time > 0.001) - moveratio = anim_time_counter / anim_time; - f32 move_end = aim_is_end ? 1.0 : 1.5; - - // Move a bit less than should, to avoid oscillation - moveratio = std::min(moveratio * 0.8f, move_end); - wrappedApproachShortest(val_current.X, val_target.X, - val_diff_v3f.X * moveratio, 360.f); - - wrappedApproachShortest(val_current.Y, val_target.Y, - val_diff_v3f.Y * moveratio, 360.f); - - wrappedApproachShortest(val_current.Z, val_target.Z, - val_diff_v3f.Z * moveratio, 360.f); -} - -/* - Other stuff -*/ - -static void setBillboardTextureMatrix(scene::IBillboardSceneNode *bill, - float txs, float tys, int col, int row) -{ - video::SMaterial& material = bill->getMaterial(0); - core::matrix4& matrix = material.getTextureMatrix(0); - matrix.setTextureTranslate(txs*col, tys*row); - matrix.setTextureScale(txs, tys); -} - -/* - TestCAO -*/ - -class TestCAO : public ClientActiveObject -{ -public: - TestCAO(Client *client, ClientEnvironment *env); - virtual ~TestCAO() = default; - - ActiveObjectType getType() const - { - return ACTIVEOBJECT_TYPE_TEST; - } - - static ClientActiveObject* create(Client *client, ClientEnvironment *env); - - void addToScene(ITextureSource *tsrc); - void removeFromScene(bool permanent); - void updateLight(u8 light_at_pos); - v3s16 getLightPosition(); - void updateNodePos(); - - void step(float dtime, ClientEnvironment *env); - - void processMessage(const std::string &data); - - bool getCollisionBox(aabb3f *toset) const { return false; } -private: - scene::IMeshSceneNode *m_node; - v3f m_position; -}; - -// Prototype -TestCAO proto_TestCAO(NULL, NULL); - -TestCAO::TestCAO(Client *client, ClientEnvironment *env): - ClientActiveObject(0, client, env), - m_node(NULL), - m_position(v3f(0,10*BS,0)) -{ - ClientActiveObject::registerType(getType(), create); -} - -ClientActiveObject* TestCAO::create(Client *client, ClientEnvironment *env) -{ - return new TestCAO(client, env); -} - -void TestCAO::addToScene(ITextureSource *tsrc) -{ - if(m_node != NULL) - return; - - //video::IVideoDriver* driver = smgr->getVideoDriver(); - - scene::SMesh *mesh = new scene::SMesh(); - scene::IMeshBuffer *buf = new scene::SMeshBuffer(); - video::SColor c(255,255,255,255); - video::S3DVertex vertices[4] = - { - video::S3DVertex(-BS/2,-BS/4,0, 0,0,0, c, 0,1), - video::S3DVertex(BS/2,-BS/4,0, 0,0,0, c, 1,1), - video::S3DVertex(BS/2,BS/4,0, 0,0,0, c, 1,0), - video::S3DVertex(-BS/2,BS/4,0, 0,0,0, c, 0,0), - }; - u16 indices[] = {0,1,2,2,3,0}; - buf->append(vertices, 4, indices, 6); - // Set material - buf->getMaterial().setFlag(video::EMF_LIGHTING, false); - buf->getMaterial().setFlag(video::EMF_BACK_FACE_CULLING, false); - buf->getMaterial().setTexture(0, tsrc->getTextureForMesh("rat.png")); - buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false); - buf->getMaterial().setFlag(video::EMF_FOG_ENABLE, true); - buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; - // Add to mesh - mesh->addMeshBuffer(buf); - buf->drop(); - m_node = RenderingEngine::get_scene_manager()->addMeshSceneNode(mesh, NULL); - mesh->drop(); - updateNodePos(); -} - -void TestCAO::removeFromScene(bool permanent) -{ - if (!m_node) - return; - - m_node->remove(); - m_node = NULL; -} - -void TestCAO::updateLight(u8 light_at_pos) -{ -} - -v3s16 TestCAO::getLightPosition() -{ - return floatToInt(m_position, BS); -} - -void TestCAO::updateNodePos() -{ - if (!m_node) - return; - - m_node->setPosition(m_position); - //m_node->setRotation(v3f(0, 45, 0)); -} - -void TestCAO::step(float dtime, ClientEnvironment *env) -{ - if(m_node) - { - v3f rot = m_node->getRotation(); - //infostream<<"dtime="<>cmd; - if(cmd == 0) - { - v3f newpos; - is>>newpos.X; - is>>newpos.Y; - is>>newpos.Z; - m_position = newpos; - updateNodePos(); - } -} - -/* - GenericCAO -*/ - -#include "genericobject.h" - -GenericCAO::GenericCAO(Client *client, ClientEnvironment *env): - ClientActiveObject(0, client, env) -{ - if (client == NULL) { - ClientActiveObject::registerType(getType(), create); - } else { - m_client = client; - } -} - -bool GenericCAO::getCollisionBox(aabb3f *toset) const -{ - if (m_prop.physical) - { - //update collision box - toset->MinEdge = m_prop.collisionbox.MinEdge * BS; - toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS; - - toset->MinEdge += m_position; - toset->MaxEdge += m_position; - - return true; - } - - return false; -} - -bool GenericCAO::collideWithObjects() const -{ - return m_prop.collideWithObjects; -} - -void GenericCAO::initialize(const std::string &data) -{ - infostream<<"GenericCAO: Got init data"<getLocalPlayer(); - if (player && strcmp(player->getName(), m_name.c_str()) == 0) { - m_is_local_player = true; - m_is_visible = false; - player->setCAO(this); - } - } -} - -void GenericCAO::processInitData(const std::string &data) -{ - std::istringstream is(data, std::ios::binary); - int num_messages = 0; - // version - u8 version = readU8(is); - // check version - if (version == 1) { // In PROTOCOL_VERSION 14 - m_name = deSerializeString(is); - m_is_player = readU8(is); - m_id = readU16(is); - m_position = readV3F1000(is); - m_rotation = readV3F1000(is); - m_hp = readS16(is); - num_messages = readU8(is); - } else { - errorstream<<"GenericCAO: Unsupported init data version" - <getAbsolutePosition(); - - return m_position; - } - return pos_translator.val_current; -} - -const bool GenericCAO::isImmortal() -{ - return itemgroup_get(getGroups(), "immortal"); -} - -scene::ISceneNode* GenericCAO::getSceneNode() -{ - if (m_meshnode) { - return m_meshnode; - } - - if (m_animated_meshnode) { - return m_animated_meshnode; - } - - if (m_wield_meshnode) { - return m_wield_meshnode; - } - - if (m_spritenode) { - return m_spritenode; - } - return NULL; -} - -scene::IAnimatedMeshSceneNode* GenericCAO::getAnimatedMeshSceneNode() -{ - return m_animated_meshnode; -} - -void GenericCAO::setChildrenVisible(bool toset) -{ - for (u16 cao_id : m_children) { - GenericCAO *obj = m_env->getGenericCAO(cao_id); - if (obj) { - obj->setVisible(toset); - } - } -} - -void GenericCAO::setAttachments() -{ - updateAttachments(); -} - -ClientActiveObject* GenericCAO::getParent() const -{ - ClientActiveObject *obj = NULL; - - u16 attached_id = m_env->attachement_parent_ids[getId()]; - - if ((attached_id != 0) && - (attached_id != getId())) { - obj = m_env->getActiveObject(attached_id); - } - return obj; -} - -void GenericCAO::removeFromScene(bool permanent) -{ - // Should be true when removing the object permanently and false when refreshing (eg: updating visuals) - if((m_env != NULL) && (permanent)) - { - for (u16 ci : m_children) { - if (m_env->attachement_parent_ids[ci] == getId()) { - m_env->attachement_parent_ids[ci] = 0; - } - } - m_children.clear(); - - m_env->attachement_parent_ids[getId()] = 0; - - LocalPlayer* player = m_env->getLocalPlayer(); - if (this == player->parent) { - player->parent = NULL; - player->isAttached = false; - } - } - - if (m_meshnode) { - m_meshnode->remove(); - m_meshnode->drop(); - m_meshnode = NULL; - } else if (m_animated_meshnode) { - m_animated_meshnode->remove(); - m_animated_meshnode->drop(); - m_animated_meshnode = NULL; - } else if (m_wield_meshnode) { - m_wield_meshnode->remove(); - m_wield_meshnode->drop(); - m_wield_meshnode = NULL; - } else if (m_spritenode) { - m_spritenode->remove(); - m_spritenode->drop(); - m_spritenode = NULL; - } - - if (m_nametag) { - m_client->getCamera()->removeNametag(m_nametag); - m_nametag = NULL; - } -} - -void GenericCAO::addToScene(ITextureSource *tsrc) -{ - m_smgr = RenderingEngine::get_scene_manager(); - - if (getSceneNode() != NULL) { - return; - } - - m_visuals_expired = false; - - if (!m_prop.is_visible) { - return; - } - - video::E_MATERIAL_TYPE material_type = (m_prop.use_texture_alpha) ? - video::EMT_TRANSPARENT_ALPHA_CHANNEL : video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; - - if (m_prop.visual == "sprite") { - infostream<<"GenericCAO::addToScene(): single_sprite"<addBillboardSceneNode( - NULL, v2f(1, 1), v3f(0,0,0), -1); - m_spritenode->grab(); - m_spritenode->setMaterialTexture(0, - tsrc->getTextureForMesh("unknown_node.png")); - m_spritenode->setMaterialFlag(video::EMF_LIGHTING, false); - m_spritenode->setMaterialFlag(video::EMF_BILINEAR_FILTER, false); - m_spritenode->setMaterialType(material_type); - m_spritenode->setMaterialFlag(video::EMF_FOG_ENABLE, true); - u8 li = m_last_light; - m_spritenode->setColor(video::SColor(255,li,li,li)); - m_spritenode->setSize(m_prop.visual_size*BS); - { - const float txs = 1.0 / 1; - const float tys = 1.0 / 1; - setBillboardTextureMatrix(m_spritenode, - txs, tys, 0, 0); - } - } else if (m_prop.visual == "upright_sprite") { - scene::SMesh *mesh = new scene::SMesh(); - double dx = BS * m_prop.visual_size.X / 2; - double dy = BS * m_prop.visual_size.Y / 2; - u8 li = m_last_light; - video::SColor c(255, li, li, li); - - { // Front - scene::IMeshBuffer *buf = new scene::SMeshBuffer(); - video::S3DVertex vertices[4] = { - video::S3DVertex(-dx, -dy, 0, 0,0,0, c, 1,1), - video::S3DVertex( dx, -dy, 0, 0,0,0, c, 0,1), - video::S3DVertex( dx, dy, 0, 0,0,0, c, 0,0), - video::S3DVertex(-dx, dy, 0, 0,0,0, c, 1,0), - }; - if (m_is_player) { - // Move minimal Y position to 0 (feet position) - for (video::S3DVertex &vertex : vertices) - vertex.Pos.Y += dy; - } - u16 indices[] = {0,1,2,2,3,0}; - buf->append(vertices, 4, indices, 6); - // Set material - buf->getMaterial().setFlag(video::EMF_LIGHTING, false); - buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false); - buf->getMaterial().setFlag(video::EMF_FOG_ENABLE, true); - buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; - // Add to mesh - mesh->addMeshBuffer(buf); - buf->drop(); - } - { // Back - scene::IMeshBuffer *buf = new scene::SMeshBuffer(); - video::S3DVertex vertices[4] = { - video::S3DVertex( dx,-dy, 0, 0,0,0, c, 1,1), - video::S3DVertex(-dx,-dy, 0, 0,0,0, c, 0,1), - video::S3DVertex(-dx, dy, 0, 0,0,0, c, 0,0), - video::S3DVertex( dx, dy, 0, 0,0,0, c, 1,0), - }; - if (m_is_player) { - // Move minimal Y position to 0 (feet position) - for (video::S3DVertex &vertex : vertices) - vertex.Pos.Y += dy; - } - u16 indices[] = {0,1,2,2,3,0}; - buf->append(vertices, 4, indices, 6); - // Set material - buf->getMaterial().setFlag(video::EMF_LIGHTING, false); - buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false); - buf->getMaterial().setFlag(video::EMF_FOG_ENABLE, true); - buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; - // Add to mesh - mesh->addMeshBuffer(buf); - buf->drop(); - } - m_meshnode = RenderingEngine::get_scene_manager()->addMeshSceneNode(mesh, NULL); - 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") { - infostream<<"GenericCAO::addToScene(): cube"<addMeshSceneNode(mesh, NULL); - m_meshnode->grab(); - mesh->drop(); - - m_meshnode->setScale(v3f(m_prop.visual_size.X, - m_prop.visual_size.Y, - m_prop.visual_size.X)); - u8 li = m_last_light; - setMeshColor(m_meshnode->getMesh(), video::SColor(255,li,li,li)); - - m_meshnode->setMaterialFlag(video::EMF_LIGHTING, false); - m_meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, false); - m_meshnode->setMaterialType(material_type); - m_meshnode->setMaterialFlag(video::EMF_FOG_ENABLE, true); - } - else if(m_prop.visual == "mesh") { - infostream<<"GenericCAO::addToScene(): mesh"<getMesh(m_prop.mesh, true); - if(mesh) - { - m_animated_meshnode = RenderingEngine::get_scene_manager()-> - addAnimatedMeshSceneNode(mesh, NULL); - m_animated_meshnode->grab(); - mesh->drop(); // The scene node took hold of it - m_animated_meshnode->animateJoints(); // Needed for some animations - m_animated_meshnode->setScale(v3f(m_prop.visual_size.X, - m_prop.visual_size.Y, - m_prop.visual_size.X)); - u8 li = m_last_light; - - // set vertex colors to ensure alpha is set - setMeshColor(m_animated_meshnode->getMesh(), video::SColor(255,li,li,li)); - - setAnimatedMeshColor(m_animated_meshnode, video::SColor(255,li,li,li)); - - m_animated_meshnode->setMaterialFlag(video::EMF_LIGHTING, true); - m_animated_meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, false); - m_animated_meshnode->setMaterialType(material_type); - m_animated_meshnode->setMaterialFlag(video::EMF_FOG_ENABLE, true); - m_animated_meshnode->setMaterialFlag(video::EMF_BACK_FACE_CULLING, - m_prop.backface_culling); - } - else - errorstream<<"GenericCAO::addToScene(): Could not load mesh "<idef(); - item = ItemStack(m_prop.textures[0], 1, 0, idef); - } - } else { - infostream << "serialized form: " << m_prop.wield_item << std::endl; - item.deSerialize(m_prop.wield_item, m_client->idef()); - } - m_wield_meshnode = new WieldMeshSceneNode( - RenderingEngine::get_scene_manager(), -1); - m_wield_meshnode->setItem(item, m_client); - - m_wield_meshnode->setScale( - v3f(m_prop.visual_size.X / 2, m_prop.visual_size.Y / 2, - m_prop.visual_size.X / 2)); - u8 li = m_last_light; - m_wield_meshnode->setColor(video::SColor(255, li, li, li)); - } else { - infostream<<"GenericCAO::addToScene(): \""<getCamera()->addNametag(node, - m_prop.nametag, m_prop.nametag_color, - pos); - } - - updateNodePos(); - updateAnimation(); - updateBonePosition(); - updateAttachments(); -} - -void GenericCAO::updateLight(u8 light_at_pos) -{ - // Don't update light of attached one - if (getParent() != NULL) { - return; - } - - updateLightNoCheck(light_at_pos); - - // Update light of all children - for (u16 i : m_children) { - ClientActiveObject *obj = m_env->getActiveObject(i); - if (obj) { - obj->updateLightNoCheck(light_at_pos); - } - } -} - -void GenericCAO::updateLightNoCheck(u8 light_at_pos) -{ - if (m_glow < 0) - return; - - u8 li = decode_light(light_at_pos + m_glow); - if (li != m_last_light) { - m_last_light = li; - video::SColor color(255,li,li,li); - if (m_meshnode) { - setMeshColor(m_meshnode->getMesh(), color); - } else if (m_animated_meshnode) { - setAnimatedMeshColor(m_animated_meshnode, color); - } else if (m_wield_meshnode) { - m_wield_meshnode->setColor(color); - } else if (m_spritenode) { - m_spritenode->setColor(color); - } - } -} - -v3s16 GenericCAO::getLightPosition() -{ - if (m_is_player) - return floatToInt(m_position + v3f(0, 0.5 * BS, 0), BS); - - return floatToInt(m_position, BS); -} - -void GenericCAO::updateNodePos() -{ - if (getParent() != NULL) - return; - - scene::ISceneNode *node = getSceneNode(); - - if (node) { - v3s16 camera_offset = m_env->getCameraOffset(); - node->setPosition(pos_translator.val_current - intToFloat(camera_offset, BS)); - if (node != m_spritenode) { // rotate if not a sprite - v3f rot = m_is_local_player ? -m_rotation : -rot_translator.val_current; - node->setRotation(rot); - } - } -} - -void GenericCAO::step(float dtime, ClientEnvironment *env) -{ - // Handel model of local player instantly to prevent lags - if (m_is_local_player) { - LocalPlayer *player = m_env->getLocalPlayer(); - if (m_is_visible) { - int old_anim = player->last_animation; - float old_anim_speed = player->last_animation_speed; - m_position = player->getPosition(); - m_rotation.Y = wrapDegrees_0_360(player->getYaw()); - m_velocity = v3f(0,0,0); - m_acceleration = v3f(0,0,0); - pos_translator.val_current = m_position; - rot_translator.val_current = m_rotation; - const PlayerControl &controls = player->getPlayerControl(); - - bool walking = false; - if (controls.up || controls.down || controls.left || controls.right || - controls.forw_move_joystick_axis != 0.f || - controls.sidew_move_joystick_axis != 0.f) - walking = true; - - f32 new_speed = player->local_animation_speed; - v2s32 new_anim = v2s32(0,0); - bool allow_update = false; - - // increase speed if using fast or flying fast - if((g_settings->getBool("fast_move") && - m_client->checkLocalPrivilege("fast")) && - (controls.aux1 || - (!player->touching_ground && - g_settings->getBool("free_move") && - m_client->checkLocalPrivilege("fly")))) - new_speed *= 1.5; - // slowdown speed if sneeking - if (controls.sneak && walking) - new_speed /= 2; - - if (walking && (controls.LMB || controls.RMB)) { - new_anim = player->local_animations[3]; - player->last_animation = WD_ANIM; - } else if(walking) { - new_anim = player->local_animations[1]; - player->last_animation = WALK_ANIM; - } else if(controls.LMB || controls.RMB) { - new_anim = player->local_animations[2]; - player->last_animation = DIG_ANIM; - } - - // Apply animations if input detected and not attached - // or set idle animation - if ((new_anim.X + new_anim.Y) > 0 && !player->isAttached) { - allow_update = true; - m_animation_range = new_anim; - m_animation_speed = new_speed; - player->last_animation_speed = m_animation_speed; - } else { - player->last_animation = NO_ANIM; - - if (old_anim != NO_ANIM) { - m_animation_range = player->local_animations[0]; - updateAnimation(); - } - } - - // Update local player animations - if ((player->last_animation != old_anim || - m_animation_speed != old_anim_speed) && - player->last_animation != NO_ANIM && allow_update) - updateAnimation(); - - } - } - - if (m_visuals_expired && m_smgr) { - m_visuals_expired = false; - - // Attachments, part 1: All attached objects must be unparented first, - // or Irrlicht causes a segmentation fault - for (auto ci = m_children.begin(); ci != m_children.end();) { - if (m_env->attachement_parent_ids[*ci] != getId()) { - ci = m_children.erase(ci); - continue; - } - ClientActiveObject *obj = m_env->getActiveObject(*ci); - if (obj) { - scene::ISceneNode *child_node = obj->getSceneNode(); - if (child_node) - child_node->setParent(m_smgr->getRootSceneNode()); - } - ++ci; - } - - removeFromScene(false); - addToScene(m_client->tsrc()); - - // Attachments, part 2: Now that the parent has been refreshed, put its attachments back - for (u16 cao_id : m_children) { - // Get the object of the child - ClientActiveObject *obj = m_env->getActiveObject(cao_id); - if (obj) - obj->setAttachments(); - } - } - - // Make sure m_is_visible is always applied - scene::ISceneNode *node = getSceneNode(); - if (node) - node->setVisible(m_is_visible); - - if(getParent() != NULL) // Attachments should be glued to their parent by Irrlicht - { - // Set these for later - m_position = getPosition(); - m_velocity = v3f(0,0,0); - m_acceleration = v3f(0,0,0); - pos_translator.val_current = m_position; - - if(m_is_local_player) // Update local player attachment position - { - LocalPlayer *player = m_env->getLocalPlayer(); - player->overridePosition = getParent()->getPosition(); - m_env->getLocalPlayer()->parent = getParent(); - } - } else { - rot_translator.translate(dtime); - v3f lastpos = pos_translator.val_current; - - if(m_prop.physical) - { - aabb3f box = m_prop.collisionbox; - box.MinEdge *= BS; - box.MaxEdge *= BS; - collisionMoveResult moveresult; - f32 pos_max_d = BS*0.125; // Distance per iteration - v3f p_pos = m_position; - v3f p_velocity = m_velocity; - moveresult = collisionMoveSimple(env,env->getGameDef(), - pos_max_d, box, m_prop.stepheight, dtime, - &p_pos, &p_velocity, m_acceleration, - this, m_prop.collideWithObjects); - // Apply results - m_position = p_pos; - m_velocity = p_velocity; - - bool is_end_position = moveresult.collides; - pos_translator.update(m_position, is_end_position, dtime); - pos_translator.translate(dtime); - updateNodePos(); - } else { - m_position += dtime * m_velocity + 0.5 * dtime * dtime * m_acceleration; - m_velocity += dtime * m_acceleration; - pos_translator.update(m_position, pos_translator.aim_is_end, - pos_translator.anim_time); - pos_translator.translate(dtime); - updateNodePos(); - } - - float moved = lastpos.getDistanceFrom(pos_translator.val_current); - m_step_distance_counter += moved; - if (m_step_distance_counter > 1.5f * BS) { - m_step_distance_counter = 0.0f; - if (!m_is_local_player && m_prop.makes_footstep_sound) { - const NodeDefManager *ndef = m_client->ndef(); - v3s16 p = floatToInt(getPosition() + - v3f(0.0f, (m_prop.collisionbox.MinEdge.Y - 0.5f) * BS, 0.0f), BS); - MapNode n = m_env->getMap().getNodeNoEx(p); - SimpleSoundSpec spec = ndef->get(n).sound_footstep; - // Reduce footstep gain, as non-local-player footsteps are - // somehow louder. - spec.gain *= 0.6f; - m_client->sound()->playSoundAt(spec, false, getPosition()); - } - } - } - - m_anim_timer += dtime; - if(m_anim_timer >= m_anim_framelength) - { - m_anim_timer -= m_anim_framelength; - m_anim_frame++; - if(m_anim_frame >= m_anim_num_frames) - m_anim_frame = 0; - } - - updateTexturePos(); - - if(m_reset_textures_timer >= 0) - { - m_reset_textures_timer -= dtime; - if(m_reset_textures_timer <= 0) { - m_reset_textures_timer = -1; - updateTextures(m_previous_texture_modifier); - } - } - if (!getParent() && std::fabs(m_prop.automatic_rotate) > 0.001) { - m_rotation.Y += dtime * m_prop.automatic_rotate * 180 / M_PI; - rot_translator.val_current = m_rotation; - updateNodePos(); - } - - if (!getParent() && m_prop.automatic_face_movement_dir && - (fabs(m_velocity.Z) > 0.001 || fabs(m_velocity.X) > 0.001)) { - - float target_yaw = atan2(m_velocity.Z, m_velocity.X) * 180 / M_PI - + m_prop.automatic_face_movement_dir_offset; - float max_rotation_delta = - dtime * m_prop.automatic_face_movement_max_rotation_per_sec; - - wrappedApproachShortest(m_rotation.Y, target_yaw, max_rotation_delta, 360.f); - rot_translator.val_current = m_rotation; - - updateNodePos(); - } -} - -void GenericCAO::updateTexturePos() -{ - if(m_spritenode) - { - scene::ICameraSceneNode* camera = - m_spritenode->getSceneManager()->getActiveCamera(); - if(!camera) - return; - v3f cam_to_entity = m_spritenode->getAbsolutePosition() - - camera->getAbsolutePosition(); - cam_to_entity.normalize(); - - int row = m_tx_basepos.Y; - int col = m_tx_basepos.X; - - if (m_tx_select_horiz_by_yawpitch) { - if (cam_to_entity.Y > 0.75) - col += 5; - else if (cam_to_entity.Y < -0.75) - col += 4; - else { - float mob_dir = - atan2(cam_to_entity.Z, cam_to_entity.X) / M_PI * 180.; - float dir = mob_dir - m_rotation.Y; - dir = wrapDegrees_180(dir); - if (std::fabs(wrapDegrees_180(dir - 0)) <= 45.1f) - col += 2; - else if(std::fabs(wrapDegrees_180(dir - 90)) <= 45.1f) - col += 3; - else if(std::fabs(wrapDegrees_180(dir - 180)) <= 45.1f) - col += 0; - else if(std::fabs(wrapDegrees_180(dir + 90)) <= 45.1f) - col += 1; - else - col += 4; - } - } - - // Animation goes downwards - row += m_anim_frame; - - float txs = m_tx_size.X; - float tys = m_tx_size.Y; - setBillboardTextureMatrix(m_spritenode, txs, tys, col, row); - } -} - -void GenericCAO::updateTextures(std::string mod) -{ - ITextureSource *tsrc = m_client->tsrc(); - - bool use_trilinear_filter = g_settings->getBool("trilinear_filter"); - bool use_bilinear_filter = g_settings->getBool("bilinear_filter"); - bool use_anisotropic_filter = g_settings->getBool("anisotropic_filter"); - - m_previous_texture_modifier = m_current_texture_modifier; - m_current_texture_modifier = mod; - m_glow = m_prop.glow; - - video::E_MATERIAL_TYPE material_type = (m_prop.use_texture_alpha) ? - video::EMT_TRANSPARENT_ALPHA_CHANNEL : video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; - - if (m_spritenode) { - if (m_prop.visual == "sprite") { - std::string texturestring = "unknown_node.png"; - if (!m_prop.textures.empty()) - texturestring = m_prop.textures[0]; - texturestring += mod; - m_spritenode->getMaterial(0).MaterialType = material_type; - m_spritenode->setMaterialTexture(0, - tsrc->getTextureForMesh(texturestring)); - - // 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()) { - m_spritenode->getMaterial(0).AmbientColor = m_prop.colors[0]; - m_spritenode->getMaterial(0).DiffuseColor = m_prop.colors[0]; - m_spritenode->getMaterial(0).SpecularColor = m_prop.colors[0]; - } - - m_spritenode->getMaterial(0).setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter); - m_spritenode->getMaterial(0).setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter); - m_spritenode->getMaterial(0).setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter); - } - } - - if (m_animated_meshnode) { - if (m_prop.visual == "mesh") { - for (u32 i = 0; i < m_prop.textures.size() && - i < m_animated_meshnode->getMaterialCount(); ++i) { - std::string texturestring = m_prop.textures[i]; - if (texturestring.empty()) - continue; // Empty texture string means don't modify that material - texturestring += mod; - video::ITexture* texture = tsrc->getTextureForMesh(texturestring); - if (!texture) { - errorstream<<"GenericCAO::updateTextures(): Could not load texture "<getMaterial(i); - material.MaterialType = material_type; - material.TextureLayer[0].Texture = texture; - material.setFlag(video::EMF_LIGHTING, true); - material.setFlag(video::EMF_BILINEAR_FILTER, false); - material.setFlag(video::EMF_BACK_FACE_CULLING, m_prop.backface_culling); - - // don't filter low-res textures, makes them look blurry - // player models have a res of 64 - const core::dimension2d &size = texture->getOriginalSize(); - const u32 res = std::min(size.Height, size.Width); - use_trilinear_filter &= res > 64; - use_bilinear_filter &= res > 64; - - m_animated_meshnode->getMaterial(i) - .setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter); - m_animated_meshnode->getMaterial(i) - .setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter); - m_animated_meshnode->getMaterial(i) - .setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter); - } - for (u32 i = 0; i < m_prop.colors.size() && - i < m_animated_meshnode->getMaterialCount(); ++i) - { - // 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. - m_animated_meshnode->getMaterial(i).AmbientColor = m_prop.colors[i]; - m_animated_meshnode->getMaterial(i).DiffuseColor = m_prop.colors[i]; - m_animated_meshnode->getMaterial(i).SpecularColor = m_prop.colors[i]; - } - } - } - if(m_meshnode) - { - if(m_prop.visual == "cube") - { - for (u32 i = 0; i < 6; ++i) - { - std::string texturestring = "unknown_node.png"; - if(m_prop.textures.size() > i) - texturestring = m_prop.textures[i]; - texturestring += mod; - - - // Set material flags and texture - video::SMaterial& material = m_meshnode->getMaterial(i); - material.MaterialType = material_type; - material.setFlag(video::EMF_LIGHTING, false); - material.setFlag(video::EMF_BILINEAR_FILTER, false); - material.setTexture(0, - tsrc->getTextureForMesh(texturestring)); - material.getTextureMatrix(0).makeIdentity(); - - // 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() > i) - { - m_meshnode->getMaterial(i).AmbientColor = m_prop.colors[i]; - m_meshnode->getMaterial(i).DiffuseColor = m_prop.colors[i]; - m_meshnode->getMaterial(i).SpecularColor = m_prop.colors[i]; - } - - m_meshnode->getMaterial(i).setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter); - m_meshnode->getMaterial(i).setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter); - m_meshnode->getMaterial(i).setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter); - } - } else if (m_prop.visual == "upright_sprite") { - scene::IMesh *mesh = m_meshnode->getMesh(); - { - std::string tname = "unknown_object.png"; - if (!m_prop.textures.empty()) - tname = m_prop.textures[0]; - tname += mod; - scene::IMeshBuffer *buf = mesh->getMeshBuffer(0); - buf->getMaterial().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]; - } - - 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); - } - { - std::string tname = "unknown_object.png"; - if (m_prop.textures.size() >= 2) - tname = m_prop.textures[1]; - else if (!m_prop.textures.empty()) - tname = m_prop.textures[0]; - tname += mod; - scene::IMeshBuffer *buf = mesh->getMeshBuffer(1); - buf->getMaterial().setTexture(0, - 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]; - setMeshColor(mesh, 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]; - setMeshColor(mesh, 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); - } - } - } -} - -void GenericCAO::updateAnimation() -{ - if (!m_animated_meshnode) - return; - - if (m_animated_meshnode->getStartFrame() != m_animation_range.X || - m_animated_meshnode->getEndFrame() != m_animation_range.Y) - m_animated_meshnode->setFrameLoop(m_animation_range.X, m_animation_range.Y); - if (m_animated_meshnode->getAnimationSpeed() != m_animation_speed) - m_animated_meshnode->setAnimationSpeed(m_animation_speed); - m_animated_meshnode->setTransitionTime(m_animation_blend); -// Requires Irrlicht 1.8 or greater -#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR >= 8) || IRRLICHT_VERSION_MAJOR > 1 - if (m_animated_meshnode->getLoopMode() != m_animation_loop) - m_animated_meshnode->setLoopMode(m_animation_loop); -#endif -} - -void GenericCAO::updateAnimationSpeed() -{ - if (!m_animated_meshnode) - return; - - m_animated_meshnode->setAnimationSpeed(m_animation_speed); -} - -void GenericCAO::updateBonePosition() -{ - if (m_bone_position.empty() || !m_animated_meshnode) - return; - - m_animated_meshnode->setJointMode(irr::scene::EJUOR_CONTROL); // To write positions to the mesh on render - for(std::unordered_map>::const_iterator - ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) { - std::string bone_name = (*ii).first; - v3f bone_pos = (*ii).second.X; - v3f bone_rot = (*ii).second.Y; - irr::scene::IBoneSceneNode* bone = m_animated_meshnode->getJointNode(bone_name.c_str()); - if(bone) - { - bone->setPosition(bone_pos); - bone->setRotation(bone_rot); - } - } -} - -void GenericCAO::updateAttachments() -{ - - if (!getParent()) { // Detach or don't attach - scene::ISceneNode *node = getSceneNode(); - if (node) { - v3f old_position = node->getAbsolutePosition(); - v3f old_rotation = node->getRotation(); - node->setParent(m_smgr->getRootSceneNode()); - node->setPosition(old_position); - node->setRotation(old_rotation); - node->updateAbsolutePosition(); - } - if (m_is_local_player) { - LocalPlayer *player = m_env->getLocalPlayer(); - player->isAttached = false; - } - } - else // Attach - { - scene::ISceneNode *my_node = getSceneNode(); - - scene::ISceneNode *parent_node = getParent()->getSceneNode(); - scene::IAnimatedMeshSceneNode *parent_animated_mesh_node = - getParent()->getAnimatedMeshSceneNode(); - if (parent_animated_mesh_node && !m_attachment_bone.empty()) { - parent_node = parent_animated_mesh_node->getJointNode(m_attachment_bone.c_str()); - } - - if (my_node && parent_node) { - my_node->setParent(parent_node); - my_node->setPosition(m_attachment_position); - my_node->setRotation(m_attachment_rotation); - my_node->updateAbsolutePosition(); - } - if (m_is_local_player) { - LocalPlayer *player = m_env->getLocalPlayer(); - player->isAttached = true; - } - } -} - -void GenericCAO::processMessage(const std::string &data) -{ - //infostream<<"GenericCAO: Got message"<getLocalPlayer(); - player->makes_footstep_sound = m_prop.makes_footstep_sound; - aabb3f collision_box = m_prop.collisionbox; - collision_box.MinEdge *= BS; - collision_box.MaxEdge *= BS; - player->setCollisionbox(collision_box); - player->setEyeHeight(m_prop.eye_height); - player->setZoomFOV(m_prop.zoom_fov); - } - - if ((m_is_player && !m_is_local_player) && m_prop.nametag.empty()) - m_prop.nametag = m_name; - - expireVisuals(); - } else if (cmd == GENERIC_CMD_UPDATE_POSITION) { - // Not sent by the server if this object is an attachment. - // We might however get here if the server notices the object being detached before the client. - m_position = readV3F1000(is); - m_velocity = readV3F1000(is); - m_acceleration = readV3F1000(is); - - if (std::fabs(m_prop.automatic_rotate) < 0.001f) - m_rotation = readV3F1000(is); - else - readV3F1000(is); - - m_rotation = wrapDegrees_0_360_v3f(m_rotation); - bool do_interpolate = readU8(is); - bool is_end_position = readU8(is); - float update_interval = readF1000(is); - - // Place us a bit higher if we're physical, to not sink into - // the ground due to sucky collision detection... - if(m_prop.physical) - m_position += v3f(0,0.002,0); - - if(getParent() != NULL) // Just in case - return; - - if(do_interpolate) - { - if(!m_prop.physical) - pos_translator.update(m_position, is_end_position, update_interval); - } else { - pos_translator.init(m_position); - } - rot_translator.update(m_rotation, false, update_interval); - updateNodePos(); - } else if (cmd == GENERIC_CMD_SET_TEXTURE_MOD) { - std::string mod = deSerializeString(is); - - // immediatly reset a engine issued texture modifier if a mod sends a different one - if (m_reset_textures_timer > 0) { - m_reset_textures_timer = -1; - updateTextures(m_previous_texture_modifier); - } - updateTextures(mod); - } else if (cmd == GENERIC_CMD_SET_SPRITE) { - v2s16 p = readV2S16(is); - int num_frames = readU16(is); - float framelength = readF1000(is); - bool select_horiz_by_yawpitch = readU8(is); - - m_tx_basepos = p; - m_anim_num_frames = num_frames; - m_anim_framelength = framelength; - m_tx_select_horiz_by_yawpitch = select_horiz_by_yawpitch; - - updateTexturePos(); - } else if (cmd == GENERIC_CMD_SET_PHYSICS_OVERRIDE) { - float override_speed = readF1000(is); - float override_jump = readF1000(is); - float override_gravity = readF1000(is); - // these are sent inverted so we get true when the server sends nothing - bool sneak = !readU8(is); - bool sneak_glitch = !readU8(is); - bool new_move = !readU8(is); - - - if(m_is_local_player) - { - LocalPlayer *player = m_env->getLocalPlayer(); - player->physics_override_speed = override_speed; - player->physics_override_jump = override_jump; - player->physics_override_gravity = override_gravity; - player->physics_override_sneak = sneak; - player->physics_override_sneak_glitch = sneak_glitch; - player->physics_override_new_move = new_move; - } - } else if (cmd == GENERIC_CMD_SET_ANIMATION) { - // TODO: change frames send as v2s32 value - v2f range = readV2F1000(is); - if (!m_is_local_player) { - m_animation_range = v2s32((s32)range.X, (s32)range.Y); - m_animation_speed = readF1000(is); - m_animation_blend = readF1000(is); - // these are sent inverted so we get true when the server sends nothing - m_animation_loop = !readU8(is); - updateAnimation(); - } else { - LocalPlayer *player = m_env->getLocalPlayer(); - if(player->last_animation == NO_ANIM) - { - m_animation_range = v2s32((s32)range.X, (s32)range.Y); - m_animation_speed = readF1000(is); - m_animation_blend = readF1000(is); - // these are sent inverted so we get true when the server sends nothing - m_animation_loop = !readU8(is); - } - // update animation only if local animations present - // and received animation is unknown (except idle animation) - bool is_known = false; - for (int i = 1;i<4;i++) - { - if(m_animation_range.Y == player->local_animations[i].Y) - is_known = true; - } - if(!is_known || - (player->local_animations[1].Y + player->local_animations[2].Y < 1)) - { - updateAnimation(); - } - } - } else if (cmd == GENERIC_CMD_SET_ANIMATION_SPEED) { - m_animation_speed = readF1000(is); - updateAnimationSpeed(); - } else if (cmd == GENERIC_CMD_SET_BONE_POSITION) { - std::string bone = deSerializeString(is); - v3f position = readV3F1000(is); - v3f rotation = readV3F1000(is); - m_bone_position[bone] = core::vector2d(position, rotation); - - updateBonePosition(); - } else if (cmd == GENERIC_CMD_ATTACH_TO) { - u16 parent_id = readS16(is); - u16 &old_parent_id = m_env->attachement_parent_ids[getId()]; - if (parent_id != old_parent_id) { - if (GenericCAO *old_parent = m_env->getGenericCAO(old_parent_id)) { - old_parent->m_children.erase(std::remove( - m_children.begin(), m_children.end(), - getId()), m_children.end()); - } - if (GenericCAO *new_parent = m_env->getGenericCAO(parent_id)) - new_parent->m_children.push_back(getId()); - - old_parent_id = parent_id; - } - - m_attachment_bone = deSerializeString(is); - m_attachment_position = readV3F1000(is); - m_attachment_rotation = readV3F1000(is); - - // localplayer itself can't be attached to localplayer - if (!m_is_local_player) { - m_attached_to_local = getParent() != NULL && getParent()->isLocalPlayer(); - // Objects attached to the local player should be hidden by default - m_is_visible = !m_attached_to_local; - } - - updateAttachments(); - } else if (cmd == GENERIC_CMD_PUNCHED) { - /*s16 damage =*/ readS16(is); - s16 result_hp = readS16(is); - - // Use this instead of the send damage to not interfere with prediction - s16 damage = m_hp - result_hp; - - m_hp = result_hp; - - if (damage > 0) - { - if (m_hp <= 0) - { - // TODO: Execute defined fast response - // As there is no definition, make a smoke puff - ClientSimpleObject *simple = createSmokePuff( - m_smgr, m_env, m_position, - m_prop.visual_size * BS); - m_env->addSimpleObject(simple); - } else if (m_reset_textures_timer < 0) { - // TODO: Execute defined fast response - // Flashing shall suffice as there is no definition - m_reset_textures_timer = 0.05; - if(damage >= 2) - m_reset_textures_timer += 0.05 * damage; - updateTextures(m_current_texture_modifier + "^[brighten"); - } - } - } else if (cmd == GENERIC_CMD_UPDATE_ARMOR_GROUPS) { - m_armor_groups.clear(); - int armor_groups_size = readU16(is); - for(int i=0; inametag_color = m_prop.nametag_color; - v3f pos; - pos.Y = m_prop.collisionbox.MaxEdge.Y + 0.3f; - m_nametag->nametag_pos = pos; - } - } else if (cmd == GENERIC_CMD_SPAWN_INFANT) { - u16 child_id = readU16(is); - u8 type = readU8(is); - - if (GenericCAO *childobj = m_env->getGenericCAO(child_id)) { - childobj->processInitData(deSerializeLongString(is)); - } else { - m_env->addActiveObject(child_id, type, deSerializeLongString(is)); - } - } else { - warningstream << FUNCTION_NAME - << ": unknown command or outdated client \"" - << +cmd << "\"" << std::endl; - } -} - -/* \pre punchitem != NULL - */ -bool GenericCAO::directReportPunch(v3f dir, const ItemStack *punchitem, - float time_from_last_punch) -{ - assert(punchitem); // pre-condition - const ToolCapabilities *toolcap = - &punchitem->getToolCapabilities(m_client->idef()); - PunchDamageResult result = getPunchDamage( - m_armor_groups, - toolcap, - punchitem, - time_from_last_punch); - - if(result.did_punch && result.damage != 0) - { - if(result.damage < m_hp) - { - m_hp -= result.damage; - } else { - m_hp = 0; - // TODO: Execute defined fast response - // As there is no definition, make a smoke puff - ClientSimpleObject *simple = createSmokePuff( - m_smgr, m_env, m_position, - m_prop.visual_size * BS); - m_env->addSimpleObject(simple); - } - // TODO: Execute defined fast response - // Flashing shall suffice as there is no definition - if (m_reset_textures_timer < 0) { - m_reset_textures_timer = 0.05; - if (result.damage >= 2) - m_reset_textures_timer += 0.05 * result.damage; - updateTextures(m_current_texture_modifier + "^[brighten"); - } - } - - return false; -} - -std::string GenericCAO::debugInfoText() -{ - std::ostringstream os(std::ios::binary); - os<<"GenericCAO hp="<first<<"="<second<<", "; - } - os<<"}"; - return os.str(); -} - -// Prototype -GenericCAO proto_GenericCAO(NULL, NULL); diff --git a/src/content_cao.h b/src/content_cao.h deleted file mode 100644 index 98932137e..000000000 --- a/src/content_cao.h +++ /dev/null @@ -1,236 +0,0 @@ -/* -Minetest -Copyright (C) 2010-2013 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#pragma once - -#include -#include "irrlichttypes_extrabloated.h" -#include "clientobject.h" -#include "object_properties.h" -#include "itemgroup.h" -#include "constants.h" - -class Camera; -class Client; -struct Nametag; - -/* - SmoothTranslator -*/ - -template -struct SmoothTranslator -{ - T val_old; - T val_current; - T val_target; - f32 anim_time = 0; - f32 anim_time_counter = 0; - bool aim_is_end = true; - - SmoothTranslator() = default; - - void init(T current); - - void update(T new_target, bool is_end_position = false, - float update_interval = -1); - - void translate(f32 dtime); -}; - -struct SmoothTranslatorWrapped : SmoothTranslator -{ - void translate(f32 dtime); -}; - -struct SmoothTranslatorWrappedv3f : SmoothTranslator -{ - void translate(f32 dtime); -}; - -class GenericCAO : public ClientActiveObject -{ -private: - // Only set at initialization - std::string m_name = ""; - bool m_is_player = false; - bool m_is_local_player = false; - // Property-ish things - ObjectProperties m_prop; - // - scene::ISceneManager *m_smgr = nullptr; - Client *m_client = nullptr; - aabb3f m_selection_box = aabb3f(-BS/3.,-BS/3.,-BS/3., BS/3.,BS/3.,BS/3.); - scene::IMeshSceneNode *m_meshnode = nullptr; - scene::IAnimatedMeshSceneNode *m_animated_meshnode = nullptr; - WieldMeshSceneNode *m_wield_meshnode = nullptr; - scene::IBillboardSceneNode *m_spritenode = nullptr; - Nametag *m_nametag = nullptr; - v3f m_position = v3f(0.0f, 10.0f * BS, 0); - v3f m_velocity; - v3f m_acceleration; - v3f m_rotation; - s16 m_hp = 1; - SmoothTranslator pos_translator; - SmoothTranslatorWrappedv3f rot_translator; - // Spritesheet/animation stuff - v2f m_tx_size = v2f(1,1); - v2s16 m_tx_basepos; - bool m_initial_tx_basepos_set = false; - bool m_tx_select_horiz_by_yawpitch = false; - v2s32 m_animation_range; - float m_animation_speed = 15.0f; - float m_animation_blend = 0.0f; - bool m_animation_loop = true; - // stores position and rotation for each bone name - std::unordered_map> m_bone_position; - std::string m_attachment_bone = ""; - v3f m_attachment_position; - v3f m_attachment_rotation; - bool m_attached_to_local = false; - int m_anim_frame = 0; - int m_anim_num_frames = 1; - float m_anim_framelength = 0.2f; - float m_anim_timer = 0.0f; - ItemGroupList m_armor_groups; - float m_reset_textures_timer = -1.0f; - // stores texture modifier before punch update - std::string m_previous_texture_modifier = ""; - // last applied texture modifier - std::string m_current_texture_modifier = ""; - bool m_visuals_expired = false; - float m_step_distance_counter = 0.0f; - u8 m_last_light = 255; - bool m_is_visible = false; - s8 m_glow = 0; - - std::vector m_children; - -public: - GenericCAO(Client *client, ClientEnvironment *env); - - ~GenericCAO(); - - static ClientActiveObject* create(Client *client, ClientEnvironment *env) - { - return new GenericCAO(client, env); - } - - inline ActiveObjectType getType() const - { - return ACTIVEOBJECT_TYPE_GENERIC; - } - inline const ItemGroupList &getGroups() const - { - return m_armor_groups; - } - void initialize(const std::string &data); - - void processInitData(const std::string &data); - - bool getCollisionBox(aabb3f *toset) const; - - bool collideWithObjects() const; - - virtual bool getSelectionBox(aabb3f *toset) const; - - v3f getPosition(); - - inline const v3f &getRotation() - { - return m_rotation; - } - - const bool isImmortal(); - - scene::ISceneNode *getSceneNode(); - - scene::IAnimatedMeshSceneNode *getAnimatedMeshSceneNode(); - - inline f32 getStepHeight() const - { - return m_prop.stepheight; - } - - inline bool isLocalPlayer() const - { - return m_is_local_player; - } - - inline bool isVisible() const - { - return m_is_visible; - } - - inline void setVisible(bool toset) - { - m_is_visible = toset; - } - - void setChildrenVisible(bool toset); - - ClientActiveObject *getParent() const; - - void setAttachments(); - - void removeFromScene(bool permanent); - - void addToScene(ITextureSource *tsrc); - - inline void expireVisuals() - { - m_visuals_expired = true; - } - - void updateLight(u8 light_at_pos); - - void updateLightNoCheck(u8 light_at_pos); - - v3s16 getLightPosition(); - - void updateNodePos(); - - void step(float dtime, ClientEnvironment *env); - - void updateTexturePos(); - - // std::string copy is mandatory as mod can be a class member and there is a swap - // on those class members... do NOT pass by reference - void updateTextures(std::string mod); - - void updateAnimation(); - - void updateAnimationSpeed(); - - void updateBonePosition(); - - void updateAttachments(); - - void processMessage(const std::string &data); - - bool directReportPunch(v3f dir, const ItemStack *punchitem=NULL, - float time_from_last_punch=1000000); - - std::string debugInfoText(); - - std::string infoText() - { - return m_prop.infotext; - } -}; diff --git a/src/content_cso.cpp b/src/content_cso.cpp deleted file mode 100644 index 04c503f44..000000000 --- a/src/content_cso.cpp +++ /dev/null @@ -1,77 +0,0 @@ -/* -Minetest -Copyright (C) 2013 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include "content_cso.h" -#include -#include "client/tile.h" -#include "clientenvironment.h" -#include "client.h" -#include "map.h" - -class SmokePuffCSO: public ClientSimpleObject -{ - float m_age = 0.0f; - scene::IBillboardSceneNode *m_spritenode = nullptr; -public: - SmokePuffCSO(scene::ISceneManager *smgr, - ClientEnvironment *env, const v3f &pos, const v2f &size) - { - infostream<<"SmokePuffCSO: constructing"<addBillboardSceneNode( - NULL, v2f(1,1), pos, -1); - m_spritenode->setMaterialTexture(0, - env->getGameDef()->tsrc()->getTextureForMesh("smoke_puff.png")); - m_spritenode->setMaterialFlag(video::EMF_LIGHTING, false); - m_spritenode->setMaterialFlag(video::EMF_BILINEAR_FILTER, false); - //m_spritenode->setMaterialType(video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF); - m_spritenode->setMaterialType(video::EMT_TRANSPARENT_ALPHA_CHANNEL); - m_spritenode->setMaterialFlag(video::EMF_FOG_ENABLE, true); - m_spritenode->setColor(video::SColor(255,0,0,0)); - m_spritenode->setVisible(true); - m_spritenode->setSize(size); - /* Update brightness */ - u8 light; - bool pos_ok; - MapNode n = env->getMap().getNodeNoEx(floatToInt(pos, BS), &pos_ok); - light = pos_ok ? decode_light(n.getLightBlend(env->getDayNightRatio(), - env->getGameDef()->ndef())) - : 64; - video::SColor color(255,light,light,light); - m_spritenode->setColor(color); - } - virtual ~SmokePuffCSO() - { - infostream<<"SmokePuffCSO: destructing"<remove(); - } - void step(float dtime) - { - m_age += dtime; - if(m_age > 1.0){ - m_to_be_removed = true; - } - } -}; - -ClientSimpleObject* createSmokePuff(scene::ISceneManager *smgr, - ClientEnvironment *env, v3f pos, v2f size) -{ - return new SmokePuffCSO(smgr, env, pos, size); -} - diff --git a/src/content_cso.h b/src/content_cso.h deleted file mode 100644 index cc9213175..000000000 --- a/src/content_cso.h +++ /dev/null @@ -1,26 +0,0 @@ -/* -Minetest -Copyright (C) 2013 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#pragma once - -#include "irrlichttypes_extrabloated.h" -#include "clientsimpleobject.h" - -ClientSimpleObject* createSmokePuff(scene::ISceneManager *smgr, - ClientEnvironment *env, v3f pos, v2f size); diff --git a/src/content_mapblock.cpp b/src/content_mapblock.cpp deleted file mode 100644 index 4a0df6171..000000000 --- a/src/content_mapblock.cpp +++ /dev/null @@ -1,1430 +0,0 @@ -/* -Minetest -Copyright (C) 2010-2013 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include "content_mapblock.h" -#include "util/numeric.h" -#include "util/directiontables.h" -#include "mapblock_mesh.h" -#include "settings.h" -#include "nodedef.h" -#include "client/tile.h" -#include "mesh.h" -#include -#include "client/meshgen/collector.h" -#include "client/renderingengine.h" -#include "client.h" -#include "noise.h" - -// Distance of light extrapolation (for oversized nodes) -// After this distance, it gives up and considers light level constant -#define SMOOTH_LIGHTING_OVERSIZE 1.0 - -// Node edge count (for glasslike-framed) -#define FRAMED_EDGE_COUNT 12 - -// Node neighbor count, including edge-connected, but not vertex-connected -// (for glasslike-framed) -// Corresponding offsets are listed in g_27dirs -#define FRAMED_NEIGHBOR_COUNT 18 - -static const v3s16 light_dirs[8] = { - v3s16(-1, -1, -1), - v3s16(-1, -1, 1), - v3s16(-1, 1, -1), - v3s16(-1, 1, 1), - v3s16( 1, -1, -1), - v3s16( 1, -1, 1), - v3s16( 1, 1, -1), - v3s16( 1, 1, 1), -}; - -// Standard index set to make a quad on 4 vertices -static constexpr u16 quad_indices[] = {0, 1, 2, 2, 3, 0}; - -const std::string MapblockMeshGenerator::raillike_groupname = "connect_to_raillike"; - -MapblockMeshGenerator::MapblockMeshGenerator(MeshMakeData *input, MeshCollector *output) -{ - data = input; - collector = output; - - nodedef = data->m_client->ndef(); - meshmanip = RenderingEngine::get_scene_manager()->getMeshManipulator(); - - enable_mesh_cache = g_settings->getBool("enable_mesh_cache") && - !data->m_smooth_lighting; // Mesh cache is not supported with smooth lighting - - blockpos_nodes = data->m_blockpos * MAP_BLOCKSIZE; -} - -void MapblockMeshGenerator::useTile(int index, u8 set_flags, u8 reset_flags, bool special) -{ - if (special) - getSpecialTile(index, &tile, p == data->m_crack_pos_relative); - else - getTile(index, &tile); - if (!data->m_smooth_lighting) - color = encode_light(light, f->light_source); - - for (auto &layer : tile.layers) { - layer.material_flags |= set_flags; - layer.material_flags &= ~reset_flags; - } -} - -// Returns a tile, ready for use, non-rotated. -void MapblockMeshGenerator::getTile(int index, TileSpec *tile) -{ - getNodeTileN(n, p, index, data, *tile); -} - -// Returns a tile, ready for use, rotated according to the node facedir. -void MapblockMeshGenerator::getTile(v3s16 direction, TileSpec *tile) -{ - getNodeTile(n, p, direction, data, *tile); -} - -// Returns a special tile, ready for use, non-rotated. -void MapblockMeshGenerator::getSpecialTile(int index, TileSpec *tile, bool apply_crack) -{ - *tile = f->special_tiles[index]; - TileLayer *top_layer = nullptr; - - for (auto &layernum : tile->layers) { - TileLayer *layer = &layernum; - if (layer->texture_id == 0) - continue; - top_layer = layer; - if (!layer->has_color) - n.getColor(*f, &layer->color); - } - - if (apply_crack) - top_layer->material_flags |= MATERIAL_FLAG_CRACK; -} - -void MapblockMeshGenerator::drawQuad(v3f *coords, const v3s16 &normal, - float vertical_tiling) -{ - const v2f tcoords[4] = {v2f(0.0, 0.0), v2f(1.0, 0.0), - v2f(1.0, vertical_tiling), v2f(0.0, vertical_tiling)}; - video::S3DVertex vertices[4]; - bool shade_face = !f->light_source && (normal != v3s16(0, 0, 0)); - v3f normal2(normal.X, normal.Y, normal.Z); - for (int j = 0; j < 4; j++) { - vertices[j].Pos = coords[j] + origin; - vertices[j].Normal = normal2; - if (data->m_smooth_lighting) - vertices[j].Color = blendLightColor(coords[j]); - else - vertices[j].Color = color; - if (shade_face) - applyFacesShading(vertices[j].Color, normal2); - vertices[j].TCoords = tcoords[j]; - } - collector->append(tile, vertices, 4, quad_indices, 6); -} - -// Create a cuboid. -// tiles - the tiles (materials) to use (for all 6 faces) -// tilecount - number of entries in tiles, 1<=tilecount<=6 -// lights - vertex light levels. The order is the same as in light_dirs. -// NULL may be passed if smooth lighting is disabled. -// txc - texture coordinates - this is a list of texture coordinates -// for the opposite corners of each face - therefore, there -// 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). -void MapblockMeshGenerator::drawCuboid(const aabb3f &box, - TileSpec *tiles, int tilecount, const LightInfo *lights, const f32 *txc) -{ - assert(tilecount >= 1 && tilecount <= 6); // pre-condition - - v3f min = box.MinEdge; - v3f max = box.MaxEdge; - - video::SColor colors[6]; - if (!data->m_smooth_lighting) { - for (int face = 0; face != 6; ++face) { - colors[face] = encode_light(light, f->light_source); - } - if (!f->light_source) { - applyFacesShading(colors[0], v3f(0, 1, 0)); - applyFacesShading(colors[1], v3f(0, -1, 0)); - applyFacesShading(colors[2], v3f(1, 0, 0)); - applyFacesShading(colors[3], v3f(-1, 0, 0)); - applyFacesShading(colors[4], v3f(0, 0, 1)); - applyFacesShading(colors[5], v3f(0, 0, -1)); - } - } - - video::S3DVertex vertices[24] = { - // top - video::S3DVertex(min.X, max.Y, max.Z, 0, 1, 0, colors[0], txc[0], txc[1]), - video::S3DVertex(max.X, max.Y, max.Z, 0, 1, 0, colors[0], txc[2], txc[1]), - video::S3DVertex(max.X, max.Y, min.Z, 0, 1, 0, colors[0], txc[2], txc[3]), - video::S3DVertex(min.X, max.Y, min.Z, 0, 1, 0, colors[0], txc[0], txc[3]), - // bottom - video::S3DVertex(min.X, min.Y, min.Z, 0, -1, 0, colors[1], txc[4], txc[5]), - video::S3DVertex(max.X, min.Y, min.Z, 0, -1, 0, colors[1], txc[6], txc[5]), - video::S3DVertex(max.X, min.Y, max.Z, 0, -1, 0, colors[1], txc[6], txc[7]), - video::S3DVertex(min.X, min.Y, max.Z, 0, -1, 0, colors[1], txc[4], txc[7]), - // right - video::S3DVertex(max.X, max.Y, min.Z, 1, 0, 0, colors[2], txc[ 8], txc[9]), - video::S3DVertex(max.X, max.Y, max.Z, 1, 0, 0, colors[2], txc[10], txc[9]), - video::S3DVertex(max.X, min.Y, max.Z, 1, 0, 0, colors[2], txc[10], txc[11]), - video::S3DVertex(max.X, min.Y, min.Z, 1, 0, 0, colors[2], txc[ 8], txc[11]), - // left - video::S3DVertex(min.X, max.Y, max.Z, -1, 0, 0, colors[3], txc[12], txc[13]), - video::S3DVertex(min.X, max.Y, min.Z, -1, 0, 0, colors[3], txc[14], txc[13]), - video::S3DVertex(min.X, min.Y, min.Z, -1, 0, 0, colors[3], txc[14], txc[15]), - video::S3DVertex(min.X, min.Y, max.Z, -1, 0, 0, colors[3], txc[12], txc[15]), - // back - video::S3DVertex(max.X, max.Y, max.Z, 0, 0, 1, colors[4], txc[16], txc[17]), - video::S3DVertex(min.X, max.Y, max.Z, 0, 0, 1, colors[4], txc[18], txc[17]), - video::S3DVertex(min.X, min.Y, max.Z, 0, 0, 1, colors[4], txc[18], txc[19]), - video::S3DVertex(max.X, min.Y, max.Z, 0, 0, 1, colors[4], txc[16], txc[19]), - // front - video::S3DVertex(min.X, max.Y, min.Z, 0, 0, -1, colors[5], txc[20], txc[21]), - video::S3DVertex(max.X, max.Y, min.Z, 0, 0, -1, colors[5], txc[22], txc[21]), - video::S3DVertex(max.X, min.Y, min.Z, 0, 0, -1, colors[5], txc[22], txc[23]), - video::S3DVertex(min.X, min.Y, min.Z, 0, 0, -1, colors[5], txc[20], txc[23]), - }; - - static const u8 light_indices[24] = { - 3, 7, 6, 2, - 0, 4, 5, 1, - 6, 7, 5, 4, - 3, 2, 0, 1, - 7, 3, 1, 5, - 2, 6, 4, 0 - }; - - for (int face = 0; face < 6; face++) { - int tileindex = MYMIN(face, tilecount - 1); - const TileSpec &tile = tiles[tileindex]; - for (int j = 0; j < 4; j++) { - video::S3DVertex &vertex = vertices[face * 4 + j]; - v2f &tcoords = vertex.TCoords; - switch (tile.rotation) { - case 0: - break; - case 1: // R90 - tcoords.rotateBy(90, irr::core::vector2df(0, 0)); - break; - case 2: // R180 - tcoords.rotateBy(180, irr::core::vector2df(0, 0)); - break; - case 3: // R270 - tcoords.rotateBy(270, irr::core::vector2df(0, 0)); - break; - case 4: // FXR90 - tcoords.X = 1.0 - tcoords.X; - tcoords.rotateBy(90, irr::core::vector2df(0, 0)); - break; - case 5: // FXR270 - tcoords.X = 1.0 - tcoords.X; - tcoords.rotateBy(270, irr::core::vector2df(0, 0)); - break; - case 6: // FYR90 - tcoords.Y = 1.0 - tcoords.Y; - tcoords.rotateBy(90, irr::core::vector2df(0, 0)); - break; - case 7: // FYR270 - tcoords.Y = 1.0 - tcoords.Y; - tcoords.rotateBy(270, irr::core::vector2df(0, 0)); - break; - case 8: // FX - tcoords.X = 1.0 - tcoords.X; - break; - case 9: // FY - tcoords.Y = 1.0 - tcoords.Y; - break; - default: - break; - } - } - } - - if (data->m_smooth_lighting) { - for (int j = 0; j < 24; ++j) { - video::S3DVertex &vertex = vertices[j]; - vertex.Color = encode_light( - lights[light_indices[j]].getPair(MYMAX(0.0f, vertex.Normal.Y)), - f->light_source); - if (!f->light_source) - applyFacesShading(vertex.Color, vertex.Normal); - } - } - - // Add to mesh collector - for (int k = 0; k < 6; ++k) { - int tileindex = MYMIN(k, tilecount - 1); - collector->append(tiles[tileindex], vertices + 4 * k, 4, quad_indices, 6); - } -} - -// Gets the base lighting values for a node -void MapblockMeshGenerator::getSmoothLightFrame() -{ - for (int k = 0; k < 8; ++k) - frame.sunlight[k] = false; - for (int k = 0; k < 8; ++k) { - LightPair light(getSmoothLightTransparent(blockpos_nodes + p, light_dirs[k], data)); - frame.lightsDay[k] = light.lightDay; - frame.lightsNight[k] = light.lightNight; - // If there is direct sunlight and no ambient occlusion at some corner, - // mark the vertical edge (top and bottom corners) containing it. - if (light.lightDay == 255) { - frame.sunlight[k] = true; - frame.sunlight[k ^ 2] = true; - } - } -} - -// Calculates vertex light level -// vertex_pos - vertex position in the node (coordinates are clamped to [0.0, 1.0] or so) -LightInfo MapblockMeshGenerator::blendLight(const v3f &vertex_pos) -{ - // Light levels at (logical) node corners are known. Here, - // trilinear interpolation is used to calculate light level - // at a given point in the node. - f32 x = core::clamp(vertex_pos.X / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, 1.0 + SMOOTH_LIGHTING_OVERSIZE); - f32 y = core::clamp(vertex_pos.Y / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, 1.0 + SMOOTH_LIGHTING_OVERSIZE); - f32 z = core::clamp(vertex_pos.Z / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, 1.0 + SMOOTH_LIGHTING_OVERSIZE); - f32 lightDay = 0.0; // daylight - f32 lightNight = 0.0; - f32 lightBoosted = 0.0; // daylight + direct sunlight, if any - for (int k = 0; k < 8; ++k) { - f32 dx = (k & 4) ? x : 1 - x; - f32 dy = (k & 2) ? y : 1 - y; - f32 dz = (k & 1) ? z : 1 - z; - // Use direct sunlight (255), if any; use daylight otherwise. - f32 light_boosted = frame.sunlight[k] ? 255 : frame.lightsDay[k]; - lightDay += dx * dy * dz * frame.lightsDay[k]; - lightNight += dx * dy * dz * frame.lightsNight[k]; - lightBoosted += dx * dy * dz * light_boosted; - } - return LightInfo{lightDay, lightNight, lightBoosted}; -} - -// Calculates vertex color to be used in mapblock mesh -// vertex_pos - vertex position in the node (coordinates are clamped to [0.0, 1.0] or so) -// tile_color - node's tile color -video::SColor MapblockMeshGenerator::blendLightColor(const v3f &vertex_pos) -{ - LightInfo light = blendLight(vertex_pos); - return encode_light(light.getPair(), f->light_source); -} - -video::SColor MapblockMeshGenerator::blendLightColor(const v3f &vertex_pos, - const v3f &vertex_normal) -{ - LightInfo light = blendLight(vertex_pos); - video::SColor color = encode_light(light.getPair(MYMAX(0.0f, vertex_normal.Y)), f->light_source); - if (!f->light_source) - applyFacesShading(color, vertex_normal); - return color; -} - -void MapblockMeshGenerator::generateCuboidTextureCoords(const aabb3f &box, f32 *coords) -{ - f32 tx1 = (box.MinEdge.X / BS) + 0.5; - f32 ty1 = (box.MinEdge.Y / BS) + 0.5; - f32 tz1 = (box.MinEdge.Z / BS) + 0.5; - f32 tx2 = (box.MaxEdge.X / BS) + 0.5; - f32 ty2 = (box.MaxEdge.Y / BS) + 0.5; - f32 tz2 = (box.MaxEdge.Z / BS) + 0.5; - f32 txc[24] = { - tx1, 1 - tz2, tx2, 1 - tz1, // up - tx1, tz1, tx2, tz2, // down - tz1, 1 - ty2, tz2, 1 - ty1, // right - 1 - tz2, 1 - ty2, 1 - tz1, 1 - ty1, // left - 1 - tx2, 1 - ty2, 1 - tx1, 1 - ty1, // back - tx1, 1 - ty2, tx2, 1 - ty1, // front - }; - for (int i = 0; i != 24; ++i) - coords[i] = txc[i]; -} - -void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, const f32 *txc, - TileSpec *tiles, int tile_count) -{ - f32 texture_coord_buf[24]; - f32 dx1 = box.MinEdge.X; - f32 dy1 = box.MinEdge.Y; - f32 dz1 = box.MinEdge.Z; - f32 dx2 = box.MaxEdge.X; - f32 dy2 = box.MaxEdge.Y; - f32 dz2 = box.MaxEdge.Z; - box.MinEdge += origin; - box.MaxEdge += origin; - if (!txc) { - generateCuboidTextureCoords(box, texture_coord_buf); - txc = texture_coord_buf; - } - if (!tiles) { - tiles = &tile; - tile_count = 1; - } - if (data->m_smooth_lighting) { - LightInfo lights[8]; - for (int j = 0; j < 8; ++j) { - v3f d; - d.X = (j & 4) ? dx2 : dx1; - d.Y = (j & 2) ? dy2 : dy1; - d.Z = (j & 1) ? dz2 : dz1; - lights[j] = blendLight(d); - } - drawCuboid(box, tiles, tile_count, lights, txc); - } else { - drawCuboid(box, tiles, tile_count, nullptr, txc); - } -} - -void MapblockMeshGenerator::prepareLiquidNodeDrawing() -{ - getSpecialTile(0, &tile_liquid_top); - getSpecialTile(1, &tile_liquid); - - MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(p.X, p.Y + 1, p.Z)); - MapNode nbottom = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(p.X, p.Y - 1, p.Z)); - c_flowing = nodedef->getId(f->liquid_alternative_flowing); - c_source = nodedef->getId(f->liquid_alternative_source); - top_is_same_liquid = (ntop.getContent() == c_flowing) || (ntop.getContent() == c_source); - draw_liquid_bottom = (nbottom.getContent() != c_flowing) && (nbottom.getContent() != c_source); - if (draw_liquid_bottom) { - const ContentFeatures &f2 = nodedef->get(nbottom.getContent()); - if (f2.solidness > 1) - draw_liquid_bottom = false; - } - - if (data->m_smooth_lighting) - return; // don't need to pre-compute anything in this case - - if (f->light_source != 0) { - // If this liquid emits light and doesn't contain light, draw - // it at what it emits, for an increased effect - u8 e = decode_light(f->light_source); - light = LightPair(std::max(e, light.lightDay), std::max(e, light.lightNight)); - } else if (nodedef->get(ntop).param_type == CPT_LIGHT) { - // Otherwise, use the light of the node on top if possible - light = LightPair(getInteriorLight(ntop, 0, nodedef)); - } - - color_liquid_top = encode_light(light, f->light_source); - color = encode_light(light, f->light_source); -} - -void MapblockMeshGenerator::getLiquidNeighborhood() -{ - u8 range = rangelim(nodedef->get(c_flowing).liquid_range, 1, 8); - - for (int w = -1; w <= 1; w++) - for (int u = -1; u <= 1; u++) { - NeighborData &neighbor = liquid_neighbors[w + 1][u + 1]; - v3s16 p2 = p + v3s16(u, 0, w); - MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2); - neighbor.content = n2.getContent(); - neighbor.level = -0.5 * BS; - neighbor.is_same_liquid = false; - neighbor.top_is_same_liquid = false; - - if (neighbor.content == CONTENT_IGNORE) - continue; - - if (neighbor.content == c_source) { - neighbor.is_same_liquid = true; - neighbor.level = 0.5 * BS; - } else if (neighbor.content == c_flowing) { - neighbor.is_same_liquid = true; - u8 liquid_level = (n2.param2 & LIQUID_LEVEL_MASK); - if (liquid_level <= LIQUID_LEVEL_MAX + 1 - range) - liquid_level = 0; - else - liquid_level -= (LIQUID_LEVEL_MAX + 1 - range); - neighbor.level = (-0.5 + (liquid_level + 0.5) / range) * BS; - } - - // Check node above neighbor. - // NOTE: This doesn't get executed if neighbor - // doesn't exist - p2.Y++; - n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2); - if (n2.getContent() == c_source || n2.getContent() == c_flowing) - neighbor.top_is_same_liquid = true; - } -} - -void MapblockMeshGenerator::calculateCornerLevels() -{ - for (int k = 0; k < 2; k++) - for (int i = 0; i < 2; i++) - corner_levels[k][i] = getCornerLevel(i, k); -} - -f32 MapblockMeshGenerator::getCornerLevel(int i, int k) -{ - float sum = 0; - int count = 0; - int air_count = 0; - for (int dk = 0; dk < 2; dk++) - for (int di = 0; di < 2; di++) { - NeighborData &neighbor_data = liquid_neighbors[k + dk][i + di]; - content_t content = neighbor_data.content; - - // If top is liquid, draw starting from top of node - if (neighbor_data.top_is_same_liquid) - return 0.5 * BS; - - // Source always has the full height - if (content == c_source) - return 0.5 * BS; - - // Flowing liquid has level information - if (content == c_flowing) { - sum += neighbor_data.level; - count++; - } else if (content == CONTENT_AIR) { - air_count++; - if (air_count >= 2) - return -0.5 * BS + 0.2; - } - } - if (count > 0) - return sum / count; - return 0; -} - -void MapblockMeshGenerator::drawLiquidSides() -{ - struct LiquidFaceDesc { - v3s16 dir; // XZ - v3s16 p[2]; // XZ only; 1 means +, 0 means - - }; - struct UV { - int u, v; - }; - static const LiquidFaceDesc base_faces[4] = { - {v3s16( 1, 0, 0), {v3s16(1, 0, 1), v3s16(1, 0, 0)}}, - {v3s16(-1, 0, 0), {v3s16(0, 0, 0), v3s16(0, 0, 1)}}, - {v3s16( 0, 0, 1), {v3s16(0, 0, 1), v3s16(1, 0, 1)}}, - {v3s16( 0, 0, -1), {v3s16(1, 0, 0), v3s16(0, 0, 0)}}, - }; - static const UV base_vertices[4] = { - {0, 1}, - {1, 1}, - {1, 0}, - {0, 0} - }; - - for (const auto &face : base_faces) { - const NeighborData &neighbor = liquid_neighbors[face.dir.Z + 1][face.dir.X + 1]; - - // No face between nodes of the same liquid, unless there is node - // at the top to which it should be connected. Again, unless the face - // there would be inside the liquid - if (neighbor.is_same_liquid) { - if (!top_is_same_liquid) - continue; - if (neighbor.top_is_same_liquid) - continue; - } - - const ContentFeatures &neighbor_features = nodedef->get(neighbor.content); - // Don't draw face if neighbor is blocking the view - if (neighbor_features.solidness == 2) - continue; - - video::S3DVertex vertices[4]; - for (int j = 0; j < 4; j++) { - const UV &vertex = base_vertices[j]; - const v3s16 &base = face.p[vertex.u]; - v3f pos; - pos.X = (base.X - 0.5) * BS; - pos.Z = (base.Z - 0.5) * BS; - if (vertex.v) - pos.Y = neighbor.is_same_liquid ? corner_levels[base.Z][base.X] : -0.5 * BS; - else - pos.Y = !top_is_same_liquid ? corner_levels[base.Z][base.X] : 0.5 * BS; - if (data->m_smooth_lighting) - color = blendLightColor(pos); - pos += origin; - vertices[j] = video::S3DVertex(pos.X, pos.Y, pos.Z, 0, 0, 0, color, vertex.u, vertex.v); - }; - collector->append(tile_liquid, vertices, 4, quad_indices, 6); - } -} - -void MapblockMeshGenerator::drawLiquidTop() -{ - // To get backface culling right, the vertices need to go - // clockwise around the front of the face. And we happened to - // calculate corner levels in exact reverse order. - static const int corner_resolve[4][2] = {{0, 1}, {1, 1}, {1, 0}, {0, 0}}; - - video::S3DVertex vertices[4] = { - video::S3DVertex(-BS / 2, 0, BS / 2, 0, 0, 0, color_liquid_top, 0, 1), - video::S3DVertex( BS / 2, 0, BS / 2, 0, 0, 0, color_liquid_top, 1, 1), - video::S3DVertex( BS / 2, 0, -BS / 2, 0, 0, 0, color_liquid_top, 1, 0), - video::S3DVertex(-BS / 2, 0, -BS / 2, 0, 0, 0, color_liquid_top, 0, 0), - }; - - for (int i = 0; i < 4; i++) { - int u = corner_resolve[i][0]; - int w = corner_resolve[i][1]; - vertices[i].Pos.Y += corner_levels[w][u]; - if (data->m_smooth_lighting) - vertices[i].Color = blendLightColor(vertices[i].Pos); - vertices[i].Pos += origin; - } - - // Default downwards-flowing texture animation goes from - // -Z towards +Z, thus the direction is +Z. - // Rotate texture to make animation go in flow direction - // Positive if liquid moves towards +Z - f32 dz = (corner_levels[0][0] + corner_levels[0][1]) - - (corner_levels[1][0] + corner_levels[1][1]); - // Positive if liquid moves towards +X - f32 dx = (corner_levels[0][0] + corner_levels[1][0]) - - (corner_levels[0][1] + corner_levels[1][1]); - f32 tcoord_angle = atan2(dz, dx) * core::RADTODEG; - v2f tcoord_center(0.5, 0.5); - v2f tcoord_translate(blockpos_nodes.Z + p.Z, blockpos_nodes.X + p.X); - tcoord_translate.rotateBy(tcoord_angle); - tcoord_translate.X -= floor(tcoord_translate.X); - tcoord_translate.Y -= floor(tcoord_translate.Y); - - for (video::S3DVertex &vertex : vertices) { - vertex.TCoords.rotateBy(tcoord_angle, tcoord_center); - vertex.TCoords += tcoord_translate; - } - - std::swap(vertices[0].TCoords, vertices[2].TCoords); - - collector->append(tile_liquid_top, vertices, 4, quad_indices, 6); -} - -void MapblockMeshGenerator::drawLiquidBottom() -{ - video::S3DVertex vertices[4] = { - video::S3DVertex(-BS / 2, -BS / 2, -BS / 2, 0, 0, 0, color_liquid_top, 0, 0), - video::S3DVertex( BS / 2, -BS / 2, -BS / 2, 0, 0, 0, color_liquid_top, 1, 0), - video::S3DVertex( BS / 2, -BS / 2, BS / 2, 0, 0, 0, color_liquid_top, 1, 1), - video::S3DVertex(-BS / 2, -BS / 2, BS / 2, 0, 0, 0, color_liquid_top, 0, 1), - }; - - for (int i = 0; i < 4; i++) { - if (data->m_smooth_lighting) - vertices[i].Color = blendLightColor(vertices[i].Pos); - vertices[i].Pos += origin; - } - - collector->append(tile_liquid_top, vertices, 4, quad_indices, 6); -} - -void MapblockMeshGenerator::drawLiquidNode() -{ - prepareLiquidNodeDrawing(); - getLiquidNeighborhood(); - calculateCornerLevels(); - drawLiquidSides(); - if (!top_is_same_liquid) - drawLiquidTop(); - if (draw_liquid_bottom) - drawLiquidBottom(); -} - -void MapblockMeshGenerator::drawGlasslikeNode() -{ - useTile(0, 0, 0); - - for (int face = 0; face < 6; face++) { - // Check this neighbor - v3s16 dir = g_6dirs[face]; - v3s16 neighbor_pos = blockpos_nodes + p + dir; - MapNode neighbor = data->m_vmanip.getNodeNoExNoEmerge(neighbor_pos); - // Don't make face if neighbor is of same type - if (neighbor.getContent() == n.getContent()) - continue; - // Face at Z- - v3f vertices[4] = { - v3f(-BS / 2, BS / 2, -BS / 2), - v3f( BS / 2, BS / 2, -BS / 2), - v3f( BS / 2, -BS / 2, -BS / 2), - v3f(-BS / 2, -BS / 2, -BS / 2), - }; - - for (v3f &vertex : vertices) { - switch (face) { - case D6D_ZP: - vertex.rotateXZBy(180); break; - case D6D_YP: - vertex.rotateYZBy( 90); break; - case D6D_XP: - vertex.rotateXZBy( 90); break; - case D6D_ZN: - vertex.rotateXZBy( 0); break; - case D6D_YN: - vertex.rotateYZBy(-90); break; - case D6D_XN: - vertex.rotateXZBy(-90); break; - } - } - drawQuad(vertices, dir); - } -} - -void MapblockMeshGenerator::drawGlasslikeFramedNode() -{ - TileSpec tiles[6]; - for (int face = 0; face < 6; face++) - getTile(g_6dirs[face], &tiles[face]); - - if (!data->m_smooth_lighting) - color = encode_light(light, f->light_source); - - TileSpec glass_tiles[6]; - for (auto &glass_tile : glass_tiles) - glass_tile = tiles[4]; - - u8 param2 = n.getParam2(); - bool H_merge = !(param2 & 128); - bool V_merge = !(param2 & 64); - param2 &= 63; - - static const float a = BS / 2.0f; - static const float g = a - 0.03f; - static const float b = 0.876f * (BS / 2.0f); - - static const aabb3f frame_edges[FRAMED_EDGE_COUNT] = { - aabb3f( b, b, -a, a, a, a), // y+ - aabb3f(-a, b, -a, -b, a, a), // y+ - aabb3f( b, -a, -a, a, -b, a), // y- - aabb3f(-a, -a, -a, -b, -b, a), // y- - aabb3f( b, -a, b, a, a, a), // x+ - aabb3f( b, -a, -a, a, a, -b), // x+ - aabb3f(-a, -a, b, -b, a, a), // x- - aabb3f(-a, -a, -a, -b, a, -b), // x- - aabb3f(-a, b, b, a, a, a), // z+ - aabb3f(-a, -a, b, a, -b, a), // z+ - aabb3f(-a, -a, -a, a, -b, -b), // z- - aabb3f(-a, b, -a, a, a, -b), // z- - }; - - // tables of neighbour (connect if same type and merge allowed), - // checked with g_26dirs - - // 1 = connect, 0 = face visible - bool nb[FRAMED_NEIGHBOR_COUNT] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; - - // 1 = check - static const bool check_nb_vertical [FRAMED_NEIGHBOR_COUNT] = - {0,1,0,0,1,0, 0,0,0,0, 0,0,0,0, 0,0,0,0}; - static const bool check_nb_horizontal [FRAMED_NEIGHBOR_COUNT] = - {1,0,1,1,0,1, 0,0,0,0, 1,1,1,1, 0,0,0,0}; - static const bool check_nb_all [FRAMED_NEIGHBOR_COUNT] = - {1,1,1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1}; - const bool *check_nb = check_nb_all; - - // neighbours checks for frames visibility - if (H_merge || V_merge) { - if (!H_merge) - check_nb = check_nb_vertical; // vertical-only merge - if (!V_merge) - check_nb = check_nb_horizontal; // horizontal-only merge - content_t current = n.getContent(); - for (int i = 0; i < FRAMED_NEIGHBOR_COUNT; i++) { - if (!check_nb[i]) - continue; - v3s16 n2p = blockpos_nodes + p + g_26dirs[i]; - MapNode n2 = data->m_vmanip.getNodeNoEx(n2p); - content_t n2c = n2.getContent(); - if (n2c == current) - nb[i] = 1; - } - } - - // edge visibility - - static const u8 nb_triplet[FRAMED_EDGE_COUNT][3] = { - {1, 2, 7}, {1, 5, 6}, {4, 2, 15}, {4, 5, 14}, - {2, 0, 11}, {2, 3, 13}, {5, 0, 10}, {5, 3, 12}, - {0, 1, 8}, {0, 4, 16}, {3, 4, 17}, {3, 1, 9}, - }; - - tile = tiles[1]; - for (int edge = 0; edge < FRAMED_EDGE_COUNT; edge++) { - bool edge_invisible; - if (nb[nb_triplet[edge][2]]) - edge_invisible = nb[nb_triplet[edge][0]] & nb[nb_triplet[edge][1]]; - else - edge_invisible = nb[nb_triplet[edge][0]] ^ nb[nb_triplet[edge][1]]; - if (edge_invisible) - continue; - drawAutoLightedCuboid(frame_edges[edge]); - } - - for (int face = 0; face < 6; face++) { - if (nb[face]) - continue; - - tile = glass_tiles[face]; - // Face at Z- - v3f vertices[4] = { - v3f(-a, a, -g), - v3f( a, a, -g), - v3f( a, -a, -g), - v3f(-a, -a, -g), - }; - - for (v3f &vertex : vertices) { - switch (face) { - case D6D_ZP: - vertex.rotateXZBy(180); break; - case D6D_YP: - vertex.rotateYZBy( 90); break; - case D6D_XP: - vertex.rotateXZBy( 90); break; - case D6D_ZN: - vertex.rotateXZBy( 0); break; - case D6D_YN: - vertex.rotateYZBy(-90); break; - case D6D_XN: - vertex.rotateXZBy(-90); break; - } - } - v3s16 dir = g_6dirs[face]; - drawQuad(vertices, dir); - } - - // Optionally render internal liquid level defined by param2 - // Liquid is textured with 1 tile defined in nodedef 'special_tiles' - if (param2 > 0 && f->param_type_2 == CPT2_GLASSLIKE_LIQUID_LEVEL && - f->special_tiles[0].layers[0].texture) { - // Internal liquid level has param2 range 0 .. 63, - // convert it to -0.5 .. 0.5 - float vlev = (param2 / 63.0f) * 2.0f - 1.0f; - getSpecialTile(0, &tile); - drawAutoLightedCuboid(aabb3f(-(nb[5] ? g : b), - -(nb[4] ? g : b), - -(nb[3] ? g : b), - (nb[2] ? g : b), - (nb[1] ? g : b) * vlev, - (nb[0] ? g : b))); - } -} - -void MapblockMeshGenerator::drawAllfacesNode() -{ - static const aabb3f box(-BS / 2, -BS / 2, -BS / 2, BS / 2, BS / 2, BS / 2); - useTile(0, 0, 0); - drawAutoLightedCuboid(box); -} - -void MapblockMeshGenerator::drawTorchlikeNode() -{ - u8 wall = n.getWallMounted(nodedef); - u8 tileindex = 0; - switch (wall) { - case DWM_YP: tileindex = 1; break; // ceiling - case DWM_YN: tileindex = 0; break; // floor - default: tileindex = 2; // side (or invalid—should we care?) - } - useTile(tileindex, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING); - - float size = BS / 2 * f->visual_scale; - v3f vertices[4] = { - v3f(-size, size, 0), - v3f( size, size, 0), - v3f( size, -size, 0), - v3f(-size, -size, 0), - }; - - for (v3f &vertex : vertices) { - switch (wall) { - case DWM_YP: - vertex.rotateXZBy(-45); break; - case DWM_YN: - vertex.rotateXZBy( 45); break; - case DWM_XP: - vertex.rotateXZBy( 0); break; - case DWM_XN: - vertex.rotateXZBy(180); break; - case DWM_ZP: - vertex.rotateXZBy( 90); break; - case DWM_ZN: - vertex.rotateXZBy(-90); break; - } - } - drawQuad(vertices); -} - -void MapblockMeshGenerator::drawSignlikeNode() -{ - u8 wall = n.getWallMounted(nodedef); - useTile(0, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING); - static const float offset = BS / 16; - float size = BS / 2 * f->visual_scale; - // Wall at X+ of node - v3f vertices[4] = { - v3f(BS / 2 - offset, size, size), - v3f(BS / 2 - offset, size, -size), - v3f(BS / 2 - offset, -size, -size), - v3f(BS / 2 - offset, -size, size), - }; - - for (v3f &vertex : vertices) { - switch (wall) { - case DWM_YP: - vertex.rotateXYBy( 90); break; - case DWM_YN: - vertex.rotateXYBy(-90); break; - case DWM_XP: - vertex.rotateXZBy( 0); break; - case DWM_XN: - vertex.rotateXZBy(180); break; - case DWM_ZP: - vertex.rotateXZBy( 90); break; - case DWM_ZN: - vertex.rotateXZBy(-90); break; - } - } - drawQuad(vertices); -} - -void MapblockMeshGenerator::drawPlantlikeQuad(float rotation, float quad_offset, - bool offset_top_only) -{ - v3f vertices[4] = { - v3f(-scale, -BS / 2 + 2.0 * scale * plant_height, 0), - v3f( scale, -BS / 2 + 2.0 * scale * plant_height, 0), - v3f( scale, -BS / 2, 0), - v3f(-scale, -BS / 2, 0), - }; - if (random_offset_Y) { - PseudoRandom yrng(face_num++ | p.X << 16 | p.Z << 8 | p.Y << 24); - offset.Y = -BS * ((yrng.next() % 16 / 16.0) * 0.125); - } - int offset_count = offset_top_only ? 2 : 4; - for (int i = 0; i < offset_count; i++) - vertices[i].Z += quad_offset; - - for (v3f &vertex : vertices) { - vertex.rotateXZBy(rotation + rotate_degree); - vertex += offset; - } - drawQuad(vertices, v3s16(0, 0, 0), plant_height); -} - -void MapblockMeshGenerator::drawPlantlike() -{ - draw_style = PLANT_STYLE_CROSS; - scale = BS / 2 * f->visual_scale; - offset = v3f(0, 0, 0); - rotate_degree = 0; - random_offset_Y = false; - face_num = 0; - plant_height = 1.0; - - switch (f->param_type_2) { - case CPT2_MESHOPTIONS: - draw_style = PlantlikeStyle(n.param2 & MO_MASK_STYLE); - if (n.param2 & MO_BIT_SCALE_SQRT2) - scale *= 1.41421; - if (n.param2 & MO_BIT_RANDOM_OFFSET) { - PseudoRandom rng(p.X << 8 | p.Z | p.Y << 16); - offset.X = BS * ((rng.next() % 16 / 16.0) * 0.29 - 0.145); - offset.Z = BS * ((rng.next() % 16 / 16.0) * 0.29 - 0.145); - } - if (n.param2 & MO_BIT_RANDOM_OFFSET_Y) - random_offset_Y = true; - break; - - case CPT2_DEGROTATE: - rotate_degree = n.param2 * 2; - break; - - case CPT2_LEVELED: - plant_height = n.param2 / 16.0; - break; - - default: - break; - } - - switch (draw_style) { - case PLANT_STYLE_CROSS: - drawPlantlikeQuad(46); - drawPlantlikeQuad(-44); - break; - - case PLANT_STYLE_CROSS2: - drawPlantlikeQuad(91); - drawPlantlikeQuad(1); - break; - - case PLANT_STYLE_STAR: - drawPlantlikeQuad(121); - drawPlantlikeQuad(241); - drawPlantlikeQuad(1); - break; - - case PLANT_STYLE_HASH: - drawPlantlikeQuad( 1, BS / 4); - drawPlantlikeQuad( 91, BS / 4); - drawPlantlikeQuad(181, BS / 4); - drawPlantlikeQuad(271, BS / 4); - break; - - case PLANT_STYLE_HASH2: - drawPlantlikeQuad( 1, -BS / 2, true); - drawPlantlikeQuad( 91, -BS / 2, true); - drawPlantlikeQuad(181, -BS / 2, true); - drawPlantlikeQuad(271, -BS / 2, true); - break; - } -} - -void MapblockMeshGenerator::drawPlantlikeNode() -{ - useTile(); - drawPlantlike(); -} - -void MapblockMeshGenerator::drawPlantlikeRootedNode() -{ - useTile(0, MATERIAL_FLAG_CRACK_OVERLAY, 0, true); - origin += v3f(0.0, BS, 0.0); - p.Y++; - if (data->m_smooth_lighting) { - getSmoothLightFrame(); - } else { - MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + p); - light = LightPair(getInteriorLight(ntop, 1, nodedef)); - } - drawPlantlike(); - p.Y--; -} - -void MapblockMeshGenerator::drawFirelikeQuad(float rotation, float opening_angle, - float offset_h, float offset_v) -{ - v3f vertices[4] = { - v3f(-scale, -BS / 2 + scale * 2, 0), - v3f( scale, -BS / 2 + scale * 2, 0), - v3f( scale, -BS / 2, 0), - v3f(-scale, -BS / 2, 0), - }; - - for (v3f &vertex : vertices) { - vertex.rotateYZBy(opening_angle); - vertex.Z += offset_h; - vertex.rotateXZBy(rotation); - vertex.Y += offset_v; - } - drawQuad(vertices); -} - -void MapblockMeshGenerator::drawFirelikeNode() -{ - useTile(); - scale = BS / 2 * f->visual_scale; - - // Check for adjacent nodes - bool neighbors = false; - bool neighbor[6] = {0, 0, 0, 0, 0, 0}; - content_t current = n.getContent(); - for (int i = 0; i < 6; i++) { - v3s16 n2p = blockpos_nodes + p + g_6dirs[i]; - MapNode n2 = data->m_vmanip.getNodeNoEx(n2p); - content_t n2c = n2.getContent(); - if (n2c != CONTENT_IGNORE && n2c != CONTENT_AIR && n2c != current) { - neighbor[i] = true; - neighbors = true; - } - } - bool drawBasicFire = neighbor[D6D_YN] || !neighbors; - bool drawBottomFire = neighbor[D6D_YP]; - - if (drawBasicFire || neighbor[D6D_ZP]) - drawFirelikeQuad(0, -10, 0.4 * BS); - else if (drawBottomFire) - drawFirelikeQuad(0, 70, 0.47 * BS, 0.484 * BS); - - if (drawBasicFire || neighbor[D6D_XN]) - drawFirelikeQuad(90, -10, 0.4 * BS); - else if (drawBottomFire) - drawFirelikeQuad(90, 70, 0.47 * BS, 0.484 * BS); - - if (drawBasicFire || neighbor[D6D_ZN]) - drawFirelikeQuad(180, -10, 0.4 * BS); - else if (drawBottomFire) - drawFirelikeQuad(180, 70, 0.47 * BS, 0.484 * BS); - - if (drawBasicFire || neighbor[D6D_XP]) - drawFirelikeQuad(270, -10, 0.4 * BS); - else if (drawBottomFire) - drawFirelikeQuad(270, 70, 0.47 * BS, 0.484 * BS); - - if (drawBasicFire) { - drawFirelikeQuad(45, 0, 0.0); - drawFirelikeQuad(-45, 0, 0.0); - } -} - -void MapblockMeshGenerator::drawFencelikeNode() -{ - useTile(0, 0, 0); - TileSpec tile_nocrack = tile; - - for (auto &layer : tile_nocrack.layers) - layer.material_flags &= ~MATERIAL_FLAG_CRACK; - - // Put wood the right way around in the posts - TileSpec tile_rot = tile; - tile_rot.rotation = 1; - - static const f32 post_rad = BS / 8; - static const f32 bar_rad = BS / 16; - static const f32 bar_len = BS / 2 - post_rad; - - // The post - always present - static const aabb3f post(-post_rad, -BS / 2, -post_rad, - post_rad, BS / 2, post_rad); - static const f32 postuv[24] = { - 0.375, 0.375, 0.625, 0.625, - 0.375, 0.375, 0.625, 0.625, - 0.000, 0.000, 0.250, 1.000, - 0.250, 0.000, 0.500, 1.000, - 0.500, 0.000, 0.750, 1.000, - 0.750, 0.000, 1.000, 1.000, - }; - tile = tile_rot; - drawAutoLightedCuboid(post, postuv); - - tile = tile_nocrack; - - // Now a section of fence, +X, if there's a post there - v3s16 p2 = p; - p2.X++; - MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2); - const ContentFeatures *f2 = &nodedef->get(n2); - if (f2->drawtype == NDT_FENCELIKE) { - static const aabb3f bar_x1(BS / 2 - bar_len, BS / 4 - bar_rad, -bar_rad, - BS / 2 + bar_len, BS / 4 + bar_rad, bar_rad); - static const aabb3f bar_x2(BS / 2 - bar_len, -BS / 4 - bar_rad, -bar_rad, - BS / 2 + bar_len, -BS / 4 + bar_rad, bar_rad); - static const f32 xrailuv[24] = { - 0.000, 0.125, 1.000, 0.250, - 0.000, 0.250, 1.000, 0.375, - 0.375, 0.375, 0.500, 0.500, - 0.625, 0.625, 0.750, 0.750, - 0.000, 0.500, 1.000, 0.625, - 0.000, 0.875, 1.000, 1.000, - }; - drawAutoLightedCuboid(bar_x1, xrailuv); - drawAutoLightedCuboid(bar_x2, xrailuv); - } - - // Now a section of fence, +Z, if there's a post there - p2 = p; - p2.Z++; - n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2); - f2 = &nodedef->get(n2); - if (f2->drawtype == NDT_FENCELIKE) { - static const aabb3f bar_z1(-bar_rad, BS / 4 - bar_rad, BS / 2 - bar_len, - bar_rad, BS / 4 + bar_rad, BS / 2 + bar_len); - static const aabb3f bar_z2(-bar_rad, -BS / 4 - bar_rad, BS / 2 - bar_len, - bar_rad, -BS / 4 + bar_rad, BS / 2 + bar_len); - static const f32 zrailuv[24] = { - 0.1875, 0.0625, 0.3125, 0.3125, // cannot rotate; stretch - 0.2500, 0.0625, 0.3750, 0.3125, // for wood texture instead - 0.0000, 0.5625, 1.0000, 0.6875, - 0.0000, 0.3750, 1.0000, 0.5000, - 0.3750, 0.3750, 0.5000, 0.5000, - 0.6250, 0.6250, 0.7500, 0.7500, - }; - drawAutoLightedCuboid(bar_z1, zrailuv); - drawAutoLightedCuboid(bar_z2, zrailuv); - } -} - -bool MapblockMeshGenerator::isSameRail(v3s16 dir) -{ - MapNode node2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p + dir); - if (node2.getContent() == n.getContent()) - return true; - const ContentFeatures &def2 = nodedef->get(node2); - return ((def2.drawtype == NDT_RAILLIKE) && - (def2.getGroup(raillike_groupname) == raillike_group)); -} - -void MapblockMeshGenerator::drawRaillikeNode() -{ - static const v3s16 direction[4] = { - v3s16( 0, 0, 1), - v3s16( 0, 0, -1), - v3s16(-1, 0, 0), - v3s16( 1, 0, 0), - }; - static const int slope_angle[4] = {0, 180, 90, -90}; - - enum RailTile { - straight, - curved, - junction, - cross, - }; - struct RailDesc { - int tile_index; - int angle; - }; - static const RailDesc rail_kinds[16] = { - // +x -x -z +z - //------------- - {straight, 0}, // . . . . - {straight, 0}, // . . . +Z - {straight, 0}, // . . -Z . - {straight, 0}, // . . -Z +Z - {straight, 90}, // . -X . . - { curved, 180}, // . -X . +Z - { curved, 270}, // . -X -Z . - {junction, 180}, // . -X -Z +Z - {straight, 90}, // +X . . . - { curved, 90}, // +X . . +Z - { curved, 0}, // +X . -Z . - {junction, 0}, // +X . -Z +Z - {straight, 90}, // +X -X . . - {junction, 90}, // +X -X . +Z - {junction, 270}, // +X -X -Z . - { cross, 0}, // +X -X -Z +Z - }; - - raillike_group = nodedef->get(n).getGroup(raillike_groupname); - - int code = 0; - int angle; - int tile_index; - bool sloped = false; - for (int dir = 0; dir < 4; dir++) { - bool rail_above = isSameRail(direction[dir] + v3s16(0, 1, 0)); - if (rail_above) { - sloped = true; - angle = slope_angle[dir]; - } - if (rail_above || - isSameRail(direction[dir]) || - isSameRail(direction[dir] + v3s16(0, -1, 0))) - code |= 1 << dir; - } - - if (sloped) { - tile_index = straight; - } else { - tile_index = rail_kinds[code].tile_index; - angle = rail_kinds[code].angle; - } - - useTile(tile_index, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING); - - static const float offset = BS / 64; - static const float size = BS / 2; - float y2 = sloped ? size : -size; - v3f vertices[4] = { - v3f(-size, y2 + offset, size), - v3f( size, y2 + offset, size), - v3f( size, -size + offset, -size), - v3f(-size, -size + offset, -size), - }; - if (angle) - for (v3f &vertex : vertices) - vertex.rotateXZBy(angle); - drawQuad(vertices); -} - -void MapblockMeshGenerator::drawNodeboxNode() -{ - static const v3s16 tile_dirs[6] = { - v3s16(0, 1, 0), - v3s16(0, -1, 0), - v3s16(1, 0, 0), - v3s16(-1, 0, 0), - v3s16(0, 0, 1), - v3s16(0, 0, -1) - }; - - // we have this order for some reason... - static const v3s16 connection_dirs[6] = { - v3s16( 0, 1, 0), // top - v3s16( 0, -1, 0), // bottom - v3s16( 0, 0, -1), // front - v3s16(-1, 0, 0), // left - v3s16( 0, 0, 1), // back - v3s16( 1, 0, 0), // right - }; - - TileSpec tiles[6]; - for (int face = 0; face < 6; face++) { - // Handles facedir rotation for textures - getTile(tile_dirs[face], &tiles[face]); - } - - // locate possible neighboring nodes to connect to - int neighbors_set = 0; - if (f->node_box.type == NODEBOX_CONNECTED) { - for (int dir = 0; dir != 6; dir++) { - int flag = 1 << dir; - v3s16 p2 = blockpos_nodes + p + connection_dirs[dir]; - MapNode n2 = data->m_vmanip.getNodeNoEx(p2); - if (nodedef->nodeboxConnects(n, n2, flag)) - neighbors_set |= flag; - } - } - - std::vector boxes; - n.getNodeBoxes(nodedef, &boxes, neighbors_set); - for (const auto &box : boxes) - drawAutoLightedCuboid(box, nullptr, tiles, 6); -} - -void MapblockMeshGenerator::drawMeshNode() -{ - u8 facedir = 0; - scene::IMesh* mesh; - bool private_mesh; // as a grab/drop pair is not thread-safe - - if (f->param_type_2 == CPT2_FACEDIR || - f->param_type_2 == CPT2_COLORED_FACEDIR) { - facedir = n.getFaceDir(nodedef); - } else if (f->param_type_2 == CPT2_WALLMOUNTED || - f->param_type_2 == CPT2_COLORED_WALLMOUNTED) { - // Convert wallmounted to 6dfacedir. - // When cache enabled, it is already converted. - facedir = n.getWallMounted(nodedef); - if (!enable_mesh_cache) - facedir = wallmounted_to_facedir[facedir]; - } - - if (!data->m_smooth_lighting && f->mesh_ptr[facedir]) { - // use cached meshes - private_mesh = false; - mesh = f->mesh_ptr[facedir]; - } else if (f->mesh_ptr[0]) { - // no cache, clone and rotate mesh - private_mesh = true; - mesh = cloneMesh(f->mesh_ptr[0]); - rotateMeshBy6dFacedir(mesh, facedir); - recalculateBoundingBox(mesh); - meshmanip->recalculateNormals(mesh, true, false); - } else - return; - - int mesh_buffer_count = mesh->getMeshBufferCount(); - for (int j = 0; j < mesh_buffer_count; j++) { - useTile(j); - scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); - video::S3DVertex *vertices = (video::S3DVertex *)buf->getVertices(); - int vertex_count = buf->getVertexCount(); - - if (data->m_smooth_lighting) { - // Mesh is always private here. So the lighting is applied to each - // vertex right here. - for (int k = 0; k < vertex_count; k++) { - video::S3DVertex &vertex = vertices[k]; - vertex.Color = blendLightColor(vertex.Pos, vertex.Normal); - vertex.Pos += origin; - } - collector->append(tile, vertices, vertex_count, - buf->getIndices(), buf->getIndexCount()); - } else { - // Don't modify the mesh, it may not be private here. - // Instead, let the collector process colors, etc. - collector->append(tile, vertices, vertex_count, - buf->getIndices(), buf->getIndexCount(), origin, - color, f->light_source); - } - } - if (private_mesh) - mesh->drop(); -} - -// also called when the drawtype is known but should have been pre-converted -void MapblockMeshGenerator::errorUnknownDrawtype() -{ - infostream << "Got drawtype " << f->drawtype << std::endl; - FATAL_ERROR("Unknown drawtype"); -} - -void MapblockMeshGenerator::drawNode() -{ - // skip some drawtypes early - switch (f->drawtype) { - case NDT_NORMAL: // Drawn by MapBlockMesh - case NDT_AIRLIKE: // Not drawn at all - case NDT_LIQUID: // Drawn by MapBlockMesh - return; - default: - break; - } - origin = intToFloat(p, BS); - if (data->m_smooth_lighting) - getSmoothLightFrame(); - else - light = LightPair(getInteriorLight(n, 1, nodedef)); - switch (f->drawtype) { - case NDT_FLOWINGLIQUID: drawLiquidNode(); break; - case NDT_GLASSLIKE: drawGlasslikeNode(); break; - case NDT_GLASSLIKE_FRAMED: drawGlasslikeFramedNode(); break; - case NDT_ALLFACES: drawAllfacesNode(); break; - case NDT_TORCHLIKE: drawTorchlikeNode(); break; - case NDT_SIGNLIKE: drawSignlikeNode(); break; - case NDT_PLANTLIKE: drawPlantlikeNode(); break; - case NDT_PLANTLIKE_ROOTED: drawPlantlikeRootedNode(); break; - case NDT_FIRELIKE: drawFirelikeNode(); break; - case NDT_FENCELIKE: drawFencelikeNode(); break; - case NDT_RAILLIKE: drawRaillikeNode(); break; - case NDT_NODEBOX: drawNodeboxNode(); break; - case NDT_MESH: drawMeshNode(); break; - default: errorUnknownDrawtype(); break; - } -} - -/* - TODO: Fix alpha blending for special nodes - Currently only the last element rendered is blended correct -*/ -void MapblockMeshGenerator::generate() -{ - for (p.Z = 0; p.Z < MAP_BLOCKSIZE; p.Z++) - for (p.Y = 0; p.Y < MAP_BLOCKSIZE; p.Y++) - for (p.X = 0; p.X < MAP_BLOCKSIZE; p.X++) { - n = data->m_vmanip.getNodeNoEx(blockpos_nodes + p); - f = &nodedef->get(n); - drawNode(); - } -} - -void MapblockMeshGenerator::renderSingle(content_t node) -{ - p = {0, 0, 0}; - n = MapNode(node, 0xff, 0x00); - f = &nodedef->get(n); - drawNode(); -} diff --git a/src/content_mapblock.h b/src/content_mapblock.h deleted file mode 100644 index 97947cdbe..000000000 --- a/src/content_mapblock.h +++ /dev/null @@ -1,178 +0,0 @@ -/* -Minetest -Copyright (C) 2010-2013 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#pragma once - -#include "nodedef.h" -#include - -struct MeshMakeData; -struct MeshCollector; - -struct LightPair { - u8 lightDay; - u8 lightNight; - - LightPair() = default; - explicit LightPair(u16 value) : lightDay(value & 0xff), lightNight(value >> 8) {} - LightPair(u8 valueA, u8 valueB) : lightDay(valueA), lightNight(valueB) {} - LightPair(float valueA, float valueB) : - lightDay(core::clamp(core::round32(valueA), 0, 255)), - lightNight(core::clamp(core::round32(valueB), 0, 255)) {} - operator u16() const { return lightDay | lightNight << 8; } -}; - -struct LightInfo { - float light_day; - float light_night; - float light_boosted; - - LightPair getPair(float sunlight_boost = 0.0) const - { - return LightPair( - (1 - sunlight_boost) * light_day - + sunlight_boost * light_boosted, - light_night); - } -}; - -struct LightFrame { - f32 lightsDay[8]; - f32 lightsNight[8]; - bool sunlight[8]; -}; - -class MapblockMeshGenerator -{ -public: - MeshMakeData *data; - MeshCollector *collector; - - const NodeDefManager *nodedef; - scene::IMeshManipulator *meshmanip; - -// options - bool enable_mesh_cache; - -// current node - v3s16 blockpos_nodes; - v3s16 p; - v3f origin; - MapNode n; - const ContentFeatures *f; - LightPair light; - LightFrame frame; - video::SColor color; - TileSpec tile; - float scale; - -// lighting - void getSmoothLightFrame(); - LightInfo blendLight(const v3f &vertex_pos); - video::SColor blendLightColor(const v3f &vertex_pos); - video::SColor blendLightColor(const v3f &vertex_pos, const v3f &vertex_normal); - - void useTile(int index = 0, u8 set_flags = MATERIAL_FLAG_CRACK_OVERLAY, - u8 reset_flags = 0, bool special = false); - void getTile(int index, TileSpec *tile); - void getTile(v3s16 direction, TileSpec *tile); - void getSpecialTile(int index, TileSpec *tile, bool apply_crack = false); - -// face drawing - void drawQuad(v3f *vertices, const v3s16 &normal = v3s16(0, 0, 0), - float vertical_tiling = 1.0); - -// cuboid drawing! - void drawCuboid(const aabb3f &box, TileSpec *tiles, int tilecount, - const LightInfo *lights , const f32 *txc); - void generateCuboidTextureCoords(aabb3f const &box, f32 *coords); - void drawAutoLightedCuboid(aabb3f box, const f32 *txc = NULL, - TileSpec *tiles = NULL, int tile_count = 0); - -// liquid-specific - bool top_is_same_liquid; - bool draw_liquid_bottom; - TileSpec tile_liquid; - TileSpec tile_liquid_top; - content_t c_flowing; - content_t c_source; - video::SColor color_liquid_top; - struct NeighborData { - f32 level; - content_t content; - bool is_same_liquid; - bool top_is_same_liquid; - }; - NeighborData liquid_neighbors[3][3]; - f32 corner_levels[2][2]; - - void prepareLiquidNodeDrawing(); - void getLiquidNeighborhood(); - void calculateCornerLevels(); - f32 getCornerLevel(int i, int k); - void drawLiquidSides(); - void drawLiquidTop(); - void drawLiquidBottom(); - -// raillike-specific - // name of the group that enables connecting to raillike nodes of different kind - static const std::string raillike_groupname; - int raillike_group; - bool isSameRail(v3s16 dir); - -// plantlike-specific - PlantlikeStyle draw_style; - v3f offset; - int rotate_degree; - bool random_offset_Y; - int face_num; - float plant_height; - - void drawPlantlikeQuad(float rotation, float quad_offset = 0, - bool offset_top_only = false); - void drawPlantlike(); - -// firelike-specific - void drawFirelikeQuad(float rotation, float opening_angle, - float offset_h, float offset_v = 0.0); - -// drawtypes - void drawLiquidNode(); - void drawGlasslikeNode(); - void drawGlasslikeFramedNode(); - void drawAllfacesNode(); - void drawTorchlikeNode(); - void drawSignlikeNode(); - void drawPlantlikeNode(); - void drawPlantlikeRootedNode(); - void drawFirelikeNode(); - void drawFencelikeNode(); - void drawRaillikeNode(); - void drawNodeboxNode(); - void drawMeshNode(); - -// common - void errorUnknownDrawtype(); - void drawNode(); - -public: - MapblockMeshGenerator(MeshMakeData *input, MeshCollector *output); - void generate(); - void renderSingle(content_t node); -}; diff --git a/src/filecache.cpp b/src/filecache.cpp deleted file mode 100644 index 3d1b302a8..000000000 --- a/src/filecache.cpp +++ /dev/null @@ -1,89 +0,0 @@ -/* -Minetest -Copyright (C) 2013 celeron55, Perttu Ahola -Copyright (C) 2013 Jonathan Neuschäfer - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include "filecache.h" - -#include "network/networkprotocol.h" -#include "log.h" -#include "filesys.h" -#include -#include -#include -#include - -bool FileCache::loadByPath(const std::string &path, std::ostream &os) -{ - std::ifstream fis(path.c_str(), std::ios_base::binary); - - if(!fis.good()){ - verbosestream<<"FileCache: File not found in cache: " - < -Copyright (C) 2013 Jonathan Neuschäfer - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#pragma once - -#include -#include - -class FileCache -{ -public: - /* - 'dir' is the file cache directory to use. - */ - FileCache(const std::string &dir) : m_dir(dir) {} - - bool update(const std::string &name, const std::string &data); - bool load(const std::string &name, std::ostream &os); - -private: - std::string m_dir; - - bool loadByPath(const std::string &path, std::ostream &os); - bool updateByPath(const std::string &path, const std::string &data); -}; diff --git a/src/fontengine.cpp b/src/fontengine.cpp deleted file mode 100644 index dc98fb1e4..000000000 --- a/src/fontengine.cpp +++ /dev/null @@ -1,505 +0,0 @@ -/* -Minetest -Copyright (C) 2010-2014 sapier - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include "fontengine.h" -#include -#include "client/renderingengine.h" -#include "config.h" -#include "porting.h" -#include "filesys.h" - -#if USE_FREETYPE -#include "gettext.h" -#include "irrlicht_changes/CGUITTFont.h" -#endif - -/** maximum size distance for getting a "similar" font size */ -#define MAX_FONT_SIZE_OFFSET 10 - -/** reference to access font engine, has to be initialized by main */ -FontEngine* g_fontengine = NULL; - -/** callback to be used on change of font size setting */ -static void font_setting_changed(const std::string &name, void *userdata) -{ - g_fontengine->readSettings(); -} - -/******************************************************************************/ -FontEngine::FontEngine(Settings* main_settings, gui::IGUIEnvironment* env) : - m_settings(main_settings), - m_env(env) -{ - - for (u32 &i : m_default_size) { - i = (FontMode) FONT_SIZE_UNSPECIFIED; - } - - assert(m_settings != NULL); // pre-condition - assert(m_env != NULL); // pre-condition - assert(m_env->getSkin() != NULL); // pre-condition - - m_currentMode = FM_Simple; - -#if USE_FREETYPE - if (g_settings->getBool("freetype")) { - m_default_size[FM_Standard] = m_settings->getU16("font_size"); - m_default_size[FM_Fallback] = m_settings->getU16("fallback_font_size"); - m_default_size[FM_Mono] = m_settings->getU16("mono_font_size"); - - if (is_yes(gettext("needs_fallback_font"))) { - m_currentMode = FM_Fallback; - } - else { - m_currentMode = FM_Standard; - } - } - - // having freetype but not using it is quite a strange case so we need to do - // special handling for it - if (m_currentMode == FM_Simple) { - std::stringstream fontsize; - fontsize << DEFAULT_FONT_SIZE; - m_settings->setDefault("font_size", fontsize.str()); - m_settings->setDefault("mono_font_size", fontsize.str()); - } -#endif - - m_default_size[FM_Simple] = m_settings->getU16("font_size"); - m_default_size[FM_SimpleMono] = m_settings->getU16("mono_font_size"); - - updateSkin(); - - if (m_currentMode == FM_Standard) { - m_settings->registerChangedCallback("font_size", font_setting_changed, NULL); - m_settings->registerChangedCallback("font_path", font_setting_changed, NULL); - m_settings->registerChangedCallback("font_shadow", font_setting_changed, NULL); - m_settings->registerChangedCallback("font_shadow_alpha", font_setting_changed, NULL); - } - else if (m_currentMode == FM_Fallback) { - m_settings->registerChangedCallback("fallback_font_size", font_setting_changed, NULL); - m_settings->registerChangedCallback("fallback_font_path", font_setting_changed, NULL); - m_settings->registerChangedCallback("fallback_font_shadow", font_setting_changed, NULL); - m_settings->registerChangedCallback("fallback_font_shadow_alpha", font_setting_changed, NULL); - } - - m_settings->registerChangedCallback("mono_font_path", font_setting_changed, NULL); - m_settings->registerChangedCallback("mono_font_size", font_setting_changed, NULL); - m_settings->registerChangedCallback("screen_dpi", font_setting_changed, NULL); - m_settings->registerChangedCallback("gui_scaling", font_setting_changed, NULL); -} - -/******************************************************************************/ -FontEngine::~FontEngine() -{ - cleanCache(); -} - -/******************************************************************************/ -void FontEngine::cleanCache() -{ - for (auto &font_cache_it : m_font_cache) { - - for (auto &font_it : font_cache_it) { - font_it.second->drop(); - font_it.second = NULL; - } - font_cache_it.clear(); - } -} - -/******************************************************************************/ -irr::gui::IGUIFont* FontEngine::getFont(unsigned int font_size, FontMode mode) -{ - if (mode == FM_Unspecified) { - mode = m_currentMode; - } - else if ((mode == FM_Mono) && (m_currentMode == FM_Simple)) { - mode = FM_SimpleMono; - } - - if (font_size == FONT_SIZE_UNSPECIFIED) { - font_size = m_default_size[mode]; - } - - if ((font_size == m_lastSize) && (mode == m_lastMode)) { - return m_lastFont; - } - - if (m_font_cache[mode].find(font_size) == m_font_cache[mode].end()) { - initFont(font_size, mode); - } - - if (m_font_cache[mode].find(font_size) == m_font_cache[mode].end()) { - return NULL; - } - - m_lastSize = font_size; - m_lastMode = mode; - m_lastFont = m_font_cache[mode][font_size]; - - return m_font_cache[mode][font_size]; -} - -/******************************************************************************/ -unsigned int FontEngine::getTextHeight(unsigned int font_size, FontMode mode) -{ - irr::gui::IGUIFont* font = getFont(font_size, mode); - - // use current skin font as fallback - if (font == NULL) { - font = m_env->getSkin()->getFont(); - } - FATAL_ERROR_IF(font == NULL, "Could not get skin font"); - - return font->getDimension(L"Some unimportant example String").Height; -} - -/******************************************************************************/ -unsigned int FontEngine::getTextWidth(const std::wstring& text, - unsigned int font_size, FontMode mode) -{ - irr::gui::IGUIFont* font = getFont(font_size, mode); - - // use current skin font as fallback - if (font == NULL) { - font = m_env->getSkin()->getFont(); - } - FATAL_ERROR_IF(font == NULL, "Could not get font"); - - return font->getDimension(text.c_str()).Width; -} - - -/** get line height for a specific font (including empty room between lines) */ -unsigned int FontEngine::getLineHeight(unsigned int font_size, FontMode mode) -{ - irr::gui::IGUIFont* font = getFont(font_size, mode); - - // use current skin font as fallback - if (font == NULL) { - font = m_env->getSkin()->getFont(); - } - FATAL_ERROR_IF(font == NULL, "Could not get font"); - - return font->getDimension(L"Some unimportant example String").Height - + font->getKerningHeight(); -} - -/******************************************************************************/ -unsigned int FontEngine::getDefaultFontSize() -{ - return m_default_size[m_currentMode]; -} - -/******************************************************************************/ -void FontEngine::readSettings() -{ -#if USE_FREETYPE - if (g_settings->getBool("freetype")) { - m_default_size[FM_Standard] = m_settings->getU16("font_size"); - m_default_size[FM_Fallback] = m_settings->getU16("fallback_font_size"); - m_default_size[FM_Mono] = m_settings->getU16("mono_font_size"); - - if (is_yes(gettext("needs_fallback_font"))) { - m_currentMode = FM_Fallback; - } - else { - m_currentMode = FM_Standard; - } - } -#endif - m_default_size[FM_Simple] = m_settings->getU16("font_size"); - m_default_size[FM_SimpleMono] = m_settings->getU16("mono_font_size"); - - cleanCache(); - updateFontCache(); - updateSkin(); -} - -/******************************************************************************/ -void FontEngine::updateSkin() -{ - gui::IGUIFont *font = getFont(); - - if (font) - m_env->getSkin()->setFont(font); - else - errorstream << "FontEngine: Default font file: " << - "\n\t\"" << m_settings->get("font_path") << "\"" << - "\n\trequired for current screen configuration was not found" << - " or was invalid file format." << - "\n\tUsing irrlicht default font." << std::endl; - - // If we did fail to create a font our own make irrlicht find a default one - font = m_env->getSkin()->getFont(); - FATAL_ERROR_IF(font == NULL, "Could not create/get font"); - - u32 text_height = font->getDimension(L"Hello, world!").Height; - infostream << "text_height=" << text_height << std::endl; -} - -/******************************************************************************/ -void FontEngine::updateFontCache() -{ - /* the only font to be initialized is default one, - * all others are re-initialized on demand */ - initFont(m_default_size[m_currentMode], m_currentMode); - - /* reset font quick access */ - m_lastMode = FM_Unspecified; - m_lastSize = 0; - m_lastFont = NULL; -} - -/******************************************************************************/ -void FontEngine::initFont(unsigned int basesize, FontMode mode) -{ - - std::string font_config_prefix; - - if (mode == FM_Unspecified) { - mode = m_currentMode; - } - - switch (mode) { - - case FM_Standard: - font_config_prefix = ""; - break; - - case FM_Fallback: - font_config_prefix = "fallback_"; - break; - - case FM_Mono: - font_config_prefix = "mono_"; - if (m_currentMode == FM_Simple) - mode = FM_SimpleMono; - break; - - case FM_Simple: /* Fallthrough */ - case FM_SimpleMono: /* Fallthrough */ - default: - font_config_prefix = ""; - - } - - if (m_font_cache[mode].find(basesize) != m_font_cache[mode].end()) - return; - - if ((mode == FM_Simple) || (mode == FM_SimpleMono)) { - initSimpleFont(basesize, mode); - return; - } -#if USE_FREETYPE - else { - if (!is_yes(m_settings->get("freetype"))) { - return; - } - u32 size = std::floor(RenderingEngine::getDisplayDensity() * - m_settings->getFloat("gui_scaling") * basesize); - u32 font_shadow = 0; - u32 font_shadow_alpha = 0; - - try { - font_shadow = - g_settings->getU16(font_config_prefix + "font_shadow"); - } catch (SettingNotFoundException&) {} - try { - font_shadow_alpha = - g_settings->getU16(font_config_prefix + "font_shadow_alpha"); - } catch (SettingNotFoundException&) {} - - std::string font_path = g_settings->get(font_config_prefix + "font_path"); - - irr::gui::IGUIFont* font = gui::CGUITTFont::createTTFont(m_env, - font_path.c_str(), size, true, true, font_shadow, - font_shadow_alpha); - - if (font) { - m_font_cache[mode][basesize] = font; - return; - } - - if (font_config_prefix == "mono_") { - const std::string &mono_font_path = m_settings->getDefault("mono_font_path"); - - if (font_path != mono_font_path) { - // try original mono font - errorstream << "FontEngine: failed to load custom mono " - "font: " << font_path << ", trying to fall back to " - "original mono font" << std::endl; - - font = gui::CGUITTFont::createTTFont(m_env, - mono_font_path.c_str(), size, true, true, - font_shadow, font_shadow_alpha); - - if (font) { - m_font_cache[mode][basesize] = font; - return; - } - } - } else { - // try fallback font - errorstream << "FontEngine: failed to load: " << font_path << - ", trying to fall back to fallback font" << std::endl; - - font_path = g_settings->get(font_config_prefix + "fallback_font_path"); - - font = gui::CGUITTFont::createTTFont(m_env, - font_path.c_str(), size, true, true, font_shadow, - font_shadow_alpha); - - if (font) { - m_font_cache[mode][basesize] = font; - return; - } - - const std::string &fallback_font_path = m_settings->getDefault("fallback_font_path"); - - if (font_path != fallback_font_path) { - // try original fallback font - errorstream << "FontEngine: failed to load custom fallback " - "font: " << font_path << ", trying to fall back to " - "original fallback font" << std::endl; - - font = gui::CGUITTFont::createTTFont(m_env, - fallback_font_path.c_str(), size, true, true, - font_shadow, font_shadow_alpha); - - if (font) { - m_font_cache[mode][basesize] = font; - return; - } - } - } - - // give up - errorstream << "FontEngine: failed to load freetype font: " - << font_path << std::endl; - errorstream << "minetest can not continue without a valid font. " - "Please correct the 'font_path' setting or install the font " - "file in the proper location" << std::endl; - abort(); - } -#endif -} - -/** initialize a font without freetype */ -void FontEngine::initSimpleFont(unsigned int basesize, FontMode mode) -{ - assert(mode == FM_Simple || mode == FM_SimpleMono); // pre-condition - - std::string font_path; - if (mode == FM_Simple) { - font_path = m_settings->get("font_path"); - } else { - font_path = m_settings->get("mono_font_path"); - } - std::string basename = font_path; - std::string ending = font_path.substr(font_path.length() -4); - - if (ending == ".ttf") { - errorstream << "FontEngine: Not trying to open \"" << font_path - << "\" which seems to be a truetype font." << std::endl; - return; - } - - if ((ending == ".xml") || (ending == ".png")) { - basename = font_path.substr(0,font_path.length()-4); - } - - if (basesize == FONT_SIZE_UNSPECIFIED) - basesize = DEFAULT_FONT_SIZE; - - u32 size = std::floor( - RenderingEngine::getDisplayDensity() * - m_settings->getFloat("gui_scaling") * - basesize); - - irr::gui::IGUIFont* font = NULL; - - for(unsigned int offset = 0; offset < MAX_FONT_SIZE_OFFSET; offset++) { - - // try opening positive offset - std::stringstream fontsize_plus_png; - fontsize_plus_png << basename << "_" << (size + offset) << ".png"; - - if (fs::PathExists(fontsize_plus_png.str())) { - font = m_env->getFont(fontsize_plus_png.str().c_str()); - - if (font) { - verbosestream << "FontEngine: found font: " << fontsize_plus_png.str() << std::endl; - break; - } - } - - std::stringstream fontsize_plus_xml; - fontsize_plus_xml << basename << "_" << (size + offset) << ".xml"; - - if (fs::PathExists(fontsize_plus_xml.str())) { - font = m_env->getFont(fontsize_plus_xml.str().c_str()); - - if (font) { - verbosestream << "FontEngine: found font: " << fontsize_plus_xml.str() << std::endl; - break; - } - } - - // try negative offset - std::stringstream fontsize_minus_png; - fontsize_minus_png << basename << "_" << (size - offset) << ".png"; - - if (fs::PathExists(fontsize_minus_png.str())) { - font = m_env->getFont(fontsize_minus_png.str().c_str()); - - if (font) { - verbosestream << "FontEngine: found font: " << fontsize_minus_png.str() << std::endl; - break; - } - } - - std::stringstream fontsize_minus_xml; - fontsize_minus_xml << basename << "_" << (size - offset) << ".xml"; - - if (fs::PathExists(fontsize_minus_xml.str())) { - font = m_env->getFont(fontsize_minus_xml.str().c_str()); - - if (font) { - verbosestream << "FontEngine: found font: " << fontsize_minus_xml.str() << std::endl; - break; - } - } - } - - // try name direct - if (font == NULL) { - if (fs::PathExists(font_path)) { - font = m_env->getFont(font_path.c_str()); - if (font) - verbosestream << "FontEngine: found font: " << font_path << std::endl; - } - } - - if (font) { - font->grab(); - m_font_cache[mode][basesize] = font; - } -} diff --git a/src/fontengine.h b/src/fontengine.h deleted file mode 100644 index a75618f86..000000000 --- a/src/fontengine.h +++ /dev/null @@ -1,128 +0,0 @@ -/* -Minetest -Copyright (C) 2010-2014 sapier - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#pragma once - -#include -#include -#include "util/basic_macros.h" -#include -#include -#include -#include "settings.h" - -#define FONT_SIZE_UNSPECIFIED 0xFFFFFFFF - -enum FontMode { - FM_Standard = 0, - FM_Mono, - FM_Fallback, - FM_Simple, - FM_SimpleMono, - FM_MaxMode, - FM_Unspecified -}; - -class FontEngine -{ -public: - - FontEngine(Settings* main_settings, gui::IGUIEnvironment* env); - - ~FontEngine(); - - /** get Font */ - irr::gui::IGUIFont* getFont(unsigned int font_size=FONT_SIZE_UNSPECIFIED, - FontMode mode=FM_Unspecified); - - /** get text height for a specific font */ - unsigned int getTextHeight(unsigned int font_size=FONT_SIZE_UNSPECIFIED, - FontMode mode=FM_Unspecified); - - /** get text width if a text for a specific font */ - unsigned int getTextWidth(const std::string& text, - unsigned int font_size=FONT_SIZE_UNSPECIFIED, - FontMode mode=FM_Unspecified) - { - return getTextWidth(utf8_to_wide(text)); - } - - /** get text width if a text for a specific font */ - unsigned int getTextWidth(const std::wstring& text, - unsigned int font_size=FONT_SIZE_UNSPECIFIED, - FontMode mode=FM_Unspecified); - - /** get line height for a specific font (including empty room between lines) */ - unsigned int getLineHeight(unsigned int font_size=FONT_SIZE_UNSPECIFIED, - FontMode mode=FM_Unspecified); - - /** get default font size */ - unsigned int getDefaultFontSize(); - - /** initialize font engine */ - void initialize(Settings* main_settings, gui::IGUIEnvironment* env); - - /** update internal parameters from settings */ - void readSettings(); - -private: - /** update content of font cache in case of a setting change made it invalid */ - void updateFontCache(); - - /** initialize a new font */ - void initFont(unsigned int basesize, FontMode mode=FM_Unspecified); - - /** initialize a font without freetype */ - void initSimpleFont(unsigned int basesize, FontMode mode); - - /** update current minetest skin with font changes */ - void updateSkin(); - - /** clean cache */ - void cleanCache(); - - /** pointer to settings for registering callbacks or reading config */ - Settings* m_settings = nullptr; - - /** pointer to irrlicht gui environment */ - gui::IGUIEnvironment* m_env = nullptr; - - /** internal storage for caching fonts of different size */ - std::map m_font_cache[FM_MaxMode]; - - /** default font size to use */ - unsigned int m_default_size[FM_MaxMode]; - - /** current font engine mode */ - FontMode m_currentMode = FM_Standard; - - /** font mode of last request */ - FontMode m_lastMode; - - /** size of last request */ - unsigned int m_lastSize = 0; - - /** last font returned */ - irr::gui::IGUIFont* m_lastFont = nullptr; - - DISABLE_CLASS_COPY(FontEngine); -}; - -/** interface to access main font engine*/ -extern FontEngine* g_fontengine; diff --git a/src/game.cpp b/src/game.cpp deleted file mode 100644 index 6cf6723e9..000000000 --- a/src/game.cpp +++ /dev/null @@ -1,4218 +0,0 @@ -/* -Minetest -Copyright (C) 2010-2013 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include "game.h" - -#include -#include -#include "client/renderingengine.h" -#include "camera.h" -#include "client.h" -#include "client/clientevent.h" -#include "client/gameui.h" -#include "client/inputhandler.h" -#include "client/tile.h" // For TextureSource -#include "client/keys.h" -#include "client/joystick_controller.h" -#include "clientmap.h" -#include "clouds.h" -#include "config.h" -#include "content_cao.h" -#include "client/event_manager.h" -#include "fontengine.h" -#include "itemdef.h" -#include "log.h" -#include "filesys.h" -#include "gettext.h" -#include "gui/guiChatConsole.h" -#include "gui/guiConfirmRegistration.h" -#include "gui/guiFormSpecMenu.h" -#include "gui/guiKeyChangeMenu.h" -#include "gui/guiPasswordChange.h" -#include "gui/guiVolumeChange.h" -#include "gui/mainmenumanager.h" -#include "gui/profilergraph.h" -#include "mapblock.h" -#include "minimap.h" -#include "nodedef.h" // Needed for determining pointing to nodes -#include "nodemetadata.h" -#include "particles.h" -#include "porting.h" -#include "profiler.h" -#include "quicktune_shortcutter.h" -#include "raycast.h" -#include "server.h" -#include "settings.h" -#include "shader.h" -#include "sky.h" -#include "translation.h" -#include "util/basic_macros.h" -#include "util/directiontables.h" -#include "util/pointedthing.h" -#include "irrlicht_changes/static_text.h" -#include "version.h" -#include "script/scripting_client.h" - -#if USE_SOUND - #include "client/sound_openal.h" -#else - #include "client/sound.h" -#endif -/* - Text input system -*/ - -struct TextDestNodeMetadata : public TextDest -{ - TextDestNodeMetadata(v3s16 p, Client *client) - { - m_p = p; - m_client = client; - } - // This is deprecated I guess? -celeron55 - void gotText(const std::wstring &text) - { - std::string ntext = wide_to_utf8(text); - infostream << "Submitting 'text' field of node at (" << m_p.X << "," - << m_p.Y << "," << m_p.Z << "): " << ntext << std::endl; - StringMap fields; - fields["text"] = ntext; - m_client->sendNodemetaFields(m_p, "", fields); - } - void gotText(const StringMap &fields) - { - m_client->sendNodemetaFields(m_p, "", fields); - } - - v3s16 m_p; - Client *m_client; -}; - -struct TextDestPlayerInventory : public TextDest -{ - TextDestPlayerInventory(Client *client) - { - m_client = client; - m_formname = ""; - } - TextDestPlayerInventory(Client *client, const std::string &formname) - { - m_client = client; - m_formname = formname; - } - void gotText(const StringMap &fields) - { - m_client->sendInventoryFields(m_formname, fields); - } - - Client *m_client; -}; - -struct LocalFormspecHandler : public TextDest -{ - LocalFormspecHandler(const std::string &formname) - { - m_formname = formname; - } - - LocalFormspecHandler(const std::string &formname, Client *client): - m_client(client) - { - m_formname = formname; - } - - void gotText(const StringMap &fields) - { - if (m_formname == "MT_PAUSE_MENU") { - if (fields.find("btn_sound") != fields.end()) { - g_gamecallback->changeVolume(); - return; - } - - if (fields.find("btn_key_config") != fields.end()) { - g_gamecallback->keyConfig(); - return; - } - - if (fields.find("btn_exit_menu") != fields.end()) { - g_gamecallback->disconnect(); - return; - } - - if (fields.find("btn_exit_os") != fields.end()) { - g_gamecallback->exitToOS(); -#ifndef __ANDROID__ - RenderingEngine::get_raw_device()->closeDevice(); -#endif - return; - } - - if (fields.find("btn_change_password") != fields.end()) { - g_gamecallback->changePassword(); - return; - } - - if (fields.find("quit") != fields.end()) { - return; - } - - if (fields.find("btn_continue") != fields.end()) { - return; - } - } - - if (m_formname == "MT_DEATH_SCREEN") { - assert(m_client != 0); - m_client->sendRespawn(); - return; - } - - if (m_client && m_client->moddingEnabled()) - m_client->getScript()->on_formspec_input(m_formname, fields); - } - - Client *m_client = nullptr; -}; - -/* Form update callback */ - -class NodeMetadataFormSource: public IFormSource -{ -public: - NodeMetadataFormSource(ClientMap *map, v3s16 p): - m_map(map), - m_p(p) - { - } - const std::string &getForm() const - { - static const std::string empty_string = ""; - NodeMetadata *meta = m_map->getNodeMetadata(m_p); - - if (!meta) - return empty_string; - - return meta->getString("formspec"); - } - - virtual std::string resolveText(const std::string &str) - { - NodeMetadata *meta = m_map->getNodeMetadata(m_p); - - if (!meta) - return str; - - return meta->resolveString(str); - } - - ClientMap *m_map; - v3s16 m_p; -}; - -class PlayerInventoryFormSource: public IFormSource -{ -public: - PlayerInventoryFormSource(Client *client): - m_client(client) - { - } - - const std::string &getForm() const - { - LocalPlayer *player = m_client->getEnv().getLocalPlayer(); - return player->inventory_formspec; - } - - Client *m_client; -}; - -class NodeDugEvent: public MtEvent -{ -public: - v3s16 p; - MapNode n; - - NodeDugEvent(v3s16 p, MapNode n): - p(p), - n(n) - {} - MtEvent::Type getType() const - { - return MtEvent::NODE_DUG; - } -}; - -class SoundMaker -{ - ISoundManager *m_sound; - const NodeDefManager *m_ndef; -public: - bool makes_footstep_sound; - float m_player_step_timer; - - SimpleSoundSpec m_player_step_sound; - SimpleSoundSpec m_player_leftpunch_sound; - SimpleSoundSpec m_player_rightpunch_sound; - - SoundMaker(ISoundManager *sound, const NodeDefManager *ndef): - m_sound(sound), - m_ndef(ndef), - makes_footstep_sound(true), - m_player_step_timer(0) - { - } - - void playPlayerStep() - { - 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); - } - } - - static void viewBobbingStep(MtEvent *e, void *data) - { - SoundMaker *sm = (SoundMaker *)data; - sm->playPlayerStep(); - } - - static void playerRegainGround(MtEvent *e, void *data) - { - SoundMaker *sm = (SoundMaker *)data; - sm->playPlayerStep(); - } - - static void playerJump(MtEvent *e, void *data) - { - //SoundMaker *sm = (SoundMaker*)data; - } - - static void cameraPunchLeft(MtEvent *e, void *data) - { - SoundMaker *sm = (SoundMaker *)data; - sm->m_sound->playSound(sm->m_player_leftpunch_sound, false); - } - - static void cameraPunchRight(MtEvent *e, void *data) - { - SoundMaker *sm = (SoundMaker *)data; - sm->m_sound->playSound(sm->m_player_rightpunch_sound, false); - } - - 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); - } - - static void playerDamage(MtEvent *e, void *data) - { - SoundMaker *sm = (SoundMaker *)data; - sm->m_sound->playSound(SimpleSoundSpec("player_damage", 0.5), false); - } - - static void playerFallingDamage(MtEvent *e, void *data) - { - SoundMaker *sm = (SoundMaker *)data; - sm->m_sound->playSound(SimpleSoundSpec("player_falling_damage", 0.5), false); - } - - void registerReceiver(MtEventManager *mgr) - { - mgr->reg(MtEvent::VIEW_BOBBING_STEP, SoundMaker::viewBobbingStep, this); - mgr->reg(MtEvent::PLAYER_REGAIN_GROUND, SoundMaker::playerRegainGround, this); - mgr->reg(MtEvent::PLAYER_JUMP, SoundMaker::playerJump, this); - mgr->reg(MtEvent::CAMERA_PUNCH_LEFT, SoundMaker::cameraPunchLeft, this); - mgr->reg(MtEvent::CAMERA_PUNCH_RIGHT, SoundMaker::cameraPunchRight, this); - mgr->reg(MtEvent::NODE_DUG, SoundMaker::nodeDug, this); - mgr->reg(MtEvent::PLAYER_DAMAGE, SoundMaker::playerDamage, this); - mgr->reg(MtEvent::PLAYER_FALLING_DAMAGE, SoundMaker::playerFallingDamage, this); - } - - void step(float dtime) - { - m_player_step_timer -= dtime; - } -}; - -// Locally stored sounds don't need to be preloaded because of this -class GameOnDemandSoundFetcher: public OnDemandSoundFetcher -{ - std::set m_fetched; -private: - void paths_insert(std::set &dst_paths, - const std::string &base, - const std::string &name) - { - dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".ogg"); - dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".0.ogg"); - dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".1.ogg"); - dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".2.ogg"); - dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".3.ogg"); - dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".4.ogg"); - dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".5.ogg"); - dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".6.ogg"); - dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".7.ogg"); - dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".8.ogg"); - dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".9.ogg"); - } -public: - void fetchSounds(const std::string &name, - std::set &dst_paths, - std::set &dst_datas) - { - if (m_fetched.count(name)) - return; - - m_fetched.insert(name); - - paths_insert(dst_paths, porting::path_share, name); - paths_insert(dst_paths, porting::path_user, name); - } -}; - - -// before 1.8 there isn't a "integer interface", only float -#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8) -typedef f32 SamplerLayer_t; -#else -typedef s32 SamplerLayer_t; -#endif - - -class GameGlobalShaderConstantSetter : public IShaderConstantSetter -{ - Sky *m_sky; - bool *m_force_fog_off; - f32 *m_fog_range; - bool m_fog_enabled; - CachedPixelShaderSetting m_sky_bg_color; - CachedPixelShaderSetting m_fog_distance; - CachedVertexShaderSetting m_animation_timer_vertex; - CachedPixelShaderSetting m_animation_timer_pixel; - CachedPixelShaderSetting m_day_light; - CachedPixelShaderSetting m_eye_position_pixel; - CachedVertexShaderSetting m_eye_position_vertex; - CachedPixelShaderSetting m_minimap_yaw; - CachedPixelShaderSetting m_base_texture; - CachedPixelShaderSetting m_normal_texture; - CachedPixelShaderSetting m_texture_flags; - Client *m_client; - -public: - void onSettingsChange(const std::string &name) - { - if (name == "enable_fog") - m_fog_enabled = g_settings->getBool("enable_fog"); - } - - static void settingsCallback(const std::string &name, void *userdata) - { - reinterpret_cast(userdata)->onSettingsChange(name); - } - - void setSky(Sky *sky) { m_sky = sky; } - - GameGlobalShaderConstantSetter(Sky *sky, bool *force_fog_off, - f32 *fog_range, Client *client) : - m_sky(sky), - m_force_fog_off(force_fog_off), - m_fog_range(fog_range), - m_sky_bg_color("skyBgColor"), - m_fog_distance("fogDistance"), - m_animation_timer_vertex("animationTimer"), - m_animation_timer_pixel("animationTimer"), - m_day_light("dayLight"), - m_eye_position_pixel("eyePosition"), - m_eye_position_vertex("eyePosition"), - m_minimap_yaw("yawVec"), - m_base_texture("baseTexture"), - m_normal_texture("normalTexture"), - m_texture_flags("textureFlags"), - m_client(client) - { - g_settings->registerChangedCallback("enable_fog", settingsCallback, this); - m_fog_enabled = g_settings->getBool("enable_fog"); - } - - ~GameGlobalShaderConstantSetter() - { - g_settings->deregisterChangedCallback("enable_fog", settingsCallback, this); - } - - virtual void onSetConstants(video::IMaterialRendererServices *services, - bool is_highlevel) - { - if (!is_highlevel) - return; - - // Background color - video::SColor bgcolor = m_sky->getBgColor(); - video::SColorf bgcolorf(bgcolor); - float bgcolorfa[4] = { - bgcolorf.r, - bgcolorf.g, - bgcolorf.b, - bgcolorf.a, - }; - m_sky_bg_color.set(bgcolorfa, services); - - // Fog distance - float fog_distance = 10000 * BS; - - if (m_fog_enabled && !*m_force_fog_off) - fog_distance = *m_fog_range; - - m_fog_distance.set(&fog_distance, services); - - u32 daynight_ratio = (float)m_client->getEnv().getDayNightRatio(); - video::SColorf sunlight; - get_sunlight_color(&sunlight, daynight_ratio); - float dnc[3] = { - sunlight.r, - sunlight.g, - sunlight.b }; - m_day_light.set(dnc, services); - - u32 animation_timer = porting::getTimeMs() % 100000; - float animation_timer_f = (float)animation_timer / 100000.f; - m_animation_timer_vertex.set(&animation_timer_f, services); - m_animation_timer_pixel.set(&animation_timer_f, services); - - float eye_position_array[3]; - v3f epos = m_client->getEnv().getLocalPlayer()->getEyePosition(); -#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8) - eye_position_array[0] = epos.X; - eye_position_array[1] = epos.Y; - eye_position_array[2] = epos.Z; -#else - epos.getAs3Values(eye_position_array); -#endif - m_eye_position_pixel.set(eye_position_array, services); - m_eye_position_vertex.set(eye_position_array, services); - - if (m_client->getMinimap()) { - float minimap_yaw_array[3]; - v3f minimap_yaw = m_client->getMinimap()->getYawVec(); -#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8) - minimap_yaw_array[0] = minimap_yaw.X; - minimap_yaw_array[1] = minimap_yaw.Y; - minimap_yaw_array[2] = minimap_yaw.Z; -#else - minimap_yaw.getAs3Values(minimap_yaw_array); -#endif - m_minimap_yaw.set(minimap_yaw_array, services); - } - - 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); - } -}; - - -class GameGlobalShaderConstantSetterFactory : public IShaderConstantSetterFactory -{ - Sky *m_sky; - bool *m_force_fog_off; - f32 *m_fog_range; - Client *m_client; - std::vector created_nosky; -public: - GameGlobalShaderConstantSetterFactory(bool *force_fog_off, - f32 *fog_range, Client *client) : - m_sky(NULL), - m_force_fog_off(force_fog_off), - m_fog_range(fog_range), - m_client(client) - {} - - void setSky(Sky *sky) { - m_sky = sky; - for (GameGlobalShaderConstantSetter *ggscs : created_nosky) { - ggscs->setSky(m_sky); - } - created_nosky.clear(); - } - - virtual IShaderConstantSetter* create() - { - GameGlobalShaderConstantSetter *scs = new GameGlobalShaderConstantSetter( - m_sky, m_force_fog_off, m_fog_range, m_client); - if (!m_sky) - created_nosky.push_back(scs); - return scs; - } -}; - -#ifdef __ANDROID__ -#define SIZE_TAG "size[11,5.5]" -#else -#define SIZE_TAG "size[11,5.5,true]" // Fixed size on desktop -#endif - -/**************************************************************************** - - ****************************************************************************/ - -const float object_hit_delay = 0.2; - -struct FpsControl { - u32 last_time, busy_time, sleep_time; -}; - - -/* The reason the following structs are not anonymous structs within the - * class is that they are not used by the majority of member functions and - * many functions that do require objects of thse types do not modify them - * (so they can be passed as a const qualified parameter) - */ - -struct GameRunData { - u16 dig_index; - u16 new_playeritem; - PointedThing pointed_old; - bool digging; - bool ldown_for_dig; - bool dig_instantly; - bool digging_blocked; - bool left_punch; - bool update_wielded_item_trigger; - bool reset_jump_timer; - float nodig_delay_timer; - float dig_time; - float dig_time_complete; - float repeat_rightclick_timer; - float object_hit_delay_timer; - float time_from_last_punch; - ClientActiveObject *selected_object; - - float jump_timer; - float damage_flash; - float update_draw_list_timer; - - f32 fog_range; - - v3f update_draw_list_last_cam_dir; - - float time_of_day_smooth; -}; - -class Game; - -struct ClientEventHandler -{ - void (Game::*handler)(ClientEvent *, CameraOrientation *); -}; - -/**************************************************************************** - THE GAME - ****************************************************************************/ - -/* This is not intended to be a public class. If a public class becomes - * desirable then it may be better to create another 'wrapper' class that - * hides most of the stuff in this class (nothing in this class is required - * by any other file) but exposes the public methods/data only. - */ -class Game { -public: - Game(); - ~Game(); - - bool startup(bool *kill, - bool random_input, - InputHandler *input, - const std::string &map_dir, - const std::string &playername, - const std::string &password, - // If address is "", local server is used and address is updated - std::string *address, - u16 port, - std::string &error_message, - bool *reconnect, - ChatBackend *chat_backend, - const SubgameSpec &gamespec, // Used for local game - bool simple_singleplayer_mode); - - void run(); - void shutdown(); - -protected: - - void extendedResourceCleanup(); - - // Basic initialisation - bool init(const std::string &map_dir, std::string *address, - u16 port, - const SubgameSpec &gamespec); - bool initSound(); - bool createSingleplayerServer(const std::string &map_dir, - const SubgameSpec &gamespec, u16 port, std::string *address); - - // Client creation - bool createClient(const std::string &playername, - const std::string &password, std::string *address, u16 port); - bool initGui(); - - // Client connection - bool connectToServer(const std::string &playername, - const std::string &password, std::string *address, u16 port, - bool *connect_ok, bool *aborted); - bool getServerContent(bool *aborted); - - // Main loop - - void updateInteractTimers(f32 dtime); - bool checkConnection(); - bool handleCallbacks(); - void processQueues(); - void updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime); - void addProfilerGraphs(const RunStats &stats, const FpsControl &draw_times, f32 dtime); - void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime); - - // Input related - void processUserInput(f32 dtime); - void processKeyInput(); - void processItemSelection(u16 *new_playeritem); - - void dropSelectedItem(bool single_item = false); - void openInventory(); - void openConsole(float scale, const wchar_t *line=NULL); - void toggleFreeMove(); - void toggleFreeMoveAlt(); - void toggleFast(); - void toggleNoClip(); - void toggleCinematic(); - void toggleAutoforward(); - - void toggleMinimap(bool shift_pressed); - void toggleFog(); - void toggleDebug(); - void toggleUpdateCamera(); - - void increaseViewRange(); - void decreaseViewRange(); - void toggleFullViewRange(); - void checkZoomEnabled(); - - void updateCameraDirection(CameraOrientation *cam, float dtime); - void updateCameraOrientation(CameraOrientation *cam, float dtime); - void updatePlayerControl(const CameraOrientation &cam); - void step(f32 *dtime); - void processClientEvents(CameraOrientation *cam); - void updateCamera(u32 busy_time, f32 dtime); - void updateSound(f32 dtime); - void processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug); - /*! - * Returns the object or node the player is pointing at. - * Also updates the selected thing in the Hud. - * - * @param[in] shootline the shootline, starting from - * the camera position. This also gives the maximal distance - * of the search. - * @param[in] liquids_pointable if false, liquids are ignored - * @param[in] look_for_object if false, objects are ignored - * @param[in] camera_offset offset of the camera - * @param[out] selected_object the selected object or - * NULL if not found - */ - PointedThing updatePointedThing( - const core::line3d &shootline, bool liquids_pointable, - bool look_for_object, const v3s16 &camera_offset); - void handlePointingAtNothing(const ItemStack &playerItem); - void handlePointingAtNode(const PointedThing &pointed, - const ItemDefinition &playeritem_def, const ItemStack &playeritem, - const ToolCapabilities &playeritem_toolcap, f32 dtime); - void handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem, - const v3f &player_position, bool show_debug); - void handleDigging(const PointedThing &pointed, const v3s16 &nodepos, - const ToolCapabilities &playeritem_toolcap, f32 dtime); - void updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, - const CameraOrientation &cam); - void updateProfilerGraphs(ProfilerGraph *graph); - - // Misc - void limitFps(FpsControl *fps_timings, f32 *dtime); - - void showOverlayMessage(const char *msg, float dtime, int percent, - bool draw_clouds = true); - - static void settingChangedCallback(const std::string &setting_name, void *data); - void readSettings(); - - inline bool isKeyDown(GameKeyType k) - { - return input->isKeyDown(k); - } - inline bool wasKeyDown(GameKeyType k) - { - return input->wasKeyDown(k); - } - -#ifdef __ANDROID__ - void handleAndroidChatInput(); -#endif - -private: - struct Flags { - bool force_fog_off = false; - bool disable_camera_update = false; - }; - - void showDeathFormspec(); - void showPauseMenu(); - - // ClientEvent handlers - void handleClientEvent_None(ClientEvent *event, CameraOrientation *cam); - void handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam); - void handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam); - void handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam); - void handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam); - void handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam); - void handleClientEvent_HandleParticleEvent(ClientEvent *event, - CameraOrientation *cam); - void handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam); - void handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam); - void handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam); - void handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam); - void handleClientEvent_OverrideDayNigthRatio(ClientEvent *event, - CameraOrientation *cam); - void handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam); - - void updateChat(f32 dtime, const v2u32 &screensize); - - bool nodePlacementPrediction(const ItemDefinition &playeritem_def, - const ItemStack &playeritem, const v3s16 &nodepos, const v3s16 &neighbourpos); - static const ClientEventHandler clientEventHandler[CLIENTEVENT_MAX]; - - InputHandler *input = nullptr; - - Client *client = nullptr; - Server *server = nullptr; - - IWritableTextureSource *texture_src = nullptr; - IWritableShaderSource *shader_src = nullptr; - - // When created, these will be filled with data received from the server - IWritableItemDefManager *itemdef_manager = nullptr; - NodeDefManager *nodedef_manager = nullptr; - - GameOnDemandSoundFetcher soundfetcher; // useful when testing - ISoundManager *sound = nullptr; - bool sound_is_dummy = false; - SoundMaker *soundmaker = nullptr; - - ChatBackend *chat_backend = nullptr; - - GUIFormSpecMenu *current_formspec = nullptr; - //default: "". If other than "", empty show_formspec packets will only close the formspec when the formname matches - std::string cur_formname; - - EventManager *eventmgr = nullptr; - QuicktuneShortcutter *quicktune = nullptr; - bool registration_confirmation_shown = false; - - std::unique_ptr m_game_ui; - GUIChatConsole *gui_chat_console = nullptr; // Free using ->Drop() - MapDrawControl *draw_control = nullptr; - Camera *camera = nullptr; - Clouds *clouds = nullptr; // Free using ->Drop() - Sky *sky = nullptr; // Free using ->Drop() - Inventory *local_inventory = nullptr; - Hud *hud = nullptr; - Minimap *mapper = nullptr; - - GameRunData runData; - Flags m_flags; - - /* 'cache' - This class does take ownership/responsibily for cleaning up etc of any of - these items (e.g. device) - */ - IrrlichtDevice *device; - video::IVideoDriver *driver; - scene::ISceneManager *smgr; - bool *kill; - std::string *error_message; - bool *reconnect_requested; - scene::ISceneNode *skybox; - - bool random_input; - bool simple_singleplayer_mode; - /* End 'cache' */ - - /* Pre-calculated values - */ - int crack_animation_length; - - IntervalLimiter profiler_interval; - - /* - * TODO: Local caching of settings is not optimal and should at some stage - * be updated to use a global settings object for getting thse values - * (as opposed to the this local caching). This can be addressed in - * a later release. - */ - bool m_cache_doubletap_jump; - bool m_cache_enable_clouds; - bool m_cache_enable_joysticks; - bool m_cache_enable_particles; - bool m_cache_enable_fog; - bool m_cache_enable_noclip; - bool m_cache_enable_free_move; - f32 m_cache_mouse_sensitivity; - f32 m_cache_joystick_frustum_sensitivity; - f32 m_repeat_right_click_time; - f32 m_cache_cam_smoothing; - f32 m_cache_fog_start; - - bool m_invert_mouse = false; - bool m_first_loop_after_window_activation = false; - bool m_camera_offset_changed = false; - - bool m_does_lost_focus_pause_game = false; - -#ifdef __ANDROID__ - bool m_cache_hold_aux1; - bool m_android_chat_open; -#endif -}; - -Game::Game() : - m_game_ui(new GameUI()) -{ - g_settings->registerChangedCallback("doubletap_jump", - &settingChangedCallback, this); - g_settings->registerChangedCallback("enable_clouds", - &settingChangedCallback, this); - g_settings->registerChangedCallback("doubletap_joysticks", - &settingChangedCallback, this); - g_settings->registerChangedCallback("enable_particles", - &settingChangedCallback, this); - g_settings->registerChangedCallback("enable_fog", - &settingChangedCallback, this); - g_settings->registerChangedCallback("mouse_sensitivity", - &settingChangedCallback, this); - g_settings->registerChangedCallback("joystick_frustum_sensitivity", - &settingChangedCallback, this); - g_settings->registerChangedCallback("repeat_rightclick_time", - &settingChangedCallback, this); - g_settings->registerChangedCallback("noclip", - &settingChangedCallback, this); - g_settings->registerChangedCallback("free_move", - &settingChangedCallback, this); - g_settings->registerChangedCallback("cinematic", - &settingChangedCallback, this); - g_settings->registerChangedCallback("cinematic_camera_smoothing", - &settingChangedCallback, this); - g_settings->registerChangedCallback("camera_smoothing", - &settingChangedCallback, this); - - readSettings(); - -#ifdef __ANDROID__ - m_cache_hold_aux1 = false; // This is initialised properly later -#endif - -} - - - -/**************************************************************************** - MinetestApp Public - ****************************************************************************/ - -Game::~Game() -{ - delete client; - delete soundmaker; - if (!sound_is_dummy) - delete sound; - - delete server; // deleted first to stop all server threads - - delete hud; - delete local_inventory; - delete camera; - delete quicktune; - delete eventmgr; - delete texture_src; - delete shader_src; - delete nodedef_manager; - delete itemdef_manager; - delete draw_control; - - extendedResourceCleanup(); - - g_settings->deregisterChangedCallback("doubletap_jump", - &settingChangedCallback, this); - g_settings->deregisterChangedCallback("enable_clouds", - &settingChangedCallback, this); - g_settings->deregisterChangedCallback("enable_particles", - &settingChangedCallback, this); - g_settings->deregisterChangedCallback("enable_fog", - &settingChangedCallback, this); - g_settings->deregisterChangedCallback("mouse_sensitivity", - &settingChangedCallback, this); - g_settings->deregisterChangedCallback("repeat_rightclick_time", - &settingChangedCallback, this); - g_settings->deregisterChangedCallback("noclip", - &settingChangedCallback, this); - g_settings->deregisterChangedCallback("free_move", - &settingChangedCallback, this); - g_settings->deregisterChangedCallback("cinematic", - &settingChangedCallback, this); - g_settings->deregisterChangedCallback("cinematic_camera_smoothing", - &settingChangedCallback, this); - g_settings->deregisterChangedCallback("camera_smoothing", - &settingChangedCallback, this); -} - -bool Game::startup(bool *kill, - bool random_input, - InputHandler *input, - const std::string &map_dir, - const std::string &playername, - const std::string &password, - std::string *address, // can change if simple_singleplayer_mode - u16 port, - std::string &error_message, - bool *reconnect, - ChatBackend *chat_backend, - const SubgameSpec &gamespec, - bool simple_singleplayer_mode) -{ - // "cache" - this->device = RenderingEngine::get_raw_device(); - this->kill = kill; - this->error_message = &error_message; - this->reconnect_requested = reconnect; - this->random_input = random_input; - this->input = input; - this->chat_backend = chat_backend; - this->simple_singleplayer_mode = simple_singleplayer_mode; - - input->keycache.populate(); - - driver = device->getVideoDriver(); - smgr = RenderingEngine::get_scene_manager(); - - RenderingEngine::get_scene_manager()->getParameters()-> - setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true); - - // Reinit runData - runData = GameRunData(); - runData.time_from_last_punch = 10.0; - runData.update_wielded_item_trigger = true; - - m_game_ui->initFlags(); - - m_invert_mouse = g_settings->getBool("invert_mouse"); - m_first_loop_after_window_activation = true; - - g_translations->clear(); - - if (!init(map_dir, address, port, gamespec)) - return false; - - if (!createClient(playername, password, address, port)) - return false; - - RenderingEngine::initialize(client, hud); - - return true; -} - - -void Game::run() -{ - ProfilerGraph graph; - RunStats stats = { 0 }; - CameraOrientation cam_view_target = { 0 }; - CameraOrientation cam_view = { 0 }; - FpsControl draw_times = { 0 }; - f32 dtime; // in seconds - - /* Clear the profiler */ - Profiler::GraphValues dummyvalues; - g_profiler->graphGet(dummyvalues); - - draw_times.last_time = RenderingEngine::get_timer_time(); - - set_light_table(g_settings->getFloat("display_gamma")); - -#ifdef __ANDROID__ - m_cache_hold_aux1 = g_settings->getBool("fast_move") - && client->checkPrivilege("fast"); -#endif - - irr::core::dimension2d previous_screen_size(g_settings->getU16("screen_w"), - g_settings->getU16("screen_h")); - - while (RenderingEngine::run() - && !(*kill || g_gamecallback->shutdown_requested - || (server && server->isShutdownRequested()))) { - - const irr::core::dimension2d ¤t_screen_size = - RenderingEngine::get_video_driver()->getScreenSize(); - // Verify if window size has changed and save it if it's the case - // Ensure evaluating settings->getBool after verifying screensize - // First condition is cheaper - if (previous_screen_size != current_screen_size && - current_screen_size != irr::core::dimension2d(0,0) && - g_settings->getBool("autosave_screensize")) { - g_settings->setU16("screen_w", current_screen_size.Width); - g_settings->setU16("screen_h", current_screen_size.Height); - previous_screen_size = current_screen_size; - } - - /* Must be called immediately after a device->run() call because it - * uses device->getTimer()->getTime() - */ - limitFps(&draw_times, &dtime); - - updateStats(&stats, draw_times, dtime); - updateInteractTimers(dtime); - - if (!checkConnection()) - break; - if (!handleCallbacks()) - break; - - processQueues(); - - m_game_ui->clearInfoText(); - hud->resizeHotbar(); - - updateProfilers(stats, draw_times, dtime); - processUserInput(dtime); - // Update camera before player movement to avoid camera lag of one frame - updateCameraDirection(&cam_view_target, dtime); - cam_view.camera_yaw += (cam_view_target.camera_yaw - - cam_view.camera_yaw) * m_cache_cam_smoothing; - cam_view.camera_pitch += (cam_view_target.camera_pitch - - cam_view.camera_pitch) * m_cache_cam_smoothing; - updatePlayerControl(cam_view); - step(&dtime); - processClientEvents(&cam_view_target); - updateCamera(draw_times.busy_time, dtime); - updateSound(dtime); - processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud, - m_game_ui->m_flags.show_debug); - updateFrame(&graph, &stats, dtime, cam_view); - updateProfilerGraphs(&graph); - - // Update if minimap has been disabled by the server - m_game_ui->m_flags.show_minimap &= client->shouldShowMinimap(); - - if (m_does_lost_focus_pause_game && !device->isWindowFocused() && !isMenuActive()) { - showPauseMenu(); - } - } -} - - -void Game::shutdown() -{ - RenderingEngine::finalize(); -#if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 8 - if (g_settings->get("3d_mode") == "pageflip") { - driver->setRenderTarget(irr::video::ERT_STEREO_BOTH_BUFFERS); - } -#endif - if (current_formspec) - current_formspec->quitMenu(); - - showOverlayMessage("Shutting down...", 0, 0, false); - - if (clouds) - clouds->drop(); - - if (gui_chat_console) - gui_chat_console->drop(); - - if (sky) - sky->drop(); - - /* cleanup menus */ - while (g_menumgr.menuCount() > 0) { - g_menumgr.m_stack.front()->setVisible(false); - g_menumgr.deletingMenu(g_menumgr.m_stack.front()); - } - - if (current_formspec) { - current_formspec->drop(); - current_formspec = NULL; - } - - chat_backend->addMessage(L"", L"# Disconnected."); - chat_backend->addMessage(L"", L""); - - if (client) { - client->Stop(); - while (!client->isShutdown()) { - assert(texture_src != NULL); - assert(shader_src != NULL); - texture_src->processQueue(); - shader_src->processQueue(); - sleep_ms(100); - } - } -} - - -/****************************************************************************/ -/**************************************************************************** - Startup - ****************************************************************************/ -/****************************************************************************/ - -bool Game::init( - const std::string &map_dir, - std::string *address, - u16 port, - const SubgameSpec &gamespec) -{ - texture_src = createTextureSource(); - - showOverlayMessage("Loading...", 0, 0); - - shader_src = createShaderSource(); - - itemdef_manager = createItemDefManager(); - nodedef_manager = createNodeDefManager(); - - eventmgr = new EventManager(); - quicktune = new QuicktuneShortcutter(); - - if (!(texture_src && shader_src && itemdef_manager && nodedef_manager - && eventmgr && quicktune)) - return false; - - if (!initSound()) - return false; - - // Create a server if not connecting to an existing one - if (address->empty()) { - if (!createSingleplayerServer(map_dir, gamespec, port, address)) - return false; - } - - return true; -} - -bool Game::initSound() -{ -#if USE_SOUND - if (g_settings->getBool("enable_sound")) { - infostream << "Attempting to use OpenAL audio" << std::endl; - sound = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher); - if (!sound) - infostream << "Failed to initialize OpenAL audio" << std::endl; - } else - infostream << "Sound disabled." << std::endl; -#endif - - if (!sound) { - infostream << "Using dummy audio." << std::endl; - sound = &dummySoundManager; - sound_is_dummy = true; - } - - soundmaker = new SoundMaker(sound, nodedef_manager); - if (!soundmaker) - return false; - - soundmaker->registerReceiver(eventmgr); - - return true; -} - -bool Game::createSingleplayerServer(const std::string &map_dir, - const SubgameSpec &gamespec, u16 port, std::string *address) -{ - showOverlayMessage("Creating server...", 0, 5); - - std::string bind_str = g_settings->get("bind_address"); - Address bind_addr(0, 0, 0, 0, port); - - if (g_settings->getBool("ipv6_server")) { - bind_addr.setAddress((IPv6AddressBytes *) NULL); - } - - try { - bind_addr.Resolve(bind_str.c_str()); - } catch (ResolveError &e) { - infostream << "Resolving bind address \"" << bind_str - << "\" failed: " << e.what() - << " -- Listening on all addresses." << std::endl; - } - - if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) { - *error_message = "Unable to listen on " + - bind_addr.serializeString() + - " because IPv6 is disabled"; - errorstream << *error_message << std::endl; - return false; - } - - server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr, false); - server->init(); - server->start(); - - return true; -} - -bool Game::createClient(const std::string &playername, - const std::string &password, std::string *address, u16 port) -{ - showOverlayMessage("Creating client...", 0, 10); - - draw_control = new MapDrawControl; - if (!draw_control) - return false; - - bool could_connect, connect_aborted; -#ifdef HAVE_TOUCHSCREENGUI - if (g_touchscreengui) { - g_touchscreengui->init(texture_src); - g_touchscreengui->hide(); - } -#endif - if (!connectToServer(playername, password, address, port, - &could_connect, &connect_aborted)) - return false; - - if (!could_connect) { - if (error_message->empty() && !connect_aborted) { - // Should not happen if error messages are set properly - *error_message = "Connection failed for unknown reason"; - errorstream << *error_message << std::endl; - } - return false; - } - - if (!getServerContent(&connect_aborted)) { - if (error_message->empty() && !connect_aborted) { - // Should not happen if error messages are set properly - *error_message = "Connection failed for unknown reason"; - errorstream << *error_message << std::endl; - } - return false; - } - - GameGlobalShaderConstantSetterFactory *scsf = new GameGlobalShaderConstantSetterFactory( - &m_flags.force_fog_off, &runData.fog_range, client); - shader_src->addShaderConstantSetterFactory(scsf); - - // Update cached textures, meshes and materials - client->afterContentReceived(); - - /* Camera - */ - camera = new Camera(*draw_control, client); - if (!camera || !camera->successfullyCreated(*error_message)) - return false; - client->setCamera(camera); - - /* Clouds - */ - if (m_cache_enable_clouds) { - clouds = new Clouds(smgr, -1, time(0)); - if (!clouds) { - *error_message = "Memory allocation error (clouds)"; - errorstream << *error_message << std::endl; - return false; - } - } - - /* Skybox - */ - sky = new Sky(-1, texture_src); - scsf->setSky(sky); - skybox = NULL; // This is used/set later on in the main run loop - - local_inventory = new Inventory(itemdef_manager); - - if (!(sky && local_inventory)) { - *error_message = "Memory allocation error (sky or local inventory)"; - errorstream << *error_message << std::endl; - return false; - } - - /* Pre-calculated values - */ - video::ITexture *t = texture_src->getTexture("crack_anylength.png"); - if (t) { - v2u32 size = t->getOriginalSize(); - crack_animation_length = size.Y / size.X; - } else { - crack_animation_length = 5; - } - - if (!initGui()) - return false; - - /* Set window caption - */ - std::wstring str = utf8_to_wide(PROJECT_NAME_C); - str += L" "; - str += utf8_to_wide(g_version_hash); - str += L" ["; - str += driver->getName(); - str += L"]"; - device->setWindowCaption(str.c_str()); - - LocalPlayer *player = client->getEnv().getLocalPlayer(); - player->hurt_tilt_timer = 0; - player->hurt_tilt_strength = 0; - - hud = new Hud(guienv, client, player, local_inventory); - - if (!hud) { - *error_message = "Memory error: could not create HUD"; - errorstream << *error_message << std::endl; - return false; - } - - mapper = client->getMinimap(); - if (mapper) - mapper->setMinimapMode(MINIMAP_MODE_OFF); - - return true; -} - -bool Game::initGui() -{ - m_game_ui->init(); - - // Remove stale "recent" chat messages from previous connections - chat_backend->clearRecentChat(); - - // Make sure the size of the recent messages buffer is right - chat_backend->applySettings(); - - // Chat backend and console - gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(), - -1, chat_backend, client, &g_menumgr); - if (!gui_chat_console) { - *error_message = "Could not allocate memory for chat console"; - errorstream << *error_message << std::endl; - return false; - } - -#ifdef HAVE_TOUCHSCREENGUI - - if (g_touchscreengui) - g_touchscreengui->show(); - -#endif - - return true; -} - -bool Game::connectToServer(const std::string &playername, - const std::string &password, std::string *address, u16 port, - bool *connect_ok, bool *connection_aborted) -{ - *connect_ok = false; // Let's not be overly optimistic - *connection_aborted = false; - bool local_server_mode = false; - - showOverlayMessage("Resolving address...", 0, 15); - - Address connect_address(0, 0, 0, 0, port); - - try { - connect_address.Resolve(address->c_str()); - - if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY - //connect_address.Resolve("localhost"); - if (connect_address.isIPv6()) { - IPv6AddressBytes addr_bytes; - addr_bytes.bytes[15] = 1; - connect_address.setAddress(&addr_bytes); - } else { - connect_address.setAddress(127, 0, 0, 1); - } - local_server_mode = true; - } - } catch (ResolveError &e) { - *error_message = std::string("Couldn't resolve address: ") + e.what(); - errorstream << *error_message << std::endl; - return false; - } - - if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) { - *error_message = "Unable to connect to " + - connect_address.serializeString() + - " because IPv6 is disabled"; - errorstream << *error_message << std::endl; - return false; - } - - client = new Client(playername.c_str(), password, *address, - *draw_control, texture_src, shader_src, - itemdef_manager, nodedef_manager, sound, eventmgr, - connect_address.isIPv6(), m_game_ui.get()); - - if (!client) - return false; - - client->m_simple_singleplayer_mode = simple_singleplayer_mode; - - infostream << "Connecting to server at "; - connect_address.print(&infostream); - infostream << std::endl; - - client->connect(connect_address, - simple_singleplayer_mode || local_server_mode); - - /* - Wait for server to accept connection - */ - - try { - input->clear(); - - FpsControl fps_control = { 0 }; - f32 dtime; - f32 wait_time = 0; // in seconds - - fps_control.last_time = RenderingEngine::get_timer_time(); - - while (RenderingEngine::run()) { - - limitFps(&fps_control, &dtime); - - // Update client and server - client->step(dtime); - - if (server != NULL) - server->step(dtime); - - // End condition - if (client->getState() == LC_Init) { - *connect_ok = true; - break; - } - - // Break conditions - if (*connection_aborted) - break; - - if (client->accessDenied()) { - *error_message = "Access denied. Reason: " - + client->accessDeniedReason(); - *reconnect_requested = client->reconnectRequested(); - errorstream << *error_message << std::endl; - break; - } - - if (input->cancelPressed()) { - *connection_aborted = true; - infostream << "Connect aborted [Escape]" << std::endl; - break; - } - - if (client->m_is_registration_confirmation_state) { - if (registration_confirmation_shown) { - // Keep drawing the GUI - RenderingEngine::draw_menu_scene(guienv, dtime, true); - } else { - registration_confirmation_shown = true; - (new GUIConfirmRegistration(guienv, guienv->getRootGUIElement(), -1, - &g_menumgr, client, playername, password, *address, connection_aborted))->drop(); - } - } else { - wait_time += dtime; - // Only time out if we aren't waiting for the server we started - if (!address->empty() && wait_time > 10) { - *error_message = "Connection timed out."; - errorstream << *error_message << std::endl; - break; - } - - // Update status - showOverlayMessage("Connecting to server...", dtime, 20); - } - } - } catch (con::PeerNotFoundException &e) { - // TODO: Should something be done here? At least an info/error - // message? - return false; - } - - return true; -} - -bool Game::getServerContent(bool *aborted) -{ - input->clear(); - - FpsControl fps_control = { 0 }; - f32 dtime; // in seconds - - fps_control.last_time = RenderingEngine::get_timer_time(); - - while (RenderingEngine::run()) { - - limitFps(&fps_control, &dtime); - - // Update client and server - client->step(dtime); - - if (server != NULL) - server->step(dtime); - - // End condition - if (client->mediaReceived() && client->itemdefReceived() && - client->nodedefReceived()) { - break; - } - - // Error conditions - if (!checkConnection()) - return false; - - if (client->getState() < LC_Init) { - *error_message = "Client disconnected"; - errorstream << *error_message << std::endl; - return false; - } - - if (input->cancelPressed()) { - *aborted = true; - infostream << "Connect aborted [Escape]" << std::endl; - return false; - } - - // Display status - int progress = 25; - - if (!client->itemdefReceived()) { - const wchar_t *text = wgettext("Item definitions..."); - progress = 25; - RenderingEngine::draw_load_screen(text, guienv, texture_src, - dtime, progress); - delete[] text; - } else if (!client->nodedefReceived()) { - const wchar_t *text = wgettext("Node definitions..."); - progress = 30; - RenderingEngine::draw_load_screen(text, guienv, texture_src, - dtime, progress); - delete[] text; - } else { - std::stringstream message; - std::fixed(message); - message.precision(0); - message << gettext("Media...") << " " << (client->mediaReceiveProgress()*100) << "%"; - message.precision(2); - - if ((USE_CURL == 0) || - (!g_settings->getBool("enable_remote_media_server"))) { - float cur = client->getCurRate(); - std::string cur_unit = gettext("KiB/s"); - - if (cur > 900) { - cur /= 1024.0; - cur_unit = gettext("MiB/s"); - } - - message << " (" << cur << ' ' << cur_unit << ")"; - } - - progress = 30 + client->mediaReceiveProgress() * 35 + 0.5; - RenderingEngine::draw_load_screen(utf8_to_wide(message.str()), guienv, - texture_src, dtime, progress); - } - } - - return true; -} - - -/****************************************************************************/ -/**************************************************************************** - Run - ****************************************************************************/ -/****************************************************************************/ - -inline void Game::updateInteractTimers(f32 dtime) -{ - if (runData.nodig_delay_timer >= 0) - runData.nodig_delay_timer -= dtime; - - if (runData.object_hit_delay_timer >= 0) - runData.object_hit_delay_timer -= dtime; - - runData.time_from_last_punch += dtime; -} - - -/* returns false if game should exit, otherwise true - */ -inline bool Game::checkConnection() -{ - if (client->accessDenied()) { - *error_message = "Access denied. Reason: " - + client->accessDeniedReason(); - *reconnect_requested = client->reconnectRequested(); - errorstream << *error_message << std::endl; - return false; - } - - return true; -} - - -/* returns false if game should exit, otherwise true - */ -inline bool Game::handleCallbacks() -{ - if (g_gamecallback->disconnect_requested) { - g_gamecallback->disconnect_requested = false; - return false; - } - - if (g_gamecallback->changepassword_requested) { - (new GUIPasswordChange(guienv, guiroot, -1, - &g_menumgr, client))->drop(); - g_gamecallback->changepassword_requested = false; - } - - if (g_gamecallback->changevolume_requested) { - (new GUIVolumeChange(guienv, guiroot, -1, - &g_menumgr))->drop(); - g_gamecallback->changevolume_requested = false; - } - - if (g_gamecallback->keyconfig_requested) { - (new GUIKeyChangeMenu(guienv, guiroot, -1, - &g_menumgr))->drop(); - g_gamecallback->keyconfig_requested = false; - } - - if (g_gamecallback->keyconfig_changed) { - input->keycache.populate(); // update the cache with new settings - g_gamecallback->keyconfig_changed = false; - } - - return true; -} - - -void Game::processQueues() -{ - texture_src->processQueue(); - itemdef_manager->processQueue(client); - shader_src->processQueue(); -} - - -void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime) -{ - float profiler_print_interval = - g_settings->getFloat("profiler_print_interval"); - bool print_to_log = true; - - if (profiler_print_interval == 0) { - print_to_log = false; - profiler_print_interval = 5; - } - - if (profiler_interval.step(dtime, profiler_print_interval)) { - if (print_to_log) { - infostream << "Profiler:" << std::endl; - g_profiler->print(infostream); - } - - m_game_ui->updateProfiler(); - g_profiler->clear(); - } - - addProfilerGraphs(stats, draw_times, dtime); -} - - -void Game::addProfilerGraphs(const RunStats &stats, - const FpsControl &draw_times, f32 dtime) -{ - g_profiler->graphAdd("mainloop_other", - draw_times.busy_time / 1000.0f - stats.drawtime / 1000.0f); - - if (draw_times.sleep_time != 0) - g_profiler->graphAdd("mainloop_sleep", draw_times.sleep_time / 1000.0f); - g_profiler->graphAdd("mainloop_dtime", dtime); - - g_profiler->add("Elapsed time", dtime); - g_profiler->avg("FPS", 1. / dtime); -} - - -void Game::updateStats(RunStats *stats, const FpsControl &draw_times, - f32 dtime) -{ - - f32 jitter; - Jitter *jp; - - /* Time average and jitter calculation - */ - jp = &stats->dtime_jitter; - jp->avg = jp->avg * 0.96 + dtime * 0.04; - - jitter = dtime - jp->avg; - - if (jitter > jp->max) - jp->max = jitter; - - jp->counter += dtime; - - if (jp->counter > 0.0) { - jp->counter -= 3.0; - jp->max_sample = jp->max; - jp->max_fraction = jp->max_sample / (jp->avg + 0.001); - jp->max = 0.0; - } - - /* Busytime average and jitter calculation - */ - jp = &stats->busy_time_jitter; - jp->avg = jp->avg + draw_times.busy_time * 0.02; - - jitter = draw_times.busy_time - jp->avg; - - if (jitter > jp->max) - jp->max = jitter; - if (jitter < jp->min) - jp->min = jitter; - - jp->counter += dtime; - - if (jp->counter > 0.0) { - jp->counter -= 3.0; - jp->max_sample = jp->max; - jp->min_sample = jp->min; - jp->max = 0.0; - jp->min = 0.0; - } -} - - - -/**************************************************************************** - Input handling - ****************************************************************************/ - -void Game::processUserInput(f32 dtime) -{ - // Reset input if window not active or some menu is active - if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console)) { - input->clear(); -#ifdef HAVE_TOUCHSCREENGUI - g_touchscreengui->hide(); -#endif - } -#ifdef HAVE_TOUCHSCREENGUI - else if (g_touchscreengui) { - /* on touchscreengui step may generate own input events which ain't - * what we want in case we just did clear them */ - g_touchscreengui->step(dtime); - } -#endif - - if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) { - gui_chat_console->closeConsoleAtOnce(); - } - - // Input handler step() (used by the random input generator) - input->step(dtime); - -#ifdef __ANDROID__ - if (current_formspec != NULL) - current_formspec->getAndroidUIInput(); - else - handleAndroidChatInput(); -#endif - - // Increase timer for double tap of "keymap_jump" - if (m_cache_doubletap_jump && runData.jump_timer <= 0.2f) - runData.jump_timer += dtime; - - processKeyInput(); - processItemSelection(&runData.new_playeritem); -} - - -void Game::processKeyInput() -{ - if (wasKeyDown(KeyType::DROP)) { - dropSelectedItem(isKeyDown(KeyType::SNEAK)); - } else if (wasKeyDown(KeyType::AUTOFORWARD)) { - toggleAutoforward(); - } else if (wasKeyDown(KeyType::BACKWARD)) { - if (g_settings->getBool("continuous_forward")) - toggleAutoforward(); - } else if (wasKeyDown(KeyType::INVENTORY)) { - openInventory(); - } else if (input->cancelPressed()) { - if (!gui_chat_console->isOpenInhibited()) { - showPauseMenu(); - } - } else if (wasKeyDown(KeyType::CHAT)) { - openConsole(0.2, L""); - } else if (wasKeyDown(KeyType::CMD)) { - openConsole(0.2, L"/"); - } else if (wasKeyDown(KeyType::CMD_LOCAL)) { - if (client->moddingEnabled()) - openConsole(0.2, L"."); - else - m_game_ui->showStatusText(wgettext("CSM is disabled")); - } else if (wasKeyDown(KeyType::CONSOLE)) { - openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f)); - } else if (wasKeyDown(KeyType::FREEMOVE)) { - toggleFreeMove(); - } else if (wasKeyDown(KeyType::JUMP)) { - toggleFreeMoveAlt(); - } else if (wasKeyDown(KeyType::FASTMOVE)) { - toggleFast(); - } else if (wasKeyDown(KeyType::NOCLIP)) { - toggleNoClip(); - } else if (wasKeyDown(KeyType::MUTE)) { - bool new_mute_sound = !g_settings->getBool("mute_sound"); - g_settings->setBool("mute_sound", new_mute_sound); - if (new_mute_sound) - m_game_ui->showTranslatedStatusText("Sound muted"); - else - m_game_ui->showTranslatedStatusText("Sound unmuted"); - } else if (wasKeyDown(KeyType::INC_VOLUME)) { - float new_volume = rangelim(g_settings->getFloat("sound_volume") + 0.1f, 0.0f, 1.0f); - wchar_t buf[100]; - g_settings->setFloat("sound_volume", new_volume); - const wchar_t *str = wgettext("Volume changed to %d%%"); - swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100)); - delete[] str; - m_game_ui->showStatusText(buf); - } else if (wasKeyDown(KeyType::DEC_VOLUME)) { - float new_volume = rangelim(g_settings->getFloat("sound_volume") - 0.1f, 0.0f, 1.0f); - wchar_t buf[100]; - g_settings->setFloat("sound_volume", new_volume); - const wchar_t *str = wgettext("Volume changed to %d%%"); - swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100)); - delete[] str; - m_game_ui->showStatusText(buf); - } else if (wasKeyDown(KeyType::CINEMATIC)) { - toggleCinematic(); - } else if (wasKeyDown(KeyType::SCREENSHOT)) { - client->makeScreenshot(); - } else if (wasKeyDown(KeyType::TOGGLE_HUD)) { - m_game_ui->toggleHud(); - } else if (wasKeyDown(KeyType::MINIMAP)) { - toggleMinimap(isKeyDown(KeyType::SNEAK)); - } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) { - m_game_ui->toggleChat(); - } else if (wasKeyDown(KeyType::TOGGLE_FOG)) { - toggleFog(); - } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) { - toggleUpdateCamera(); - } else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) { - toggleDebug(); - } else if (wasKeyDown(KeyType::TOGGLE_PROFILER)) { - m_game_ui->toggleProfiler(); - } else if (wasKeyDown(KeyType::INCREASE_VIEWING_RANGE)) { - increaseViewRange(); - } else if (wasKeyDown(KeyType::DECREASE_VIEWING_RANGE)) { - decreaseViewRange(); - } else if (wasKeyDown(KeyType::RANGESELECT)) { - toggleFullViewRange(); - } else if (wasKeyDown(KeyType::ZOOM)) { - checkZoomEnabled(); - } else if (wasKeyDown(KeyType::QUICKTUNE_NEXT)) { - quicktune->next(); - } else if (wasKeyDown(KeyType::QUICKTUNE_PREV)) { - quicktune->prev(); - } else if (wasKeyDown(KeyType::QUICKTUNE_INC)) { - quicktune->inc(); - } else if (wasKeyDown(KeyType::QUICKTUNE_DEC)) { - quicktune->dec(); - } - - if (!isKeyDown(KeyType::JUMP) && runData.reset_jump_timer) { - runData.reset_jump_timer = false; - runData.jump_timer = 0.0f; - } - - if (quicktune->hasMessage()) { - m_game_ui->showStatusText(utf8_to_wide(quicktune->getMessage())); - } -} - -void Game::processItemSelection(u16 *new_playeritem) -{ - LocalPlayer *player = client->getEnv().getLocalPlayer(); - - /* Item selection using mouse wheel - */ - *new_playeritem = client->getPlayerItem(); - - s32 wheel = input->getMouseWheel(); - u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1, - player->hud_hotbar_itemcount - 1); - - s32 dir = wheel; - - if (input->joystick.wasKeyDown(KeyType::SCROLL_DOWN) || - wasKeyDown(KeyType::HOTBAR_NEXT)) { - dir = -1; - } - - if (input->joystick.wasKeyDown(KeyType::SCROLL_UP) || - wasKeyDown(KeyType::HOTBAR_PREV)) { - dir = 1; - } - - if (dir < 0) - *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0; - else if (dir > 0) - *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item; - // else dir == 0 - - /* Item selection using hotbar slot keys - */ - for (u16 i = 0; i < 23; i++) { - if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) { - if (i < PLAYER_INVENTORY_SIZE && i < player->hud_hotbar_itemcount) { - *new_playeritem = i; - infostream << "Selected item: " << new_playeritem << std::endl; - } - break; - } - } -} - - -void Game::dropSelectedItem(bool single_item) -{ - IDropAction *a = new IDropAction(); - a->count = single_item ? 1 : 0; - a->from_inv.setCurrentPlayer(); - a->from_list = "main"; - a->from_i = client->getPlayerItem(); - client->inventoryAction(a); -} - - -void Game::openInventory() -{ - /* - * Don't permit to open inventory is CAO or player doesn't exists. - * This prevent showing an empty inventory at player load - */ - - LocalPlayer *player = client->getEnv().getLocalPlayer(); - if (!player || !player->getCAO()) - return; - - infostream << "the_game: " << "Launching inventory" << std::endl; - - PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client); - - InventoryLocation inventoryloc; - inventoryloc.setCurrentPlayer(); - - if (!client->moddingEnabled() - || !client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) { - TextDest *txt_dst = new TextDestPlayerInventory(client); - GUIFormSpecMenu::create(current_formspec, client, &input->joystick, fs_src, - txt_dst, client->getFormspecPrepend()); - cur_formname = ""; - current_formspec->setFormSpec(fs_src->getForm(), inventoryloc); - } -} - - -void Game::openConsole(float scale, const wchar_t *line) -{ - assert(scale > 0.0f && scale <= 1.0f); - -#ifdef __ANDROID__ - porting::showInputDialog(gettext("ok"), "", "", 2); - m_android_chat_open = true; -#else - if (gui_chat_console->isOpenInhibited()) - return; - gui_chat_console->openConsole(scale); - if (line) { - gui_chat_console->setCloseOnEnter(true); - gui_chat_console->replaceAndAddToHistory(line); - } -#endif -} - -#ifdef __ANDROID__ -void Game::handleAndroidChatInput() -{ - if (m_android_chat_open && porting::getInputDialogState() == 0) { - std::string text = porting::getInputDialogValue(); - client->typeChatMessage(utf8_to_wide(text)); - } -} -#endif - - -void Game::toggleFreeMove() -{ - bool free_move = !g_settings->getBool("free_move"); - g_settings->set("free_move", bool_to_cstr(free_move)); - - if (free_move) { - if (client->checkPrivilege("fly")) { - m_game_ui->showTranslatedStatusText("Fly mode enabled"); - } else { - m_game_ui->showTranslatedStatusText("Fly mode enabled (note: no 'fly' privilege)"); - } - } else { - m_game_ui->showTranslatedStatusText("Fly mode disabled"); - } -} - -void Game::toggleFreeMoveAlt() -{ - if (m_cache_doubletap_jump && runData.jump_timer < 0.2f) - toggleFreeMove(); - - runData.reset_jump_timer = true; -} - - -void Game::toggleFast() -{ - bool fast_move = !g_settings->getBool("fast_move"); - bool has_fast_privs = client->checkPrivilege("fast"); - g_settings->set("fast_move", bool_to_cstr(fast_move)); - - if (fast_move) { - if (has_fast_privs) { - m_game_ui->showTranslatedStatusText("Fast mode enabled"); - } else { - m_game_ui->showTranslatedStatusText("Fast mode enabled (note: no 'fast' privilege)"); - } - } else { - m_game_ui->showTranslatedStatusText("Fast mode disabled"); - } - -#ifdef __ANDROID__ - m_cache_hold_aux1 = fast_move && has_fast_privs; -#endif -} - - -void Game::toggleNoClip() -{ - bool noclip = !g_settings->getBool("noclip"); - g_settings->set("noclip", bool_to_cstr(noclip)); - - if (noclip) { - if (client->checkPrivilege("noclip")) { - m_game_ui->showTranslatedStatusText("Noclip mode enabled"); - } else { - m_game_ui->showTranslatedStatusText("Noclip mode enabled (note: no 'noclip' privilege)"); - } - } else { - m_game_ui->showTranslatedStatusText("Noclip mode disabled"); - } -} - -void Game::toggleCinematic() -{ - bool cinematic = !g_settings->getBool("cinematic"); - g_settings->set("cinematic", bool_to_cstr(cinematic)); - - if (cinematic) - m_game_ui->showTranslatedStatusText("Cinematic mode enabled"); - else - m_game_ui->showTranslatedStatusText("Cinematic mode disabled"); -} - -// Autoforward by toggling continuous forward. -void Game::toggleAutoforward() -{ - bool autorun_enabled = !g_settings->getBool("continuous_forward"); - g_settings->set("continuous_forward", bool_to_cstr(autorun_enabled)); - - if (autorun_enabled) - m_game_ui->showTranslatedStatusText("Automatic forwards enabled"); - else - m_game_ui->showTranslatedStatusText("Automatic forwards disabled"); -} - -void Game::toggleMinimap(bool shift_pressed) -{ - if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap")) - return; - - if (shift_pressed) { - mapper->toggleMinimapShape(); - return; - } - - u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags; - - MinimapMode mode = MINIMAP_MODE_OFF; - if (hud_flags & HUD_FLAG_MINIMAP_VISIBLE) { - mode = mapper->getMinimapMode(); - mode = (MinimapMode)((int)mode + 1); - // If radar is disabled and in, or switching to, radar mode - if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE) && mode > 3) - mode = MINIMAP_MODE_OFF; - } - - m_game_ui->m_flags.show_minimap = true; - switch (mode) { - case MINIMAP_MODE_SURFACEx1: - m_game_ui->showTranslatedStatusText("Minimap in surface mode, Zoom x1"); - break; - case MINIMAP_MODE_SURFACEx2: - m_game_ui->showTranslatedStatusText("Minimap in surface mode, Zoom x2"); - break; - case MINIMAP_MODE_SURFACEx4: - m_game_ui->showTranslatedStatusText("Minimap in surface mode, Zoom x4"); - break; - case MINIMAP_MODE_RADARx1: - m_game_ui->showTranslatedStatusText("Minimap in radar mode, Zoom x1"); - break; - case MINIMAP_MODE_RADARx2: - m_game_ui->showTranslatedStatusText("Minimap in radar mode, Zoom x2"); - break; - case MINIMAP_MODE_RADARx4: - m_game_ui->showTranslatedStatusText("Minimap in radar mode, Zoom x4"); - break; - default: - mode = MINIMAP_MODE_OFF; - m_game_ui->m_flags.show_minimap = false; - if (hud_flags & HUD_FLAG_MINIMAP_VISIBLE) - m_game_ui->showTranslatedStatusText("Minimap hidden"); - else - m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod"); - } - - mapper->setMinimapMode(mode); -} - -void Game::toggleFog() -{ - bool fog_enabled = g_settings->getBool("enable_fog"); - g_settings->setBool("enable_fog", !fog_enabled); - if (fog_enabled) - m_game_ui->showTranslatedStatusText("Fog disabled"); - else - m_game_ui->showTranslatedStatusText("Fog enabled"); -} - - -void Game::toggleDebug() -{ - // Initial / 4x toggle: Chat only - // 1x toggle: Debug text with chat - // 2x toggle: Debug text with profiler graph - // 3x toggle: Debug text and wireframe - if (!m_game_ui->m_flags.show_debug) { - m_game_ui->m_flags.show_debug = true; - m_game_ui->m_flags.show_profiler_graph = false; - draw_control->show_wireframe = false; - m_game_ui->showTranslatedStatusText("Debug info shown"); - } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) { - m_game_ui->m_flags.show_profiler_graph = true; - m_game_ui->showTranslatedStatusText("Profiler graph shown"); - } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) { - m_game_ui->m_flags.show_profiler_graph = false; - draw_control->show_wireframe = true; - m_game_ui->showTranslatedStatusText("Wireframe shown"); - } else { - m_game_ui->m_flags.show_debug = false; - m_game_ui->m_flags.show_profiler_graph = false; - draw_control->show_wireframe = false; - if (client->checkPrivilege("debug")) { - m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden"); - } else { - m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden"); - } - } -} - - -void Game::toggleUpdateCamera() -{ - m_flags.disable_camera_update = !m_flags.disable_camera_update; - if (m_flags.disable_camera_update) - m_game_ui->showTranslatedStatusText("Camera update disabled"); - else - m_game_ui->showTranslatedStatusText("Camera update enabled"); -} - - -void Game::increaseViewRange() -{ - s16 range = g_settings->getS16("viewing_range"); - s16 range_new = range + 10; - - wchar_t buf[255]; - const wchar_t *str; - if (range_new > 4000) { - range_new = 4000; - str = wgettext("Viewing range is at maximum: %d"); - swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new); - delete[] str; - m_game_ui->showStatusText(buf); - - } else { - str = wgettext("Viewing range changed to %d"); - swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new); - delete[] str; - m_game_ui->showStatusText(buf); - } - g_settings->set("viewing_range", itos(range_new)); -} - - -void Game::decreaseViewRange() -{ - s16 range = g_settings->getS16("viewing_range"); - s16 range_new = range - 10; - - wchar_t buf[255]; - const wchar_t *str; - if (range_new < 20) { - range_new = 20; - str = wgettext("Viewing range is at minimum: %d"); - swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new); - delete[] str; - m_game_ui->showStatusText(buf); - } else { - str = wgettext("Viewing range changed to %d"); - swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new); - delete[] str; - m_game_ui->showStatusText(buf); - } - g_settings->set("viewing_range", itos(range_new)); -} - - -void Game::toggleFullViewRange() -{ - draw_control->range_all = !draw_control->range_all; - if (draw_control->range_all) - m_game_ui->showTranslatedStatusText("Enabled unlimited viewing range"); - else - m_game_ui->showTranslatedStatusText("Disabled unlimited viewing range"); -} - - -void Game::checkZoomEnabled() -{ - LocalPlayer *player = client->getEnv().getLocalPlayer(); - if (player->getZoomFOV() < 0.001f) - m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod"); -} - - -void Game::updateCameraDirection(CameraOrientation *cam, float dtime) -{ - if ((device->isWindowActive() && device->isWindowFocused() - && !isMenuActive()) || random_input) { - -#ifndef __ANDROID__ - if (!random_input) { - // Mac OSX gets upset if this is set every frame - if (device->getCursorControl()->isVisible()) - device->getCursorControl()->setVisible(false); - } -#endif - - if (m_first_loop_after_window_activation) { - m_first_loop_after_window_activation = false; - - input->setMousePos(driver->getScreenSize().Width / 2, - driver->getScreenSize().Height / 2); - } else { - updateCameraOrientation(cam, dtime); - } - - } else { - -#ifndef ANDROID - // Mac OSX gets upset if this is set every frame - if (!device->getCursorControl()->isVisible()) - device->getCursorControl()->setVisible(true); -#endif - - m_first_loop_after_window_activation = true; - - } -} - -void Game::updateCameraOrientation(CameraOrientation *cam, float dtime) -{ -#ifdef HAVE_TOUCHSCREENGUI - if (g_touchscreengui) { - cam->camera_yaw += g_touchscreengui->getYawChange(); - cam->camera_pitch = g_touchscreengui->getPitch(); - } else { -#endif - v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2); - v2s32 dist = input->getMousePos() - center; - - if (m_invert_mouse || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) { - dist.Y = -dist.Y; - } - - cam->camera_yaw -= dist.X * m_cache_mouse_sensitivity; - cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity; - - if (dist.X != 0 || dist.Y != 0) - input->setMousePos(center.X, center.Y); -#ifdef HAVE_TOUCHSCREENGUI - } -#endif - - if (m_cache_enable_joysticks) { - f32 c = m_cache_joystick_frustum_sensitivity * (1.f / 32767.f) * dtime; - cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c; - cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c; - } - - cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5); -} - - -void Game::updatePlayerControl(const CameraOrientation &cam) -{ - //TimeTaker tt("update player control", NULL, PRECISION_NANO); - - // DO NOT use the isKeyDown method for the forward, backward, left, right - // buttons, as the code that uses the controls needs to be able to - // distinguish between the two in order to know when to use joysticks. - - PlayerControl control( - input->isKeyDown(KeyType::FORWARD), - input->isKeyDown(KeyType::BACKWARD), - input->isKeyDown(KeyType::LEFT), - input->isKeyDown(KeyType::RIGHT), - isKeyDown(KeyType::JUMP), - isKeyDown(KeyType::SPECIAL1), - isKeyDown(KeyType::SNEAK), - isKeyDown(KeyType::ZOOM), - input->getLeftState(), - input->getRightState(), - cam.camera_pitch, - cam.camera_yaw, - input->joystick.getAxisWithoutDead(JA_SIDEWARD_MOVE), - input->joystick.getAxisWithoutDead(JA_FORWARD_MOVE) - ); - - u32 keypress_bits = - ( (u32)(isKeyDown(KeyType::FORWARD) & 0x1) << 0) | - ( (u32)(isKeyDown(KeyType::BACKWARD) & 0x1) << 1) | - ( (u32)(isKeyDown(KeyType::LEFT) & 0x1) << 2) | - ( (u32)(isKeyDown(KeyType::RIGHT) & 0x1) << 3) | - ( (u32)(isKeyDown(KeyType::JUMP) & 0x1) << 4) | - ( (u32)(isKeyDown(KeyType::SPECIAL1) & 0x1) << 5) | - ( (u32)(isKeyDown(KeyType::SNEAK) & 0x1) << 6) | - ( (u32)(input->getLeftState() & 0x1) << 7) | - ( (u32)(input->getRightState() & 0x1) << 8 - ); - -#ifdef ANDROID - /* For Android, simulate holding down AUX1 (fast move) if the user has - * the fast_move setting toggled on. If there is an aux1 key defined for - * Android then its meaning is inverted (i.e. holding aux1 means walk and - * not fast) - */ - if (m_cache_hold_aux1) { - control.aux1 = control.aux1 ^ true; - keypress_bits ^= ((u32)(1U << 5)); - } -#endif - - LocalPlayer *player = client->getEnv().getLocalPlayer(); - - // autojump if set: simulate "jump" key - if (player->getAutojump()) { - control.jump = true; - keypress_bits |= 1U << 4; - } - - client->setPlayerControl(control); - player->keyPressed = keypress_bits; - - //tt.stop(); -} - - -inline void Game::step(f32 *dtime) -{ - bool can_be_and_is_paused = - (simple_singleplayer_mode && g_menumgr.pausesGame()); - - if (can_be_and_is_paused) { // This is for a singleplayer server - *dtime = 0; // No time passes - } else { - if (server) - server->step(*dtime); - - client->step(*dtime); - } -} - -const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = { - {&Game::handleClientEvent_None}, - {&Game::handleClientEvent_PlayerDamage}, - {&Game::handleClientEvent_PlayerForceMove}, - {&Game::handleClientEvent_Deathscreen}, - {&Game::handleClientEvent_ShowFormSpec}, - {&Game::handleClientEvent_ShowLocalFormSpec}, - {&Game::handleClientEvent_HandleParticleEvent}, - {&Game::handleClientEvent_HandleParticleEvent}, - {&Game::handleClientEvent_HandleParticleEvent}, - {&Game::handleClientEvent_HudAdd}, - {&Game::handleClientEvent_HudRemove}, - {&Game::handleClientEvent_HudChange}, - {&Game::handleClientEvent_SetSky}, - {&Game::handleClientEvent_OverrideDayNigthRatio}, - {&Game::handleClientEvent_CloudParams}, -}; - -void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam) -{ - FATAL_ERROR("ClientEvent type None received"); -} - -void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam) -{ - if (client->moddingEnabled()) { - client->getScript()->on_damage_taken(event->player_damage.amount); - } - - // Damage flash and hurt tilt are not used at death - if (client->getHP() > 0) { - runData.damage_flash += 95.0f + 3.2f * event->player_damage.amount; - runData.damage_flash = MYMIN(runData.damage_flash, 127.0f); - - LocalPlayer *player = client->getEnv().getLocalPlayer(); - - player->hurt_tilt_timer = 1.5f; - player->hurt_tilt_strength = - rangelim(event->player_damage.amount / 4.0f, 1.0f, 4.0f); - } - - // Play damage sound - client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_DAMAGE)); -} - -void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam) -{ - cam->camera_yaw = event->player_force_move.yaw; - cam->camera_pitch = event->player_force_move.pitch; -} - -void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam) -{ - // If CSM enabled, deathscreen is handled by CSM code in - // builtin/client/init.lua - if (client->moddingEnabled()) - client->getScript()->on_death(); - else - showDeathFormspec(); - - /* Handle visualization */ - LocalPlayer *player = client->getEnv().getLocalPlayer(); - runData.damage_flash = 0; - player->hurt_tilt_timer = 0; - player->hurt_tilt_strength = 0; -} - -void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam) -{ - if (event->show_formspec.formspec->empty()) { - if (current_formspec && (event->show_formspec.formname->empty() - || *(event->show_formspec.formname) == cur_formname)) { - current_formspec->quitMenu(); - } - } else { - FormspecFormSource *fs_src = - new FormspecFormSource(*(event->show_formspec.formspec)); - TextDestPlayerInventory *txt_dst = - new TextDestPlayerInventory(client, *(event->show_formspec.formname)); - - GUIFormSpecMenu::create(current_formspec, client, &input->joystick, - fs_src, txt_dst, client->getFormspecPrepend()); - cur_formname = *(event->show_formspec.formname); - } - - delete event->show_formspec.formspec; - delete event->show_formspec.formname; -} - -void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam) -{ - FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec); - LocalFormspecHandler *txt_dst = - new LocalFormspecHandler(*event->show_formspec.formname, client); - GUIFormSpecMenu::create(current_formspec, client, &input->joystick, - fs_src, txt_dst, client->getFormspecPrepend()); - - delete event->show_formspec.formspec; - delete event->show_formspec.formname; -} - -void Game::handleClientEvent_HandleParticleEvent(ClientEvent *event, - CameraOrientation *cam) -{ - LocalPlayer *player = client->getEnv().getLocalPlayer(); - client->getParticleManager()->handleParticleEvent(event, client, player); -} - -void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam) -{ - LocalPlayer *player = client->getEnv().getLocalPlayer(); - auto &hud_server_to_client = client->getHUDTranslationMap(); - - u32 server_id = event->hudadd.server_id; - // ignore if we already have a HUD with that ID - auto i = hud_server_to_client.find(server_id); - if (i != hud_server_to_client.end()) { - delete event->hudadd.pos; - delete event->hudadd.name; - delete event->hudadd.scale; - delete event->hudadd.text; - delete event->hudadd.align; - delete event->hudadd.offset; - delete event->hudadd.world_pos; - delete event->hudadd.size; - return; - } - - HudElement *e = new HudElement; - e->type = (HudElementType)event->hudadd.type; - e->pos = *event->hudadd.pos; - e->name = *event->hudadd.name; - e->scale = *event->hudadd.scale; - e->text = *event->hudadd.text; - e->number = event->hudadd.number; - e->item = event->hudadd.item; - e->dir = event->hudadd.dir; - e->align = *event->hudadd.align; - e->offset = *event->hudadd.offset; - e->world_pos = *event->hudadd.world_pos; - e->size = *event->hudadd.size; - hud_server_to_client[server_id] = player->addHud(e); - - delete event->hudadd.pos; - delete event->hudadd.name; - delete event->hudadd.scale; - delete event->hudadd.text; - delete event->hudadd.align; - delete event->hudadd.offset; - delete event->hudadd.world_pos; - delete event->hudadd.size; -} - -void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam) -{ - LocalPlayer *player = client->getEnv().getLocalPlayer(); - HudElement *e = player->removeHud(event->hudrm.id); - delete e; -} - -void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam) -{ - LocalPlayer *player = client->getEnv().getLocalPlayer(); - - u32 id = event->hudchange.id; - HudElement *e = player->getHud(id); - - if (e == NULL) { - delete event->hudchange.v3fdata; - delete event->hudchange.v2fdata; - delete event->hudchange.sdata; - delete event->hudchange.v2s32data; - return; - } - - switch (event->hudchange.stat) { - case HUD_STAT_POS: - e->pos = *event->hudchange.v2fdata; - break; - - case HUD_STAT_NAME: - e->name = *event->hudchange.sdata; - break; - - case HUD_STAT_SCALE: - e->scale = *event->hudchange.v2fdata; - break; - - case HUD_STAT_TEXT: - e->text = *event->hudchange.sdata; - break; - - case HUD_STAT_NUMBER: - e->number = event->hudchange.data; - break; - - case HUD_STAT_ITEM: - e->item = event->hudchange.data; - break; - - case HUD_STAT_DIR: - e->dir = event->hudchange.data; - break; - - case HUD_STAT_ALIGN: - e->align = *event->hudchange.v2fdata; - break; - - case HUD_STAT_OFFSET: - e->offset = *event->hudchange.v2fdata; - break; - - case HUD_STAT_WORLD_POS: - e->world_pos = *event->hudchange.v3fdata; - break; - - case HUD_STAT_SIZE: - e->size = *event->hudchange.v2s32data; - break; - } - - delete event->hudchange.v3fdata; - delete event->hudchange.v2fdata; - delete event->hudchange.sdata; - delete event->hudchange.v2s32data; -} - -void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam) -{ - sky->setVisible(false); - // Whether clouds are visible in front of a custom skybox - sky->setCloudsEnabled(event->set_sky.clouds); - - if (skybox) { - skybox->remove(); - skybox = NULL; - } - - // Handle according to type - if (*event->set_sky.type == "regular") { - sky->setVisible(true); - sky->setCloudsEnabled(true); - } else if (*event->set_sky.type == "skybox" && - event->set_sky.params->size() == 6) { - sky->setFallbackBgColor(*event->set_sky.bgcolor); - skybox = RenderingEngine::get_scene_manager()->addSkyBoxSceneNode( - texture_src->getTextureForMesh((*event->set_sky.params)[0]), - texture_src->getTextureForMesh((*event->set_sky.params)[1]), - texture_src->getTextureForMesh((*event->set_sky.params)[2]), - texture_src->getTextureForMesh((*event->set_sky.params)[3]), - texture_src->getTextureForMesh((*event->set_sky.params)[4]), - texture_src->getTextureForMesh((*event->set_sky.params)[5])); - } - // Handle everything else as plain color - else { - if (*event->set_sky.type != "plain") - infostream << "Unknown sky type: " - << (*event->set_sky.type) << std::endl; - - sky->setFallbackBgColor(*event->set_sky.bgcolor); - } - - delete event->set_sky.bgcolor; - delete event->set_sky.type; - delete event->set_sky.params; -} - -void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event, - CameraOrientation *cam) -{ - client->getEnv().setDayNightRatioOverride( - event->override_day_night_ratio.do_override, - event->override_day_night_ratio.ratio_f * 1000.0f); -} - -void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam) -{ - if (!clouds) - return; - - clouds->setDensity(event->cloud_params.density); - clouds->setColorBright(video::SColor(event->cloud_params.color_bright)); - clouds->setColorAmbient(video::SColor(event->cloud_params.color_ambient)); - clouds->setHeight(event->cloud_params.height); - clouds->setThickness(event->cloud_params.thickness); - clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y)); -} - -void Game::processClientEvents(CameraOrientation *cam) -{ - while (client->hasClientEvents()) { - std::unique_ptr event(client->getClientEvent()); - FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, "Invalid clientevent type"); - const ClientEventHandler& evHandler = clientEventHandler[event->type]; - (this->*evHandler.handler)(event.get(), cam); - } -} - -void Game::updateChat(f32 dtime, const v2u32 &screensize) -{ - // Add chat log output for errors to be shown in chat - static LogOutputBuffer chat_log_error_buf(g_logger, LL_ERROR); - - // Get new messages from error log buffer - while (!chat_log_error_buf.empty()) { - std::wstring error_message = utf8_to_wide(chat_log_error_buf.get()); - if (!g_settings->getBool("disable_escape_sequences")) { - error_message.insert(0, L"\x1b(c@red)"); - error_message.append(L"\x1b(c@white)"); - } - chat_backend->addMessage(L"", error_message); - } - - // Get new messages from client - std::wstring message; - while (client->getChatMessage(message)) { - chat_backend->addUnparsedMessage(message); - } - - // Remove old messages - chat_backend->step(dtime); - - // Display all messages in a static text element - m_game_ui->setChatText(chat_backend->getRecentChat(), - chat_backend->getRecentBuffer().getLineCount()); -} - -void Game::updateCamera(u32 busy_time, f32 dtime) -{ - LocalPlayer *player = client->getEnv().getLocalPlayer(); - - /* - For interaction purposes, get info about the held item - - What item is it? - - Is it a usable item? - - Can it point to liquids? - */ - ItemStack playeritem; - { - InventoryList *mlist = local_inventory->getList("main"); - - if (mlist && client->getPlayerItem() < mlist->getSize()) - playeritem = mlist->getItem(client->getPlayerItem()); - } - - if (playeritem.getDefinition(itemdef_manager).name.empty()) { // override the hand - InventoryList *hlist = local_inventory->getList("hand"); - if (hlist) - playeritem = hlist->getItem(0); - } - - - ToolCapabilities playeritem_toolcap = - playeritem.getToolCapabilities(itemdef_manager); - - v3s16 old_camera_offset = camera->getOffset(); - - if (wasKeyDown(KeyType::CAMERA_MODE)) { - GenericCAO *playercao = player->getCAO(); - - // If playercao not loaded, don't change camera - if (!playercao) - return; - - camera->toggleCameraMode(); - - playercao->setVisible(camera->getCameraMode() > CAMERA_MODE_FIRST); - playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST); - } - - float full_punch_interval = playeritem_toolcap.full_punch_interval; - float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval; - - tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0); - camera->update(player, dtime, busy_time / 1000.0f, tool_reload_ratio); - camera->step(dtime); - - v3f camera_position = camera->getPosition(); - v3f camera_direction = camera->getDirection(); - f32 camera_fov = camera->getFovMax(); - v3s16 camera_offset = camera->getOffset(); - - m_camera_offset_changed = (camera_offset != old_camera_offset); - - if (!m_flags.disable_camera_update) { - client->getEnv().getClientMap().updateCamera(camera_position, - camera_direction, camera_fov, camera_offset); - - if (m_camera_offset_changed) { - client->updateCameraOffset(camera_offset); - client->getEnv().updateCameraOffset(camera_offset); - - if (clouds) - clouds->updateCameraOffset(camera_offset); - } - } -} - - -void Game::updateSound(f32 dtime) -{ - // Update sound listener - v3s16 camera_offset = camera->getOffset(); - sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS), - v3f(0, 0, 0), // velocity - camera->getDirection(), - camera->getCameraNode()->getUpVector()); - - bool mute_sound = g_settings->getBool("mute_sound"); - if (mute_sound) { - sound->setListenerGain(0.0f); - } else { - // Check if volume is in the proper range, else fix it. - float old_volume = g_settings->getFloat("sound_volume"); - float new_volume = rangelim(old_volume, 0.0f, 1.0f); - sound->setListenerGain(new_volume); - - if (old_volume != new_volume) { - g_settings->setFloat("sound_volume", new_volume); - } - } - - LocalPlayer *player = client->getEnv().getLocalPlayer(); - - // Tell the sound maker whether to make footstep sounds - soundmaker->makes_footstep_sound = player->makes_footstep_sound; - - // Update sound maker - if (player->makes_footstep_sound) - soundmaker->step(dtime); - - ClientMap &map = client->getEnv().getClientMap(); - MapNode n = map.getNodeNoEx(player->getFootstepNodePos()); - soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep; -} - - -void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug) -{ - LocalPlayer *player = client->getEnv().getLocalPlayer(); - - ItemStack playeritem; - { - InventoryList *mlist = local_inventory->getList("main"); - - if (mlist && client->getPlayerItem() < mlist->getSize()) - playeritem = mlist->getItem(client->getPlayerItem()); - } - - const ItemDefinition &playeritem_def = - playeritem.getDefinition(itemdef_manager); - InventoryList *hlist = local_inventory->getList("hand"); - const ItemDefinition &hand_def = - hlist ? hlist->getItem(0).getDefinition(itemdef_manager) : itemdef_manager->get(""); - - v3f player_position = player->getPosition(); - v3f camera_position = camera->getPosition(); - v3f camera_direction = camera->getDirection(); - v3s16 camera_offset = camera->getOffset(); - - - /* - Calculate what block is the crosshair pointing to - */ - - f32 d = playeritem_def.range; // max. distance - f32 d_hand = hand_def.range; - - if (d < 0 && d_hand >= 0) - d = d_hand; - else if (d < 0) - d = 4.0; - - core::line3d shootline; - - if (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT) { - shootline = core::line3d(camera_position, - camera_position + camera_direction * BS * d); - } else { - // prevent player pointing anything in front-view - shootline = core::line3d(camera_position,camera_position); - } - -#ifdef HAVE_TOUCHSCREENGUI - - if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) { - shootline = g_touchscreengui->getShootline(); - // Scale shootline to the acual distance the player can reach - shootline.end = shootline.start - + shootline.getVector().normalize() * BS * d; - shootline.start += intToFloat(camera_offset, BS); - shootline.end += intToFloat(camera_offset, BS); - } - -#endif - - PointedThing pointed = updatePointedThing(shootline, - playeritem_def.liquids_pointable, - !runData.ldown_for_dig, - camera_offset); - - if (pointed != runData.pointed_old) { - infostream << "Pointing at " << pointed.dump() << std::endl; - hud->updateSelectionMesh(camera_offset); - } - - if (runData.digging_blocked && !input->getLeftState()) { - // allow digging again if button is not pressed - runData.digging_blocked = false; - } - - /* - Stop digging when - - releasing left mouse button - - pointing away from node - */ - if (runData.digging) { - if (input->getLeftReleased()) { - infostream << "Left button released" - << " (stopped digging)" << std::endl; - runData.digging = false; - } else if (pointed != runData.pointed_old) { - if (pointed.type == POINTEDTHING_NODE - && runData.pointed_old.type == POINTEDTHING_NODE - && pointed.node_undersurface - == runData.pointed_old.node_undersurface) { - // Still pointing to the same node, but a different face. - // Don't reset. - } else { - infostream << "Pointing away from node" - << " (stopped digging)" << std::endl; - runData.digging = false; - hud->updateSelectionMesh(camera_offset); - } - } - - if (!runData.digging) { - client->interact(1, runData.pointed_old); - client->setCrack(-1, v3s16(0, 0, 0)); - runData.dig_time = 0.0; - } - } else if (runData.dig_instantly && input->getLeftReleased()) { - // Remove e.g. torches faster when clicking instead of holding LMB - runData.nodig_delay_timer = 0; - runData.dig_instantly = false; - } - - if (!runData.digging && runData.ldown_for_dig && !input->getLeftState()) { - runData.ldown_for_dig = false; - } - - runData.left_punch = false; - - soundmaker->m_player_leftpunch_sound.name = ""; - - // Prepare for repeating, unless we're not supposed to - if (input->getRightState() && !g_settings->getBool("safe_dig_and_place")) - runData.repeat_rightclick_timer += dtime; - else - runData.repeat_rightclick_timer = 0; - - if (playeritem_def.usable && input->getLeftState()) { - if (input->getLeftClicked() && (!client->moddingEnabled() - || !client->getScript()->on_item_use(playeritem, pointed))) - client->interact(4, pointed); - } else if (pointed.type == POINTEDTHING_NODE) { - ToolCapabilities playeritem_toolcap = - playeritem.getToolCapabilities(itemdef_manager); - if (playeritem.name.empty()) { - const ToolCapabilities *handToolcap = hlist - ? &hlist->getItem(0).getToolCapabilities(itemdef_manager) - : itemdef_manager->get("").tool_capabilities; - - if (handToolcap != nullptr) - playeritem_toolcap = *handToolcap; - } - handlePointingAtNode(pointed, playeritem_def, playeritem, - playeritem_toolcap, dtime); - } else if (pointed.type == POINTEDTHING_OBJECT) { - handlePointingAtObject(pointed, playeritem, player_position, show_debug); - } else if (input->getLeftState()) { - // When button is held down in air, show continuous animation - runData.left_punch = true; - } else if (input->getRightClicked()) { - handlePointingAtNothing(playeritem); - } - - runData.pointed_old = pointed; - - if (runData.left_punch || input->getLeftClicked()) - camera->setDigging(0); // left click animation - - input->resetLeftClicked(); - input->resetRightClicked(); - - input->resetLeftReleased(); - input->resetRightReleased(); -} - - -PointedThing Game::updatePointedThing( - const core::line3d &shootline, - bool liquids_pointable, - bool look_for_object, - const v3s16 &camera_offset) -{ - std::vector *selectionboxes = hud->getSelectionBoxes(); - selectionboxes->clear(); - hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0)); - static thread_local const bool show_entity_selectionbox = g_settings->getBool( - "show_entity_selectionbox"); - - ClientEnvironment &env = client->getEnv(); - ClientMap &map = env.getClientMap(); - const NodeDefManager *nodedef = map.getNodeDefManager(); - - runData.selected_object = NULL; - - RaycastState s(shootline, look_for_object, liquids_pointable); - PointedThing result; - env.continueRaycast(&s, &result); - if (result.type == POINTEDTHING_OBJECT) { - runData.selected_object = client->getEnv().getActiveObject(result.object_id); - aabb3f selection_box; - if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() && - runData.selected_object->getSelectionBox(&selection_box)) { - v3f pos = runData.selected_object->getPosition(); - selectionboxes->push_back(aabb3f(selection_box)); - hud->setSelectionPos(pos, camera_offset); - } - } else if (result.type == POINTEDTHING_NODE) { - // Update selection boxes - MapNode n = map.getNodeNoEx(result.node_undersurface); - std::vector boxes; - n.getSelectionBoxes(nodedef, &boxes, - n.getNeighbors(result.node_undersurface, &map)); - - f32 d = 0.002 * BS; - for (std::vector::const_iterator i = boxes.begin(); - i != boxes.end(); ++i) { - aabb3f box = *i; - box.MinEdge -= v3f(d, d, d); - box.MaxEdge += v3f(d, d, d); - selectionboxes->push_back(box); - } - hud->setSelectionPos(intToFloat(result.node_undersurface, BS), - camera_offset); - hud->setSelectedFaceNormal(v3f( - result.intersection_normal.X, - result.intersection_normal.Y, - result.intersection_normal.Z)); - } - - // Update selection mesh light level and vertex colors - if (!selectionboxes->empty()) { - v3f pf = hud->getSelectionPos(); - v3s16 p = floatToInt(pf, BS); - - // Get selection mesh light level - MapNode n = map.getNodeNoEx(p); - u16 node_light = getInteriorLight(n, -1, nodedef); - u16 light_level = node_light; - - for (const v3s16 &dir : g_6dirs) { - n = map.getNodeNoEx(p + dir); - node_light = getInteriorLight(n, -1, nodedef); - if (node_light > light_level) - light_level = node_light; - } - - u32 daynight_ratio = client->getEnv().getDayNightRatio(); - video::SColor c; - final_color_blend(&c, light_level, daynight_ratio); - - // Modify final color a bit with time - u32 timer = porting::getTimeMs() % 5000; - float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5)); - float sin_r = 0.08f * std::sin(timerf); - float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f); - float sin_b = 0.08f * std::sin(timerf + irr::core::PI); - c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255)); - c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255)); - c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255)); - - // Set mesh final color - hud->setSelectionMeshColor(c); - } - return result; -} - - -void Game::handlePointingAtNothing(const ItemStack &playerItem) -{ - infostream << "Right Clicked in Air" << std::endl; - PointedThing fauxPointed; - fauxPointed.type = POINTEDTHING_NOTHING; - client->interact(5, fauxPointed); -} - - -void Game::handlePointingAtNode(const PointedThing &pointed, - const ItemDefinition &playeritem_def, const ItemStack &playeritem, - const ToolCapabilities &playeritem_toolcap, f32 dtime) -{ - v3s16 nodepos = pointed.node_undersurface; - v3s16 neighbourpos = pointed.node_abovesurface; - - /* - Check information text of node - */ - - ClientMap &map = client->getEnv().getClientMap(); - - if (runData.nodig_delay_timer <= 0.0 && input->getLeftState() - && !runData.digging_blocked - && client->checkPrivilege("interact")) { - handleDigging(pointed, nodepos, playeritem_toolcap, dtime); - } - - // This should be done after digging handling - NodeMetadata *meta = map.getNodeMetadata(nodepos); - - if (meta) { - m_game_ui->setInfoText(unescape_translate(utf8_to_wide( - meta->getString("infotext")))); - } else { - MapNode n = map.getNodeNoEx(nodepos); - - if (nodedef_manager->get(n).tiledef[0].name == "unknown_node.png") { - m_game_ui->setInfoText(L"Unknown node: " + - utf8_to_wide(nodedef_manager->get(n).name)); - } - } - - if ((input->getRightClicked() || - runData.repeat_rightclick_timer >= m_repeat_right_click_time) && - client->checkPrivilege("interact")) { - runData.repeat_rightclick_timer = 0; - infostream << "Ground right-clicked" << std::endl; - - if (meta && !meta->getString("formspec").empty() && !random_input - && !isKeyDown(KeyType::SNEAK)) { - // Report right click to server - if (nodedef_manager->get(map.getNodeNoEx(nodepos)).rightclickable) { - client->interact(3, pointed); - } - - infostream << "Launching custom inventory view" << std::endl; - - InventoryLocation inventoryloc; - inventoryloc.setNodeMeta(nodepos); - - NodeMetadataFormSource *fs_src = new NodeMetadataFormSource( - &client->getEnv().getClientMap(), nodepos); - TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client); - - GUIFormSpecMenu::create(current_formspec, client, &input->joystick, fs_src, - txt_dst, client->getFormspecPrepend()); - cur_formname.clear(); - - current_formspec->setFormSpec(meta->getString("formspec"), inventoryloc); - } else { - // Report right click to server - - camera->setDigging(1); // right click animation (always shown for feedback) - - // If the wielded item has node placement prediction, - // make that happen - bool placed = nodePlacementPrediction(playeritem_def, playeritem, nodepos, - neighbourpos); - - if (placed) { - // Report to server - client->interact(3, pointed); - // Read the sound - soundmaker->m_player_rightpunch_sound = - playeritem_def.sound_place; - - if (client->moddingEnabled()) - client->getScript()->on_placenode(pointed, playeritem_def); - } else { - soundmaker->m_player_rightpunch_sound = - SimpleSoundSpec(); - - if (playeritem_def.node_placement_prediction.empty() || - nodedef_manager->get(map.getNodeNoEx(nodepos)).rightclickable) { - client->interact(3, pointed); // Report to server - } else { - soundmaker->m_player_rightpunch_sound = - playeritem_def.sound_place_failed; - } - } - } - } -} - -bool Game::nodePlacementPrediction(const ItemDefinition &playeritem_def, - const ItemStack &playeritem, const v3s16 &nodepos, const v3s16 &neighbourpos) -{ - std::string prediction = playeritem_def.node_placement_prediction; - const NodeDefManager *nodedef = client->ndef(); - ClientMap &map = client->getEnv().getClientMap(); - MapNode node; - bool is_valid_position; - - node = map.getNodeNoEx(nodepos, &is_valid_position); - if (!is_valid_position) - return false; - - if (!prediction.empty() && !nodedef->get(node).rightclickable) { - verbosestream << "Node placement prediction for " - << playeritem_def.name << " is " - << prediction << std::endl; - v3s16 p = neighbourpos; - - // Place inside node itself if buildable_to - MapNode n_under = map.getNodeNoEx(nodepos, &is_valid_position); - if (is_valid_position) - { - if (nodedef->get(n_under).buildable_to) - p = nodepos; - else { - node = map.getNodeNoEx(p, &is_valid_position); - if (is_valid_position &&!nodedef->get(node).buildable_to) - return false; - } - } - - // Find id of predicted node - content_t id; - bool found = nodedef->getId(prediction, id); - - if (!found) { - errorstream << "Node placement prediction failed for " - << playeritem_def.name << " (places " - << prediction - << ") - Name not known" << std::endl; - return false; - } - - const ContentFeatures &predicted_f = nodedef->get(id); - - // Predict param2 for facedir and wallmounted nodes - u8 param2 = 0; - - if (predicted_f.param_type_2 == CPT2_WALLMOUNTED || - predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) { - v3s16 dir = nodepos - neighbourpos; - - if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) { - param2 = dir.Y < 0 ? 1 : 0; - } else if (abs(dir.X) > abs(dir.Z)) { - param2 = dir.X < 0 ? 3 : 2; - } else { - param2 = dir.Z < 0 ? 5 : 4; - } - } - - if (predicted_f.param_type_2 == CPT2_FACEDIR || - predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) { - v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS); - - if (abs(dir.X) > abs(dir.Z)) { - param2 = dir.X < 0 ? 3 : 1; - } else { - param2 = dir.Z < 0 ? 2 : 0; - } - } - - assert(param2 <= 5); - - //Check attachment if node is in group attached_node - if (((ItemGroupList) predicted_f.groups)["attached_node"] != 0) { - static v3s16 wallmounted_dirs[8] = { - v3s16(0, 1, 0), - v3s16(0, -1, 0), - v3s16(1, 0, 0), - v3s16(-1, 0, 0), - v3s16(0, 0, 1), - v3s16(0, 0, -1), - }; - v3s16 pp; - - if (predicted_f.param_type_2 == CPT2_WALLMOUNTED || - predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) - pp = p + wallmounted_dirs[param2]; - else - pp = p + v3s16(0, -1, 0); - - if (!nodedef->get(map.getNodeNoEx(pp)).walkable) - return false; - } - - // Apply color - if ((predicted_f.param_type_2 == CPT2_COLOR - || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR - || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) { - const std::string &indexstr = playeritem.metadata.getString( - "palette_index", 0); - if (!indexstr.empty()) { - s32 index = mystoi(indexstr); - if (predicted_f.param_type_2 == CPT2_COLOR) { - param2 = index; - } else if (predicted_f.param_type_2 - == CPT2_COLORED_WALLMOUNTED) { - // param2 = pure palette index + other - param2 = (index & 0xf8) | (param2 & 0x07); - } else if (predicted_f.param_type_2 - == CPT2_COLORED_FACEDIR) { - // param2 = pure palette index + other - param2 = (index & 0xe0) | (param2 & 0x1f); - } - } - } - - // Add node to client map - MapNode n(id, 0, param2); - - try { - LocalPlayer *player = client->getEnv().getLocalPlayer(); - - // Dont place node when player would be inside new node - // NOTE: This is to be eventually implemented by a mod as client-side Lua - if (!nodedef->get(n).walkable || - g_settings->getBool("enable_build_where_you_stand") || - (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) || - (nodedef->get(n).walkable && - neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) && - neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) { - - // This triggers the required mesh update too - client->addNode(p, n); - return true; - } - } catch (InvalidPositionException &e) { - errorstream << "Node placement prediction failed for " - << playeritem_def.name << " (places " - << prediction - << ") - Position not loaded" << std::endl; - } - } - - return false; -} - -void Game::handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem, - const v3f &player_position, bool show_debug) -{ - std::wstring infotext = unescape_translate( - utf8_to_wide(runData.selected_object->infoText())); - - if (show_debug) { - if (!infotext.empty()) { - infotext += L"\n"; - } - infotext += utf8_to_wide(runData.selected_object->debugInfoText()); - } - - m_game_ui->setInfoText(infotext); - - if (input->getLeftState()) { - bool do_punch = false; - bool do_punch_damage = false; - - if (runData.object_hit_delay_timer <= 0.0) { - do_punch = true; - do_punch_damage = true; - runData.object_hit_delay_timer = object_hit_delay; - } - - if (input->getLeftClicked()) - do_punch = true; - - if (do_punch) { - infostream << "Left-clicked object" << std::endl; - runData.left_punch = true; - } - - if (do_punch_damage) { - // Report direct punch - v3f objpos = runData.selected_object->getPosition(); - v3f dir = (objpos - player_position).normalize(); - ItemStack item = playeritem; - if (playeritem.name.empty()) { - InventoryList *hlist = local_inventory->getList("hand"); - if (hlist) { - item = hlist->getItem(0); - } - } - - bool disable_send = runData.selected_object->directReportPunch( - dir, &item, runData.time_from_last_punch); - runData.time_from_last_punch = 0; - - if (!disable_send) - client->interact(0, pointed); - } - } else if (input->getRightClicked()) { - infostream << "Right-clicked object" << std::endl; - client->interact(3, pointed); // place - } -} - - -void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos, - const ToolCapabilities &playeritem_toolcap, f32 dtime) -{ - LocalPlayer *player = client->getEnv().getLocalPlayer(); - ClientMap &map = client->getEnv().getClientMap(); - MapNode n = client->getEnv().getClientMap().getNodeNoEx(nodepos); - - // NOTE: Similar piece of code exists on the server side for - // cheat detection. - // Get digging parameters - DigParams params = getDigParams(nodedef_manager->get(n).groups, - &playeritem_toolcap); - - // If can't dig, try hand - if (!params.diggable) { - InventoryList *hlist = local_inventory->getList("hand"); - const ToolCapabilities *tp = hlist - ? &hlist->getItem(0).getToolCapabilities(itemdef_manager) - : itemdef_manager->get("").tool_capabilities; - - if (tp) - params = getDigParams(nodedef_manager->get(n).groups, tp); - } - - if (!params.diggable) { - // I guess nobody will wait for this long - runData.dig_time_complete = 10000000.0; - } else { - runData.dig_time_complete = params.time; - - if (m_cache_enable_particles) { - const ContentFeatures &features = client->getNodeDefManager()->get(n); - client->getParticleManager()->addNodeParticle(client, - player, nodepos, n, features); - } - } - - if (!runData.digging) { - infostream << "Started digging" << std::endl; - runData.dig_instantly = runData.dig_time_complete == 0; - if (client->moddingEnabled() && client->getScript()->on_punchnode(nodepos, n)) - return; - client->interact(0, pointed); - runData.digging = true; - runData.ldown_for_dig = true; - } - - if (!runData.dig_instantly) { - runData.dig_index = (float)crack_animation_length - * runData.dig_time - / runData.dig_time_complete; - } else { - // This is for e.g. torches - runData.dig_index = crack_animation_length; - } - - SimpleSoundSpec sound_dig = nodedef_manager->get(n).sound_dig; - - if (sound_dig.exists() && params.diggable) { - if (sound_dig.name == "__group") { - if (!params.main_group.empty()) { - soundmaker->m_player_leftpunch_sound.gain = 0.5; - soundmaker->m_player_leftpunch_sound.name = - std::string("default_dig_") + - params.main_group; - } - } else { - soundmaker->m_player_leftpunch_sound = sound_dig; - } - } - - // Don't show cracks if not diggable - if (runData.dig_time_complete >= 100000.0) { - } else if (runData.dig_index < crack_animation_length) { - //TimeTaker timer("client.setTempMod"); - //infostream<<"dig_index="<setCrack(runData.dig_index, nodepos); - } else { - infostream << "Digging completed" << std::endl; - client->setCrack(-1, v3s16(0, 0, 0)); - - runData.dig_time = 0; - runData.digging = false; - // we successfully dug, now block it from repeating if we want to be safe - if (g_settings->getBool("safe_dig_and_place")) - runData.digging_blocked = true; - - runData.nodig_delay_timer = - runData.dig_time_complete / (float)crack_animation_length; - - // We don't want a corresponding delay to very time consuming nodes - // and nodes without digging time (e.g. torches) get a fixed delay. - if (runData.nodig_delay_timer > 0.3) - runData.nodig_delay_timer = 0.3; - else if (runData.dig_instantly) - runData.nodig_delay_timer = 0.15; - - bool is_valid_position; - MapNode wasnode = map.getNodeNoEx(nodepos, &is_valid_position); - if (is_valid_position) { - if (client->moddingEnabled() && - client->getScript()->on_dignode(nodepos, wasnode)) { - return; - } - - const ContentFeatures &f = client->ndef()->get(wasnode); - if (f.node_dig_prediction == "air") { - client->removeNode(nodepos); - } else if (!f.node_dig_prediction.empty()) { - content_t id; - bool found = client->ndef()->getId(f.node_dig_prediction, id); - if (found) - client->addNode(nodepos, id, true); - } - // implicit else: no prediction - } - - client->interact(2, pointed); - - if (m_cache_enable_particles) { - const ContentFeatures &features = - client->getNodeDefManager()->get(wasnode); - client->getParticleManager()->addDiggingParticles(client, - player, nodepos, wasnode, features); - } - - - // Send event to trigger sound - client->getEventManager()->put(new NodeDugEvent(nodepos, wasnode)); - } - - if (runData.dig_time_complete < 100000.0) { - runData.dig_time += dtime; - } else { - runData.dig_time = 0; - client->setCrack(-1, nodepos); - } - - camera->setDigging(0); // left click animation -} - - -void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, - const CameraOrientation &cam) -{ - LocalPlayer *player = client->getEnv().getLocalPlayer(); - - /* - Fog range - */ - - if (draw_control->range_all) { - runData.fog_range = 100000 * BS; - } else { - runData.fog_range = draw_control->wanted_range * BS; - } - - /* - Calculate general brightness - */ - u32 daynight_ratio = client->getEnv().getDayNightRatio(); - float time_brightness = decode_light_f((float)daynight_ratio / 1000.0); - float direct_brightness; - bool sunlight_seen; - - if (m_cache_enable_noclip && m_cache_enable_free_move) { - direct_brightness = time_brightness; - sunlight_seen = true; - } else { - ScopeProfiler sp(g_profiler, "Detecting background light", SPT_AVG); - float old_brightness = sky->getBrightness(); - direct_brightness = client->getEnv().getClientMap() - .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS), - daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen) - / 255.0; - } - - float time_of_day_smooth = runData.time_of_day_smooth; - float time_of_day = client->getEnv().getTimeOfDayF(); - - static const float maxsm = 0.05f; - static const float todsm = 0.05f; - - if (std::fabs(time_of_day - time_of_day_smooth) > maxsm && - std::fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm && - std::fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm) - time_of_day_smooth = time_of_day; - - if (time_of_day_smooth > 0.8 && time_of_day < 0.2) - time_of_day_smooth = time_of_day_smooth * (1.0 - todsm) - + (time_of_day + 1.0) * todsm; - else - time_of_day_smooth = time_of_day_smooth * (1.0 - todsm) - + time_of_day * todsm; - - runData.time_of_day_smooth = time_of_day_smooth; - - sky->update(time_of_day_smooth, time_brightness, direct_brightness, - sunlight_seen, camera->getCameraMode(), player->getYaw(), - player->getPitch()); - - /* - Update clouds - */ - if (clouds) { - if (sky->getCloudsVisible()) { - clouds->setVisible(true); - clouds->step(dtime); - // camera->getPosition is not enough for 3rd person views - v3f camera_node_position = camera->getCameraNode()->getPosition(); - v3s16 camera_offset = camera->getOffset(); - camera_node_position.X = camera_node_position.X + camera_offset.X * BS; - camera_node_position.Y = camera_node_position.Y + camera_offset.Y * BS; - camera_node_position.Z = camera_node_position.Z + camera_offset.Z * BS; - clouds->update(camera_node_position, - sky->getCloudColor()); - if (clouds->isCameraInsideCloud() && m_cache_enable_fog) { - // if inside clouds, and fog enabled, use that as sky - // color(s) - video::SColor clouds_dark = clouds->getColor() - .getInterpolated(video::SColor(255, 0, 0, 0), 0.9); - sky->overrideColors(clouds_dark, clouds->getColor()); - sky->setBodiesVisible(false); - runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS); - // do not draw clouds after all - clouds->setVisible(false); - } - } else { - clouds->setVisible(false); - } - } - - /* - Update particles - */ - client->getParticleManager()->step(dtime); - - /* - Fog - */ - - if (m_cache_enable_fog) { - driver->setFog( - sky->getBgColor(), - video::EFT_FOG_LINEAR, - runData.fog_range * m_cache_fog_start, - runData.fog_range * 1.0, - 0.01, - false, // pixel fog - true // range fog - ); - } else { - driver->setFog( - sky->getBgColor(), - video::EFT_FOG_LINEAR, - 100000 * BS, - 110000 * BS, - 0.01f, - false, // pixel fog - false // range fog - ); - } - - /* - Get chat messages from client - */ - - v2u32 screensize = driver->getScreenSize(); - - updateChat(dtime, screensize); - - /* - Inventory - */ - - if (client->getPlayerItem() != runData.new_playeritem) - client->selectPlayerItem(runData.new_playeritem); - - // Update local inventory if it has changed - if (client->getLocalInventoryUpdated()) { - //infostream<<"Updating local inventory"<getLocalInventory(*local_inventory); - runData.update_wielded_item_trigger = true; - } - - if (runData.update_wielded_item_trigger) { - // Update wielded tool - InventoryList *mlist = local_inventory->getList("main"); - - if (mlist && (client->getPlayerItem() < mlist->getSize())) { - ItemStack item = mlist->getItem(client->getPlayerItem()); - if (item.getDefinition(itemdef_manager).name.empty()) { // override the hand - InventoryList *hlist = local_inventory->getList("hand"); - if (hlist) - item = hlist->getItem(0); - } - camera->wield(item); - } - - runData.update_wielded_item_trigger = false; - } - - /* - Update block draw list every 200ms or when camera direction has - changed much - */ - runData.update_draw_list_timer += dtime; - - v3f camera_direction = camera->getDirection(); - if (runData.update_draw_list_timer >= 0.2 - || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2 - || m_camera_offset_changed) { - runData.update_draw_list_timer = 0; - client->getEnv().getClientMap().updateDrawList(); - runData.update_draw_list_last_cam_dir = camera_direction; - } - - m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, dtime); - - /* - make sure menu is on top - 1. Delete formspec menu reference if menu was removed - 2. Else, make sure formspec menu is on top - */ - if (current_formspec) { - if (current_formspec->getReferenceCount() == 1) { - current_formspec->drop(); - current_formspec = NULL; - } else if (isMenuActive()) { - guiroot->bringToFront(current_formspec); - } - } - - /* - Drawing begins - */ - const video::SColor &skycolor = sky->getSkyColor(); - - TimeTaker tt_draw("mainloop: draw"); - driver->beginScene(true, true, skycolor); - - bool draw_wield_tool = (m_game_ui->m_flags.show_hud && - (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) && - (camera->getCameraMode() == CAMERA_MODE_FIRST)); - bool draw_crosshair = ( - (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) && - (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT)); -#ifdef HAVE_TOUCHSCREENGUI - try { - draw_crosshair = !g_settings->getBool("touchtarget"); - } catch (SettingNotFoundException) { - } -#endif - RenderingEngine::draw_scene(skycolor, m_game_ui->m_flags.show_hud, - m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair); - - /* - Profiler graph - */ - if (m_game_ui->m_flags.show_profiler_graph) - graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont()); - - /* - Damage flash - */ - if (runData.damage_flash > 0.0f) { - video::SColor color(runData.damage_flash, 180, 0, 0); - driver->draw2DRectangle(color, - core::rect(0, 0, screensize.X, screensize.Y), - NULL); - - runData.damage_flash -= 384.0f * dtime; - } - - /* - Damage camera tilt - */ - if (player->hurt_tilt_timer > 0.0f) { - player->hurt_tilt_timer -= dtime * 6.0f; - - if (player->hurt_tilt_timer < 0.0f) - player->hurt_tilt_strength = 0.0f; - } - - /* - Update minimap pos and rotation - */ - if (mapper && m_game_ui->m_flags.show_minimap && m_game_ui->m_flags.show_hud) { - mapper->setPos(floatToInt(player->getPosition(), BS)); - mapper->setAngle(player->getYaw()); - } - - /* - End scene - */ - driver->endScene(); - - stats->drawtime = tt_draw.stop(true); - g_profiler->graphAdd("mainloop_draw", stats->drawtime / 1000.0f); -} - -/* Log times and stuff for visualization */ -inline void Game::updateProfilerGraphs(ProfilerGraph *graph) -{ - Profiler::GraphValues values; - g_profiler->graphGet(values); - graph->put(values); -} - - - -/**************************************************************************** - Misc - ****************************************************************************/ - -/* On some computers framerate doesn't seem to be automatically limited - */ -inline void Game::limitFps(FpsControl *fps_timings, f32 *dtime) -{ - // not using getRealTime is necessary for wine - device->getTimer()->tick(); // Maker sure device time is up-to-date - u32 time = device->getTimer()->getTime(); - u32 last_time = fps_timings->last_time; - - if (time > last_time) // Make sure time hasn't overflowed - fps_timings->busy_time = time - last_time; - else - fps_timings->busy_time = 0; - - u32 frametime_min = 1000 / (g_menumgr.pausesGame() - ? g_settings->getFloat("pause_fps_max") - : g_settings->getFloat("fps_max")); - - if (fps_timings->busy_time < frametime_min) { - fps_timings->sleep_time = frametime_min - fps_timings->busy_time; - device->sleep(fps_timings->sleep_time); - } else { - fps_timings->sleep_time = 0; - } - - /* Get the new value of the device timer. Note that device->sleep() may - * not sleep for the entire requested time as sleep may be interrupted and - * therefore it is arguably more accurate to get the new time from the - * device rather than calculating it by adding sleep_time to time. - */ - - device->getTimer()->tick(); // Update device timer - time = device->getTimer()->getTime(); - - if (time > last_time) // Make sure last_time hasn't overflowed - *dtime = (time - last_time) / 1000.0; - else - *dtime = 0; - - fps_timings->last_time = time; -} - -void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds) -{ - const wchar_t *wmsg = wgettext(msg); - RenderingEngine::draw_load_screen(wmsg, guienv, texture_src, dtime, percent, - draw_clouds); - delete[] wmsg; -} - -void Game::settingChangedCallback(const std::string &setting_name, void *data) -{ - ((Game *)data)->readSettings(); -} - -void Game::readSettings() -{ - m_cache_doubletap_jump = g_settings->getBool("doubletap_jump"); - m_cache_enable_clouds = g_settings->getBool("enable_clouds"); - 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_right_click_time = g_settings->getFloat("repeat_rightclick_time"); - - m_cache_enable_noclip = g_settings->getBool("noclip"); - m_cache_enable_free_move = g_settings->getBool("free_move"); - - m_cache_fog_start = g_settings->getFloat("fog_start"); - - m_cache_cam_smoothing = 0; - if (g_settings->getBool("cinematic")) - m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing"); - else - m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing"); - - m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f); - m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f); - m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0); - - m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus"); -} - -/****************************************************************************/ -/**************************************************************************** - Shutdown / cleanup - ****************************************************************************/ -/****************************************************************************/ - -void Game::extendedResourceCleanup() -{ - // Extended resource accounting - infostream << "Irrlicht resources after cleanup:" << std::endl; - infostream << "\tRemaining meshes : " - << RenderingEngine::get_mesh_cache()->getMeshCount() << std::endl; - infostream << "\tRemaining textures : " - << driver->getTextureCount() << std::endl; - - for (unsigned int i = 0; i < driver->getTextureCount(); i++) { - irr::video::ITexture *texture = driver->getTextureByIndex(i); - infostream << "\t\t" << i << ":" << texture->getName().getPath().c_str() - << std::endl; - } - - clearTextureNameCache(); - infostream << "\tRemaining materials: " - << driver-> getMaterialRendererCount() - << " (note: irrlicht doesn't support removing renderers)" << std::endl; -} - -void Game::showDeathFormspec() -{ - static std::string formspec = - std::string(FORMSPEC_VERSION_STRING) + - SIZE_TAG - "bgcolor[#320000b4;true]" - "label[4.85,1.35;" + gettext("You died") + "]" - "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]" - ; - - /* Create menu */ - /* Note: FormspecFormSource and LocalFormspecHandler * - * are deleted by guiFormSpecMenu */ - FormspecFormSource *fs_src = new FormspecFormSource(formspec); - LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client); - - GUIFormSpecMenu::create(current_formspec, client, &input->joystick, fs_src, - txt_dst, client->getFormspecPrepend()); - current_formspec->setFocus("btn_respawn"); -} - -#define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name()) -void Game::showPauseMenu() -{ -#ifdef __ANDROID__ - static const std::string control_text = strgettext("Default Controls:\n" - "No menu visible:\n" - "- single tap: button activate\n" - "- double tap: place/use\n" - "- slide finger: look around\n" - "Menu/Inventory visible:\n" - "- double tap (outside):\n" - " -->close\n" - "- touch stack, touch slot:\n" - " --> move stack\n" - "- touch&drag, tap 2nd finger\n" - " --> place single item to slot\n" - ); -#else - static const std::string control_text_template = strgettext("Controls:\n" - "- %s: move forwards\n" - "- %s: move backwards\n" - "- %s: move left\n" - "- %s: move right\n" - "- %s: jump/climb\n" - "- %s: sneak/go down\n" - "- %s: drop item\n" - "- %s: inventory\n" - "- Mouse: turn/look\n" - "- Mouse left: dig/punch\n" - "- Mouse right: place/use\n" - "- Mouse wheel: select item\n" - "- %s: chat\n" - ); - - char control_text_buf[600]; - - porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(), - GET_KEY_NAME(keymap_forward), - GET_KEY_NAME(keymap_backward), - GET_KEY_NAME(keymap_left), - GET_KEY_NAME(keymap_right), - GET_KEY_NAME(keymap_jump), - GET_KEY_NAME(keymap_sneak), - GET_KEY_NAME(keymap_drop), - GET_KEY_NAME(keymap_inventory), - GET_KEY_NAME(keymap_chat) - ); - - std::string control_text = std::string(control_text_buf); - str_formspec_escape(control_text); -#endif - - float ypos = simple_singleplayer_mode ? 0.7f : 0.1f; - std::ostringstream os; - - os << FORMSPEC_VERSION_STRING << SIZE_TAG - << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;" - << strgettext("Continue") << "]"; - - if (!simple_singleplayer_mode) { - os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;" - << strgettext("Change Password") << "]"; - } else { - os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]"; - } - -#ifndef __ANDROID__ - os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;" - << strgettext("Sound Volume") << "]"; - os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;" - << strgettext("Change Keys") << "]"; -#endif - os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;" - << strgettext("Exit to Menu") << "]"; - os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;" - << strgettext("Exit to OS") << "]" - << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]" - << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n" - << "\n" - << strgettext("Game info:") << "\n"; - const std::string &address = client->getAddressName(); - static const std::string mode = strgettext("- Mode: "); - if (!simple_singleplayer_mode) { - Address serverAddress = client->getServerAddress(); - if (!address.empty()) { - os << mode << strgettext("Remote server") << "\n" - << strgettext("- Address: ") << address; - } else { - os << mode << strgettext("Hosting server"); - } - os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n"; - } else { - os << mode << strgettext("Singleplayer") << "\n"; - } - if (simple_singleplayer_mode || address.empty()) { - static const std::string on = strgettext("On"); - static const std::string off = strgettext("Off"); - const std::string &damage = g_settings->getBool("enable_damage") ? on : off; - const std::string &creative = g_settings->getBool("creative_mode") ? on : off; - const std::string &announced = g_settings->getBool("server_announce") ? on : off; - os << strgettext("- Damage: ") << damage << "\n" - << strgettext("- Creative Mode: ") << creative << "\n"; - if (!simple_singleplayer_mode) { - const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off; - os << strgettext("- PvP: ") << pvp << "\n" - << strgettext("- Public: ") << announced << "\n"; - std::string server_name = g_settings->get("server_name"); - str_formspec_escape(server_name); - if (announced == on && !server_name.empty()) - os << strgettext("- Server Name: ") << server_name; - - } - } - os << ";]"; - - /* Create menu */ - /* Note: FormspecFormSource and LocalFormspecHandler * - * are deleted by guiFormSpecMenu */ - FormspecFormSource *fs_src = new FormspecFormSource(os.str()); - LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU"); - - GUIFormSpecMenu::create(current_formspec, client, &input->joystick, - fs_src, txt_dst, client->getFormspecPrepend()); - current_formspec->setFocus("btn_continue"); - current_formspec->doPause = true; -} - -/****************************************************************************/ -/**************************************************************************** - extern function for launching the game - ****************************************************************************/ -/****************************************************************************/ - -void the_game(bool *kill, - bool random_input, - InputHandler *input, - const std::string &map_dir, - const std::string &playername, - const std::string &password, - const std::string &address, // If empty local server is created - u16 port, - - std::string &error_message, - ChatBackend &chat_backend, - bool *reconnect_requested, - const SubgameSpec &gamespec, // Used for local game - bool simple_singleplayer_mode) -{ - Game game; - - /* Make a copy of the server address because if a local singleplayer server - * is created then this is updated and we don't want to change the value - * passed to us by the calling function - */ - std::string server_address = address; - - try { - - if (game.startup(kill, random_input, input, map_dir, - playername, password, &server_address, port, error_message, - reconnect_requested, &chat_backend, gamespec, - simple_singleplayer_mode)) { - game.run(); - game.shutdown(); - } - - } catch (SerializationError &e) { - error_message = std::string("A serialization error occurred:\n") - + e.what() + "\n\nThe server is probably " - " running a different version of " PROJECT_NAME_C "."; - errorstream << error_message << std::endl; - } catch (ServerError &e) { - error_message = e.what(); - errorstream << "ServerError: " << error_message << std::endl; - } catch (ModError &e) { - error_message = e.what() + strgettext("\nCheck debug.txt for details."); - errorstream << "ModError: " << error_message << std::endl; - } -} diff --git a/src/game.h b/src/game.h deleted file mode 100644 index 69e6eed0b..000000000 --- a/src/game.h +++ /dev/null @@ -1,56 +0,0 @@ -/* -Minetest -Copyright (C) 2013 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#pragma once - -#include "irrlichttypes.h" -#include - -class InputHandler; -class ChatBackend; /* to avoid having to include chat.h */ -struct SubgameSpec; - -struct Jitter { - f32 max, min, avg, counter, max_sample, min_sample, max_fraction; -}; - -struct RunStats { - u32 drawtime; - - Jitter dtime_jitter, busy_time_jitter; -}; - -struct CameraOrientation { - f32 camera_yaw; // "right/left" - f32 camera_pitch; // "up/down" -}; - -void the_game(bool *kill, - bool random_input, - InputHandler *input, - const std::string &map_dir, - const std::string &playername, - const std::string &password, - const std::string &address, // If "", local server is used - u16 port, - std::string &error_message, - ChatBackend &chat_backend, - bool *reconnect_requested, - const SubgameSpec &gamespec, // Used for local game - bool simple_singleplayer_mode); diff --git a/src/gui/guiChatConsole.cpp b/src/gui/guiChatConsole.cpp index b194834e2..1ccb4e6d1 100644 --- a/src/gui/guiChatConsole.cpp +++ b/src/gui/guiChatConsole.cpp @@ -19,14 +19,14 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "guiChatConsole.h" #include "chat.h" -#include "client.h" +#include "client/client.h" #include "debug.h" #include "gettime.h" -#include "keycode.h" +#include "client/keycode.h" #include "settings.h" #include "porting.h" #include "client/tile.h" -#include "fontengine.h" +#include "client/fontengine.h" #include "log.h" #include "gettext.h" #include diff --git a/src/gui/guiConfirmRegistration.cpp b/src/gui/guiConfirmRegistration.cpp index a13929a48..9a374b405 100644 --- a/src/gui/guiConfirmRegistration.cpp +++ b/src/gui/guiConfirmRegistration.cpp @@ -19,7 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "guiConfirmRegistration.h" -#include "client.h" +#include "client/client.h" #include #include #include diff --git a/src/gui/guiEngine.cpp b/src/gui/guiEngine.cpp index 67ab6437d..a3ef1b9a6 100644 --- a/src/gui/guiEngine.cpp +++ b/src/gui/guiEngine.cpp @@ -32,11 +32,11 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "guiMainMenu.h" #include "sound.h" #include "client/sound_openal.h" -#include "clouds.h" +#include "client/clouds.h" #include "httpfetch.h" #include "log.h" -#include "fontengine.h" -#include "guiscalingfilter.h" +#include "client/fontengine.h" +#include "client/guiscalingfilter.h" #include "irrlicht_changes/static_text.h" #ifdef __ANDROID__ diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index f23055821..c52a6ee27 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -27,7 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "guiTable.h" #include "constants.h" #include "gamedef.h" -#include "keycode.h" +#include "client/keycode.h" #include "util/strfnd.h" #include #include @@ -47,13 +47,13 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mainmenumanager.h" #include "porting.h" #include "settings.h" -#include "client.h" -#include "fontengine.h" +#include "client/client.h" +#include "client/fontengine.h" #include "util/hex.h" #include "util/numeric.h" #include "util/string.h" // for parseColorString() #include "irrlicht_changes/static_text.h" -#include "guiscalingfilter.h" +#include "client/guiscalingfilter.h" #include "guiEditBoxWithScrollbar.h" #include "intlGUIEditBox.h" @@ -673,10 +673,10 @@ void GUIFormSpecMenu::parseBackground(parserData* data, const std::string &eleme pos.Y = stoi(v_pos[1]); //acts as offset clip = true; } - + if (!data->explicit_size && !clip) warningstream << "invalid use of unclipped background without a size[] element" << std::endl; - + m_backgrounds.emplace_back(name, pos, geom, clip); return; diff --git a/src/gui/guiKeyChangeMenu.h b/src/gui/guiKeyChangeMenu.h index eded61e4c..0aaa05e18 100644 --- a/src/gui/guiKeyChangeMenu.h +++ b/src/gui/guiKeyChangeMenu.h @@ -24,7 +24,7 @@ #include "irrlichttypes_extrabloated.h" #include "modalMenu.h" #include "gettext.h" -#include "keycode.h" +#include "client/keycode.h" #include #include diff --git a/src/gui/guiPasswordChange.cpp b/src/gui/guiPasswordChange.cpp index e29ef27e6..469c38dbe 100644 --- a/src/gui/guiPasswordChange.cpp +++ b/src/gui/guiPasswordChange.cpp @@ -17,7 +17,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "guiPasswordChange.h" -#include "client.h" +#include "client/client.h" #include #include #include diff --git a/src/gui/guiTable.cpp b/src/gui/guiTable.cpp index b1a027e9b..a123bdd6d 100644 --- a/src/gui/guiTable.cpp +++ b/src/gui/guiTable.cpp @@ -36,7 +36,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/string.h" // for parseColorString() #include "settings.h" // for settings #include "porting.h" // for dpi -#include "guiscalingfilter.h" +#include "client/guiscalingfilter.h" /* GUITable diff --git a/src/guiscalingfilter.cpp b/src/guiscalingfilter.cpp deleted file mode 100644 index 3b4377da5..000000000 --- a/src/guiscalingfilter.cpp +++ /dev/null @@ -1,169 +0,0 @@ -/* -Copyright (C) 2015 Aaron Suen - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include "guiscalingfilter.h" -#include "imagefilters.h" -#include "porting.h" -#include "settings.h" -#include "util/numeric.h" -#include -#include "client/renderingengine.h" - -/* Maintain a static cache to store the images that correspond to textures - * in a format that's manipulable by code. Some platforms exhibit issues - * converting textures back into images repeatedly, and some don't even - * allow it at all. - */ -std::map g_imgCache; - -/* Maintain a static cache of all pre-scaled textures. These need to be - * cleared as well when the cached images. - */ -std::map g_txrCache; - -/* Manually insert an image into the cache, useful to avoid texture-to-image - * conversion whenever we can intercept it. - */ -void guiScalingCache(io::path key, video::IVideoDriver *driver, video::IImage *value) -{ - if (!g_settings->getBool("gui_scaling_filter")) - return; - video::IImage *copied = driver->createImage(value->getColorFormat(), - value->getDimension()); - value->copyTo(copied); - g_imgCache[key] = copied; -} - -// Manually clear the cache, e.g. when switching to different worlds. -void guiScalingCacheClear() -{ - for (auto &it : g_imgCache) { - if (it.second) - it.second->drop(); - } - g_imgCache.clear(); - for (auto &it : g_txrCache) { - if (it.second) - RenderingEngine::get_video_driver()->removeTexture(it.second); - } - g_txrCache.clear(); -} - -/* Get a cached, high-quality pre-scaled texture for display purposes. If the - * texture is not already cached, attempt to create it. Returns a pre-scaled texture, - * or the original texture if unable to pre-scale it. - */ -video::ITexture *guiScalingResizeCached(video::IVideoDriver *driver, - video::ITexture *src, const core::rect &srcrect, - const core::rect &destrect) -{ - if (src == NULL) - return src; - if (!g_settings->getBool("gui_scaling_filter")) - return src; - - // Calculate scaled texture name. - char rectstr[200]; - porting::mt_snprintf(rectstr, sizeof(rectstr), "%d:%d:%d:%d:%d:%d", - srcrect.UpperLeftCorner.X, - srcrect.UpperLeftCorner.Y, - srcrect.getWidth(), - srcrect.getHeight(), - destrect.getWidth(), - destrect.getHeight()); - io::path origname = src->getName().getPath(); - io::path scalename = origname + "@guiScalingFilter:" + rectstr; - - // Search for existing scaled texture. - video::ITexture *scaled = g_txrCache[scalename]; - if (scaled) - return scaled; - - // Try to find the texture converted to an image in the cache. - // If the image was not found, try to extract it from the texture. - video::IImage* srcimg = g_imgCache[origname]; - if (srcimg == NULL) { - if (!g_settings->getBool("gui_scaling_filter_txr2img")) - return src; - srcimg = driver->createImageFromData(src->getColorFormat(), - src->getSize(), src->lock(), false); - src->unlock(); - g_imgCache[origname] = srcimg; - } - - // Create a new destination image and scale the source into it. - imageCleanTransparent(srcimg, 0); - video::IImage *destimg = driver->createImage(src->getColorFormat(), - core::dimension2d((u32)destrect.getWidth(), - (u32)destrect.getHeight())); - imageScaleNNAA(srcimg, srcrect, destimg); - -#ifdef __ANDROID__ - // Android is very picky about textures being powers of 2, so expand - // the image dimensions to the next power of 2, if necessary, for - // that platform. - video::IImage *po2img = driver->createImage(src->getColorFormat(), - core::dimension2d(npot2((u32)destrect.getWidth()), - npot2((u32)destrect.getHeight()))); - po2img->fill(video::SColor(0, 0, 0, 0)); - destimg->copyTo(po2img); - destimg->drop(); - destimg = po2img; -#endif - - // Convert the scaled image back into a texture. - scaled = driver->addTexture(scalename, destimg, NULL); - destimg->drop(); - g_txrCache[scalename] = scaled; - - return scaled; -} - -/* Convenience wrapper for guiScalingResizeCached that accepts parameters that - * are available at GUI imagebutton creation time. - */ -video::ITexture *guiScalingImageButton(video::IVideoDriver *driver, - video::ITexture *src, s32 width, s32 height) -{ - if (src == NULL) - return src; - return guiScalingResizeCached(driver, src, - core::rect(0, 0, src->getSize().Width, src->getSize().Height), - core::rect(0, 0, width, height)); -} - -/* Replacement for driver->draw2DImage() that uses the high-quality pre-scaled - * texture, if configured. - */ -void draw2DImageFilterScaled(video::IVideoDriver *driver, video::ITexture *txr, - const core::rect &destrect, const core::rect &srcrect, - const core::rect *cliprect, const video::SColor *const colors, - bool usealpha) -{ - // Attempt to pre-scale image in software in high quality. - video::ITexture *scaled = guiScalingResizeCached(driver, txr, srcrect, destrect); - if (scaled == NULL) - return; - - // Correct source rect based on scaled image. - const core::rect mysrcrect = (scaled != txr) - ? core::rect(0, 0, destrect.getWidth(), destrect.getHeight()) - : srcrect; - - driver->draw2DImage(scaled, destrect, mysrcrect, cliprect, colors, usealpha); -} diff --git a/src/guiscalingfilter.h b/src/guiscalingfilter.h deleted file mode 100644 index 4661bf8da..000000000 --- a/src/guiscalingfilter.h +++ /dev/null @@ -1,50 +0,0 @@ -/* -Copyright (C) 2015 Aaron Suen - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#pragma once - -#include "irrlichttypes_extrabloated.h" - -/* Manually insert an image into the cache, useful to avoid texture-to-image - * conversion whenever we can intercept it. - */ -void guiScalingCache(io::path key, video::IVideoDriver *driver, video::IImage *value); - -// Manually clear the cache, e.g. when switching to different worlds. -void guiScalingCacheClear(); - -/* Get a cached, high-quality pre-scaled texture for display purposes. If the - * texture is not already cached, attempt to create it. Returns a pre-scaled texture, - * or the original texture if unable to pre-scale it. - */ -video::ITexture *guiScalingResizeCached(video::IVideoDriver *driver, video::ITexture *src, - const core::rect &srcrect, const core::rect &destrect); - -/* Convenience wrapper for guiScalingResizeCached that accepts parameters that - * are available at GUI imagebutton creation time. - */ -video::ITexture *guiScalingImageButton(video::IVideoDriver *driver, video::ITexture *src, - s32 width, s32 height); - -/* Replacement for driver->draw2DImage() that uses the high-quality pre-scaled - * texture, if configured. - */ -void draw2DImageFilterScaled(video::IVideoDriver *driver, video::ITexture *txr, - const core::rect &destrect, const core::rect &srcrect, - const core::rect *cliprect = 0, const video::SColor *const colors = 0, - bool usealpha = false); diff --git a/src/imagefilters.cpp b/src/imagefilters.cpp deleted file mode 100644 index dd029628f..000000000 --- a/src/imagefilters.cpp +++ /dev/null @@ -1,172 +0,0 @@ -/* -Copyright (C) 2015 Aaron Suen - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include "imagefilters.h" -#include "util/numeric.h" -#include - -/* Fill in RGB values for transparent pixels, to correct for odd colors - * appearing at borders when blending. This is because many PNG optimizers - * like to discard RGB values of transparent pixels, but when blending then - * with non-transparent neighbors, their RGB values will shpw up nonetheless. - * - * This function modifies the original image in-place. - * - * Parameter "threshold" is the alpha level below which pixels are considered - * transparent. Should be 127 for 3d where alpha is threshold, but 0 for - * 2d where alpha is blended. - */ -void imageCleanTransparent(video::IImage *src, u32 threshold) -{ - core::dimension2d dim = src->getDimension(); - - // Walk each pixel looking for fully transparent ones. - // Note: loop y around x for better cache locality. - for (u32 ctry = 0; ctry < dim.Height; ctry++) - for (u32 ctrx = 0; ctrx < dim.Width; ctrx++) { - - // Ignore opaque pixels. - irr::video::SColor c = src->getPixel(ctrx, ctry); - if (c.getAlpha() > threshold) - continue; - - // Sample size and total weighted r, g, b values. - u32 ss = 0, sr = 0, sg = 0, sb = 0; - - // Walk each neighbor pixel (clipped to image bounds). - for (u32 sy = (ctry < 1) ? 0 : (ctry - 1); - sy <= (ctry + 1) && sy < dim.Height; sy++) - for (u32 sx = (ctrx < 1) ? 0 : (ctrx - 1); - sx <= (ctrx + 1) && sx < dim.Width; sx++) { - - // Ignore transparent pixels. - irr::video::SColor d = src->getPixel(sx, sy); - if (d.getAlpha() <= threshold) - continue; - - // Add RGB values weighted by alpha. - u32 a = d.getAlpha(); - ss += a; - sr += a * d.getRed(); - sg += a * d.getGreen(); - sb += a * d.getBlue(); - } - - // If we found any neighbor RGB data, set pixel to average - // weighted by alpha. - if (ss > 0) { - c.setRed(sr / ss); - c.setGreen(sg / ss); - c.setBlue(sb / ss); - src->setPixel(ctrx, ctry, c); - } - } -} - -/* Scale a region of an image into another image, using nearest-neighbor with - * anti-aliasing; treat pixels as crisp rectangles, but blend them at boundaries - * to prevent non-integer scaling ratio artifacts. Note that this may cause - * some blending at the edges where pixels don't line up perfectly, but this - * filter is designed to produce the most accurate results for both upscaling - * and downscaling. - */ -void imageScaleNNAA(video::IImage *src, const core::rect &srcrect, video::IImage *dest) -{ - double sx, sy, minsx, maxsx, minsy, maxsy, area, ra, ga, ba, aa, pw, ph, pa; - u32 dy, dx; - video::SColor pxl; - - // Cache rectsngle boundaries. - double sox = srcrect.UpperLeftCorner.X * 1.0; - double soy = srcrect.UpperLeftCorner.Y * 1.0; - double sw = srcrect.getWidth() * 1.0; - double sh = srcrect.getHeight() * 1.0; - - // Walk each destination image pixel. - // Note: loop y around x for better cache locality. - core::dimension2d dim = dest->getDimension(); - for (dy = 0; dy < dim.Height; dy++) - for (dx = 0; dx < dim.Width; dx++) { - - // Calculate floating-point source rectangle bounds. - // Do some basic clipping, and for mirrored/flipped rects, - // make sure min/max are in the right order. - minsx = sox + (dx * sw / dim.Width); - minsx = rangelim(minsx, 0, sw); - maxsx = minsx + sw / dim.Width; - maxsx = rangelim(maxsx, 0, sw); - if (minsx > maxsx) - SWAP(double, minsx, maxsx); - minsy = soy + (dy * sh / dim.Height); - minsy = rangelim(minsy, 0, sh); - maxsy = minsy + sh / dim.Height; - maxsy = rangelim(maxsy, 0, sh); - if (minsy > maxsy) - SWAP(double, minsy, maxsy); - - // Total area, and integral of r, g, b values over that area, - // initialized to zero, to be summed up in next loops. - area = 0; - ra = 0; - ga = 0; - ba = 0; - aa = 0; - - // Loop over the integral pixel positions described by those bounds. - for (sy = floor(minsy); sy < maxsy; sy++) - for (sx = floor(minsx); sx < maxsx; sx++) { - - // Calculate width, height, then area of dest pixel - // that's covered by this source pixel. - pw = 1; - if (minsx > sx) - pw += sx - minsx; - if (maxsx < (sx + 1)) - pw += maxsx - sx - 1; - ph = 1; - if (minsy > sy) - ph += sy - minsy; - if (maxsy < (sy + 1)) - ph += maxsy - sy - 1; - pa = pw * ph; - - // Get source pixel and add it to totals, weighted - // by covered area and alpha. - pxl = src->getPixel((u32)sx, (u32)sy); - area += pa; - ra += pa * pxl.getRed(); - ga += pa * pxl.getGreen(); - ba += pa * pxl.getBlue(); - aa += pa * pxl.getAlpha(); - } - - // Set the destination image pixel to the average color. - if (area > 0) { - pxl.setRed(ra / area + 0.5); - pxl.setGreen(ga / area + 0.5); - pxl.setBlue(ba / area + 0.5); - pxl.setAlpha(aa / area + 0.5); - } else { - pxl.setRed(0); - pxl.setGreen(0); - pxl.setBlue(0); - pxl.setAlpha(0); - } - dest->setPixel(dx, dy, pxl); - } -} diff --git a/src/imagefilters.h b/src/imagefilters.h deleted file mode 100644 index 5676faf85..000000000 --- a/src/imagefilters.h +++ /dev/null @@ -1,43 +0,0 @@ -/* -Copyright (C) 2015 Aaron Suen - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#pragma once - -#include "irrlichttypes_extrabloated.h" - -/* Fill in RGB values for transparent pixels, to correct for odd colors - * appearing at borders when blending. This is because many PNG optimizers - * like to discard RGB values of transparent pixels, but when blending then - * with non-transparent neighbors, their RGB values will shpw up nonetheless. - * - * This function modifies the original image in-place. - * - * Parameter "threshold" is the alpha level below which pixels are considered - * transparent. Should be 127 for 3d where alpha is threshold, but 0 for - * 2d where alpha is blended. - */ -void imageCleanTransparent(video::IImage *src, u32 threshold); - -/* Scale a region of an image into another image, using nearest-neighbor with - * anti-aliasing; treat pixels as crisp rectangles, but blend them at boundaries - * to prevent non-integer scaling ratio artifacts. Note that this may cause - * some blending at the edges where pixels don't line up perfectly, but this - * filter is designed to produce the most accurate results for both upscaling - * and downscaling. - */ -void imageScaleNNAA(video::IImage *src, const core::rect &srcrect, video::IImage *dest); diff --git a/src/itemdef.cpp b/src/itemdef.cpp index f7d366c8a..11a52f85c 100644 --- a/src/itemdef.cpp +++ b/src/itemdef.cpp @@ -24,11 +24,11 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "tool.h" #include "inventory.h" #ifndef SERVER -#include "mapblock_mesh.h" -#include "mesh.h" -#include "wieldmesh.h" +#include "client/mapblock_mesh.h" +#include "client/mesh.h" +#include "client/wieldmesh.h" #include "client/tile.h" -#include "client.h" +#include "client/client.h" #endif #include "log.h" #include "settings.h" diff --git a/src/keycode.cpp b/src/keycode.cpp deleted file mode 100644 index 646d181e0..000000000 --- a/src/keycode.cpp +++ /dev/null @@ -1,384 +0,0 @@ -/* -Minetest -Copyright (C) 2010-2013 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include "keycode.h" -#include "exceptions.h" -#include "settings.h" -#include "log.h" -#include "debug.h" -#include "util/hex.h" -#include "util/string.h" -#include "util/basic_macros.h" - -class UnknownKeycode : public BaseException -{ -public: - UnknownKeycode(const char *s) : - BaseException(s) {}; -}; - -struct table_key { - const char *Name; - irr::EKEY_CODE Key; - wchar_t Char; // L'\0' means no character assigned - const char *LangName; // NULL means it doesn't have a human description -}; - -#define DEFINEKEY1(x, lang) /* Irrlicht key without character */ \ - { #x, irr::x, L'\0', lang }, -#define DEFINEKEY2(x, ch, lang) /* Irrlicht key with character */ \ - { #x, irr::x, ch, lang }, -#define DEFINEKEY3(ch) /* single Irrlicht key (e.g. KEY_KEY_X) */ \ - { "KEY_KEY_" TOSTRING(ch), irr::KEY_KEY_ ## ch, (wchar_t) *TOSTRING(ch), TOSTRING(ch) }, -#define DEFINEKEY4(ch) /* single Irrlicht function key (e.g. KEY_F3) */ \ - { "KEY_F" TOSTRING(ch), irr::KEY_F ## ch, L'\0', "F" TOSTRING(ch) }, -#define DEFINEKEY5(ch) /* key without Irrlicht keycode */ \ - { ch, irr::KEY_KEY_CODES_COUNT, (wchar_t) *ch, ch }, - -#define N_(text) text - -static const struct table_key table[] = { - // Keys that can be reliably mapped between Char and Key - DEFINEKEY3(0) - DEFINEKEY3(1) - DEFINEKEY3(2) - DEFINEKEY3(3) - DEFINEKEY3(4) - DEFINEKEY3(5) - DEFINEKEY3(6) - DEFINEKEY3(7) - DEFINEKEY3(8) - DEFINEKEY3(9) - DEFINEKEY3(A) - DEFINEKEY3(B) - DEFINEKEY3(C) - DEFINEKEY3(D) - DEFINEKEY3(E) - DEFINEKEY3(F) - DEFINEKEY3(G) - DEFINEKEY3(H) - DEFINEKEY3(I) - DEFINEKEY3(J) - DEFINEKEY3(K) - DEFINEKEY3(L) - DEFINEKEY3(M) - DEFINEKEY3(N) - DEFINEKEY3(O) - DEFINEKEY3(P) - DEFINEKEY3(Q) - DEFINEKEY3(R) - DEFINEKEY3(S) - DEFINEKEY3(T) - DEFINEKEY3(U) - DEFINEKEY3(V) - DEFINEKEY3(W) - DEFINEKEY3(X) - DEFINEKEY3(Y) - DEFINEKEY3(Z) - DEFINEKEY2(KEY_PLUS, L'+', "+") - DEFINEKEY2(KEY_COMMA, L',', ",") - DEFINEKEY2(KEY_MINUS, L'-', "-") - DEFINEKEY2(KEY_PERIOD, L'.', ".") - - // Keys without a Char - DEFINEKEY1(KEY_LBUTTON, N_("Left Button")) - DEFINEKEY1(KEY_RBUTTON, N_("Right Button")) - DEFINEKEY1(KEY_CANCEL, N_("Cancel")) - DEFINEKEY1(KEY_MBUTTON, N_("Middle Button")) - DEFINEKEY1(KEY_XBUTTON1, N_("X Button 1")) - DEFINEKEY1(KEY_XBUTTON2, N_("X Button 2")) - DEFINEKEY1(KEY_BACK, N_("Backspace")) - DEFINEKEY1(KEY_TAB, N_("Tab")) - DEFINEKEY1(KEY_CLEAR, N_("Clear")) - DEFINEKEY1(KEY_RETURN, N_("Return")) - DEFINEKEY1(KEY_SHIFT, N_("Shift")) - DEFINEKEY1(KEY_CONTROL, N_("Control")) - DEFINEKEY1(KEY_MENU, N_("Menu")) - DEFINEKEY1(KEY_PAUSE, N_("Pause")) - DEFINEKEY1(KEY_CAPITAL, N_("Caps Lock")) - DEFINEKEY1(KEY_SPACE, N_("Space")) - DEFINEKEY1(KEY_PRIOR, N_("Page up")) - DEFINEKEY1(KEY_NEXT, N_("Page down")) - DEFINEKEY1(KEY_END, N_("End")) - DEFINEKEY1(KEY_HOME, N_("Home")) - DEFINEKEY1(KEY_LEFT, N_("Left")) - DEFINEKEY1(KEY_UP, N_("Up")) - DEFINEKEY1(KEY_RIGHT, N_("Right")) - DEFINEKEY1(KEY_DOWN, N_("Down")) - DEFINEKEY1(KEY_SELECT, N_("Select")) - DEFINEKEY1(KEY_PRINT, N_("Print")) - DEFINEKEY1(KEY_EXECUT, N_("Execute")) - DEFINEKEY1(KEY_SNAPSHOT, N_("Snapshot")) - DEFINEKEY1(KEY_INSERT, N_("Insert")) - DEFINEKEY1(KEY_DELETE, N_("Delete")) - DEFINEKEY1(KEY_HELP, N_("Help")) - DEFINEKEY1(KEY_LWIN, N_("Left Windows")) - DEFINEKEY1(KEY_RWIN, N_("Right Windows")) - DEFINEKEY1(KEY_NUMPAD0, N_("Numpad 0")) // These are not assigned to a char - DEFINEKEY1(KEY_NUMPAD1, N_("Numpad 1")) // to prevent interference with KEY_KEY_[0-9]. - DEFINEKEY1(KEY_NUMPAD2, N_("Numpad 2")) - DEFINEKEY1(KEY_NUMPAD3, N_("Numpad 3")) - DEFINEKEY1(KEY_NUMPAD4, N_("Numpad 4")) - DEFINEKEY1(KEY_NUMPAD5, N_("Numpad 5")) - DEFINEKEY1(KEY_NUMPAD6, N_("Numpad 6")) - DEFINEKEY1(KEY_NUMPAD7, N_("Numpad 7")) - DEFINEKEY1(KEY_NUMPAD8, N_("Numpad 8")) - DEFINEKEY1(KEY_NUMPAD9, N_("Numpad 9")) - DEFINEKEY1(KEY_MULTIPLY, N_("Numpad *")) - DEFINEKEY1(KEY_ADD, N_("Numpad +")) - DEFINEKEY1(KEY_SEPARATOR, N_("Numpad .")) - DEFINEKEY1(KEY_SUBTRACT, N_("Numpad -")) - DEFINEKEY1(KEY_DECIMAL, NULL) - DEFINEKEY1(KEY_DIVIDE, N_("Numpad /")) - DEFINEKEY4(1) - DEFINEKEY4(2) - DEFINEKEY4(3) - DEFINEKEY4(4) - DEFINEKEY4(5) - DEFINEKEY4(6) - DEFINEKEY4(7) - DEFINEKEY4(8) - DEFINEKEY4(9) - DEFINEKEY4(10) - DEFINEKEY4(11) - DEFINEKEY4(12) - DEFINEKEY4(13) - DEFINEKEY4(14) - DEFINEKEY4(15) - DEFINEKEY4(16) - DEFINEKEY4(17) - DEFINEKEY4(18) - DEFINEKEY4(19) - DEFINEKEY4(20) - DEFINEKEY4(21) - DEFINEKEY4(22) - DEFINEKEY4(23) - DEFINEKEY4(24) - DEFINEKEY1(KEY_NUMLOCK, N_("Num Lock")) - DEFINEKEY1(KEY_SCROLL, N_("Scroll Lock")) - DEFINEKEY1(KEY_LSHIFT, N_("Left Shift")) - DEFINEKEY1(KEY_RSHIFT, N_("Right Shift")) - DEFINEKEY1(KEY_LCONTROL, N_("Left Control")) - DEFINEKEY1(KEY_RCONTROL, N_("Right Control")) - DEFINEKEY1(KEY_LMENU, N_("Left Menu")) - DEFINEKEY1(KEY_RMENU, N_("Right Menu")) - - // Rare/weird keys - DEFINEKEY1(KEY_KANA, "Kana") - DEFINEKEY1(KEY_HANGUEL, "Hangul") - DEFINEKEY1(KEY_HANGUL, "Hangul") - DEFINEKEY1(KEY_JUNJA, "Junja") - DEFINEKEY1(KEY_FINAL, "Final") - DEFINEKEY1(KEY_KANJI, "Kanji") - DEFINEKEY1(KEY_HANJA, "Hanja") - DEFINEKEY1(KEY_ESCAPE, N_("IME Escape")) - DEFINEKEY1(KEY_CONVERT, N_("IME Convert")) - DEFINEKEY1(KEY_NONCONVERT, N_("IME Nonconvert")) - DEFINEKEY1(KEY_ACCEPT, N_("IME Accept")) - DEFINEKEY1(KEY_MODECHANGE, N_("IME Mode Change")) - DEFINEKEY1(KEY_APPS, N_("Apps")) - DEFINEKEY1(KEY_SLEEP, N_("Sleep")) -#if !(IRRLICHT_VERSION_MAJOR <= 1 && IRRLICHT_VERSION_MINOR <= 7 && IRRLICHT_VERSION_REVISION < 3) - DEFINEKEY1(KEY_OEM_1, "OEM 1") // KEY_OEM_[0-9] and KEY_OEM_102 are assigned to multiple - DEFINEKEY1(KEY_OEM_2, "OEM 2") // different chars (on different platforms too) and thus w/o char - DEFINEKEY1(KEY_OEM_3, "OEM 3") - DEFINEKEY1(KEY_OEM_4, "OEM 4") - DEFINEKEY1(KEY_OEM_5, "OEM 5") - DEFINEKEY1(KEY_OEM_6, "OEM 6") - DEFINEKEY1(KEY_OEM_7, "OEM 7") - DEFINEKEY1(KEY_OEM_8, "OEM 8") - DEFINEKEY1(KEY_OEM_AX, "OEM AX") - DEFINEKEY1(KEY_OEM_102, "OEM 102") -#endif - DEFINEKEY1(KEY_ATTN, "Attn") - DEFINEKEY1(KEY_CRSEL, "CrSel") - DEFINEKEY1(KEY_EXSEL, "ExSel") - DEFINEKEY1(KEY_EREOF, N_("Erase EOF")) - DEFINEKEY1(KEY_PLAY, N_("Play")) - DEFINEKEY1(KEY_ZOOM, N_("Zoom")) - DEFINEKEY1(KEY_PA1, "PA1") - DEFINEKEY1(KEY_OEM_CLEAR, N_("OEM Clear")) - - // Keys without Irrlicht keycode - DEFINEKEY5("!") - DEFINEKEY5("\"") - DEFINEKEY5("#") - DEFINEKEY5("$") - DEFINEKEY5("%") - DEFINEKEY5("&") - DEFINEKEY5("'") - DEFINEKEY5("(") - DEFINEKEY5(")") - DEFINEKEY5("*") - DEFINEKEY5("/") - DEFINEKEY5(":") - DEFINEKEY5(";") - DEFINEKEY5("<") - DEFINEKEY5("=") - DEFINEKEY5(">") - DEFINEKEY5("?") - DEFINEKEY5("@") - DEFINEKEY5("[") - DEFINEKEY5("\\") - DEFINEKEY5("]") - DEFINEKEY5("^") - DEFINEKEY5("_") -}; - -#undef N_ - - -struct table_key lookup_keyname(const char *name) -{ - for (const auto &table_key : table) { - if (strcmp(table_key.Name, name) == 0) - return table_key; - } - - throw UnknownKeycode(name); -} - -struct table_key lookup_keykey(irr::EKEY_CODE key) -{ - for (const auto &table_key : table) { - if (table_key.Key == key) - return table_key; - } - - std::ostringstream os; - os << ""; - throw UnknownKeycode(os.str().c_str()); -} - -struct table_key lookup_keychar(wchar_t Char) -{ - for (const auto &table_key : table) { - if (table_key.Char == Char) - return table_key; - } - - std::ostringstream os; - os << ""; - throw UnknownKeycode(os.str().c_str()); -} - -KeyPress::KeyPress(const char *name) -{ - if (strlen(name) == 0) { - Key = irr::KEY_KEY_CODES_COUNT; - Char = L'\0'; - m_name = ""; - return; - } - - if (strlen(name) <= 4) { - // Lookup by resulting character - int chars_read = mbtowc(&Char, name, 1); - FATAL_ERROR_IF(chars_read != 1, "Unexpected multibyte character"); - try { - struct table_key k = lookup_keychar(Char); - m_name = k.Name; - Key = k.Key; - return; - } catch (UnknownKeycode &e) {}; - } else { - // Lookup by name - m_name = name; - try { - struct table_key k = lookup_keyname(name); - Key = k.Key; - Char = k.Char; - return; - } catch (UnknownKeycode &e) {}; - } - - // It's not a known key, complain and try to do something - Key = irr::KEY_KEY_CODES_COUNT; - int chars_read = mbtowc(&Char, name, 1); - FATAL_ERROR_IF(chars_read != 1, "Unexpected multibyte character"); - m_name = ""; - warningstream << "KeyPress: Unknown key '" << name << "', falling back to first char."; -} - -KeyPress::KeyPress(const irr::SEvent::SKeyInput &in, bool prefer_character) -{ - if (prefer_character) - Key = irr::KEY_KEY_CODES_COUNT; - else - Key = in.Key; - Char = in.Char; - - try { - if (valid_kcode(Key)) - m_name = lookup_keykey(Key).Name; - else - m_name = lookup_keychar(Char).Name; - } catch (UnknownKeycode &e) { - m_name = ""; - }; -} - -const char *KeyPress::sym() const -{ - return m_name.c_str(); -} - -const char *KeyPress::name() const -{ - if (m_name.empty()) - return ""; - const char *ret; - if (valid_kcode(Key)) - ret = lookup_keykey(Key).LangName; - else - ret = lookup_keychar(Char).LangName; - return ret ? ret : ""; -} - -const KeyPress EscapeKey("KEY_ESCAPE"); -const KeyPress CancelKey("KEY_CANCEL"); - -/* - Key config -*/ - -// A simple cache for quicker lookup -std::unordered_map g_key_setting_cache; - -KeyPress getKeySetting(const char *settingname) -{ - std::unordered_map::iterator n; - n = g_key_setting_cache.find(settingname); - if (n != g_key_setting_cache.end()) - return n->second; - - KeyPress k(g_settings->get(settingname).c_str()); - g_key_setting_cache[settingname] = k; - return k; -} - -void clearKeyCache() -{ - g_key_setting_cache.clear(); -} - -irr::EKEY_CODE keyname_to_keycode(const char *name) -{ - return lookup_keyname(name).Key; -} diff --git a/src/keycode.h b/src/keycode.h deleted file mode 100644 index 7036705d1..000000000 --- a/src/keycode.h +++ /dev/null @@ -1,67 +0,0 @@ -/* -Minetest -Copyright (C) 2010-2013 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#pragma once - -#include "irrlichttypes.h" -#include "Keycodes.h" -#include -#include - -/* A key press, consisting of either an Irrlicht keycode - or an actual char */ - -class KeyPress -{ -public: - KeyPress() = default; - - KeyPress(const char *name); - - KeyPress(const irr::SEvent::SKeyInput &in, bool prefer_character = false); - - bool operator==(const KeyPress &o) const - { - return (Char > 0 && Char == o.Char) || (valid_kcode(Key) && Key == o.Key); - } - - const char *sym() const; - const char *name() const; - -protected: - static bool valid_kcode(irr::EKEY_CODE k) - { - return k > 0 && k < irr::KEY_KEY_CODES_COUNT; - } - - irr::EKEY_CODE Key = irr::KEY_KEY_CODES_COUNT; - wchar_t Char = L'\0'; - std::string m_name = ""; -}; - -extern const KeyPress EscapeKey; -extern const KeyPress CancelKey; - -// Key configuration getter -KeyPress getKeySetting(const char *settingname); - -// Clear fast lookup cache -void clearKeyCache(); - -irr::EKEY_CODE keyname_to_keycode(const char *name); diff --git a/src/localplayer.cpp b/src/localplayer.cpp deleted file mode 100644 index 1c65d3b4d..000000000 --- a/src/localplayer.cpp +++ /dev/null @@ -1,1158 +0,0 @@ -/* -Minetest -Copyright (C) 2010-2013 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include "localplayer.h" -#include -#include "event.h" -#include "collision.h" -#include "nodedef.h" -#include "settings.h" -#include "environment.h" -#include "map.h" -#include "client.h" -#include "content_cao.h" - -/* - LocalPlayer -*/ - -LocalPlayer::LocalPlayer(Client *client, const char *name): - Player(name, client->idef()), - m_client(client) -{ -} - -static aabb3f getNodeBoundingBox(const std::vector &nodeboxes) -{ - if (nodeboxes.empty()) - return aabb3f(0, 0, 0, 0, 0, 0); - - aabb3f b_max; - - std::vector::const_iterator it = nodeboxes.begin(); - b_max = aabb3f(it->MinEdge, it->MaxEdge); - - ++it; - for (; it != nodeboxes.end(); ++it) - b_max.addInternalBox(*it); - - return b_max; -} - -bool LocalPlayer::updateSneakNode(Map *map, const v3f &position, - const v3f &sneak_max) -{ - static const v3s16 dir9_center[9] = { - v3s16( 0, 0, 0), - v3s16( 1, 0, 0), - v3s16(-1, 0, 0), - v3s16( 0, 0, 1), - v3s16( 0, 0, -1), - v3s16( 1, 0, 1), - v3s16(-1, 0, 1), - v3s16( 1, 0, -1), - v3s16(-1, 0, -1) - }; - - const NodeDefManager *nodemgr = m_client->ndef(); - MapNode node; - bool is_valid_position; - bool new_sneak_node_exists = m_sneak_node_exists; - - // We want the top of the sneak node to be below the players feet - f32 position_y_mod = 0.05 * BS; - if (m_sneak_node_exists) - position_y_mod = m_sneak_node_bb_top.MaxEdge.Y - position_y_mod; - - // Get position of current standing node - const v3s16 current_node = floatToInt(position - v3f(0, position_y_mod, 0), BS); - - if (current_node != m_sneak_node) { - new_sneak_node_exists = false; - } else { - node = map->getNodeNoEx(current_node, &is_valid_position); - if (!is_valid_position || !nodemgr->get(node).walkable) - new_sneak_node_exists = false; - } - - // Keep old sneak node - if (new_sneak_node_exists) - return true; - - // Get new sneak node - m_sneak_ladder_detected = false; - f32 min_distance_f = 100000.0 * BS; - - for (const auto &d : dir9_center) { - const v3s16 p = current_node + d; - const v3f pf = intToFloat(p, BS); - const v2f diff(position.X - pf.X, position.Z - pf.Z); - f32 distance_f = diff.getLength(); - - if (distance_f > min_distance_f || - fabs(diff.X) > (.5 + .1) * BS + sneak_max.X || - fabs(diff.Y) > (.5 + .1) * BS + sneak_max.Z) - continue; - - - // The node to be sneaked on has to be walkable - node = map->getNodeNoEx(p, &is_valid_position); - if (!is_valid_position || !nodemgr->get(node).walkable) - continue; - // And the node(s) above have to be nonwalkable - bool ok = true; - if (!physics_override_sneak_glitch) { - u16 height = ceilf( - (m_collisionbox.MaxEdge.Y - m_collisionbox.MinEdge.Y) / BS - ); - for (u16 y = 1; y <= height; y++) { - node = map->getNodeNoEx(p + v3s16(0, y, 0), &is_valid_position); - if (!is_valid_position || nodemgr->get(node).walkable) { - ok = false; - break; - } - } - } else { - // legacy behaviour: check just one node - node = map->getNodeNoEx(p + v3s16(0, 1, 0), &is_valid_position); - ok = is_valid_position && !nodemgr->get(node).walkable; - } - if (!ok) - continue; - - min_distance_f = distance_f; - m_sneak_node = p; - new_sneak_node_exists = true; - } - - if (!new_sneak_node_exists) - return false; - - // Update saved top bounding box of sneak node - node = map->getNodeNoEx(m_sneak_node); - std::vector nodeboxes; - node.getCollisionBoxes(nodemgr, &nodeboxes); - m_sneak_node_bb_top = getNodeBoundingBox(nodeboxes); - - if (physics_override_sneak_glitch) { - // Detect sneak ladder: - // Node two meters above sneak node must be solid - node = map->getNodeNoEx(m_sneak_node + v3s16(0, 2, 0), - &is_valid_position); - if (is_valid_position && nodemgr->get(node).walkable) { - // Node three meters above: must be non-solid - node = map->getNodeNoEx(m_sneak_node + v3s16(0, 3, 0), - &is_valid_position); - m_sneak_ladder_detected = is_valid_position && - !nodemgr->get(node).walkable; - } - } - return true; -} - -void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, - std::vector *collision_info) -{ - if (!collision_info || collision_info->empty()) { - // Node below the feet, update each ClientEnvironment::step() - m_standing_node = floatToInt(m_position, BS) - v3s16(0, 1, 0); - } - - // Temporary option for old move code - if (!physics_override_new_move) { - old_move(dtime, env, pos_max_d, collision_info); - return; - } - - Map *map = &env->getMap(); - const NodeDefManager *nodemgr = m_client->ndef(); - - v3f position = getPosition(); - - // Copy parent position if local player is attached - if (isAttached) { - setPosition(overridePosition); - return; - } - - PlayerSettings &player_settings = getPlayerSettings(); - - // Skip collision detection if noclip mode is used - bool fly_allowed = m_client->checkLocalPrivilege("fly"); - bool noclip = m_client->checkLocalPrivilege("noclip") && player_settings.noclip; - bool free_move = player_settings.free_move && fly_allowed; - - if (noclip && free_move) { - position += m_speed * dtime; - setPosition(position); - return; - } - - /* - Collision detection - */ - - bool is_valid_position; - MapNode node; - v3s16 pp; - - /* - Check if player is in liquid (the oscillating value) - */ - - // If in liquid, the threshold of coming out is at higher y - if (in_liquid) - { - pp = floatToInt(position + v3f(0,BS*0.1,0), BS); - node = map->getNodeNoEx(pp, &is_valid_position); - if (is_valid_position) { - in_liquid = nodemgr->get(node.getContent()).isLiquid(); - liquid_viscosity = nodemgr->get(node.getContent()).liquid_viscosity; - } else { - in_liquid = false; - } - } - // If not in liquid, the threshold of going in is at lower y - else - { - pp = floatToInt(position + v3f(0,BS*0.5,0), BS); - node = map->getNodeNoEx(pp, &is_valid_position); - if (is_valid_position) { - in_liquid = nodemgr->get(node.getContent()).isLiquid(); - liquid_viscosity = nodemgr->get(node.getContent()).liquid_viscosity; - } else { - in_liquid = false; - } - } - - - /* - Check if player is in liquid (the stable value) - */ - pp = floatToInt(position + v3f(0,0,0), BS); - node = map->getNodeNoEx(pp, &is_valid_position); - if (is_valid_position) { - in_liquid_stable = nodemgr->get(node.getContent()).isLiquid(); - } else { - in_liquid_stable = false; - } - - /* - Check if player is climbing - */ - - - pp = floatToInt(position + v3f(0,0.5*BS,0), BS); - v3s16 pp2 = floatToInt(position + v3f(0,-0.2*BS,0), BS); - node = map->getNodeNoEx(pp, &is_valid_position); - bool is_valid_position2; - MapNode node2 = map->getNodeNoEx(pp2, &is_valid_position2); - - if (!(is_valid_position && is_valid_position2)) { - is_climbing = false; - } else { - is_climbing = (nodemgr->get(node.getContent()).climbable - || nodemgr->get(node2.getContent()).climbable) && !free_move; - } - - /* - Collision uncertainty radius - Make it a bit larger than the maximum distance of movement - */ - //f32 d = pos_max_d * 1.1; - // A fairly large value in here makes moving smoother - f32 d = 0.15*BS; - - // This should always apply, otherwise there are glitches - sanity_check(d > pos_max_d); - - // Player object property step height is multiplied by BS in - // /src/script/common/c_content.cpp and /src/content_sao.cpp - float player_stepheight = (m_cao == nullptr) ? 0.0f : - (touching_ground ? m_cao->getStepHeight() : (0.2f * BS)); - - v3f accel_f = v3f(0,0,0); - const v3f initial_position = position; - const v3f initial_speed = m_speed; - - collisionMoveResult result = collisionMoveSimple(env, m_client, - pos_max_d, m_collisionbox, player_stepheight, dtime, - &position, &m_speed, accel_f); - - bool could_sneak = control.sneak && !free_move && !in_liquid && - !is_climbing && physics_override_sneak; - - // Add new collisions to the vector - if (collision_info && !free_move) { - v3f diff = intToFloat(m_standing_node, BS) - position; - f32 distance = diff.getLength(); - // Force update each ClientEnvironment::step() - bool is_first = collision_info->empty(); - - for (const auto &colinfo : result.collisions) { - collision_info->push_back(colinfo); - - if (colinfo.type != COLLISION_NODE || - colinfo.new_speed.Y != 0 || - (could_sneak && m_sneak_node_exists)) - continue; - - diff = intToFloat(colinfo.node_p, BS) - position; - - // Find nearest colliding node - f32 len = diff.getLength(); - if (is_first || len < distance) { - m_standing_node = colinfo.node_p; - distance = len; - } - } - } - - /* - If the player's feet touch the topside of any node, this is - set to true. - - Player is allowed to jump when this is true. - */ - bool touching_ground_was = touching_ground; - touching_ground = result.touching_ground; - bool sneak_can_jump = false; - - // Max. distance (X, Z) over border for sneaking determined by collision box - // * 0.49 to keep the center just barely on the node - v3f sneak_max = m_collisionbox.getExtent() * 0.49; - - if (m_sneak_ladder_detected) { - // restore legacy behaviour (this makes the m_speed.Y hack necessary) - sneak_max = v3f(0.4 * BS, 0, 0.4 * BS); - } - - /* - If sneaking, keep on top of last walked node and don't fall off - */ - if (could_sneak && m_sneak_node_exists) { - const v3f sn_f = intToFloat(m_sneak_node, BS); - const v3f bmin = sn_f + m_sneak_node_bb_top.MinEdge; - const v3f bmax = sn_f + m_sneak_node_bb_top.MaxEdge; - const v3f old_pos = position; - const v3f old_speed = m_speed; - f32 y_diff = bmax.Y - position.Y; - m_standing_node = m_sneak_node; - - // (BS * 0.6f) is the basic stepheight while standing on ground - if (y_diff < BS * 0.6f) { - // Only center player when they're on the node - position.X = rangelim(position.X, - bmin.X - sneak_max.X, bmax.X + sneak_max.X); - position.Z = rangelim(position.Z, - bmin.Z - sneak_max.Z, bmax.Z + sneak_max.Z); - - if (position.X != old_pos.X) - m_speed.X = 0; - if (position.Z != old_pos.Z) - m_speed.Z = 0; - } - - if (y_diff > 0 && m_speed.Y <= 0 && - (physics_override_sneak_glitch || y_diff < BS * 0.6f)) { - // Move player to the maximal height when falling or when - // the ledge is climbed on the next step. - - // Smoothen the movement (based on 'position.Y = bmax.Y') - position.Y += y_diff * dtime * 22.0f + BS * 0.01f; - position.Y = std::min(position.Y, bmax.Y); - m_speed.Y = 0; - } - - // Allow jumping on node edges while sneaking - if (m_speed.Y == 0 || m_sneak_ladder_detected) - sneak_can_jump = true; - - if (collision_info && - m_speed.Y - old_speed.Y > BS) { - // Collide with sneak node, report fall damage - CollisionInfo sn_info; - sn_info.node_p = m_sneak_node; - sn_info.old_speed = old_speed; - sn_info.new_speed = m_speed; - collision_info->push_back(sn_info); - } - } - - /* - Find the next sneak node if necessary - */ - bool new_sneak_node_exists = false; - - if (could_sneak) - new_sneak_node_exists = updateSneakNode(map, position, sneak_max); - - /* - Set new position but keep sneak node set - */ - setPosition(position); - m_sneak_node_exists = new_sneak_node_exists; - - /* - Report collisions - */ - - if(!result.standing_on_object && !touching_ground_was && touching_ground) { - m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_REGAIN_GROUND)); - - // Set camera impact value to be used for view bobbing - camera_impact = getSpeed().Y * -1; - } - - { - camera_barely_in_ceiling = false; - v3s16 camera_np = floatToInt(getEyePosition(), BS); - MapNode n = map->getNodeNoEx(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 - */ - const ContentFeatures &f = nodemgr->get(map->getNodeNoEx(m_standing_node)); - // Determine if jumping is possible - m_can_jump = (touching_ground && !in_liquid && !is_climbing) - || sneak_can_jump; - if (itemgroup_get(f.groups, "disable_jump")) - m_can_jump = false; - - // Jump key pressed while jumping off from a bouncy block - if (m_can_jump && control.jump && itemgroup_get(f.groups, "bouncy") && - m_speed.Y >= -0.5 * BS) { - float jumpspeed = movement_speed_jump * physics_override_jump; - if (m_speed.Y > 1) { - // Reduce boost when speed already is high - m_speed.Y += jumpspeed / (1 + (m_speed.Y / 16 )); - } else { - m_speed.Y += jumpspeed; - } - setSpeed(m_speed); - m_can_jump = false; - } - - // Autojump - handleAutojump(dtime, env, result, initial_position, initial_speed, pos_max_d); -} - -void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d) -{ - move(dtime, env, pos_max_d, NULL); -} - -void LocalPlayer::applyControl(float dtime, Environment *env) -{ - // Clear stuff - swimming_vertical = false; - - setPitch(control.pitch); - setYaw(control.yaw); - - // Nullify speed and don't run positioning code if the player is attached - if(isAttached) - { - setSpeed(v3f(0,0,0)); - return; - } - - PlayerSettings &player_settings = getPlayerSettings(); - - v3f move_direction = v3f(0,0,1); - move_direction.rotateXZBy(getYaw()); - - v3f speedH = v3f(0,0,0); // Horizontal (X, Z) - v3f speedV = v3f(0,0,0); // Vertical (Y) - - bool fly_allowed = m_client->checkLocalPrivilege("fly"); - bool fast_allowed = m_client->checkLocalPrivilege("fast"); - - bool free_move = fly_allowed && player_settings.free_move; - bool fast_move = fast_allowed && player_settings.fast_move; - // When aux1_descends is enabled the fast key is used to go down, so fast isn't possible - bool fast_climb = fast_move && control.aux1 && !player_settings.aux1_descends; - bool continuous_forward = player_settings.continuous_forward; - bool always_fly_fast = player_settings.always_fly_fast; - - // Whether superspeed mode is used or not - bool superspeed = false; - - if (always_fly_fast && free_move && fast_move) - superspeed = true; - - // Old descend control - if (player_settings.aux1_descends) - { - // If free movement and fast movement, always move fast - if(free_move && fast_move) - superspeed = true; - - // Auxiliary button 1 (E) - if(control.aux1) - { - if(free_move) - { - // In free movement mode, aux1 descends - if(fast_move) - speedV.Y = -movement_speed_fast; - else - speedV.Y = -movement_speed_walk; - } - else if(in_liquid || in_liquid_stable) - { - speedV.Y = -movement_speed_walk; - swimming_vertical = true; - } - else if(is_climbing) - { - speedV.Y = -movement_speed_climb; - } - else - { - // If not free movement but fast is allowed, aux1 is - // "Turbo button" - if(fast_move) - superspeed = true; - } - } - } - // New minecraft-like descend control - else - { - // Auxiliary button 1 (E) - if(control.aux1) - { - if(!is_climbing) - { - // aux1 is "Turbo button" - if(fast_move) - superspeed = true; - } - } - - if(control.sneak) - { - if(free_move) - { - // In free movement mode, sneak descends - if (fast_move && (control.aux1 || always_fly_fast)) - speedV.Y = -movement_speed_fast; - else - speedV.Y = -movement_speed_walk; - } - else if(in_liquid || in_liquid_stable) - { - if(fast_climb) - speedV.Y = -movement_speed_fast; - else - speedV.Y = -movement_speed_walk; - swimming_vertical = true; - } - else if(is_climbing) - { - if(fast_climb) - speedV.Y = -movement_speed_fast; - else - speedV.Y = -movement_speed_climb; - } - } - } - - if (continuous_forward) - speedH += move_direction; - - if (control.up) { - if (continuous_forward) { - if (fast_move) - superspeed = true; - } else { - speedH += move_direction; - } - } - if (control.down) { - speedH -= move_direction; - } - if (!control.up && !control.down) { - speedH -= move_direction * - (control.forw_move_joystick_axis / 32767.f); - } - if (control.left) { - speedH += move_direction.crossProduct(v3f(0,1,0)); - } - if (control.right) { - speedH += move_direction.crossProduct(v3f(0,-1,0)); - } - if (!control.left && !control.right) { - speedH -= move_direction.crossProduct(v3f(0,1,0)) * - (control.sidew_move_joystick_axis / 32767.f); - } - if(control.jump) - { - if (free_move) { - if (player_settings.aux1_descends || always_fly_fast) { - if (fast_move) - speedV.Y = movement_speed_fast; - else - speedV.Y = movement_speed_walk; - } else { - if(fast_move && control.aux1) - speedV.Y = movement_speed_fast; - else - speedV.Y = movement_speed_walk; - } - } - else if(m_can_jump) - { - /* - NOTE: The d value in move() affects jump height by - raising the height at which the jump speed is kept - at its starting value - */ - v3f speedJ = getSpeed(); - if(speedJ.Y >= -0.5 * BS) { - speedJ.Y = movement_speed_jump * physics_override_jump; - setSpeed(speedJ); - m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_JUMP)); - } - } - else if(in_liquid) - { - if(fast_climb) - speedV.Y = movement_speed_fast; - else - speedV.Y = movement_speed_walk; - swimming_vertical = true; - } - else if(is_climbing) - { - if(fast_climb) - speedV.Y = movement_speed_fast; - else - speedV.Y = movement_speed_climb; - } - } - - // The speed of the player (Y is ignored) - if(superspeed || (is_climbing && fast_climb) || ((in_liquid || in_liquid_stable) && fast_climb)) - speedH = speedH.normalize() * movement_speed_fast; - else if(control.sneak && !free_move && !in_liquid && !in_liquid_stable) - speedH = speedH.normalize() * movement_speed_crouch; - else - speedH = speedH.normalize() * movement_speed_walk; - - // Acceleration increase - f32 incH = 0; // Horizontal (X, Z) - f32 incV = 0; // Vertical (Y) - if((!touching_ground && !free_move && !is_climbing && !in_liquid) || (!free_move && m_can_jump && control.jump)) - { - // Jumping and falling - if(superspeed || (fast_move && control.aux1)) - incH = movement_acceleration_fast * BS * dtime; - else - incH = movement_acceleration_air * BS * dtime; - incV = 0; // No vertical acceleration in air - } - else if (superspeed || (is_climbing && fast_climb) || ((in_liquid || in_liquid_stable) && fast_climb)) - incH = incV = movement_acceleration_fast * BS * dtime; - else - incH = incV = movement_acceleration_default * BS * dtime; - - float slip_factor = 1.0f; - if (!free_move) - slip_factor = getSlipFactor(env, speedH); - - // Accelerate to target speed with maximum increment - accelerateHorizontal(speedH * physics_override_speed, - incH * physics_override_speed * slip_factor); - accelerateVertical(speedV * physics_override_speed, - incV * physics_override_speed); -} - -v3s16 LocalPlayer::getStandingNodePos() -{ - if(m_sneak_node_exists) - return m_sneak_node; - return m_standing_node; -} - -v3s16 LocalPlayer::getFootstepNodePos() -{ - if (in_liquid_stable) - // Emit swimming sound if the player is in liquid - return floatToInt(getPosition(), BS); - if (touching_ground) - // BS * 0.05 below the player's feet ensures a 1/16th height - // nodebox is detected instead of the node below it. - return floatToInt(getPosition() - v3f(0, BS * 0.05f, 0), BS); - // A larger distance below is necessary for a footstep sound - // when landing after a jump or fall. BS * 0.5 ensures water - // sounds when swimming in 1 node deep water. - return floatToInt(getPosition() - v3f(0, BS * 0.5f, 0), BS); -} - -v3s16 LocalPlayer::getLightPosition() const -{ - return floatToInt(m_position + v3f(0,BS+BS/2,0), BS); -} - -v3f LocalPlayer::getEyeOffset() const -{ - float eye_height = camera_barely_in_ceiling ? - m_eye_height - 0.125f : m_eye_height; - return v3f(0, BS * eye_height, 0); -} - -// Horizontal acceleration (X and Z), Y direction is ignored -void LocalPlayer::accelerateHorizontal(const v3f &target_speed, - const f32 max_increase) -{ - if (max_increase == 0) - return; - - v3f d_wanted = target_speed - m_speed; - d_wanted.Y = 0.0f; - f32 dl = d_wanted.getLength(); - if (dl > max_increase) - dl = max_increase; - - v3f d = d_wanted.normalize() * dl; - - m_speed.X += d.X; - m_speed.Z += d.Z; -} - -// Vertical acceleration (Y), X and Z directions are ignored -void LocalPlayer::accelerateVertical(const v3f &target_speed, const f32 max_increase) -{ - if (max_increase == 0) - return; - - f32 d_wanted = target_speed.Y - m_speed.Y; - if (d_wanted > max_increase) - d_wanted = max_increase; - else if (d_wanted < -max_increase) - d_wanted = -max_increase; - - m_speed.Y += d_wanted; -} - -// Temporary option for old move code -void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d, - std::vector *collision_info) -{ - Map *map = &env->getMap(); - const NodeDefManager *nodemgr = m_client->ndef(); - - v3f position = getPosition(); - - // Copy parent position if local player is attached - if (isAttached) { - setPosition(overridePosition); - m_sneak_node_exists = false; - return; - } - - PlayerSettings &player_settings = getPlayerSettings(); - - // Skip collision detection if noclip mode is used - bool fly_allowed = m_client->checkLocalPrivilege("fly"); - bool noclip = m_client->checkLocalPrivilege("noclip") && player_settings.noclip; - bool free_move = noclip && fly_allowed && player_settings.free_move; - if (free_move) { - position += m_speed * dtime; - setPosition(position); - m_sneak_node_exists = false; - return; - } - - /* - Collision detection - */ - bool is_valid_position; - MapNode node; - v3s16 pp; - - /* - Check if player is in liquid (the oscillating value) - */ - if (in_liquid) { - // If in liquid, the threshold of coming out is at higher y - pp = floatToInt(position + v3f(0, BS * 0.1, 0), BS); - node = map->getNodeNoEx(pp, &is_valid_position); - if (is_valid_position) { - in_liquid = nodemgr->get(node.getContent()).isLiquid(); - liquid_viscosity = nodemgr->get(node.getContent()).liquid_viscosity; - } else { - in_liquid = false; - } - } else { - // If not in liquid, the threshold of going in is at lower y - pp = floatToInt(position + v3f(0, BS * 0.5, 0), BS); - node = map->getNodeNoEx(pp, &is_valid_position); - if (is_valid_position) { - in_liquid = nodemgr->get(node.getContent()).isLiquid(); - liquid_viscosity = nodemgr->get(node.getContent()).liquid_viscosity; - } else { - in_liquid = false; - } - } - - /* - Check if player is in liquid (the stable value) - */ - pp = floatToInt(position + v3f(0, 0, 0), BS); - node = map->getNodeNoEx(pp, &is_valid_position); - if (is_valid_position) - in_liquid_stable = nodemgr->get(node.getContent()).isLiquid(); - else - in_liquid_stable = false; - - /* - Check if player is climbing - */ - pp = floatToInt(position + v3f(0, 0.5 * BS, 0), BS); - v3s16 pp2 = floatToInt(position + v3f(0, -0.2 * BS, 0), BS); - node = map->getNodeNoEx(pp, &is_valid_position); - bool is_valid_position2; - MapNode node2 = map->getNodeNoEx(pp2, &is_valid_position2); - - if (!(is_valid_position && is_valid_position2)) - is_climbing = false; - else - is_climbing = (nodemgr->get(node.getContent()).climbable || - nodemgr->get(node2.getContent()).climbable) && !free_move; - - /* - Collision uncertainty radius - Make it a bit larger than the maximum distance of movement - */ - //f32 d = pos_max_d * 1.1; - // A fairly large value in here makes moving smoother - f32 d = 0.15 * BS; - // This should always apply, otherwise there are glitches - sanity_check(d > pos_max_d); - // Maximum distance over border for sneaking - f32 sneak_max = BS * 0.4; - - /* - If sneaking, keep in range from the last walked node and don't - fall off from it - */ - if (control.sneak && m_sneak_node_exists && - !(fly_allowed && player_settings.free_move) && !in_liquid && - physics_override_sneak) { - f32 maxd = 0.5 * BS + sneak_max; - v3f lwn_f = intToFloat(m_sneak_node, BS); - position.X = rangelim(position.X, lwn_f.X - maxd, lwn_f.X + maxd); - position.Z = rangelim(position.Z, lwn_f.Z - maxd, lwn_f.Z + maxd); - - if (!is_climbing) { - // Move up if necessary - f32 new_y = (lwn_f.Y - 0.5 * BS) + m_sneak_node_bb_ymax; - if (position.Y < new_y) - position.Y = new_y; - /* - Collision seems broken, since player is sinking when - sneaking over the edges of current sneaking_node. - TODO (when fixed): Set Y-speed only to 0 when position.Y < new_y. - */ - if (m_speed.Y < 0) - m_speed.Y = 0; - } - } - - // this shouldn't be hardcoded but transmitted from server - float player_stepheight = touching_ground ? (BS * 0.6) : (BS * 0.2); - - v3f accel_f = v3f(0, 0, 0); - const v3f initial_position = position; - const v3f initial_speed = m_speed; - - collisionMoveResult result = collisionMoveSimple(env, m_client, - pos_max_d, m_collisionbox, player_stepheight, dtime, - &position, &m_speed, accel_f); - - /* - If the player's feet touch the topside of any node, this is - set to true. - - Player is allowed to jump when this is true. - */ - bool touching_ground_was = touching_ground; - touching_ground = result.touching_ground; - - //bool standing_on_unloaded = result.standing_on_unloaded; - - /* - Check the nodes under the player to see from which node the - player is sneaking from, if any. If the node from under - the player has been removed, the player falls. - */ - f32 position_y_mod = 0.05 * BS; - if (m_sneak_node_bb_ymax > 0) - position_y_mod = m_sneak_node_bb_ymax - position_y_mod; - v3s16 current_node = floatToInt(position - v3f(0, position_y_mod, 0), BS); - if (m_sneak_node_exists && - nodemgr->get(map->getNodeNoEx(m_old_node_below)).name == "air" && - m_old_node_below_type != "air") { - // Old node appears to have been removed; that is, - // it wasn't air before but now it is - m_need_to_get_new_sneak_node = false; - m_sneak_node_exists = false; - } else if (nodemgr->get(map->getNodeNoEx(current_node)).name != "air") { - // We are on something, so make sure to recalculate the sneak - // node. - m_need_to_get_new_sneak_node = true; - } - - if (m_need_to_get_new_sneak_node && physics_override_sneak) { - m_sneak_node_bb_ymax = 0; - v3s16 pos_i_bottom = floatToInt(position - v3f(0, position_y_mod, 0), BS); - v2f player_p2df(position.X, position.Z); - f32 min_distance_f = 100000.0 * BS; - // If already seeking from some node, compare to it. - v3s16 new_sneak_node = m_sneak_node; - for (s16 x= -1; x <= 1; x++) - for (s16 z= -1; z <= 1; z++) { - v3s16 p = pos_i_bottom + v3s16(x, 0, z); - v3f pf = intToFloat(p, BS); - v2f node_p2df(pf.X, pf.Z); - f32 distance_f = player_p2df.getDistanceFrom(node_p2df); - f32 max_axis_distance_f = MYMAX( - std::fabs(player_p2df.X - node_p2df.X), - std::fabs(player_p2df.Y - node_p2df.Y)); - - if (distance_f > min_distance_f || - max_axis_distance_f > 0.5 * BS + sneak_max + 0.1 * BS) - continue; - - // The node to be sneaked on has to be walkable - node = map->getNodeNoEx(p, &is_valid_position); - if (!is_valid_position || !nodemgr->get(node).walkable) - continue; - // And the node above it has to be nonwalkable - node = map->getNodeNoEx(p + v3s16(0, 1, 0), &is_valid_position); - if (!is_valid_position || nodemgr->get(node).walkable) - continue; - // If not 'sneak_glitch' the node 2 nodes above it has to be nonwalkable - if (!physics_override_sneak_glitch) { - node =map->getNodeNoEx(p + v3s16(0, 2, 0), &is_valid_position); - if (!is_valid_position || nodemgr->get(node).walkable) - continue; - } - - min_distance_f = distance_f; - new_sneak_node = p; - } - - bool sneak_node_found = (min_distance_f < 100000.0 * BS * 0.9); - - m_sneak_node = new_sneak_node; - m_sneak_node_exists = sneak_node_found; - - if (sneak_node_found) { - f32 cb_max = 0; - MapNode n = map->getNodeNoEx(m_sneak_node); - std::vector nodeboxes; - n.getCollisionBoxes(nodemgr, &nodeboxes); - for (const auto &box : nodeboxes) { - if (box.MaxEdge.Y > cb_max) - cb_max = box.MaxEdge.Y; - } - m_sneak_node_bb_ymax = cb_max; - } - - /* - If sneaking, the player's collision box can be in air, so - this has to be set explicitly - */ - if (sneak_node_found && control.sneak) - touching_ground = true; - } - - /* - Set new position but keep sneak node set - */ - bool sneak_node_exists = m_sneak_node_exists; - setPosition(position); - m_sneak_node_exists = sneak_node_exists; - - /* - Report collisions - */ - // Dont report if flying - if (collision_info && !(player_settings.free_move && fly_allowed)) { - for (const auto &info : result.collisions) { - collision_info->push_back(info); - } - } - - if (!result.standing_on_object && !touching_ground_was && touching_ground) { - m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_REGAIN_GROUND)); - // Set camera impact value to be used for view bobbing - camera_impact = getSpeed().Y * -1; - } - - { - camera_barely_in_ceiling = false; - v3s16 camera_np = floatToInt(getEyePosition(), BS); - MapNode n = map->getNodeNoEx(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 - */ - m_old_node_below = floatToInt(position - v3f(0, BS / 2, 0), BS); - m_old_node_below_type = nodemgr->get(map->getNodeNoEx(m_old_node_below)).name; - - /* - Check properties of the node on which the player is standing - */ - const ContentFeatures &f = nodemgr->get(map->getNodeNoEx(getStandingNodePos())); - // Determine if jumping is possible - m_can_jump = touching_ground && !in_liquid; - if (itemgroup_get(f.groups, "disable_jump")) - m_can_jump = false; - // Jump key pressed while jumping off from a bouncy block - if (m_can_jump && control.jump && itemgroup_get(f.groups, "bouncy") && - m_speed.Y >= -0.5 * BS) { - float jumpspeed = movement_speed_jump * physics_override_jump; - if (m_speed.Y > 1) { - // Reduce boost when speed already is high - m_speed.Y += jumpspeed / (1 + (m_speed.Y / 16 )); - } else { - m_speed.Y += jumpspeed; - } - setSpeed(m_speed); - m_can_jump = false; - } - - // Autojump - handleAutojump(dtime, env, result, initial_position, initial_speed, pos_max_d); -} - -float LocalPlayer::getSlipFactor(Environment *env, const v3f &speedH) -{ - // Slip on slippery nodes - const NodeDefManager *nodemgr = env->getGameDef()->ndef(); - Map *map = &env->getMap(); - const ContentFeatures &f = nodemgr->get(map->getNodeNoEx( - getStandingNodePos())); - int slippery = 0; - if (f.walkable) - slippery = itemgroup_get(f.groups, "slippery"); - - if (slippery >= 1) { - if (speedH == v3f(0.0f)) { - slippery = slippery * 2; - } - return core::clamp(1.0f / (slippery + 1), 0.001f, 1.0f); - } - return 1.0f; -} - -void LocalPlayer::handleAutojump(f32 dtime, Environment *env, - const collisionMoveResult &result, const v3f &initial_position, - const v3f &initial_speed, f32 pos_max_d) -{ - PlayerSettings &player_settings = getPlayerSettings(); - if (!player_settings.autojump) - return; - - if (m_autojump) { - // release autojump after a given time - m_autojump_time -= dtime; - if (m_autojump_time <= 0.0f) - m_autojump = false; - return; - } - - bool control_forward = control.up || - (!control.up && !control.down && - control.forw_move_joystick_axis < -0.05); - bool could_autojump = - m_can_jump && !control.jump && !control.sneak && control_forward; - if (!could_autojump) - return; - - bool horizontal_collision = false; - for (const auto &colinfo : result.collisions) { - if (colinfo.type == COLLISION_NODE && colinfo.plane != 1) { - horizontal_collision = true; - break; // one is enough - } - } - - // must be running against something to trigger autojumping - if (!horizontal_collision) - return; - - // check for nodes above - v3f headpos_min = m_position + m_collisionbox.MinEdge * 0.99f; - v3f headpos_max = m_position + m_collisionbox.MaxEdge * 0.99f; - headpos_min.Y = headpos_max.Y; // top face of collision box - v3s16 ceilpos_min = floatToInt(headpos_min, BS) + v3s16(0, 1, 0); - v3s16 ceilpos_max = floatToInt(headpos_max, BS) + v3s16(0, 1, 0); - const NodeDefManager *ndef = env->getGameDef()->ndef(); - bool is_position_valid; - for (s16 z = ceilpos_min.Z; z <= ceilpos_max.Z; z++) { - for (s16 x = ceilpos_min.X; x <= ceilpos_max.X; x++) { - MapNode n = env->getMap().getNodeNoEx(v3s16(x, ceilpos_max.Y, z), &is_position_valid); - - if (!is_position_valid) - break; // won't collide with the void outside - if (n.getContent() == CONTENT_IGNORE) - return; // players collide with ignore blocks -> same as walkable - const ContentFeatures &f = ndef->get(n); - if (f.walkable) - return; // would bump head, don't jump - } - } - - float jump_height = 1.1f; // TODO: better than a magic number - v3f jump_pos = initial_position + v3f(0.0f, jump_height * BS, 0.0f); - v3f jump_speed = initial_speed; - - // try at peak of jump, zero step height - collisionMoveResult jump_result = collisionMoveSimple(env, m_client, pos_max_d, - m_collisionbox, 0.0f, dtime, &jump_pos, &jump_speed, - v3f(0, 0, 0)); - - // see if we can get a little bit farther horizontally if we had - // jumped - v3f run_delta = m_position - initial_position; - run_delta.Y = 0.0f; - v3f jump_delta = jump_pos - initial_position; - jump_delta.Y = 0.0f; - if (jump_delta.getLengthSQ() > run_delta.getLengthSQ() * 1.01f) { - m_autojump = true; - m_autojump_time = 0.1f; - } -} diff --git a/src/localplayer.h b/src/localplayer.h deleted file mode 100644 index 7148bc4de..000000000 --- a/src/localplayer.h +++ /dev/null @@ -1,198 +0,0 @@ -/* -Minetest -Copyright (C) 2010-2013 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#pragma once - -#include "player.h" -#include "environment.h" -#include "constants.h" -#include "settings.h" -#include - -class Client; -class Environment; -class GenericCAO; -class ClientActiveObject; -class ClientEnvironment; -class IGameDef; -struct collisionMoveResult; - -enum LocalPlayerAnimations -{ - NO_ANIM, - WALK_ANIM, - DIG_ANIM, - WD_ANIM -}; // no local animation, walking, digging, both - -class LocalPlayer : public Player -{ -public: - LocalPlayer(Client *client, const char *name); - virtual ~LocalPlayer() = default; - - ClientActiveObject *parent = nullptr; - - // Initialize hp to 0, so that no hearts will be shown if server - // doesn't support health points - u16 hp = 0; - bool isAttached = false; - bool touching_ground = false; - // This oscillates so that the player jumps a bit above the surface - bool in_liquid = false; - // This is more stable and defines the maximum speed of the player - bool in_liquid_stable = false; - // Gets the viscosity of water to calculate friction - u8 liquid_viscosity = 0; - bool is_climbing = false; - bool swimming_vertical = false; - - float physics_override_speed = 1.0f; - float physics_override_jump = 1.0f; - float physics_override_gravity = 1.0f; - bool physics_override_sneak = true; - bool physics_override_sneak_glitch = false; - // Temporary option for old move code - bool physics_override_new_move = true; - - v3f overridePosition; - - void move(f32 dtime, Environment *env, f32 pos_max_d); - void move(f32 dtime, Environment *env, f32 pos_max_d, - std::vector *collision_info); - // Temporary option for old move code - void old_move(f32 dtime, Environment *env, f32 pos_max_d, - std::vector *collision_info); - - void applyControl(float dtime, Environment *env); - - v3s16 getStandingNodePos(); - v3s16 getFootstepNodePos(); - - // Used to check if anything changed and prevent sending packets if not - v3f last_position; - v3f last_speed; - float last_pitch = 0.0f; - float last_yaw = 0.0f; - unsigned int last_keyPressed = 0; - u8 last_camera_fov = 0; - u8 last_wanted_range = 0; - - float camera_impact = 0.0f; - - bool makes_footstep_sound = true; - - int last_animation = NO_ANIM; - float last_animation_speed; - - std::string hotbar_image = ""; - std::string hotbar_selected_image = ""; - - video::SColor light_color = video::SColor(255, 255, 255, 255); - - float hurt_tilt_timer = 0.0f; - float hurt_tilt_strength = 0.0f; - - GenericCAO *getCAO() const { return m_cao; } - - void setCAO(GenericCAO *toset) - { - assert(!m_cao); // Pre-condition - m_cao = toset; - } - - u32 maxHudId() const { return hud.size(); } - - u16 getBreath() const { return m_breath; } - void setBreath(u16 breath) { m_breath = breath; } - - v3s16 getLightPosition() const; - - void setYaw(f32 yaw) { m_yaw = yaw; } - f32 getYaw() const { return m_yaw; } - - void setPitch(f32 pitch) { m_pitch = pitch; } - f32 getPitch() const { return m_pitch; } - - inline void setPosition(const v3f &position) - { - m_position = position; - m_sneak_node_exists = false; - } - - v3f getPosition() const { return m_position; } - v3f getEyePosition() const { return m_position + getEyeOffset(); } - v3f getEyeOffset() const; - void setEyeHeight(float eye_height) { m_eye_height = eye_height; } - - void setCollisionbox(const aabb3f &box) { m_collisionbox = box; } - - float getZoomFOV() const { return m_zoom_fov; } - void setZoomFOV(float zoom_fov) { m_zoom_fov = zoom_fov; } - - bool getAutojump() const { return m_autojump; } - -private: - void accelerateHorizontal(const v3f &target_speed, const f32 max_increase); - void accelerateVertical(const v3f &target_speed, const f32 max_increase); - bool updateSneakNode(Map *map, const v3f &position, const v3f &sneak_max); - float getSlipFactor(Environment *env, const v3f &speedH); - void handleAutojump(f32 dtime, Environment *env, - const collisionMoveResult &result, - const v3f &position_before_move, const v3f &speed_before_move, - f32 pos_max_d); - - v3f m_position; - v3s16 m_standing_node; - - v3s16 m_sneak_node = v3s16(32767, 32767, 32767); - // Stores the top bounding box of m_sneak_node - aabb3f m_sneak_node_bb_top = aabb3f(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f); - // Whether the player is allowed to sneak - bool m_sneak_node_exists = false; - // Whether a "sneak ladder" structure is detected at the players pos - // see detectSneakLadder() in the .cpp for more info (always false if disabled) - bool m_sneak_ladder_detected = false; - - // ***** Variables for temporary option of the old move code ***** - // Stores the max player uplift by m_sneak_node - f32 m_sneak_node_bb_ymax = 0.0f; - // Whether recalculation of m_sneak_node and its top bbox is needed - bool m_need_to_get_new_sneak_node = true; - // Node below player, used to determine whether it has been removed, - // and its old type - v3s16 m_old_node_below = v3s16(32767, 32767, 32767); - std::string m_old_node_below_type = "air"; - // ***** End of variables for temporary option ***** - - bool m_can_jump = false; - 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; - float m_zoom_fov = 0.0f; - bool m_autojump = false; - float m_autojump_time = 0.0f; - - GenericCAO *m_cao = nullptr; - Client *m_client; -}; diff --git a/src/main.cpp b/src/main.cpp index 2033518e3..546eb2a00 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -25,7 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "server.h" #include "filesys.h" #include "version.h" -#include "game.h" +#include "client/game.h" #include "defaultsettings.h" #include "gettext.h" #include "log.h" diff --git a/src/mapblock.cpp b/src/mapblock.cpp index cf815d16b..dfb4c783e 100644 --- a/src/mapblock.cpp +++ b/src/mapblock.cpp @@ -31,7 +31,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "content_nodemeta.h" // For legacy deserialization #include "serialization.h" #ifndef SERVER -#include "mapblock_mesh.h" +#include "client/mapblock_mesh.h" #endif #include "porting.h" #include "util/string.h" diff --git a/src/mapblock_mesh.cpp b/src/mapblock_mesh.cpp deleted file mode 100644 index ed8a073de..000000000 --- a/src/mapblock_mesh.cpp +++ /dev/null @@ -1,1389 +0,0 @@ -/* -Minetest -Copyright (C) 2010-2013 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include "mapblock_mesh.h" -#include "client.h" -#include "mapblock.h" -#include "map.h" -#include "profiler.h" -#include "shader.h" -#include "mesh.h" -#include "minimap.h" -#include "content_mapblock.h" -#include "util/directiontables.h" -#include "client/meshgen/collector.h" -#include "client/renderingengine.h" -#include - -/* - MeshMakeData -*/ - -MeshMakeData::MeshMakeData(Client *client, bool use_shaders, - bool use_tangent_vertices): - m_client(client), - m_use_shaders(use_shaders), - m_use_tangent_vertices(use_tangent_vertices) -{} - -void MeshMakeData::fillBlockDataBegin(const v3s16 &blockpos) -{ - m_blockpos = blockpos; - - v3s16 blockpos_nodes = m_blockpos*MAP_BLOCKSIZE; - - m_vmanip.clear(); - VoxelArea voxel_area(blockpos_nodes - v3s16(1,1,1) * MAP_BLOCKSIZE, - blockpos_nodes + v3s16(1,1,1) * MAP_BLOCKSIZE*2-v3s16(1,1,1)); - m_vmanip.addArea(voxel_area); -} - -void MeshMakeData::fillBlockData(const v3s16 &block_offset, MapNode *data) -{ - v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE); - VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1)); - - v3s16 bp = m_blockpos + block_offset; - v3s16 blockpos_nodes = bp * MAP_BLOCKSIZE; - m_vmanip.copyFrom(data, data_area, v3s16(0,0,0), blockpos_nodes, data_size); -} - -void MeshMakeData::fill(MapBlock *block) -{ - fillBlockDataBegin(block->getPos()); - - fillBlockData(v3s16(0,0,0), block->getData()); - - // Get map for reading neighbor blocks - Map *map = block->getParent(); - - for (const v3s16 &dir : g_26dirs) { - v3s16 bp = m_blockpos + dir; - MapBlock *b = map->getBlockNoCreateNoEx(bp); - if(b) - fillBlockData(dir, b->getData()); - } -} - -void MeshMakeData::fillSingleNode(MapNode *node) -{ - m_blockpos = v3s16(0,0,0); - - v3s16 blockpos_nodes = v3s16(0,0,0); - VoxelArea area(blockpos_nodes-v3s16(1,1,1)*MAP_BLOCKSIZE, - blockpos_nodes+v3s16(1,1,1)*MAP_BLOCKSIZE*2-v3s16(1,1,1)); - s32 volume = area.getVolume(); - s32 our_node_index = area.index(1,1,1); - - // Allocate this block + neighbors - m_vmanip.clear(); - m_vmanip.addArea(area); - - // Fill in data - MapNode *data = new MapNode[volume]; - for(s32 i = 0; i < volume; i++) - { - if (i == our_node_index) - data[i] = *node; - else - data[i] = MapNode(CONTENT_AIR, LIGHT_MAX, 0); - } - m_vmanip.copyFrom(data, area, area.MinEdge, area.MinEdge, area.getExtent()); - delete[] data; -} - -void MeshMakeData::setCrack(int crack_level, v3s16 crack_pos) -{ - if (crack_level >= 0) - m_crack_pos_relative = crack_pos - m_blockpos*MAP_BLOCKSIZE; -} - -void MeshMakeData::setSmoothLighting(bool smooth_lighting) -{ - m_smooth_lighting = smooth_lighting; -} - -/* - Light and vertex color functions -*/ - -/* - Calculate non-smooth lighting at interior of node. - Single light bank. -*/ -static u8 getInteriorLight(enum LightBank bank, MapNode n, s32 increment, - const NodeDefManager *ndef) -{ - u8 light = n.getLight(bank, ndef); - if (light > 0) - light = rangelim(light + increment, 0, LIGHT_SUN); - return decode_light(light); -} - -/* - Calculate non-smooth lighting at interior of node. - Both light banks. -*/ -u16 getInteriorLight(MapNode n, s32 increment, const NodeDefManager *ndef) -{ - u16 day = getInteriorLight(LIGHTBANK_DAY, n, increment, ndef); - u16 night = getInteriorLight(LIGHTBANK_NIGHT, n, increment, ndef); - return day | (night << 8); -} - -/* - Calculate non-smooth lighting at face of node. - Single light bank. -*/ -static u8 getFaceLight(enum LightBank bank, MapNode n, MapNode n2, - v3s16 face_dir, const NodeDefManager *ndef) -{ - u8 light; - u8 l1 = n.getLight(bank, ndef); - u8 l2 = n2.getLight(bank, ndef); - if(l1 > l2) - light = l1; - else - light = l2; - - // Boost light level for light sources - u8 light_source = MYMAX(ndef->get(n).light_source, - ndef->get(n2).light_source); - if(light_source > light) - light = light_source; - - return decode_light(light); -} - -/* - Calculate non-smooth lighting at face of node. - Both light banks. -*/ -u16 getFaceLight(MapNode n, MapNode n2, const v3s16 &face_dir, - const NodeDefManager *ndef) -{ - u16 day = getFaceLight(LIGHTBANK_DAY, n, n2, face_dir, ndef); - u16 night = getFaceLight(LIGHTBANK_NIGHT, n, n2, face_dir, ndef); - return day | (night << 8); -} - -/* - Calculate smooth lighting at the XYZ- corner of p. - Both light banks -*/ -static u16 getSmoothLightCombined(const v3s16 &p, - const std::array &dirs, MeshMakeData *data) -{ - const NodeDefManager *ndef = data->m_client->ndef(); - - u16 ambient_occlusion = 0; - u16 light_count = 0; - u8 light_source_max = 0; - u16 light_day = 0; - u16 light_night = 0; - bool direct_sunlight = false; - - auto add_node = [&] (u8 i, bool obstructed = false) -> bool { - if (obstructed) { - ambient_occlusion++; - return false; - } - MapNode n = data->m_vmanip.getNodeNoExNoEmerge(p + dirs[i]); - if (n.getContent() == CONTENT_IGNORE) - return true; - const ContentFeatures &f = ndef->get(n); - if (f.light_source > light_source_max) - light_source_max = f.light_source; - // Check f.solidness because fast-style leaves look better this way - if (f.param_type == CPT_LIGHT && f.solidness != 2) { - u8 light_level_day = n.getLightNoChecks(LIGHTBANK_DAY, &f); - u8 light_level_night = n.getLightNoChecks(LIGHTBANK_NIGHT, &f); - if (light_level_day == LIGHT_SUN) - direct_sunlight = true; - light_day += decode_light(light_level_day); - light_night += decode_light(light_level_night); - light_count++; - } else { - ambient_occlusion++; - } - return f.light_propagates; - }; - - std::array obstructed = {{ 1, 1, 1, 1 }}; - add_node(0); - bool opaque1 = !add_node(1); - bool opaque2 = !add_node(2); - bool opaque3 = !add_node(3); - obstructed[0] = opaque1 && opaque2; - obstructed[1] = opaque1 && opaque3; - obstructed[2] = opaque2 && opaque3; - for (u8 k = 0; k < 3; ++k) - if (add_node(k + 4, obstructed[k])) - obstructed[3] = false; - if (add_node(7, obstructed[3])) { // wrap light around nodes - ambient_occlusion -= 3; - for (u8 k = 0; k < 3; ++k) - add_node(k + 4, !obstructed[k]); - } - - if (light_count == 0) { - light_day = light_night = 0; - } else { - light_day /= light_count; - light_night /= light_count; - } - - // boost direct sunlight, if any - if (direct_sunlight) - light_day = 0xFF; - - // Boost brightness around light sources - bool skip_ambient_occlusion_day = false; - if (decode_light(light_source_max) >= light_day) { - light_day = decode_light(light_source_max); - skip_ambient_occlusion_day = true; - } - - bool skip_ambient_occlusion_night = false; - if(decode_light(light_source_max) >= light_night) { - light_night = decode_light(light_source_max); - skip_ambient_occlusion_night = true; - } - - if (ambient_occlusion > 4) { - static thread_local const float ao_gamma = rangelim( - g_settings->getFloat("ambient_occlusion_gamma"), 0.25, 4.0); - - // Table of gamma space multiply factors. - static thread_local const float light_amount[3] = { - powf(0.75, 1.0 / ao_gamma), - powf(0.5, 1.0 / ao_gamma), - powf(0.25, 1.0 / ao_gamma) - }; - - //calculate table index for gamma space multiplier - ambient_occlusion -= 5; - - if (!skip_ambient_occlusion_day) - light_day = rangelim(core::round32( - light_day * light_amount[ambient_occlusion]), 0, 255); - if (!skip_ambient_occlusion_night) - light_night = rangelim(core::round32( - light_night * light_amount[ambient_occlusion]), 0, 255); - } - - return light_day | (light_night << 8); -} - -/* - Calculate smooth lighting at the given corner of p. - Both light banks. - Node at p is solid, and thus the lighting is face-dependent. -*/ -u16 getSmoothLightSolid(const v3s16 &p, const v3s16 &face_dir, const v3s16 &corner, MeshMakeData *data) -{ - return getSmoothLightTransparent(p + face_dir, corner - 2 * face_dir, data); -} - -/* - Calculate smooth lighting at the given corner of p. - Both light banks. - Node at p is not solid, and the lighting is not face-dependent. -*/ -u16 getSmoothLightTransparent(const v3s16 &p, const v3s16 &corner, MeshMakeData *data) -{ - const std::array dirs = {{ - // Always shine light - v3s16(0,0,0), - v3s16(corner.X,0,0), - v3s16(0,corner.Y,0), - v3s16(0,0,corner.Z), - - // Can be obstructed - v3s16(corner.X,corner.Y,0), - v3s16(corner.X,0,corner.Z), - v3s16(0,corner.Y,corner.Z), - v3s16(corner.X,corner.Y,corner.Z) - }}; - return getSmoothLightCombined(p, dirs, data); -} - -void get_sunlight_color(video::SColorf *sunlight, u32 daynight_ratio){ - f32 rg = daynight_ratio / 1000.0f - 0.04f; - f32 b = (0.98f * daynight_ratio) / 1000.0f + 0.078f; - sunlight->r = rg; - sunlight->g = rg; - sunlight->b = b; -} - -void final_color_blend(video::SColor *result, - u16 light, u32 daynight_ratio) -{ - video::SColorf dayLight; - get_sunlight_color(&dayLight, daynight_ratio); - final_color_blend(result, - encode_light(light, 0), dayLight); -} - -void final_color_blend(video::SColor *result, - const video::SColor &data, const video::SColorf &dayLight) -{ - static const video::SColorf artificialColor(1.04f, 1.04f, 1.04f); - - video::SColorf c(data); - f32 n = 1 - c.a; - - f32 r = c.r * (c.a * dayLight.r + n * artificialColor.r) * 2.0f; - f32 g = c.g * (c.a * dayLight.g + n * artificialColor.g) * 2.0f; - f32 b = c.b * (c.a * dayLight.b + n * artificialColor.b) * 2.0f; - - // Emphase blue a bit in darker places - // Each entry of this array represents a range of 8 blue levels - static const u8 emphase_blue_when_dark[32] = { - 1, 4, 6, 6, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - }; - - b += emphase_blue_when_dark[irr::core::clamp((s32) ((r + g + b) / 3 * 255), - 0, 255) / 8] / 255.0f; - - result->setRed(core::clamp((s32) (r * 255.0f), 0, 255)); - result->setGreen(core::clamp((s32) (g * 255.0f), 0, 255)); - result->setBlue(core::clamp((s32) (b * 255.0f), 0, 255)); -} - -/* - Mesh generation helpers -*/ - -/* - vertex_dirs: v3s16[4] -*/ -static void getNodeVertexDirs(const v3s16 &dir, v3s16 *vertex_dirs) -{ - /* - If looked from outside the node towards the face, the corners are: - 0: bottom-right - 1: bottom-left - 2: top-left - 3: top-right - */ - if (dir == v3s16(0, 0, 1)) { - // If looking towards z+, this is the face that is behind - // the center point, facing towards z+. - vertex_dirs[0] = v3s16(-1,-1, 1); - vertex_dirs[1] = v3s16( 1,-1, 1); - vertex_dirs[2] = v3s16( 1, 1, 1); - vertex_dirs[3] = v3s16(-1, 1, 1); - } else if (dir == v3s16(0, 0, -1)) { - // faces towards Z- - vertex_dirs[0] = v3s16( 1,-1,-1); - vertex_dirs[1] = v3s16(-1,-1,-1); - vertex_dirs[2] = v3s16(-1, 1,-1); - vertex_dirs[3] = v3s16( 1, 1,-1); - } else if (dir == v3s16(1, 0, 0)) { - // faces towards X+ - vertex_dirs[0] = v3s16( 1,-1, 1); - vertex_dirs[1] = v3s16( 1,-1,-1); - vertex_dirs[2] = v3s16( 1, 1,-1); - vertex_dirs[3] = v3s16( 1, 1, 1); - } else if (dir == v3s16(-1, 0, 0)) { - // faces towards X- - vertex_dirs[0] = v3s16(-1,-1,-1); - vertex_dirs[1] = v3s16(-1,-1, 1); - vertex_dirs[2] = v3s16(-1, 1, 1); - vertex_dirs[3] = v3s16(-1, 1,-1); - } else if (dir == v3s16(0, 1, 0)) { - // faces towards Y+ (assume Z- as "down" in texture) - vertex_dirs[0] = v3s16( 1, 1,-1); - vertex_dirs[1] = v3s16(-1, 1,-1); - vertex_dirs[2] = v3s16(-1, 1, 1); - vertex_dirs[3] = v3s16( 1, 1, 1); - } else if (dir == v3s16(0, -1, 0)) { - // faces towards Y- (assume Z+ as "down" in texture) - vertex_dirs[0] = v3s16( 1,-1, 1); - vertex_dirs[1] = v3s16(-1,-1, 1); - vertex_dirs[2] = v3s16(-1,-1,-1); - vertex_dirs[3] = v3s16( 1,-1,-1); - } -} - -static void getNodeTextureCoords(v3f base, const v3f &scale, const v3s16 &dir, float *u, float *v) -{ - if (dir.X > 0 || dir.Y > 0 || dir.Z < 0) - base -= scale; - if (dir == v3s16(0,0,1)) { - *u = -base.X - 1; - *v = -base.Y - 1; - } else if (dir == v3s16(0,0,-1)) { - *u = base.X + 1; - *v = -base.Y - 2; - } else if (dir == v3s16(1,0,0)) { - *u = base.Z + 1; - *v = -base.Y - 2; - } else if (dir == v3s16(-1,0,0)) { - *u = -base.Z - 1; - *v = -base.Y - 1; - } else if (dir == v3s16(0,1,0)) { - *u = base.X + 1; - *v = -base.Z - 2; - } else if (dir == v3s16(0,-1,0)) { - *u = base.X; - *v = base.Z; - } -} - -struct FastFace -{ - TileSpec tile; - video::S3DVertex vertices[4]; // Precalculated vertices - /*! - * The face is divided into two triangles. If this is true, - * vertices 0 and 2 are connected, othervise vertices 1 and 3 - * are connected. - */ - bool vertex_0_2_connected; -}; - -static void makeFastFace(const TileSpec &tile, u16 li0, u16 li1, u16 li2, u16 li3, - const v3f &tp, const v3f &p, const v3s16 &dir, const v3f &scale, std::vector &dest) -{ - // Position is at the center of the cube. - v3f pos = p * BS; - - float x0 = 0.0f; - float y0 = 0.0f; - float w = 1.0f; - float h = 1.0f; - - v3f vertex_pos[4]; - v3s16 vertex_dirs[4]; - getNodeVertexDirs(dir, vertex_dirs); - if (tile.world_aligned) - getNodeTextureCoords(tp, scale, dir, &x0, &y0); - - v3s16 t; - u16 t1; - switch (tile.rotation) { - case 0: - break; - case 1: //R90 - t = vertex_dirs[0]; - vertex_dirs[0] = vertex_dirs[3]; - vertex_dirs[3] = vertex_dirs[2]; - vertex_dirs[2] = vertex_dirs[1]; - vertex_dirs[1] = t; - t1 = li0; - li0 = li3; - li3 = li2; - li2 = li1; - li1 = t1; - break; - case 2: //R180 - t = vertex_dirs[0]; - vertex_dirs[0] = vertex_dirs[2]; - vertex_dirs[2] = t; - t = vertex_dirs[1]; - vertex_dirs[1] = vertex_dirs[3]; - vertex_dirs[3] = t; - t1 = li0; - li0 = li2; - li2 = t1; - t1 = li1; - li1 = li3; - li3 = t1; - break; - case 3: //R270 - t = vertex_dirs[0]; - vertex_dirs[0] = vertex_dirs[1]; - vertex_dirs[1] = vertex_dirs[2]; - vertex_dirs[2] = vertex_dirs[3]; - vertex_dirs[3] = t; - t1 = li0; - li0 = li1; - li1 = li2; - li2 = li3; - li3 = t1; - break; - case 4: //FXR90 - t = vertex_dirs[0]; - vertex_dirs[0] = vertex_dirs[3]; - vertex_dirs[3] = vertex_dirs[2]; - vertex_dirs[2] = vertex_dirs[1]; - vertex_dirs[1] = t; - t1 = li0; - li0 = li3; - li3 = li2; - li2 = li1; - li1 = t1; - y0 += h; - h *= -1; - break; - case 5: //FXR270 - t = vertex_dirs[0]; - vertex_dirs[0] = vertex_dirs[1]; - vertex_dirs[1] = vertex_dirs[2]; - vertex_dirs[2] = vertex_dirs[3]; - vertex_dirs[3] = t; - t1 = li0; - li0 = li1; - li1 = li2; - li2 = li3; - li3 = t1; - y0 += h; - h *= -1; - break; - case 6: //FYR90 - t = vertex_dirs[0]; - vertex_dirs[0] = vertex_dirs[3]; - vertex_dirs[3] = vertex_dirs[2]; - vertex_dirs[2] = vertex_dirs[1]; - vertex_dirs[1] = t; - t1 = li0; - li0 = li3; - li3 = li2; - li2 = li1; - li1 = t1; - x0 += w; - w *= -1; - break; - case 7: //FYR270 - t = vertex_dirs[0]; - vertex_dirs[0] = vertex_dirs[1]; - vertex_dirs[1] = vertex_dirs[2]; - vertex_dirs[2] = vertex_dirs[3]; - vertex_dirs[3] = t; - t1 = li0; - li0 = li1; - li1 = li2; - li2 = li3; - li3 = t1; - x0 += w; - w *= -1; - break; - case 8: //FX - y0 += h; - h *= -1; - break; - case 9: //FY - x0 += w; - w *= -1; - break; - default: - break; - } - - for (u16 i = 0; i < 4; i++) { - vertex_pos[i] = v3f( - BS / 2 * vertex_dirs[i].X, - BS / 2 * vertex_dirs[i].Y, - BS / 2 * vertex_dirs[i].Z - ); - } - - for (v3f &vpos : vertex_pos) { - vpos.X *= scale.X; - vpos.Y *= scale.Y; - vpos.Z *= scale.Z; - vpos += pos; - } - - f32 abs_scale = 1.0f; - if (scale.X < 0.999f || scale.X > 1.001f) abs_scale = scale.X; - else if (scale.Y < 0.999f || scale.Y > 1.001f) abs_scale = scale.Y; - else if (scale.Z < 0.999f || scale.Z > 1.001f) abs_scale = scale.Z; - - v3f normal(dir.X, dir.Y, dir.Z); - - u16 li[4] = { li0, li1, li2, li3 }; - u16 day[4]; - u16 night[4]; - - for (u8 i = 0; i < 4; i++) { - day[i] = li[i] >> 8; - night[i] = li[i] & 0xFF; - } - - bool vertex_0_2_connected = abs(day[0] - day[2]) + abs(night[0] - night[2]) - < abs(day[1] - day[3]) + abs(night[1] - night[3]); - - v2f32 f[4] = { - core::vector2d(x0 + w * abs_scale, y0 + h), - core::vector2d(x0, y0 + h), - core::vector2d(x0, y0), - core::vector2d(x0 + w * abs_scale, y0) }; - - // equivalent to dest.push_back(FastFace()) but faster - dest.emplace_back(); - FastFace& face = *dest.rbegin(); - - for (u8 i = 0; i < 4; i++) { - video::SColor c = encode_light(li[i], tile.emissive_light); - if (!tile.emissive_light) - applyFacesShading(c, normal); - - face.vertices[i] = video::S3DVertex(vertex_pos[i], normal, c, f[i]); - } - - /* - Revert triangles for nicer looking gradient if the - brightness of vertices 1 and 3 differ less than - the brightness of vertices 0 and 2. - */ - face.vertex_0_2_connected = vertex_0_2_connected; - face.tile = tile; -} - -/* - Nodes make a face if contents differ and solidness differs. - Return value: - 0: No face - 1: Face uses m1's content - 2: Face uses m2's content - equivalent: Whether the blocks share the same face (eg. water and glass) - - TODO: Add 3: Both faces drawn with backface culling, remove equivalent -*/ -static u8 face_contents(content_t m1, content_t m2, bool *equivalent, - const NodeDefManager *ndef) -{ - *equivalent = false; - - if (m1 == m2 || m1 == CONTENT_IGNORE || m2 == CONTENT_IGNORE) - return 0; - - const ContentFeatures &f1 = ndef->get(m1); - const ContentFeatures &f2 = ndef->get(m2); - - // Contents don't differ for different forms of same liquid - if (f1.sameLiquid(f2)) - return 0; - - u8 c1 = f1.solidness; - u8 c2 = f2.solidness; - - if (c1 == c2) - return 0; - - if (c1 == 0) - c1 = f1.visual_solidness; - else if (c2 == 0) - c2 = f2.visual_solidness; - - if (c1 == c2) { - *equivalent = true; - // If same solidness, liquid takes precense - if (f1.isLiquid()) - return 1; - if (f2.isLiquid()) - return 2; - } - - if (c1 > c2) - return 1; - - return 2; -} - -/* - Gets nth node tile (0 <= n <= 5). -*/ -void getNodeTileN(MapNode mn, const v3s16 &p, u8 tileindex, MeshMakeData *data, TileSpec &tile) -{ - const NodeDefManager *ndef = data->m_client->ndef(); - const ContentFeatures &f = ndef->get(mn); - tile = f.tiles[tileindex]; - bool has_crack = p == data->m_crack_pos_relative; - for (TileLayer &layer : tile.layers) { - if (layer.texture_id == 0) - continue; - if (!layer.has_color) - mn.getColor(f, &(layer.color)); - // Apply temporary crack - if (has_crack) - layer.material_flags |= MATERIAL_FLAG_CRACK; - } -} - -/* - Gets node tile given a face direction. -*/ -void getNodeTile(MapNode mn, const v3s16 &p, const v3s16 &dir, MeshMakeData *data, TileSpec &tile) -{ - const NodeDefManager *ndef = data->m_client->ndef(); - - // Direction must be (1,0,0), (-1,0,0), (0,1,0), (0,-1,0), - // (0,0,1), (0,0,-1) or (0,0,0) - assert(dir.X * dir.X + dir.Y * dir.Y + dir.Z * dir.Z <= 1); - - // Convert direction to single integer for table lookup - // 0 = (0,0,0) - // 1 = (1,0,0) - // 2 = (0,1,0) - // 3 = (0,0,1) - // 4 = invalid, treat as (0,0,0) - // 5 = (0,0,-1) - // 6 = (0,-1,0) - // 7 = (-1,0,0) - u8 dir_i = ((dir.X + 2 * dir.Y + 3 * dir.Z) & 7) * 2; - - // Get rotation for things like chests - u8 facedir = mn.getFaceDir(ndef); - - static const u16 dir_to_tile[24 * 16] = - { - // 0 +X +Y +Z -Z -Y -X -> value=tile,rotation - 0,0, 2,0 , 0,0 , 4,0 , 0,0, 5,0 , 1,0 , 3,0 , // rotate around y+ 0 - 3 - 0,0, 4,0 , 0,3 , 3,0 , 0,0, 2,0 , 1,1 , 5,0 , - 0,0, 3,0 , 0,2 , 5,0 , 0,0, 4,0 , 1,2 , 2,0 , - 0,0, 5,0 , 0,1 , 2,0 , 0,0, 3,0 , 1,3 , 4,0 , - - 0,0, 2,3 , 5,0 , 0,2 , 0,0, 1,0 , 4,2 , 3,1 , // rotate around z+ 4 - 7 - 0,0, 4,3 , 2,0 , 0,1 , 0,0, 1,1 , 3,2 , 5,1 , - 0,0, 3,3 , 4,0 , 0,0 , 0,0, 1,2 , 5,2 , 2,1 , - 0,0, 5,3 , 3,0 , 0,3 , 0,0, 1,3 , 2,2 , 4,1 , - - 0,0, 2,1 , 4,2 , 1,2 , 0,0, 0,0 , 5,0 , 3,3 , // rotate around z- 8 - 11 - 0,0, 4,1 , 3,2 , 1,3 , 0,0, 0,3 , 2,0 , 5,3 , - 0,0, 3,1 , 5,2 , 1,0 , 0,0, 0,2 , 4,0 , 2,3 , - 0,0, 5,1 , 2,2 , 1,1 , 0,0, 0,1 , 3,0 , 4,3 , - - 0,0, 0,3 , 3,3 , 4,1 , 0,0, 5,3 , 2,3 , 1,3 , // rotate around x+ 12 - 15 - 0,0, 0,2 , 5,3 , 3,1 , 0,0, 2,3 , 4,3 , 1,0 , - 0,0, 0,1 , 2,3 , 5,1 , 0,0, 4,3 , 3,3 , 1,1 , - 0,0, 0,0 , 4,3 , 2,1 , 0,0, 3,3 , 5,3 , 1,2 , - - 0,0, 1,1 , 2,1 , 4,3 , 0,0, 5,1 , 3,1 , 0,1 , // rotate around x- 16 - 19 - 0,0, 1,2 , 4,1 , 3,3 , 0,0, 2,1 , 5,1 , 0,0 , - 0,0, 1,3 , 3,1 , 5,3 , 0,0, 4,1 , 2,1 , 0,3 , - 0,0, 1,0 , 5,1 , 2,3 , 0,0, 3,1 , 4,1 , 0,2 , - - 0,0, 3,2 , 1,2 , 4,2 , 0,0, 5,2 , 0,2 , 2,2 , // rotate around y- 20 - 23 - 0,0, 5,2 , 1,3 , 3,2 , 0,0, 2,2 , 0,1 , 4,2 , - 0,0, 2,2 , 1,0 , 5,2 , 0,0, 4,2 , 0,0 , 3,2 , - 0,0, 4,2 , 1,1 , 2,2 , 0,0, 3,2 , 0,3 , 5,2 - - }; - u16 tile_index = facedir * 16 + dir_i; - getNodeTileN(mn, p, dir_to_tile[tile_index], data, tile); - tile.rotation = tile.world_aligned ? 0 : dir_to_tile[tile_index + 1]; -} - -static void getTileInfo( - // Input: - MeshMakeData *data, - const v3s16 &p, - const v3s16 &face_dir, - // Output: - bool &makes_face, - v3s16 &p_corrected, - v3s16 &face_dir_corrected, - u16 *lights, - TileSpec &tile - ) -{ - VoxelManipulator &vmanip = data->m_vmanip; - const NodeDefManager *ndef = data->m_client->ndef(); - v3s16 blockpos_nodes = data->m_blockpos * MAP_BLOCKSIZE; - - const MapNode &n0 = vmanip.getNodeRefUnsafe(blockpos_nodes + p); - - // Don't even try to get n1 if n0 is already CONTENT_IGNORE - if (n0.getContent() == CONTENT_IGNORE) { - makes_face = false; - return; - } - - const MapNode &n1 = vmanip.getNodeRefUnsafeCheckFlags(blockpos_nodes + p + face_dir); - - if (n1.getContent() == CONTENT_IGNORE) { - makes_face = false; - return; - } - - // This is hackish - bool equivalent = false; - u8 mf = face_contents(n0.getContent(), n1.getContent(), - &equivalent, ndef); - - if (mf == 0) { - makes_face = false; - return; - } - - makes_face = true; - - MapNode n = n0; - - if (mf == 1) { - p_corrected = p; - face_dir_corrected = face_dir; - } else { - n = n1; - p_corrected = p + face_dir; - face_dir_corrected = -face_dir; - } - - getNodeTile(n, p_corrected, face_dir_corrected, data, tile); - const ContentFeatures &f = ndef->get(n); - tile.emissive_light = f.light_source; - - // eg. water and glass - if (equivalent) { - for (TileLayer &layer : tile.layers) - layer.material_flags |= MATERIAL_FLAG_BACKFACE_CULLING; - } - - if (!data->m_smooth_lighting) { - lights[0] = lights[1] = lights[2] = lights[3] = - getFaceLight(n0, n1, face_dir, ndef); - } else { - v3s16 vertex_dirs[4]; - getNodeVertexDirs(face_dir_corrected, vertex_dirs); - - v3s16 light_p = blockpos_nodes + p_corrected; - for (u16 i = 0; i < 4; i++) - lights[i] = getSmoothLightSolid(light_p, face_dir_corrected, vertex_dirs[i], data); - } -} - -/* - startpos: - translate_dir: unit vector with only one of x, y or z - face_dir: unit vector with only one of x, y or z -*/ -static void updateFastFaceRow( - MeshMakeData *data, - const v3s16 &&startpos, - v3s16 translate_dir, - const v3f &&translate_dir_f, - const v3s16 &&face_dir, - std::vector &dest) -{ - v3s16 p = startpos; - - u16 continuous_tiles_count = 1; - - bool makes_face = false; - v3s16 p_corrected; - v3s16 face_dir_corrected; - u16 lights[4] = {0, 0, 0, 0}; - TileSpec tile; - getTileInfo(data, p, face_dir, - makes_face, p_corrected, face_dir_corrected, - lights, tile); - - // Unroll this variable which has a significant build cost - TileSpec next_tile; - for (u16 j = 0; j < MAP_BLOCKSIZE; j++) { - // If tiling can be done, this is set to false in the next step - bool next_is_different = true; - - v3s16 p_next; - - bool next_makes_face = false; - v3s16 next_p_corrected; - v3s16 next_face_dir_corrected; - u16 next_lights[4] = {0, 0, 0, 0}; - - // If at last position, there is nothing to compare to and - // the face must be drawn anyway - if (j != MAP_BLOCKSIZE - 1) { - p_next = p + translate_dir; - - getTileInfo(data, p_next, face_dir, - next_makes_face, next_p_corrected, - next_face_dir_corrected, next_lights, - next_tile); - - if (next_makes_face == makes_face - && next_p_corrected == p_corrected + translate_dir - && next_face_dir_corrected == face_dir_corrected - && memcmp(next_lights, lights, ARRLEN(lights) * sizeof(u16)) == 0 - && next_tile.isTileable(tile)) { - next_is_different = false; - continuous_tiles_count++; - } - } - if (next_is_different) { - /* - Create a face if there should be one - */ - if (makes_face) { - // Floating point conversion of the position vector - v3f pf(p_corrected.X, p_corrected.Y, p_corrected.Z); - // Center point of face (kind of) - v3f sp = pf - ((f32)continuous_tiles_count * 0.5f - 0.5f) - * translate_dir_f; - v3f scale(1, 1, 1); - - if (translate_dir.X != 0) - scale.X = continuous_tiles_count; - if (translate_dir.Y != 0) - scale.Y = continuous_tiles_count; - if (translate_dir.Z != 0) - scale.Z = continuous_tiles_count; - - makeFastFace(tile, lights[0], lights[1], lights[2], lights[3], - pf, sp, face_dir_corrected, scale, dest); - - g_profiler->avg("Meshgen: faces drawn by tiling", 0); - for (int i = 1; i < continuous_tiles_count; i++) - g_profiler->avg("Meshgen: faces drawn by tiling", 1); - } - - continuous_tiles_count = 1; - } - - makes_face = next_makes_face; - p_corrected = next_p_corrected; - face_dir_corrected = next_face_dir_corrected; - std::memcpy(lights, next_lights, ARRLEN(lights) * sizeof(u16)); - if (next_is_different) - tile = next_tile; - p = p_next; - } -} - -static void updateAllFastFaceRows(MeshMakeData *data, - std::vector &dest) -{ - /* - Go through every y,z and get top(y+) faces in rows of x+ - */ - for (s16 y = 0; y < MAP_BLOCKSIZE; y++) - for (s16 z = 0; z < MAP_BLOCKSIZE; z++) - updateFastFaceRow(data, - v3s16(0, y, z), - v3s16(1, 0, 0), //dir - v3f (1, 0, 0), - v3s16(0, 1, 0), //face dir - dest); - - /* - Go through every x,y and get right(x+) faces in rows of z+ - */ - for (s16 x = 0; x < MAP_BLOCKSIZE; x++) - for (s16 y = 0; y < MAP_BLOCKSIZE; y++) - updateFastFaceRow(data, - v3s16(x, y, 0), - v3s16(0, 0, 1), //dir - v3f (0, 0, 1), - v3s16(1, 0, 0), //face dir - dest); - - /* - Go through every y,z and get back(z+) faces in rows of x+ - */ - for (s16 z = 0; z < MAP_BLOCKSIZE; z++) - for (s16 y = 0; y < MAP_BLOCKSIZE; y++) - updateFastFaceRow(data, - v3s16(0, y, z), - v3s16(1, 0, 0), //dir - v3f (1, 0, 0), - v3s16(0, 0, 1), //face dir - dest); -} - -static void applyTileColor(PreMeshBuffer &pmb) -{ - video::SColor tc = pmb.layer.color; - if (tc == video::SColor(0xFFFFFFFF)) - return; - for (video::S3DVertex &vertex : pmb.vertices) { - video::SColor *c = &vertex.Color; - c->set(c->getAlpha(), - c->getRed() * tc.getRed() / 255, - c->getGreen() * tc.getGreen() / 255, - c->getBlue() * tc.getBlue() / 255); - } -} - -/* - MapBlockMesh -*/ - -MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): - m_minimap_mapblock(NULL), - m_tsrc(data->m_client->getTextureSource()), - m_shdrsrc(data->m_client->getShaderSource()), - m_animation_force_timer(0), // force initial animation - m_last_crack(-1), - m_last_daynight_ratio((u32) -1) -{ - for (auto &m : m_mesh) - m = new scene::SMesh(); - m_enable_shaders = data->m_use_shaders; - m_use_tangent_vertices = data->m_use_tangent_vertices; - m_enable_vbo = g_settings->getBool("enable_vbo"); - - if (g_settings->getBool("enable_minimap")) { - m_minimap_mapblock = new MinimapMapblock; - m_minimap_mapblock->getMinimapNodes( - &data->m_vmanip, data->m_blockpos * MAP_BLOCKSIZE); - } - - // 4-21ms for MAP_BLOCKSIZE=16 (NOTE: probably outdated) - // 24-155ms for MAP_BLOCKSIZE=32 (NOTE: probably outdated) - //TimeTaker timer1("MapBlockMesh()"); - - std::vector fastfaces_new; - fastfaces_new.reserve(512); - - /* - We are including the faces of the trailing edges of the block. - This means that when something changes, the caller must - also update the meshes of the blocks at the leading edges. - - NOTE: This is the slowest part of this method. - */ - { - // 4-23ms for MAP_BLOCKSIZE=16 (NOTE: probably outdated) - //TimeTaker timer2("updateAllFastFaceRows()"); - updateAllFastFaceRows(data, fastfaces_new); - } - // End of slow part - - /* - Convert FastFaces to MeshCollector - */ - - MeshCollector collector; - - { - // avg 0ms (100ms spikes when loading textures the first time) - // (NOTE: probably outdated) - //TimeTaker timer2("MeshCollector building"); - - for (const FastFace &f : fastfaces_new) { - static const u16 indices[] = {0, 1, 2, 2, 3, 0}; - static const u16 indices_alternate[] = {0, 1, 3, 2, 3, 1}; - const u16 *indices_p = - f.vertex_0_2_connected ? indices : indices_alternate; - collector.append(f.tile, f.vertices, 4, indices_p, 6); - } - } - - /* - Add special graphics: - - torches - - flowing water - - fences - - whatever - */ - - { - MapblockMeshGenerator generator(data, &collector); - generator.generate(); - } - - /* - Convert MeshCollector to SMesh - */ - - for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) { - for(u32 i = 0; i < collector.prebuffers[layer].size(); i++) - { - PreMeshBuffer &p = collector.prebuffers[layer][i]; - - applyTileColor(p); - - // Generate animation data - // - Cracks - if (p.layer.material_flags & MATERIAL_FLAG_CRACK) { - // Find the texture name plus ^[crack:N: - std::ostringstream os(std::ios::binary); - os << m_tsrc->getTextureName(p.layer.texture_id) << "^[crack"; - if (p.layer.material_flags & MATERIAL_FLAG_CRACK_OVERLAY) - os << "o"; // use ^[cracko - u8 tiles = p.layer.scale; - if (tiles > 1) - os << ":" << (u32)tiles; - os << ":" << (u32)p.layer.animation_frame_count << ":"; - m_crack_materials.insert(std::make_pair( - std::pair(layer, i), os.str())); - // Replace tile texture with the cracked one - p.layer.texture = m_tsrc->getTextureForMesh( - os.str() + "0", - &p.layer.texture_id); - } - // - Texture animation - if (p.layer.material_flags & MATERIAL_FLAG_ANIMATION) { - // Add to MapBlockMesh in order to animate these tiles - m_animation_tiles[std::pair(layer, i)] = p.layer; - m_animation_frames[std::pair(layer, i)] = 0; - if (g_settings->getBool( - "desynchronize_mapblock_texture_animation")) { - // Get starting position from noise - m_animation_frame_offsets[std::pair(layer, i)] = - 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(layer, i)] = 0; - } - // Replace tile texture with the first animation frame - p.layer.texture = (*p.layer.frames)[0].texture; - } - - if (!m_enable_shaders) { - // Extract colors for day-night animation - // Dummy sunlight to handle non-sunlit areas - video::SColorf sunlight; - get_sunlight_color(&sunlight, 0); - 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(layer, i)][j] = copy; - - // The sunlight ratio has been stored, - // delete alpha (for the final rendering). - vc->setAlpha(255); - } - } - - // Create material - video::SMaterial material; - material.setFlag(video::EMF_LIGHTING, false); - material.setFlag(video::EMF_BACK_FACE_CULLING, true); - material.setFlag(video::EMF_BILINEAR_FILTER, false); - material.setFlag(video::EMF_FOG_ENABLE, true); - material.setTexture(0, p.layer.texture); - - if (m_enable_shaders) { - material.MaterialType = m_shdrsrc->getShaderInfo( - p.layer.shader_id).material; - p.layer.applyMaterialOptionsWithShaders(material); - if (p.layer.normal_texture) - material.setTexture(1, p.layer.normal_texture); - material.setTexture(2, p.layer.flags_texture); - } else { - p.layer.applyMaterialOptions(material); - } - - scene::SMesh *mesh = (scene::SMesh *)m_mesh[layer]; - - // Create meshbuffer, add to mesh - if (m_use_tangent_vertices) { - scene::SMeshBufferTangents *buf = - new scene::SMeshBufferTangents(); - buf->Material = material; - buf->Vertices.reallocate(p.vertices.size()); - buf->Indices.reallocate(p.indices.size()); - for (const video::S3DVertex &v: p.vertices) - buf->Vertices.push_back(video::S3DVertexTangents(v.Pos, v.Color, v.TCoords)); - for (u16 i: p.indices) - buf->Indices.push_back(i); - buf->recalculateBoundingBox(); - mesh->addMeshBuffer(buf); - buf->drop(); - } else { - scene::SMeshBuffer *buf = new scene::SMeshBuffer(); - buf->Material = material; - buf->append(&p.vertices[0], p.vertices.size(), - &p.indices[0], p.indices.size()); - mesh->addMeshBuffer(buf); - buf->drop(); - } - } - - /* - Do some stuff to the mesh - */ - m_camera_offset = camera_offset; - translateMesh(m_mesh[layer], - intToFloat(data->m_blockpos * MAP_BLOCKSIZE - camera_offset, BS)); - - if (m_use_tangent_vertices) { - scene::IMeshManipulator* meshmanip = - RenderingEngine::get_scene_manager()->getMeshManipulator(); - meshmanip->recalculateTangents(m_mesh[layer], true, false, false); - } - - if (m_mesh[layer]) { -#if 0 - // Usually 1-700 faces and 1-7 materials - std::cout << "Updated MapBlock has " << fastfaces_new.size() - << " faces and uses " << m_mesh[layer]->getMeshBufferCount() - << " materials (meshbuffers)" << std::endl; -#endif - - // Use VBO for mesh (this just would set this for ever buffer) - if (m_enable_vbo) - m_mesh[layer]->setHardwareMappingHint(scene::EHM_STATIC); - } - } - - //std::cout<<"added "<getMeshBufferCount(); i++) { - scene::IMeshBuffer *buf = m->getMeshBuffer(i); - RenderingEngine::get_video_driver()->removeHardwareBuffer(buf); - } - m->drop(); - m = NULL; - } - delete m_minimap_mapblock; -} - -bool MapBlockMesh::animate(bool faraway, float time, int crack, - u32 daynight_ratio) -{ - if (!m_has_animation) { - m_animation_force_timer = 100000; - return false; - } - - m_animation_force_timer = myrand_range(5, 100); - - // Cracks - if (crack != m_last_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; - u32 new_texture_id = 0; - video::ITexture *new_texture = - m_tsrc->getTextureForMesh(os.str(), &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; - tile.texture = new_texture; - tile.texture_id = new_texture_id; - // force animation update - m_animation_frames[crack_material.first] = -1; - } - } - - m_last_crack = crack; - } - - // Texture animation - for (auto &animation_tile : m_animation_tiles) { - const TileLayer &tile = animation_tile.second; - // 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; - // If frame doesn't change, skip - if (frame == m_animation_frames[animation_tile.first]) - continue; - - m_animation_frames[animation_tile.first] = frame; - - scene::IMeshBuffer *buf = m_mesh[animation_tile.first.first]-> - getMeshBuffer(animation_tile.first.second); - - const FrameSpec &animation_frame = (*tile.frames)[frame]; - buf->getMaterial().setTexture(0, animation_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); - } - } - - // Day-night transition - if (!m_enable_shaders && (daynight_ratio != m_last_daynight_ratio)) { - // Force reload mesh to VBO - if (m_enable_vbo) - for (scene::IMesh *m : m_mesh) - m->setDirty(); - video::SColorf day_color; - get_sunlight_color(&day_color, daynight_ratio); - - for (auto &daynight_diff : m_daynight_diffs) { - scene::IMeshBuffer *buf = m_mesh[daynight_diff.first.first]-> - getMeshBuffer(daynight_diff.first.second); - video::S3DVertex *vertices = (video::S3DVertex *)buf->getVertices(); - for (const auto &j : daynight_diff.second) - final_color_blend(&(vertices[j.first].Color), j.second, - day_color); - } - m_last_daynight_ratio = daynight_ratio; - } - - return true; -} - -void MapBlockMesh::updateCameraOffset(v3s16 camera_offset) -{ - if (camera_offset != m_camera_offset) { - for (scene::IMesh *layer : m_mesh) { - translateMesh(layer, - intToFloat(m_camera_offset - camera_offset, BS)); - if (m_enable_vbo) - layer->setDirty(); - } - m_camera_offset = camera_offset; - } -} - -video::SColor encode_light(u16 light, u8 emissive_light) -{ - // Get components - u32 day = (light & 0xff); - u32 night = (light >> 8); - // Add emissive light - night += emissive_light * 2.5f; - if (night > 255) - night = 255; - // Since we don't know if the day light is sunlight or - // artificial light, assume it is artificial when the night - // light bank is also lit. - if (day < night) - day = 0; - else - day = day - night; - u32 sum = day + night; - // Ratio of sunlight: - u32 r; - if (sum > 0) - r = day * 255 / sum; - else - r = 0; - // Average light: - float b = (day + night) / 2; - return video::SColor(r, b, b, b); -} diff --git a/src/mapblock_mesh.h b/src/mapblock_mesh.h deleted file mode 100644 index 6af23a656..000000000 --- a/src/mapblock_mesh.h +++ /dev/null @@ -1,228 +0,0 @@ -/* -Minetest -Copyright (C) 2010-2013 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#pragma once - -#include "irrlichttypes_extrabloated.h" -#include "client/tile.h" -#include "voxel.h" -#include -#include - -class Client; -class IShaderSource; - -/* - Mesh making stuff -*/ - - -class MapBlock; -struct MinimapMapblock; - -struct MeshMakeData -{ - VoxelManipulator m_vmanip; - v3s16 m_blockpos = v3s16(-1337,-1337,-1337); - v3s16 m_crack_pos_relative = v3s16(-1337,-1337,-1337); - bool m_smooth_lighting = false; - - Client *m_client; - bool m_use_shaders; - bool m_use_tangent_vertices; - - MeshMakeData(Client *client, bool use_shaders, - bool use_tangent_vertices = false); - - /* - Copy block data manually (to allow optimizations by the caller) - */ - void fillBlockDataBegin(const v3s16 &blockpos); - void fillBlockData(const v3s16 &block_offset, MapNode *data); - - /* - Copy central data directly from block, and other data from - parent of block. - */ - void fill(MapBlock *block); - - /* - Set up with only a single node at (1,1,1) - */ - void fillSingleNode(MapNode *node); - - /* - Set the (node) position of a crack - */ - void setCrack(int crack_level, v3s16 crack_pos); - - /* - Enable or disable smooth lighting - */ - void setSmoothLighting(bool smooth_lighting); -}; - -/* - Holds a mesh for a mapblock. - - Besides the SMesh*, this contains information used for animating - the vertex positions, colors and texture coordinates of the mesh. - For example: - - cracks [implemented] - - day/night transitions [implemented] - - animated flowing liquids [not implemented] - - animating vertex positions for e.g. axles [not implemented] -*/ -class MapBlockMesh -{ -public: - // Builds the mesh given - MapBlockMesh(MeshMakeData *data, v3s16 camera_offset); - ~MapBlockMesh(); - - // Main animation function, parameters: - // faraway: whether the block is far away from the camera (~50 nodes) - // time: the global animation time, 0 .. 60 (repeats every minute) - // daynight_ratio: 0 .. 1000 - // crack: -1 .. CRACK_ANIMATION_LENGTH-1 (-1 for off) - // Returns true if anything has been changed. - bool animate(bool faraway, float time, int crack, u32 daynight_ratio); - - scene::IMesh *getMesh() - { - return m_mesh[0]; - } - - scene::IMesh *getMesh(u8 layer) - { - return m_mesh[layer]; - } - - MinimapMapblock *moveMinimapMapblock() - { - MinimapMapblock *p = m_minimap_mapblock; - m_minimap_mapblock = NULL; - return p; - } - - bool isAnimationForced() const - { - return m_animation_force_timer == 0; - } - - void decreaseAnimationForceTimer() - { - if(m_animation_force_timer > 0) - m_animation_force_timer--; - } - - void updateCameraOffset(v3s16 camera_offset); - -private: - scene::IMesh *m_mesh[MAX_TILE_LAYERS]; - MinimapMapblock *m_minimap_mapblock; - ITextureSource *m_tsrc; - IShaderSource *m_shdrsrc; - - bool m_enable_shaders; - bool m_use_tangent_vertices; - bool m_enable_vbo; - - // Must animate() be called before rendering? - bool m_has_animation; - int m_animation_force_timer; - - // Animation info: cracks - // Last crack value passed to animate() - int m_last_crack; - // Maps mesh and mesh buffer (i.e. material) indices to base texture names - std::map, std::string> m_crack_materials; - - // Animation info: texture animationi - // Maps mesh and mesh buffer indices to TileSpecs - // Keys are pairs of (mesh index, buffer index in the mesh) - std::map, TileLayer> m_animation_tiles; - std::map, int> m_animation_frames; // last animation frame - std::map, int> m_animation_frame_offsets; - - // Animation info: day/night transitions - // Last daynight_ratio value passed to animate() - u32 m_last_daynight_ratio; - // For each mesh and mesh buffer, stores pre-baked colors - // of sunlit vertices - // Keys are pairs of (mesh index, buffer index in the mesh) - std::map, std::map > m_daynight_diffs; - - // Camera offset info -> do we have to translate the mesh? - v3s16 m_camera_offset; -}; - -/*! - * Encodes light of a node. - * The result is not the final color, but a - * half-baked vertex color. - * You have to multiply the resulting color - * with the node's color. - * - * \param light the first 8 bits are day light, - * the last 8 bits are night light - * \param emissive_light amount of light the surface emits, - * from 0 to LIGHT_SUN. - */ -video::SColor encode_light(u16 light, u8 emissive_light); - -// Compute light at node -u16 getInteriorLight(MapNode n, s32 increment, const NodeDefManager *ndef); -u16 getFaceLight(MapNode n, MapNode n2, const v3s16 &face_dir, - const NodeDefManager *ndef); -u16 getSmoothLightSolid(const v3s16 &p, const v3s16 &face_dir, const v3s16 &corner, MeshMakeData *data); -u16 getSmoothLightTransparent(const v3s16 &p, const v3s16 &corner, MeshMakeData *data); - -/*! - * Returns the sunlight's color from the current - * day-night ratio. - */ -void get_sunlight_color(video::SColorf *sunlight, u32 daynight_ratio); - -/*! - * Gives the final SColor shown on screen. - * - * \param result output color - * \param light first 8 bits are day light, second 8 bits are - * night light - */ -void final_color_blend(video::SColor *result, - u16 light, u32 daynight_ratio); - -/*! - * Gives the final SColor shown on screen. - * - * \param result output color - * \param data the half-baked vertex color - * \param dayLight color of the sunlight - */ -void final_color_blend(video::SColor *result, - const video::SColor &data, const video::SColorf &dayLight); - -// Retrieves the TileSpec of a face of a node -// Adds MATERIAL_FLAG_CRACK if the node is cracked -// TileSpec should be passed as reference due to the underlying TileFrame and its vector -// TileFrame vector copy cost very much to client -void getNodeTileN(MapNode mn, const v3s16 &p, u8 tileindex, MeshMakeData *data, TileSpec &tile); -void getNodeTile(MapNode mn, const v3s16 &p, const v3s16 &dir, MeshMakeData *data, TileSpec &tile); diff --git a/src/mesh.cpp b/src/mesh.cpp deleted file mode 100644 index 7fc7531f2..000000000 --- a/src/mesh.cpp +++ /dev/null @@ -1,1135 +0,0 @@ -/* -Minetest -Copyright (C) 2010-2013 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include "mesh.h" -#include "debug.h" -#include "log.h" -#include "irrMap.h" -#include -#include -#include -#include - -// In Irrlicht 1.8 the signature of ITexture::lock was changed from -// (bool, u32) to (E_TEXTURE_LOCK_MODE, u32). -#if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 7 -#define MY_ETLM_READ_ONLY true -#else -#define MY_ETLM_READ_ONLY video::ETLM_READ_ONLY -#endif - -inline static void applyShadeFactor(video::SColor& color, float factor) -{ - color.setRed(core::clamp(core::round32(color.getRed()*factor), 0, 255)); - color.setGreen(core::clamp(core::round32(color.getGreen()*factor), 0, 255)); - color.setBlue(core::clamp(core::round32(color.getBlue()*factor), 0, 255)); -} - -void applyFacesShading(video::SColor &color, const v3f &normal) -{ - /* - Some drawtypes have normals set to (0, 0, 0), this must result in - maximum brightness: shade factor 1.0. - Shade factors for aligned cube faces are: - +Y 1.000000 sqrt(1.0) - -Y 0.447213 sqrt(0.2) - +-X 0.670820 sqrt(0.45) - +-Z 0.836660 sqrt(0.7) - */ - float x2 = normal.X * normal.X; - float y2 = normal.Y * normal.Y; - float z2 = normal.Z * normal.Z; - if (normal.Y < 0) - applyShadeFactor(color, 0.670820f * x2 + 0.447213f * y2 + 0.836660f * z2); - else if ((x2 > 1e-3) || (z2 > 1e-3)) - applyShadeFactor(color, 0.670820f * x2 + 1.000000f * y2 + 0.836660f * z2); -} - -scene::IAnimatedMesh* createCubeMesh(v3f scale) -{ - video::SColor c(255,255,255,255); - video::S3DVertex vertices[24] = - { - // Up - video::S3DVertex(-0.5,+0.5,-0.5, 0,1,0, c, 0,1), - video::S3DVertex(-0.5,+0.5,+0.5, 0,1,0, c, 0,0), - video::S3DVertex(+0.5,+0.5,+0.5, 0,1,0, c, 1,0), - video::S3DVertex(+0.5,+0.5,-0.5, 0,1,0, c, 1,1), - // Down - video::S3DVertex(-0.5,-0.5,-0.5, 0,-1,0, c, 0,0), - video::S3DVertex(+0.5,-0.5,-0.5, 0,-1,0, c, 1,0), - video::S3DVertex(+0.5,-0.5,+0.5, 0,-1,0, c, 1,1), - video::S3DVertex(-0.5,-0.5,+0.5, 0,-1,0, c, 0,1), - // Right - video::S3DVertex(+0.5,-0.5,-0.5, 1,0,0, c, 0,1), - video::S3DVertex(+0.5,+0.5,-0.5, 1,0,0, c, 0,0), - video::S3DVertex(+0.5,+0.5,+0.5, 1,0,0, c, 1,0), - video::S3DVertex(+0.5,-0.5,+0.5, 1,0,0, c, 1,1), - // Left - video::S3DVertex(-0.5,-0.5,-0.5, -1,0,0, c, 1,1), - video::S3DVertex(-0.5,-0.5,+0.5, -1,0,0, c, 0,1), - video::S3DVertex(-0.5,+0.5,+0.5, -1,0,0, c, 0,0), - video::S3DVertex(-0.5,+0.5,-0.5, -1,0,0, c, 1,0), - // Back - video::S3DVertex(-0.5,-0.5,+0.5, 0,0,1, c, 1,1), - video::S3DVertex(+0.5,-0.5,+0.5, 0,0,1, c, 0,1), - video::S3DVertex(+0.5,+0.5,+0.5, 0,0,1, c, 0,0), - video::S3DVertex(-0.5,+0.5,+0.5, 0,0,1, c, 1,0), - // Front - video::S3DVertex(-0.5,-0.5,-0.5, 0,0,-1, c, 0,1), - video::S3DVertex(-0.5,+0.5,-0.5, 0,0,-1, c, 0,0), - video::S3DVertex(+0.5,+0.5,-0.5, 0,0,-1, c, 1,0), - video::S3DVertex(+0.5,-0.5,-0.5, 0,0,-1, c, 1,1), - }; - - u16 indices[6] = {0,1,2,2,3,0}; - - scene::SMesh *mesh = new scene::SMesh(); - for (u32 i=0; i<6; ++i) - { - scene::IMeshBuffer *buf = new scene::SMeshBuffer(); - buf->append(vertices + 4 * i, 4, indices, 6); - // Set default material - buf->getMaterial().setFlag(video::EMF_LIGHTING, false); - buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false); - buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; - // Add mesh buffer to mesh - mesh->addMeshBuffer(buf); - buf->drop(); - } - - scene::SAnimatedMesh *anim_mesh = new scene::SAnimatedMesh(mesh); - mesh->drop(); - scaleMesh(anim_mesh, scale); // also recalculates bounding box - return anim_mesh; -} - -void scaleMesh(scene::IMesh *mesh, v3f scale) -{ - if (mesh == NULL) - return; - - aabb3f bbox; - bbox.reset(0, 0, 0); - - u32 mc = mesh->getMeshBufferCount(); - for (u32 j = 0; j < mc; j++) { - scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); - const u32 stride = getVertexPitchFromType(buf->getVertexType()); - u32 vertex_count = buf->getVertexCount(); - u8 *vertices = (u8 *)buf->getVertices(); - for (u32 i = 0; i < vertex_count; i++) - ((video::S3DVertex *)(vertices + i * stride))->Pos *= scale; - - buf->recalculateBoundingBox(); - - // calculate total bounding box - if (j == 0) - bbox = buf->getBoundingBox(); - else - bbox.addInternalBox(buf->getBoundingBox()); - } - mesh->setBoundingBox(bbox); -} - -void translateMesh(scene::IMesh *mesh, v3f vec) -{ - if (mesh == NULL) - return; - - aabb3f bbox; - bbox.reset(0, 0, 0); - - u32 mc = mesh->getMeshBufferCount(); - for (u32 j = 0; j < mc; j++) { - scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); - const u32 stride = getVertexPitchFromType(buf->getVertexType()); - u32 vertex_count = buf->getVertexCount(); - u8 *vertices = (u8 *)buf->getVertices(); - for (u32 i = 0; i < vertex_count; i++) - ((video::S3DVertex *)(vertices + i * stride))->Pos += vec; - - buf->recalculateBoundingBox(); - - // calculate total bounding box - if (j == 0) - bbox = buf->getBoundingBox(); - else - bbox.addInternalBox(buf->getBoundingBox()); - } - mesh->setBoundingBox(bbox); -} - -void setMeshBufferColor(scene::IMeshBuffer *buf, const video::SColor &color) -{ - const u32 stride = getVertexPitchFromType(buf->getVertexType()); - u32 vertex_count = buf->getVertexCount(); - u8 *vertices = (u8 *) buf->getVertices(); - for (u32 i = 0; i < vertex_count; i++) - ((video::S3DVertex *) (vertices + i * stride))->Color = color; -} - -void setAnimatedMeshColor(scene::IAnimatedMeshSceneNode *node, const video::SColor &color) -{ - for (u32 i = 0; i < node->getMaterialCount(); ++i) { - node->getMaterial(i).EmissiveColor = color; - } -} - -void setMeshColor(scene::IMesh *mesh, const video::SColor &color) -{ - if (mesh == NULL) - return; - - u32 mc = mesh->getMeshBufferCount(); - for (u32 j = 0; j < mc; j++) - setMeshBufferColor(mesh->getMeshBuffer(j), color); -} - -void colorizeMeshBuffer(scene::IMeshBuffer *buf, const video::SColor *buffercolor) -{ - const u32 stride = getVertexPitchFromType(buf->getVertexType()); - u32 vertex_count = buf->getVertexCount(); - u8 *vertices = (u8 *) buf->getVertices(); - for (u32 i = 0; i < vertex_count; i++) { - video::S3DVertex *vertex = (video::S3DVertex *) (vertices + i * stride); - video::SColor *vc = &(vertex->Color); - // Reset color - *vc = *buffercolor; - // Apply shading - applyFacesShading(*vc, vertex->Normal); - } -} - -void setMeshColorByNormalXYZ(scene::IMesh *mesh, - const video::SColor &colorX, - const video::SColor &colorY, - const video::SColor &colorZ) -{ - if (mesh == NULL) - return; - - u16 mc = mesh->getMeshBufferCount(); - for (u16 j = 0; j < mc; j++) { - scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); - const u32 stride = getVertexPitchFromType(buf->getVertexType()); - u32 vertex_count = buf->getVertexCount(); - u8 *vertices = (u8 *)buf->getVertices(); - for (u32 i = 0; i < vertex_count; i++) { - video::S3DVertex *vertex = (video::S3DVertex *)(vertices + i * stride); - f32 x = fabs(vertex->Normal.X); - f32 y = fabs(vertex->Normal.Y); - f32 z = fabs(vertex->Normal.Z); - if (x >= y && x >= z) - vertex->Color = colorX; - else if (y >= z) - vertex->Color = colorY; - else - vertex->Color = colorZ; - } - } -} - -void setMeshColorByNormal(scene::IMesh *mesh, const v3f &normal, - const video::SColor &color) -{ - if (!mesh) - return; - - u16 mc = mesh->getMeshBufferCount(); - for (u16 j = 0; j < mc; j++) { - scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); - const u32 stride = getVertexPitchFromType(buf->getVertexType()); - u32 vertex_count = buf->getVertexCount(); - u8 *vertices = (u8 *)buf->getVertices(); - for (u32 i = 0; i < vertex_count; i++) { - video::S3DVertex *vertex = (video::S3DVertex *)(vertices + i * stride); - if (normal == vertex->Normal) { - vertex->Color = color; - } - } - } -} - -void rotateMeshXYby(scene::IMesh *mesh, f64 degrees) -{ - u16 mc = mesh->getMeshBufferCount(); - for (u16 j = 0; j < mc; j++) { - scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); - const u32 stride = getVertexPitchFromType(buf->getVertexType()); - u32 vertex_count = buf->getVertexCount(); - u8 *vertices = (u8 *)buf->getVertices(); - for (u32 i = 0; i < vertex_count; i++) - ((video::S3DVertex *)(vertices + i * stride))->Pos.rotateXYBy(degrees); - } -} - -void rotateMeshXZby(scene::IMesh *mesh, f64 degrees) -{ - u16 mc = mesh->getMeshBufferCount(); - for (u16 j = 0; j < mc; j++) { - scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); - const u32 stride = getVertexPitchFromType(buf->getVertexType()); - u32 vertex_count = buf->getVertexCount(); - u8 *vertices = (u8 *)buf->getVertices(); - for (u32 i = 0; i < vertex_count; i++) - ((video::S3DVertex *)(vertices + i * stride))->Pos.rotateXZBy(degrees); - } -} - -void rotateMeshYZby(scene::IMesh *mesh, f64 degrees) -{ - u16 mc = mesh->getMeshBufferCount(); - for (u16 j = 0; j < mc; j++) { - scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); - const u32 stride = getVertexPitchFromType(buf->getVertexType()); - u32 vertex_count = buf->getVertexCount(); - u8 *vertices = (u8 *)buf->getVertices(); - for (u32 i = 0; i < vertex_count; i++) - ((video::S3DVertex *)(vertices + i * stride))->Pos.rotateYZBy(degrees); - } -} - -void rotateMeshBy6dFacedir(scene::IMesh *mesh, int facedir) -{ - int axisdir = facedir >> 2; - facedir &= 0x03; - - u16 mc = mesh->getMeshBufferCount(); - for (u16 j = 0; j < mc; j++) { - scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); - const u32 stride = getVertexPitchFromType(buf->getVertexType()); - u32 vertex_count = buf->getVertexCount(); - u8 *vertices = (u8 *)buf->getVertices(); - for (u32 i = 0; i < vertex_count; i++) { - video::S3DVertex *vertex = (video::S3DVertex *)(vertices + i * stride); - switch (axisdir) { - case 0: - if (facedir == 1) - vertex->Pos.rotateXZBy(-90); - else if (facedir == 2) - vertex->Pos.rotateXZBy(180); - else if (facedir == 3) - vertex->Pos.rotateXZBy(90); - break; - case 1: // z+ - vertex->Pos.rotateYZBy(90); - if (facedir == 1) - vertex->Pos.rotateXYBy(90); - else if (facedir == 2) - vertex->Pos.rotateXYBy(180); - else if (facedir == 3) - vertex->Pos.rotateXYBy(-90); - break; - case 2: //z- - vertex->Pos.rotateYZBy(-90); - if (facedir == 1) - vertex->Pos.rotateXYBy(-90); - else if (facedir == 2) - vertex->Pos.rotateXYBy(180); - else if (facedir == 3) - vertex->Pos.rotateXYBy(90); - break; - case 3: //x+ - vertex->Pos.rotateXYBy(-90); - if (facedir == 1) - vertex->Pos.rotateYZBy(90); - else if (facedir == 2) - vertex->Pos.rotateYZBy(180); - else if (facedir == 3) - vertex->Pos.rotateYZBy(-90); - break; - case 4: //x- - vertex->Pos.rotateXYBy(90); - if (facedir == 1) - vertex->Pos.rotateYZBy(-90); - else if (facedir == 2) - vertex->Pos.rotateYZBy(180); - else if (facedir == 3) - vertex->Pos.rotateYZBy(90); - break; - case 5: - vertex->Pos.rotateXYBy(-180); - if (facedir == 1) - vertex->Pos.rotateXZBy(90); - else if (facedir == 2) - vertex->Pos.rotateXZBy(180); - else if (facedir == 3) - vertex->Pos.rotateXZBy(-90); - break; - default: - break; - } - } - } -} - -void recalculateBoundingBox(scene::IMesh *src_mesh) -{ - aabb3f bbox; - bbox.reset(0,0,0); - for (u16 j = 0; j < src_mesh->getMeshBufferCount(); j++) { - scene::IMeshBuffer *buf = src_mesh->getMeshBuffer(j); - buf->recalculateBoundingBox(); - if (j == 0) - bbox = buf->getBoundingBox(); - else - bbox.addInternalBox(buf->getBoundingBox()); - } - src_mesh->setBoundingBox(bbox); -} - -scene::IMeshBuffer* cloneMeshBuffer(scene::IMeshBuffer *mesh_buffer) -{ - switch (mesh_buffer->getVertexType()) { - case video::EVT_STANDARD: { - video::S3DVertex *v = (video::S3DVertex *) mesh_buffer->getVertices(); - u16 *indices = mesh_buffer->getIndices(); - scene::SMeshBuffer *cloned_buffer = new scene::SMeshBuffer(); - cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices, - mesh_buffer->getIndexCount()); - return cloned_buffer; - } - case video::EVT_2TCOORDS: { - video::S3DVertex2TCoords *v = - (video::S3DVertex2TCoords *) mesh_buffer->getVertices(); - u16 *indices = mesh_buffer->getIndices(); - scene::SMeshBufferTangents *cloned_buffer = - new scene::SMeshBufferTangents(); - cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices, - mesh_buffer->getIndexCount()); - return cloned_buffer; - } - case video::EVT_TANGENTS: { - video::S3DVertexTangents *v = - (video::S3DVertexTangents *) mesh_buffer->getVertices(); - u16 *indices = mesh_buffer->getIndices(); - scene::SMeshBufferTangents *cloned_buffer = - new scene::SMeshBufferTangents(); - cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices, - mesh_buffer->getIndexCount()); - return cloned_buffer; - } - } - // This should not happen. - sanity_check(false); - return NULL; -} - -scene::SMesh* cloneMesh(scene::IMesh *src_mesh) -{ - scene::SMesh* dst_mesh = new scene::SMesh(); - for (u16 j = 0; j < src_mesh->getMeshBufferCount(); j++) { - scene::IMeshBuffer *temp_buf = cloneMeshBuffer( - src_mesh->getMeshBuffer(j)); - dst_mesh->addMeshBuffer(temp_buf); - temp_buf->drop(); - - } - return dst_mesh; -} - -scene::IMesh* convertNodeboxesToMesh(const std::vector &boxes, - const f32 *uv_coords, float expand) -{ - scene::SMesh* dst_mesh = new scene::SMesh(); - - for (u16 j = 0; j < 6; j++) - { - scene::IMeshBuffer *buf = new scene::SMeshBuffer(); - buf->getMaterial().setFlag(video::EMF_LIGHTING, false); - buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false); - dst_mesh->addMeshBuffer(buf); - buf->drop(); - } - - video::SColor c(255,255,255,255); - - for (aabb3f box : boxes) { - box.repair(); - - box.MinEdge.X -= expand; - box.MinEdge.Y -= expand; - box.MinEdge.Z -= expand; - box.MaxEdge.X += expand; - box.MaxEdge.Y += expand; - box.MaxEdge.Z += expand; - - // Compute texture UV coords - f32 tx1 = (box.MinEdge.X / BS) + 0.5; - f32 ty1 = (box.MinEdge.Y / BS) + 0.5; - f32 tz1 = (box.MinEdge.Z / BS) + 0.5; - f32 tx2 = (box.MaxEdge.X / BS) + 0.5; - f32 ty2 = (box.MaxEdge.Y / BS) + 0.5; - f32 tz2 = (box.MaxEdge.Z / BS) + 0.5; - - f32 txc_default[24] = { - // up - tx1, 1 - tz2, tx2, 1 - tz1, - // down - tx1, tz1, tx2, tz2, - // right - tz1, 1 - ty2, tz2, 1 - ty1, - // left - 1 - tz2, 1 - ty2, 1 - tz1, 1 - ty1, - // back - 1 - tx2, 1 - ty2, 1 - tx1, 1 - ty1, - // front - tx1, 1 - ty2, tx2, 1 - ty1, - }; - - // use default texture UV mapping if not provided - const f32 *txc = uv_coords ? uv_coords : txc_default; - - v3f min = box.MinEdge; - v3f max = box.MaxEdge; - - video::S3DVertex vertices[24] = - { - // up - video::S3DVertex(min.X,max.Y,max.Z, 0,1,0, c, txc[0],txc[1]), - video::S3DVertex(max.X,max.Y,max.Z, 0,1,0, c, txc[2],txc[1]), - video::S3DVertex(max.X,max.Y,min.Z, 0,1,0, c, txc[2],txc[3]), - video::S3DVertex(min.X,max.Y,min.Z, 0,1,0, c, txc[0],txc[3]), - // down - video::S3DVertex(min.X,min.Y,min.Z, 0,-1,0, c, txc[4],txc[5]), - video::S3DVertex(max.X,min.Y,min.Z, 0,-1,0, c, txc[6],txc[5]), - video::S3DVertex(max.X,min.Y,max.Z, 0,-1,0, c, txc[6],txc[7]), - video::S3DVertex(min.X,min.Y,max.Z, 0,-1,0, c, txc[4],txc[7]), - // right - video::S3DVertex(max.X,max.Y,min.Z, 1,0,0, c, txc[ 8],txc[9]), - video::S3DVertex(max.X,max.Y,max.Z, 1,0,0, c, txc[10],txc[9]), - video::S3DVertex(max.X,min.Y,max.Z, 1,0,0, c, txc[10],txc[11]), - video::S3DVertex(max.X,min.Y,min.Z, 1,0,0, c, txc[ 8],txc[11]), - // left - video::S3DVertex(min.X,max.Y,max.Z, -1,0,0, c, txc[12],txc[13]), - video::S3DVertex(min.X,max.Y,min.Z, -1,0,0, c, txc[14],txc[13]), - video::S3DVertex(min.X,min.Y,min.Z, -1,0,0, c, txc[14],txc[15]), - video::S3DVertex(min.X,min.Y,max.Z, -1,0,0, c, txc[12],txc[15]), - // back - video::S3DVertex(max.X,max.Y,max.Z, 0,0,1, c, txc[16],txc[17]), - video::S3DVertex(min.X,max.Y,max.Z, 0,0,1, c, txc[18],txc[17]), - video::S3DVertex(min.X,min.Y,max.Z, 0,0,1, c, txc[18],txc[19]), - video::S3DVertex(max.X,min.Y,max.Z, 0,0,1, c, txc[16],txc[19]), - // front - video::S3DVertex(min.X,max.Y,min.Z, 0,0,-1, c, txc[20],txc[21]), - video::S3DVertex(max.X,max.Y,min.Z, 0,0,-1, c, txc[22],txc[21]), - video::S3DVertex(max.X,min.Y,min.Z, 0,0,-1, c, txc[22],txc[23]), - video::S3DVertex(min.X,min.Y,min.Z, 0,0,-1, c, txc[20],txc[23]), - }; - - u16 indices[] = {0,1,2,2,3,0}; - - for(u16 j = 0; j < 24; j += 4) - { - scene::IMeshBuffer *buf = dst_mesh->getMeshBuffer(j / 4); - buf->append(vertices + j, 4, indices, 6); - } - } - return dst_mesh; -} - -struct vcache -{ - core::array tris; - float score; - s16 cachepos; - u16 NumActiveTris; -}; - -struct tcache -{ - u16 ind[3]; - float score; - bool drawn; -}; - -const u16 cachesize = 32; - -float FindVertexScore(vcache *v) -{ - const float CacheDecayPower = 1.5f; - const float LastTriScore = 0.75f; - const float ValenceBoostScale = 2.0f; - const float ValenceBoostPower = 0.5f; - const float MaxSizeVertexCache = 32.0f; - - if (v->NumActiveTris == 0) - { - // No tri needs this vertex! - return -1.0f; - } - - float Score = 0.0f; - int CachePosition = v->cachepos; - if (CachePosition < 0) - { - // Vertex is not in FIFO cache - no score. - } - else - { - if (CachePosition < 3) - { - // This vertex was used in the last triangle, - // so it has a fixed score. - Score = LastTriScore; - } - else - { - // Points for being high in the cache. - const float Scaler = 1.0f / (MaxSizeVertexCache - 3); - Score = 1.0f - (CachePosition - 3) * Scaler; - Score = powf(Score, CacheDecayPower); - } - } - - // Bonus points for having a low number of tris still to - // use the vert, so we get rid of lone verts quickly. - float ValenceBoost = powf(v->NumActiveTris, - -ValenceBoostPower); - Score += ValenceBoostScale * ValenceBoost; - - return Score; -} - -/* - A specialized LRU cache for the Forsyth algorithm. -*/ - -class f_lru -{ - -public: - f_lru(vcache *v, tcache *t): vc(v), tc(t) - { - for (int &i : cache) { - i = -1; - } - } - - // Adds this vertex index and returns the highest-scoring triangle index - u32 add(u16 vert, bool updatetris = false) - { - bool found = false; - - // Mark existing pos as empty - for (u16 i = 0; i < cachesize; i++) - { - if (cache[i] == vert) - { - // Move everything down - for (u16 j = i; j; j--) - { - cache[j] = cache[j - 1]; - } - - found = true; - break; - } - } - - if (!found) - { - if (cache[cachesize-1] != -1) - vc[cache[cachesize-1]].cachepos = -1; - - // Move everything down - for (u16 i = cachesize - 1; i; i--) - { - cache[i] = cache[i - 1]; - } - } - - cache[0] = vert; - - u32 highest = 0; - float hiscore = 0; - - if (updatetris) - { - // Update cache positions - for (u16 i = 0; i < cachesize; i++) - { - if (cache[i] == -1) - break; - - vc[cache[i]].cachepos = i; - vc[cache[i]].score = FindVertexScore(&vc[cache[i]]); - } - - // Update triangle scores - for (int i : cache) { - if (i == -1) - break; - - const u16 trisize = vc[i].tris.size(); - for (u16 t = 0; t < trisize; t++) - { - tcache *tri = &tc[vc[i].tris[t]]; - - tri->score = - vc[tri->ind[0]].score + - vc[tri->ind[1]].score + - vc[tri->ind[2]].score; - - if (tri->score > hiscore) - { - hiscore = tri->score; - highest = vc[i].tris[t]; - } - } - } - } - - return highest; - } - -private: - s32 cache[cachesize]; - vcache *vc; - tcache *tc; -}; - -/** -Vertex cache optimization according to the Forsyth paper: -http://home.comcast.net/~tom_forsyth/papers/fast_vert_cache_opt.html - -The function is thread-safe (read: you can optimize several meshes in different threads) - -\param mesh Source mesh for the operation. */ -scene::IMesh* createForsythOptimizedMesh(const scene::IMesh *mesh) -{ - if (!mesh) - return 0; - - scene::SMesh *newmesh = new scene::SMesh(); - newmesh->BoundingBox = mesh->getBoundingBox(); - - const u32 mbcount = mesh->getMeshBufferCount(); - - for (u32 b = 0; b < mbcount; ++b) - { - const scene::IMeshBuffer *mb = mesh->getMeshBuffer(b); - - if (mb->getIndexType() != video::EIT_16BIT) - { - //os::Printer::log("Cannot optimize a mesh with 32bit indices", ELL_ERROR); - newmesh->drop(); - return 0; - } - - const u32 icount = mb->getIndexCount(); - const u32 tcount = icount / 3; - const u32 vcount = mb->getVertexCount(); - const u16 *ind = mb->getIndices(); - - vcache *vc = new vcache[vcount]; - tcache *tc = new tcache[tcount]; - - f_lru lru(vc, tc); - - // init - for (u16 i = 0; i < vcount; i++) - { - vc[i].score = 0; - vc[i].cachepos = -1; - vc[i].NumActiveTris = 0; - } - - // First pass: count how many times a vert is used - for (u32 i = 0; i < icount; i += 3) - { - vc[ind[i]].NumActiveTris++; - vc[ind[i + 1]].NumActiveTris++; - vc[ind[i + 2]].NumActiveTris++; - - const u32 tri_ind = i/3; - tc[tri_ind].ind[0] = ind[i]; - tc[tri_ind].ind[1] = ind[i + 1]; - tc[tri_ind].ind[2] = ind[i + 2]; - } - - // Second pass: list of each triangle - for (u32 i = 0; i < tcount; i++) - { - vc[tc[i].ind[0]].tris.push_back(i); - vc[tc[i].ind[1]].tris.push_back(i); - vc[tc[i].ind[2]].tris.push_back(i); - - tc[i].drawn = false; - } - - // Give initial scores - for (u16 i = 0; i < vcount; i++) - { - vc[i].score = FindVertexScore(&vc[i]); - } - for (u32 i = 0; i < tcount; i++) - { - tc[i].score = - vc[tc[i].ind[0]].score + - vc[tc[i].ind[1]].score + - vc[tc[i].ind[2]].score; - } - - switch(mb->getVertexType()) - { - case video::EVT_STANDARD: - { - video::S3DVertex *v = (video::S3DVertex *) mb->getVertices(); - - scene::SMeshBuffer *buf = new scene::SMeshBuffer(); - buf->Material = mb->getMaterial(); - - buf->Vertices.reallocate(vcount); - buf->Indices.reallocate(icount); - - core::map sind; // search index for fast operation - typedef core::map::Node snode; - - // Main algorithm - u32 highest = 0; - u32 drawcalls = 0; - for (;;) - { - if (tc[highest].drawn) - { - bool found = false; - float hiscore = 0; - for (u32 t = 0; t < tcount; t++) - { - if (!tc[t].drawn) - { - if (tc[t].score > hiscore) - { - highest = t; - hiscore = tc[t].score; - found = true; - } - } - } - if (!found) - break; - } - - // Output the best triangle - u16 newind = buf->Vertices.size(); - - snode *s = sind.find(v[tc[highest].ind[0]]); - - if (!s) - { - buf->Vertices.push_back(v[tc[highest].ind[0]]); - buf->Indices.push_back(newind); - sind.insert(v[tc[highest].ind[0]], newind); - newind++; - } - else - { - buf->Indices.push_back(s->getValue()); - } - - s = sind.find(v[tc[highest].ind[1]]); - - if (!s) - { - buf->Vertices.push_back(v[tc[highest].ind[1]]); - buf->Indices.push_back(newind); - sind.insert(v[tc[highest].ind[1]], newind); - newind++; - } - else - { - buf->Indices.push_back(s->getValue()); - } - - s = sind.find(v[tc[highest].ind[2]]); - - if (!s) - { - buf->Vertices.push_back(v[tc[highest].ind[2]]); - buf->Indices.push_back(newind); - sind.insert(v[tc[highest].ind[2]], newind); - } - else - { - buf->Indices.push_back(s->getValue()); - } - - vc[tc[highest].ind[0]].NumActiveTris--; - vc[tc[highest].ind[1]].NumActiveTris--; - vc[tc[highest].ind[2]].NumActiveTris--; - - tc[highest].drawn = true; - - for (u16 j : tc[highest].ind) { - vcache *vert = &vc[j]; - for (u16 t = 0; t < vert->tris.size(); t++) - { - if (highest == vert->tris[t]) - { - vert->tris.erase(t); - break; - } - } - } - - lru.add(tc[highest].ind[0]); - lru.add(tc[highest].ind[1]); - highest = lru.add(tc[highest].ind[2], true); - drawcalls++; - } - - buf->setBoundingBox(mb->getBoundingBox()); - newmesh->addMeshBuffer(buf); - buf->drop(); - } - break; - case video::EVT_2TCOORDS: - { - video::S3DVertex2TCoords *v = (video::S3DVertex2TCoords *) mb->getVertices(); - - scene::SMeshBufferLightMap *buf = new scene::SMeshBufferLightMap(); - buf->Material = mb->getMaterial(); - - buf->Vertices.reallocate(vcount); - buf->Indices.reallocate(icount); - - core::map sind; // search index for fast operation - typedef core::map::Node snode; - - // Main algorithm - u32 highest = 0; - u32 drawcalls = 0; - for (;;) - { - if (tc[highest].drawn) - { - bool found = false; - float hiscore = 0; - for (u32 t = 0; t < tcount; t++) - { - if (!tc[t].drawn) - { - if (tc[t].score > hiscore) - { - highest = t; - hiscore = tc[t].score; - found = true; - } - } - } - if (!found) - break; - } - - // Output the best triangle - u16 newind = buf->Vertices.size(); - - snode *s = sind.find(v[tc[highest].ind[0]]); - - if (!s) - { - buf->Vertices.push_back(v[tc[highest].ind[0]]); - buf->Indices.push_back(newind); - sind.insert(v[tc[highest].ind[0]], newind); - newind++; - } - else - { - buf->Indices.push_back(s->getValue()); - } - - s = sind.find(v[tc[highest].ind[1]]); - - if (!s) - { - buf->Vertices.push_back(v[tc[highest].ind[1]]); - buf->Indices.push_back(newind); - sind.insert(v[tc[highest].ind[1]], newind); - newind++; - } - else - { - buf->Indices.push_back(s->getValue()); - } - - s = sind.find(v[tc[highest].ind[2]]); - - if (!s) - { - buf->Vertices.push_back(v[tc[highest].ind[2]]); - buf->Indices.push_back(newind); - sind.insert(v[tc[highest].ind[2]], newind); - } - else - { - buf->Indices.push_back(s->getValue()); - } - - vc[tc[highest].ind[0]].NumActiveTris--; - vc[tc[highest].ind[1]].NumActiveTris--; - vc[tc[highest].ind[2]].NumActiveTris--; - - tc[highest].drawn = true; - - for (u16 j : tc[highest].ind) { - vcache *vert = &vc[j]; - for (u16 t = 0; t < vert->tris.size(); t++) - { - if (highest == vert->tris[t]) - { - vert->tris.erase(t); - break; - } - } - } - - lru.add(tc[highest].ind[0]); - lru.add(tc[highest].ind[1]); - highest = lru.add(tc[highest].ind[2]); - drawcalls++; - } - - buf->setBoundingBox(mb->getBoundingBox()); - newmesh->addMeshBuffer(buf); - buf->drop(); - - } - break; - case video::EVT_TANGENTS: - { - video::S3DVertexTangents *v = (video::S3DVertexTangents *) mb->getVertices(); - - scene::SMeshBufferTangents *buf = new scene::SMeshBufferTangents(); - buf->Material = mb->getMaterial(); - - buf->Vertices.reallocate(vcount); - buf->Indices.reallocate(icount); - - core::map sind; // search index for fast operation - typedef core::map::Node snode; - - // Main algorithm - u32 highest = 0; - u32 drawcalls = 0; - for (;;) - { - if (tc[highest].drawn) - { - bool found = false; - float hiscore = 0; - for (u32 t = 0; t < tcount; t++) - { - if (!tc[t].drawn) - { - if (tc[t].score > hiscore) - { - highest = t; - hiscore = tc[t].score; - found = true; - } - } - } - if (!found) - break; - } - - // Output the best triangle - u16 newind = buf->Vertices.size(); - - snode *s = sind.find(v[tc[highest].ind[0]]); - - if (!s) - { - buf->Vertices.push_back(v[tc[highest].ind[0]]); - buf->Indices.push_back(newind); - sind.insert(v[tc[highest].ind[0]], newind); - newind++; - } - else - { - buf->Indices.push_back(s->getValue()); - } - - s = sind.find(v[tc[highest].ind[1]]); - - if (!s) - { - buf->Vertices.push_back(v[tc[highest].ind[1]]); - buf->Indices.push_back(newind); - sind.insert(v[tc[highest].ind[1]], newind); - newind++; - } - else - { - buf->Indices.push_back(s->getValue()); - } - - s = sind.find(v[tc[highest].ind[2]]); - - if (!s) - { - buf->Vertices.push_back(v[tc[highest].ind[2]]); - buf->Indices.push_back(newind); - sind.insert(v[tc[highest].ind[2]], newind); - } - else - { - buf->Indices.push_back(s->getValue()); - } - - vc[tc[highest].ind[0]].NumActiveTris--; - vc[tc[highest].ind[1]].NumActiveTris--; - vc[tc[highest].ind[2]].NumActiveTris--; - - tc[highest].drawn = true; - - for (u16 j : tc[highest].ind) { - vcache *vert = &vc[j]; - for (u16 t = 0; t < vert->tris.size(); t++) - { - if (highest == vert->tris[t]) - { - vert->tris.erase(t); - break; - } - } - } - - lru.add(tc[highest].ind[0]); - lru.add(tc[highest].ind[1]); - highest = lru.add(tc[highest].ind[2]); - drawcalls++; - } - - buf->setBoundingBox(mb->getBoundingBox()); - newmesh->addMeshBuffer(buf); - buf->drop(); - } - break; - } - - delete [] vc; - delete [] tc; - - } // for each meshbuffer - - return newmesh; -} diff --git a/src/mesh.h b/src/mesh.h deleted file mode 100644 index 0c4094de2..000000000 --- a/src/mesh.h +++ /dev/null @@ -1,129 +0,0 @@ -/* -Minetest -Copyright (C) 2010-2013 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#pragma once - -#include "irrlichttypes_extrabloated.h" -#include "nodedef.h" - -/*! - * Applies shading to a color based on the surface's - * normal vector. - */ -void applyFacesShading(video::SColor &color, const v3f &normal); - -/* - Create a new cube mesh. - Vertices are at (+-scale.X/2, +-scale.Y/2, +-scale.Z/2). - - The resulting mesh has 6 materials (up, down, right, left, back, front) - which must be defined by the caller. -*/ -scene::IAnimatedMesh* createCubeMesh(v3f scale); - -/* - Multiplies each vertex coordinate by the specified scaling factors - (componentwise vector multiplication). -*/ -void scaleMesh(scene::IMesh *mesh, v3f scale); - -/* - Translate each vertex coordinate by the specified vector. -*/ -void translateMesh(scene::IMesh *mesh, v3f vec); - -/*! - * Sets a constant color for all vertices in the mesh buffer. - */ -void setMeshBufferColor(scene::IMeshBuffer *buf, const video::SColor &color); - -/* - Set a constant color for all vertices in the mesh -*/ -void setMeshColor(scene::IMesh *mesh, const video::SColor &color); - -/* - Set a constant color for an animated mesh -*/ -void setAnimatedMeshColor(scene::IAnimatedMeshSceneNode *node, const video::SColor &color); - -/*! - * Overwrites the color of a mesh buffer. - * The color is darkened based on the normal vector of the vertices. - */ -void colorizeMeshBuffer(scene::IMeshBuffer *buf, const video::SColor *buffercolor); - -/* - Set the color of all vertices in the mesh. - For each vertex, determine the largest absolute entry in - the normal vector, and choose one of colorX, colorY or - colorZ accordingly. -*/ -void setMeshColorByNormalXYZ(scene::IMesh *mesh, - const video::SColor &colorX, - const video::SColor &colorY, - const video::SColor &colorZ); - -void setMeshColorByNormal(scene::IMesh *mesh, const v3f &normal, - const video::SColor &color); - -/* - Rotate the mesh by 6d facedir value. - Method only for meshnodes, not suitable for entities. -*/ -void rotateMeshBy6dFacedir(scene::IMesh *mesh, int facedir); - -/* - Rotate the mesh around the axis and given angle in degrees. -*/ -void rotateMeshXYby (scene::IMesh *mesh, f64 degrees); -void rotateMeshXZby (scene::IMesh *mesh, f64 degrees); -void rotateMeshYZby (scene::IMesh *mesh, f64 degrees); - -/* - * Clone the mesh buffer. - * The returned pointer should be dropped. - */ -scene::IMeshBuffer* cloneMeshBuffer(scene::IMeshBuffer *mesh_buffer); - -/* - Clone the mesh. -*/ -scene::SMesh* cloneMesh(scene::IMesh *src_mesh); - -/* - Convert nodeboxes to mesh. Each tile goes into a different buffer. - boxes - set of nodeboxes to be converted into cuboids - uv_coords[24] - table of texture uv coords for each cuboid face - expand - factor by which cuboids will be resized -*/ -scene::IMesh* convertNodeboxesToMesh(const std::vector &boxes, - const f32 *uv_coords = NULL, float expand = 0); - -/* - Update bounding box for a mesh. -*/ -void recalculateBoundingBox(scene::IMesh *src_mesh); - -/* - Vertex cache optimization according to the Forsyth paper: - http://home.comcast.net/~tom_forsyth/papers/fast_vert_cache_opt.html - Ported from irrlicht 1.8 -*/ -scene::IMesh* createForsythOptimizedMesh(const scene::IMesh *mesh); diff --git a/src/mesh_generator_thread.cpp b/src/mesh_generator_thread.cpp deleted file mode 100644 index be4bcc1f4..000000000 --- a/src/mesh_generator_thread.cpp +++ /dev/null @@ -1,308 +0,0 @@ -/* -Minetest -Copyright (C) 2013, 2017 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include "mesh_generator_thread.h" -#include "settings.h" -#include "profiler.h" -#include "client.h" -#include "mapblock.h" -#include "map.h" - -/* - CachedMapBlockData -*/ - -CachedMapBlockData::~CachedMapBlockData() -{ - assert(refcount_from_queue == 0); - - delete[] data; -} - -/* - QueuedMeshUpdate -*/ - -QueuedMeshUpdate::~QueuedMeshUpdate() -{ - delete data; -} - -/* - MeshUpdateQueue -*/ - -MeshUpdateQueue::MeshUpdateQueue(Client *client): - m_client(client) -{ - m_cache_enable_shaders = g_settings->getBool("enable_shaders"); - m_cache_use_tangent_vertices = m_cache_enable_shaders && ( - g_settings->getBool("enable_bumpmapping") || - g_settings->getBool("enable_parallax_occlusion")); - m_cache_smooth_lighting = g_settings->getBool("smooth_lighting"); - m_meshgen_block_cache_size = g_settings->getS32("meshgen_block_cache_size"); -} - -MeshUpdateQueue::~MeshUpdateQueue() -{ - MutexAutoLock lock(m_mutex); - - for (auto &i : m_cache) { - delete i.second; - } - - for (QueuedMeshUpdate *q : m_queue) { - delete q; - } -} - -void MeshUpdateQueue::addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool urgent) -{ - MutexAutoLock lock(m_mutex); - - cleanupCache(); - - /* - Cache the block data (force-update the center block, don't update the - neighbors but get them if they aren't already cached) - */ - std::vector cached_blocks; - size_t cache_hit_counter = 0; - cached_blocks.reserve(3*3*3); - v3s16 dp; - for (dp.X = -1; dp.X <= 1; dp.X++) - for (dp.Y = -1; dp.Y <= 1; dp.Y++) - for (dp.Z = -1; dp.Z <= 1; dp.Z++) { - v3s16 p1 = p + dp; - CachedMapBlockData *cached_block; - if (dp == v3s16(0, 0, 0)) - cached_block = cacheBlock(map, p1, FORCE_UPDATE); - else - cached_block = cacheBlock(map, p1, SKIP_UPDATE_IF_ALREADY_CACHED, - &cache_hit_counter); - cached_blocks.push_back(cached_block); - } - g_profiler->avg("MeshUpdateQueue MapBlock cache hit %", - 100.0f * cache_hit_counter / cached_blocks.size()); - - /* - Mark the block as urgent if requested - */ - if (urgent) - m_urgents.insert(p); - - /* - Find if block is already in queue. - If it is, update the data and quit. - */ - for (QueuedMeshUpdate *q : m_queue) { - if (q->p == p) { - // NOTE: We are not adding a new position to the queue, thus - // refcount_from_queue stays the same. - if(ack_block_to_server) - q->ack_block_to_server = true; - q->crack_level = m_client->getCrackLevel(); - q->crack_pos = m_client->getCrackPos(); - return; - } - } - - /* - Add the block - */ - QueuedMeshUpdate *q = new QueuedMeshUpdate; - q->p = p; - q->ack_block_to_server = ack_block_to_server; - q->crack_level = m_client->getCrackLevel(); - q->crack_pos = m_client->getCrackPos(); - m_queue.push_back(q); - - // This queue entry is a new reference to the cached blocks - for (CachedMapBlockData *cached_block : cached_blocks) { - cached_block->refcount_from_queue++; - } -} - -// Returned pointer must be deleted -// Returns NULL if queue is empty -QueuedMeshUpdate *MeshUpdateQueue::pop() -{ - MutexAutoLock lock(m_mutex); - - bool must_be_urgent = !m_urgents.empty(); - for (std::vector::iterator i = m_queue.begin(); - i != m_queue.end(); ++i) { - QueuedMeshUpdate *q = *i; - if(must_be_urgent && m_urgents.count(q->p) == 0) - continue; - m_queue.erase(i); - m_urgents.erase(q->p); - fillDataFromMapBlockCache(q); - return q; - } - return NULL; -} - -CachedMapBlockData* MeshUpdateQueue::cacheBlock(Map *map, v3s16 p, UpdateMode mode, - size_t *cache_hit_counter) -{ - std::map::iterator it = - m_cache.find(p); - if (it != m_cache.end()) { - // Already in cache - CachedMapBlockData *cached_block = it->second; - if (mode == SKIP_UPDATE_IF_ALREADY_CACHED) { - if (cache_hit_counter) - (*cache_hit_counter)++; - return cached_block; - } - MapBlock *b = map->getBlockNoCreateNoEx(p); - if (b) { - if (cached_block->data == NULL) - cached_block->data = - new MapNode[MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE]; - memcpy(cached_block->data, b->getData(), - MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE * sizeof(MapNode)); - } else { - delete[] cached_block->data; - cached_block->data = NULL; - } - return cached_block; - } - - // Not yet in cache - CachedMapBlockData *cached_block = new CachedMapBlockData(); - m_cache[p] = cached_block; - MapBlock *b = map->getBlockNoCreateNoEx(p); - if (b) { - cached_block->data = - new MapNode[MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE]; - memcpy(cached_block->data, b->getData(), - MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE * sizeof(MapNode)); - } - return cached_block; -} - -CachedMapBlockData* MeshUpdateQueue::getCachedBlock(const v3s16 &p) -{ - std::map::iterator it = m_cache.find(p); - if (it != m_cache.end()) { - return it->second; - } - return NULL; -} - -void MeshUpdateQueue::fillDataFromMapBlockCache(QueuedMeshUpdate *q) -{ - MeshMakeData *data = new MeshMakeData(m_client, m_cache_enable_shaders, - m_cache_use_tangent_vertices); - q->data = data; - - data->fillBlockDataBegin(q->p); - - std::time_t t_now = std::time(0); - - // Collect data for 3*3*3 blocks from cache - v3s16 dp; - for (dp.X = -1; dp.X <= 1; dp.X++) - for (dp.Y = -1; dp.Y <= 1; dp.Y++) - for (dp.Z = -1; dp.Z <= 1; dp.Z++) { - v3s16 p = q->p + dp; - CachedMapBlockData *cached_block = getCachedBlock(p); - if (cached_block) { - cached_block->refcount_from_queue--; - cached_block->last_used_timestamp = t_now; - if (cached_block->data) - data->fillBlockData(dp, cached_block->data); - } - } - - data->setCrack(q->crack_level, q->crack_pos); - data->setSmoothLighting(m_cache_smooth_lighting); -} - -void MeshUpdateQueue::cleanupCache() -{ - const int mapblock_kB = MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE * - sizeof(MapNode) / 1000; - g_profiler->avg("MeshUpdateQueue MapBlock cache size kB", - mapblock_kB * m_cache.size()); - - // 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; - const int cache_soft_max_size = m_meshgen_block_cache_size * 1000 / mapblock_kB; - int cache_seconds = MYMAX(2, cache_seconds_max - - m_cache.size() / (cache_soft_max_size / cache_seconds_max)); - - int t_now = time(0); - - for (std::map::iterator 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++); - delete cached_block; - } else { - ++it; - } - } -} - -/* - MeshUpdateThread -*/ - -MeshUpdateThread::MeshUpdateThread(Client *client): - UpdateThread("Mesh"), - m_queue_in(client) -{ - m_generation_interval = g_settings->getU16("mesh_generation_interval"); - m_generation_interval = rangelim(m_generation_interval, 0, 50); -} - -void MeshUpdateThread::updateBlock(Map *map, v3s16 p, bool ack_block_to_server, - bool urgent) -{ - // Allow the MeshUpdateQueue to do whatever it wants - m_queue_in.addBlock(map, p, ack_block_to_server, urgent); - deferUpdate(); -} - -void MeshUpdateThread::doUpdate() -{ - QueuedMeshUpdate *q; - while ((q = m_queue_in.pop())) { - if (m_generation_interval) - sleep_ms(m_generation_interval); - ScopeProfiler sp(g_profiler, "Client: Mesh making"); - - MapBlockMesh *mesh_new = new MapBlockMesh(q->data, m_camera_offset); - - MeshUpdateResult r; - r.p = q->p; - r.mesh = mesh_new; - r.ack_block_to_server = q->ack_block_to_server; - - m_queue_out.push_back(r); - - delete q; - } -} diff --git a/src/mesh_generator_thread.h b/src/mesh_generator_thread.h deleted file mode 100644 index 9a42852a3..000000000 --- a/src/mesh_generator_thread.h +++ /dev/null @@ -1,131 +0,0 @@ -/* -Minetest -Copyright (C) 2013, 2017 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#pragma once - -#include -#include -#include "mapblock_mesh.h" -#include "threading/mutex_auto_lock.h" -#include "util/thread.h" - -struct CachedMapBlockData -{ - v3s16 p = v3s16(-1337, -1337, -1337); - MapNode *data = nullptr; // A copy of the MapBlock's data member - int refcount_from_queue = 0; - std::time_t last_used_timestamp = std::time(0); - - CachedMapBlockData() = default; - ~CachedMapBlockData(); -}; - -struct QueuedMeshUpdate -{ - v3s16 p = v3s16(-1337, -1337, -1337); - bool ack_block_to_server = false; - bool urgent = false; - int crack_level = -1; - v3s16 crack_pos; - MeshMakeData *data = nullptr; // This is generated in MeshUpdateQueue::pop() - - QueuedMeshUpdate() = default; - ~QueuedMeshUpdate(); -}; - -/* - A thread-safe queue of mesh update tasks and a cache of MapBlock data -*/ -class MeshUpdateQueue -{ - enum UpdateMode - { - FORCE_UPDATE, - SKIP_UPDATE_IF_ALREADY_CACHED, - }; - -public: - MeshUpdateQueue(Client *client); - - ~MeshUpdateQueue(); - - // Caches the block at p and its neighbors (if needed) and queues a mesh - // update for the block at p - void addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool urgent); - - // Returned pointer must be deleted - // Returns NULL if queue is empty - QueuedMeshUpdate *pop(); - - u32 size() - { - MutexAutoLock lock(m_mutex); - return m_queue.size(); - } - -private: - Client *m_client; - std::vector m_queue; - std::set m_urgents; - std::map m_cache; - std::mutex m_mutex; - - // TODO: Add callback to update these when g_settings changes - bool m_cache_enable_shaders; - bool m_cache_use_tangent_vertices; - bool m_cache_smooth_lighting; - int m_meshgen_block_cache_size; - - CachedMapBlockData *cacheBlock(Map *map, v3s16 p, UpdateMode mode, - size_t *cache_hit_counter = NULL); - CachedMapBlockData *getCachedBlock(const v3s16 &p); - void fillDataFromMapBlockCache(QueuedMeshUpdate *q); - void cleanupCache(); -}; - -struct MeshUpdateResult -{ - v3s16 p = v3s16(-1338, -1338, -1338); - MapBlockMesh *mesh = nullptr; - bool ack_block_to_server = false; - - MeshUpdateResult() = default; -}; - -class MeshUpdateThread : public UpdateThread -{ -public: - MeshUpdateThread(Client *client); - - // Caches the block at p and its neighbors (if needed) and queues a mesh - // update for the block at p - void updateBlock(Map *map, v3s16 p, bool ack_block_to_server, bool urgent); - - v3s16 m_camera_offset; - MutexedQueue m_queue_out; - -private: - MeshUpdateQueue m_queue_in; - - // TODO: Add callback to update these when g_settings changes - int m_generation_interval; - -protected: - virtual void doUpdate(); -}; diff --git a/src/minimap.cpp b/src/minimap.cpp deleted file mode 100644 index 4d83c088a..000000000 --- a/src/minimap.cpp +++ /dev/null @@ -1,624 +0,0 @@ -/* -Minetest -Copyright (C) 2010-2015 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include "minimap.h" -#include -#include "client.h" -#include "clientmap.h" -#include "settings.h" -#include "shader.h" -#include "mapblock.h" -#include "client/renderingengine.h" - - -//// -//// MinimapUpdateThread -//// - -MinimapUpdateThread::~MinimapUpdateThread() -{ - for (auto &it : m_blocks_cache) { - delete it.second; - } - - for (auto &q : m_update_queue) { - delete q.data; - } -} - -bool MinimapUpdateThread::pushBlockUpdate(v3s16 pos, MinimapMapblock *data) -{ - MutexAutoLock lock(m_queue_mutex); - - // Find if block is already in queue. - // If it is, update the data and quit. - for (QueuedMinimapUpdate &q : m_update_queue) { - if (q.pos == pos) { - delete q.data; - q.data = data; - return false; - } - } - - // Add the block - QueuedMinimapUpdate q; - q.pos = pos; - q.data = data; - m_update_queue.push_back(q); - - return true; -} - -bool MinimapUpdateThread::popBlockUpdate(QueuedMinimapUpdate *update) -{ - MutexAutoLock lock(m_queue_mutex); - - if (m_update_queue.empty()) - return false; - - *update = m_update_queue.front(); - m_update_queue.pop_front(); - - return true; -} - -void MinimapUpdateThread::enqueueBlock(v3s16 pos, MinimapMapblock *data) -{ - pushBlockUpdate(pos, data); - deferUpdate(); -} - - -void MinimapUpdateThread::doUpdate() -{ - QueuedMinimapUpdate update; - - while (popBlockUpdate(&update)) { - if (update.data) { - // Swap two values in the map using single lookup - std::pair::iterator, bool> - result = m_blocks_cache.insert(std::make_pair(update.pos, update.data)); - if (!result.second) { - delete result.first->second; - result.first->second = update.data; - } - } else { - std::map::iterator it; - it = m_blocks_cache.find(update.pos); - if (it != m_blocks_cache.end()) { - delete it->second; - m_blocks_cache.erase(it); - } - } - } - - if (data->map_invalidated && data->mode != MINIMAP_MODE_OFF) { - getMap(data->pos, data->map_size, data->scan_height); - data->map_invalidated = false; - } -} - -void MinimapUpdateThread::getMap(v3s16 pos, s16 size, s16 height) -{ - v3s16 pos_min(pos.X - size / 2, pos.Y - height / 2, pos.Z - size / 2); - v3s16 pos_max(pos_min.X + size - 1, pos.Y + height / 2, pos_min.Z + size - 1); - v3s16 blockpos_min = getNodeBlockPos(pos_min); - v3s16 blockpos_max = getNodeBlockPos(pos_max); - -// clear the map - for (int z = 0; z < size; z++) - for (int x = 0; x < size; x++) { - MinimapPixel &mmpixel = data->minimap_scan[x + z * size]; - mmpixel.air_count = 0; - mmpixel.height = 0; - mmpixel.n = MapNode(CONTENT_AIR); - } - -// draw the map - v3s16 blockpos; - for (blockpos.Z = blockpos_min.Z; blockpos.Z <= blockpos_max.Z; ++blockpos.Z) - for (blockpos.Y = blockpos_min.Y; blockpos.Y <= blockpos_max.Y; ++blockpos.Y) - for (blockpos.X = blockpos_min.X; blockpos.X <= blockpos_max.X; ++blockpos.X) { - std::map::const_iterator pblock = - m_blocks_cache.find(blockpos); - if (pblock == m_blocks_cache.end()) - continue; - const MinimapMapblock &block = *pblock->second; - - v3s16 block_node_min(blockpos * MAP_BLOCKSIZE); - v3s16 block_node_max(block_node_min + MAP_BLOCKSIZE - 1); - // clip - v3s16 range_min = componentwise_max(block_node_min, pos_min); - v3s16 range_max = componentwise_min(block_node_max, pos_max); - - v3s16 pos; - pos.Y = range_min.Y; - for (pos.Z = range_min.Z; pos.Z <= range_max.Z; ++pos.Z) - for (pos.X = range_min.X; pos.X <= range_max.X; ++pos.X) { - v3s16 inblock_pos = pos - block_node_min; - const MinimapPixel &in_pixel = - block.data[inblock_pos.Z * MAP_BLOCKSIZE + inblock_pos.X]; - - v3s16 inmap_pos = pos - pos_min; - MinimapPixel &out_pixel = - data->minimap_scan[inmap_pos.X + inmap_pos.Z * size]; - - out_pixel.air_count += in_pixel.air_count; - if (in_pixel.n.param0 != CONTENT_AIR) { - out_pixel.n = in_pixel.n; - out_pixel.height = inmap_pos.Y + in_pixel.height; - } - } - } -} - -//// -//// Mapper -//// - -Minimap::Minimap(Client *client) -{ - this->client = client; - this->driver = RenderingEngine::get_video_driver(); - this->m_tsrc = client->getTextureSource(); - this->m_shdrsrc = client->getShaderSource(); - this->m_ndef = client->getNodeDefManager(); - - m_angle = 0.f; - - // Initialize static settings - m_enable_shaders = g_settings->getBool("enable_shaders"); - m_surface_mode_scan_height = - g_settings->getBool("minimap_double_scan_height") ? 256 : 128; - - // Initialize minimap data - data = new MinimapData; - data->mode = MINIMAP_MODE_OFF; - data->is_radar = false; - data->map_invalidated = true; - data->texture = NULL; - data->heightmap_texture = NULL; - data->minimap_shape_round = g_settings->getBool("minimap_shape_round"); - - // Get round minimap textures - data->minimap_mask_round = driver->createImage( - m_tsrc->getTexture("minimap_mask_round.png"), - core::position2d(0, 0), - core::dimension2d(MINIMAP_MAX_SX, MINIMAP_MAX_SY)); - data->minimap_overlay_round = m_tsrc->getTexture("minimap_overlay_round.png"); - - // Get square minimap textures - data->minimap_mask_square = driver->createImage( - m_tsrc->getTexture("minimap_mask_square.png"), - core::position2d(0, 0), - core::dimension2d(MINIMAP_MAX_SX, MINIMAP_MAX_SY)); - data->minimap_overlay_square = m_tsrc->getTexture("minimap_overlay_square.png"); - - // Create player marker texture - data->player_marker = m_tsrc->getTexture("player_marker.png"); - // Create object marker texture - data->object_marker_red = m_tsrc->getTexture("object_marker_red.png"); - - // Create mesh buffer for minimap - m_meshbuffer = getMinimapMeshBuffer(); - - // Initialize and start thread - m_minimap_update_thread = new MinimapUpdateThread(); - m_minimap_update_thread->data = data; - m_minimap_update_thread->start(); -} - -Minimap::~Minimap() -{ - m_minimap_update_thread->stop(); - m_minimap_update_thread->wait(); - - m_meshbuffer->drop(); - - data->minimap_mask_round->drop(); - data->minimap_mask_square->drop(); - - driver->removeTexture(data->texture); - driver->removeTexture(data->heightmap_texture); - driver->removeTexture(data->minimap_overlay_round); - driver->removeTexture(data->minimap_overlay_square); - driver->removeTexture(data->object_marker_red); - - delete data; - delete m_minimap_update_thread; -} - -void Minimap::addBlock(v3s16 pos, MinimapMapblock *data) -{ - m_minimap_update_thread->enqueueBlock(pos, data); -} - -void Minimap::toggleMinimapShape() -{ - MutexAutoLock lock(m_mutex); - - data->minimap_shape_round = !data->minimap_shape_round; - g_settings->setBool("minimap_shape_round", data->minimap_shape_round); - m_minimap_update_thread->deferUpdate(); -} - -void Minimap::setMinimapShape(MinimapShape shape) -{ - MutexAutoLock lock(m_mutex); - - if (shape == MINIMAP_SHAPE_SQUARE) - data->minimap_shape_round = false; - else if (shape == MINIMAP_SHAPE_ROUND) - data->minimap_shape_round = true; - - g_settings->setBool("minimap_shape_round", data->minimap_shape_round); - m_minimap_update_thread->deferUpdate(); -} - -MinimapShape Minimap::getMinimapShape() -{ - if (data->minimap_shape_round) { - return MINIMAP_SHAPE_ROUND; - } - - return MINIMAP_SHAPE_SQUARE; -} - -void Minimap::setMinimapMode(MinimapMode mode) -{ - static const MinimapModeDef modedefs[MINIMAP_MODE_COUNT] = { - {false, 0, 0}, - {false, m_surface_mode_scan_height, 256}, - {false, m_surface_mode_scan_height, 128}, - {false, m_surface_mode_scan_height, 64}, - {true, 32, 128}, - {true, 32, 64}, - {true, 32, 32} - }; - - if (mode >= MINIMAP_MODE_COUNT) - return; - - MutexAutoLock lock(m_mutex); - - data->is_radar = modedefs[mode].is_radar; - data->scan_height = modedefs[mode].scan_height; - data->map_size = modedefs[mode].map_size; - data->mode = mode; - - m_minimap_update_thread->deferUpdate(); -} - -void Minimap::setPos(v3s16 pos) -{ - bool do_update = false; - - { - MutexAutoLock lock(m_mutex); - - if (pos != data->old_pos) { - data->old_pos = data->pos; - data->pos = pos; - do_update = true; - } - } - - if (do_update) - m_minimap_update_thread->deferUpdate(); -} - -void Minimap::setAngle(f32 angle) -{ - m_angle = angle; -} - -void Minimap::blitMinimapPixelsToImageRadar(video::IImage *map_image) -{ - video::SColor c(240, 0, 0, 0); - for (s16 x = 0; x < data->map_size; x++) - for (s16 z = 0; z < data->map_size; z++) { - MinimapPixel *mmpixel = &data->minimap_scan[x + z * data->map_size]; - - if (mmpixel->air_count > 0) - c.setGreen(core::clamp(core::round32(32 + mmpixel->air_count * 8), 0, 255)); - else - c.setGreen(0); - - map_image->setPixel(x, data->map_size - z - 1, c); - } -} - -void Minimap::blitMinimapPixelsToImageSurface( - video::IImage *map_image, video::IImage *heightmap_image) -{ - // This variable creation/destruction has a 1% cost on rendering minimap - video::SColor tilecolor; - for (s16 x = 0; x < data->map_size; x++) - for (s16 z = 0; z < data->map_size; z++) { - MinimapPixel *mmpixel = &data->minimap_scan[x + z * data->map_size]; - - const ContentFeatures &f = m_ndef->get(mmpixel->n); - const TileDef *tile = &f.tiledef[0]; - - // Color of the 0th tile (mostly this is the topmost) - if(tile->has_color) - tilecolor = tile->color; - else - mmpixel->n.getColor(f, &tilecolor); - - tilecolor.setRed(tilecolor.getRed() * f.minimap_color.getRed() / 255); - tilecolor.setGreen(tilecolor.getGreen() * f.minimap_color.getGreen() / 255); - tilecolor.setBlue(tilecolor.getBlue() * f.minimap_color.getBlue() / 255); - tilecolor.setAlpha(240); - - map_image->setPixel(x, data->map_size - z - 1, tilecolor); - - u32 h = mmpixel->height; - heightmap_image->setPixel(x,data->map_size - z - 1, - video::SColor(255, h, h, h)); - } -} - -video::ITexture *Minimap::getMinimapTexture() -{ - // update minimap textures when new scan is ready - if (data->map_invalidated) - return data->texture; - - // create minimap and heightmap images in memory - core::dimension2d dim(data->map_size, data->map_size); - video::IImage *map_image = driver->createImage(video::ECF_A8R8G8B8, dim); - video::IImage *heightmap_image = driver->createImage(video::ECF_A8R8G8B8, dim); - video::IImage *minimap_image = driver->createImage(video::ECF_A8R8G8B8, - core::dimension2d(MINIMAP_MAX_SX, MINIMAP_MAX_SY)); - - // Blit MinimapPixels to images - if (data->is_radar) - blitMinimapPixelsToImageRadar(map_image); - else - blitMinimapPixelsToImageSurface(map_image, heightmap_image); - - map_image->copyToScaling(minimap_image); - map_image->drop(); - - video::IImage *minimap_mask = data->minimap_shape_round ? - data->minimap_mask_round : data->minimap_mask_square; - - if (minimap_mask) { - for (s16 y = 0; y < MINIMAP_MAX_SY; y++) - for (s16 x = 0; x < MINIMAP_MAX_SX; x++) { - const video::SColor &mask_col = minimap_mask->getPixel(x, y); - if (!mask_col.getAlpha()) - minimap_image->setPixel(x, y, video::SColor(0,0,0,0)); - } - } - - if (data->texture) - driver->removeTexture(data->texture); - if (data->heightmap_texture) - driver->removeTexture(data->heightmap_texture); - - data->texture = driver->addTexture("minimap__", minimap_image); - data->heightmap_texture = - driver->addTexture("minimap_heightmap__", heightmap_image); - minimap_image->drop(); - heightmap_image->drop(); - - data->map_invalidated = true; - - return data->texture; -} - -v3f Minimap::getYawVec() -{ - if (data->minimap_shape_round) { - return v3f( - std::cos(m_angle * core::DEGTORAD), - std::sin(m_angle * core::DEGTORAD), - 1.0); - } - - return v3f(1.0, 0.0, 1.0); -} - -scene::SMeshBuffer *Minimap::getMinimapMeshBuffer() -{ - scene::SMeshBuffer *buf = new scene::SMeshBuffer(); - buf->Vertices.set_used(4); - buf->Indices.set_used(6); - static const video::SColor c(255, 255, 255, 255); - - buf->Vertices[0] = video::S3DVertex(-1, -1, 0, 0, 0, 1, c, 0, 1); - buf->Vertices[1] = video::S3DVertex(-1, 1, 0, 0, 0, 1, c, 0, 0); - buf->Vertices[2] = video::S3DVertex( 1, 1, 0, 0, 0, 1, c, 1, 0); - buf->Vertices[3] = video::S3DVertex( 1, -1, 0, 0, 0, 1, c, 1, 1); - - buf->Indices[0] = 0; - buf->Indices[1] = 1; - buf->Indices[2] = 2; - buf->Indices[3] = 2; - buf->Indices[4] = 3; - buf->Indices[5] = 0; - - return buf; -} - -void Minimap::drawMinimap() -{ - video::ITexture *minimap_texture = getMinimapTexture(); - if (!minimap_texture) - return; - - updateActiveMarkers(); - v2u32 screensize = RenderingEngine::get_instance()->getWindowSize(); - const u32 size = 0.25 * screensize.Y; - - core::rect oldViewPort = driver->getViewPort(); - core::matrix4 oldProjMat = driver->getTransform(video::ETS_PROJECTION); - core::matrix4 oldViewMat = driver->getTransform(video::ETS_VIEW); - - driver->setViewPort(core::rect( - screensize.X - size - 10, 10, - screensize.X - 10, size + 10)); - driver->setTransform(video::ETS_PROJECTION, core::matrix4()); - driver->setTransform(video::ETS_VIEW, core::matrix4()); - - core::matrix4 matrix; - matrix.makeIdentity(); - - video::SMaterial &material = m_meshbuffer->getMaterial(); - material.setFlag(video::EMF_TRILINEAR_FILTER, true); - material.Lighting = false; - material.TextureLayer[0].Texture = minimap_texture; - material.TextureLayer[1].Texture = data->heightmap_texture; - - if (m_enable_shaders && !data->is_radar) { - u16 sid = m_shdrsrc->getShader("minimap_shader", 1, 1); - material.MaterialType = m_shdrsrc->getShaderInfo(sid).material; - } else { - material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; - } - - if (data->minimap_shape_round) - matrix.setRotationDegrees(core::vector3df(0, 0, 360 - m_angle)); - - // Draw minimap - driver->setTransform(video::ETS_WORLD, matrix); - driver->setMaterial(material); - driver->drawMeshBuffer(m_meshbuffer); - - // Draw overlay - video::ITexture *minimap_overlay = data->minimap_shape_round ? - data->minimap_overlay_round : data->minimap_overlay_square; - material.TextureLayer[0].Texture = minimap_overlay; - material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; - driver->setMaterial(material); - driver->drawMeshBuffer(m_meshbuffer); - - // Draw player marker on minimap - if (data->minimap_shape_round) { - matrix.setRotationDegrees(core::vector3df(0, 0, 0)); - } else { - matrix.setRotationDegrees(core::vector3df(0, 0, m_angle)); - } - - material.TextureLayer[0].Texture = data->player_marker; - driver->setTransform(video::ETS_WORLD, matrix); - driver->setMaterial(material); - driver->drawMeshBuffer(m_meshbuffer); - - // Reset transformations - driver->setTransform(video::ETS_VIEW, oldViewMat); - driver->setTransform(video::ETS_PROJECTION, oldProjMat); - driver->setViewPort(oldViewPort); - - // Draw player markers - v2s32 s_pos(screensize.X - size - 10, 10); - core::dimension2di imgsize(data->object_marker_red->getOriginalSize()); - core::rect img_rect(0, 0, imgsize.Width, imgsize.Height); - static const video::SColor col(255, 255, 255, 255); - static const video::SColor c[4] = {col, col, col, col}; - f32 sin_angle = std::sin(m_angle * core::DEGTORAD); - f32 cos_angle = std::cos(m_angle * core::DEGTORAD); - s32 marker_size2 = 0.025 * (float)size; - for (std::list::const_iterator - i = m_active_markers.begin(); - i != m_active_markers.end(); ++i) { - v2f posf = *i; - if (data->minimap_shape_round) { - f32 t1 = posf.X * cos_angle - posf.Y * sin_angle; - f32 t2 = posf.X * sin_angle + posf.Y * cos_angle; - posf.X = t1; - posf.Y = t2; - } - posf.X = (posf.X + 0.5) * (float)size; - posf.Y = (posf.Y + 0.5) * (float)size; - core::rect dest_rect( - s_pos.X + posf.X - marker_size2, - s_pos.Y + posf.Y - marker_size2, - s_pos.X + posf.X + marker_size2, - s_pos.Y + posf.Y + marker_size2); - driver->draw2DImage(data->object_marker_red, dest_rect, - img_rect, &dest_rect, &c[0], true); - } -} - -void Minimap::updateActiveMarkers() -{ - video::IImage *minimap_mask = data->minimap_shape_round ? - data->minimap_mask_round : data->minimap_mask_square; - - const std::list &nametags = client->getCamera()->getNametags(); - - m_active_markers.clear(); - - for (Nametag *nametag : nametags) { - v3s16 pos = floatToInt(nametag->parent_node->getPosition() + - intToFloat(client->getCamera()->getOffset(), BS), BS); - pos -= data->pos - v3s16(data->map_size / 2, - data->scan_height / 2, - data->map_size / 2); - if (pos.X < 0 || pos.X > data->map_size || - pos.Y < 0 || pos.Y > data->scan_height || - pos.Z < 0 || pos.Z > data->map_size) { - continue; - } - pos.X = ((float)pos.X / data->map_size) * MINIMAP_MAX_SX; - pos.Z = ((float)pos.Z / data->map_size) * MINIMAP_MAX_SY; - const video::SColor &mask_col = minimap_mask->getPixel(pos.X, pos.Z); - if (!mask_col.getAlpha()) { - continue; - } - - m_active_markers.emplace_back(((float)pos.X / (float)MINIMAP_MAX_SX) - 0.5, - (1.0 - (float)pos.Z / (float)MINIMAP_MAX_SY) - 0.5); - } -} - -//// -//// MinimapMapblock -//// - -void MinimapMapblock::getMinimapNodes(VoxelManipulator *vmanip, const v3s16 &pos) -{ - - for (s16 x = 0; x < MAP_BLOCKSIZE; x++) - for (s16 z = 0; z < MAP_BLOCKSIZE; z++) { - s16 air_count = 0; - bool surface_found = false; - MinimapPixel *mmpixel = &data[z * MAP_BLOCKSIZE + x]; - - for (s16 y = MAP_BLOCKSIZE -1; y >= 0; y--) { - v3s16 p(x, y, z); - MapNode n = vmanip->getNodeNoEx(pos + p); - if (!surface_found && n.getContent() != CONTENT_AIR) { - mmpixel->height = y; - mmpixel->n = n; - surface_found = true; - } else if (n.getContent() == CONTENT_AIR) { - air_count++; - } - } - - if (!surface_found) - mmpixel->n = MapNode(CONTENT_AIR); - - mmpixel->air_count = air_count; - } -} diff --git a/src/minimap.h b/src/minimap.h deleted file mode 100644 index 258d5330d..000000000 --- a/src/minimap.h +++ /dev/null @@ -1,163 +0,0 @@ -/* -Minetest -Copyright (C) 2010-2015 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#pragma once - -#include "irrlichttypes_extrabloated.h" -#include "util/thread.h" -#include "voxel.h" -#include -#include -#include - -class Client; -class ITextureSource; -class IShaderSource; - -#define MINIMAP_MAX_SX 512 -#define MINIMAP_MAX_SY 512 - -enum MinimapMode { - MINIMAP_MODE_OFF, - MINIMAP_MODE_SURFACEx1, - MINIMAP_MODE_SURFACEx2, - MINIMAP_MODE_SURFACEx4, - MINIMAP_MODE_RADARx1, - MINIMAP_MODE_RADARx2, - MINIMAP_MODE_RADARx4, - MINIMAP_MODE_COUNT, -}; - -enum MinimapShape { - MINIMAP_SHAPE_SQUARE, - MINIMAP_SHAPE_ROUND, -}; - -struct MinimapModeDef { - bool is_radar; - u16 scan_height; - u16 map_size; -}; - -struct MinimapPixel { - //! The topmost node that the minimap displays. - MapNode n; - u16 height; - u16 air_count; -}; - -struct MinimapMapblock { - void getMinimapNodes(VoxelManipulator *vmanip, const v3s16 &pos); - - MinimapPixel data[MAP_BLOCKSIZE * MAP_BLOCKSIZE]; -}; - -struct MinimapData { - bool is_radar; - MinimapMode mode; - v3s16 pos; - v3s16 old_pos; - u16 scan_height; - u16 map_size; - MinimapPixel minimap_scan[MINIMAP_MAX_SX * MINIMAP_MAX_SY]; - bool map_invalidated; - bool minimap_shape_round; - video::IImage *minimap_mask_round = nullptr; - video::IImage *minimap_mask_square = nullptr; - video::ITexture *texture = nullptr; - video::ITexture *heightmap_texture = nullptr; - video::ITexture *minimap_overlay_round = nullptr; - video::ITexture *minimap_overlay_square = nullptr; - video::ITexture *player_marker = nullptr; - video::ITexture *object_marker_red = nullptr; -}; - -struct QueuedMinimapUpdate { - v3s16 pos; - MinimapMapblock *data = nullptr; -}; - -class MinimapUpdateThread : public UpdateThread { -public: - MinimapUpdateThread() : UpdateThread("Minimap") {} - virtual ~MinimapUpdateThread(); - - void getMap(v3s16 pos, s16 size, s16 height); - void enqueueBlock(v3s16 pos, MinimapMapblock *data); - bool pushBlockUpdate(v3s16 pos, MinimapMapblock *data); - bool popBlockUpdate(QueuedMinimapUpdate *update); - - MinimapData *data = nullptr; - -protected: - virtual void doUpdate(); - -private: - std::mutex m_queue_mutex; - std::deque m_update_queue; - std::map m_blocks_cache; -}; - -class Minimap { -public: - Minimap(Client *client); - ~Minimap(); - - void addBlock(v3s16 pos, MinimapMapblock *data); - - v3f getYawVec(); - - void setPos(v3s16 pos); - v3s16 getPos() const { return data->pos; } - void setAngle(f32 angle); - f32 getAngle() const { return m_angle; } - void setMinimapMode(MinimapMode mode); - MinimapMode getMinimapMode() const { return data->mode; } - void toggleMinimapShape(); - void setMinimapShape(MinimapShape shape); - MinimapShape getMinimapShape(); - - - video::ITexture *getMinimapTexture(); - - void blitMinimapPixelsToImageRadar(video::IImage *map_image); - void blitMinimapPixelsToImageSurface(video::IImage *map_image, - video::IImage *heightmap_image); - - scene::SMeshBuffer *getMinimapMeshBuffer(); - - void updateActiveMarkers(); - void drawMinimap(); - - video::IVideoDriver *driver; - Client* client; - MinimapData *data; - -private: - ITextureSource *m_tsrc; - IShaderSource *m_shdrsrc; - const NodeDefManager *m_ndef; - MinimapUpdateThread *m_minimap_update_thread; - scene::SMeshBuffer *m_meshbuffer; - bool m_enable_shaders; - u16 m_surface_mode_scan_height; - f32 m_angle; - std::mutex m_mutex; - std::list m_active_markers; -}; diff --git a/src/network/clientopcodes.h b/src/network/clientopcodes.h index 57846f095..d03dc457d 100644 --- a/src/network/clientopcodes.h +++ b/src/network/clientopcodes.h @@ -20,7 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once -#include "client.h" +#include "client/client.h" #include "networkprotocol.h" class NetworkPacket; diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 1899b496e..1be7d4eeb 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -17,15 +17,15 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "client.h" +#include "client/client.h" #include "util/base64.h" #include "chatmessage.h" -#include "clientmedia.h" +#include "client/clientmedia.h" #include "log.h" #include "map.h" #include "mapsector.h" -#include "minimap.h" +#include "client/minimap.h" #include "modchannels.h" #include "nodedef.h" #include "serialization.h" diff --git a/src/nodedef.cpp b/src/nodedef.cpp index c3f2ccd60..70974a572 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -21,9 +21,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "itemdef.h" #ifndef SERVER -#include "mesh.h" -#include "shader.h" -#include "client.h" +#include "client/mesh.h" +#include "client/shader.h" +#include "client/client.h" #include "client/renderingengine.h" #include "client/tile.h" #include diff --git a/src/particles.cpp b/src/particles.cpp deleted file mode 100644 index 25cfa081e..000000000 --- a/src/particles.cpp +++ /dev/null @@ -1,684 +0,0 @@ -/* -Minetest -Copyright (C) 2013 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include "particles.h" -#include -#include "client.h" -#include "collision.h" -#include "client/clientevent.h" -#include "client/renderingengine.h" -#include "util/numeric.h" -#include "light.h" -#include "environment.h" -#include "clientmap.h" -#include "mapnode.h" -#include "nodedef.h" -#include "client.h" -#include "settings.h" - -/* - Utility -*/ - -v3f random_v3f(v3f min, v3f max) -{ - return v3f( rand()/(float)RAND_MAX*(max.X-min.X)+min.X, - rand()/(float)RAND_MAX*(max.Y-min.Y)+min.Y, - rand()/(float)RAND_MAX*(max.Z-min.Z)+min.Z); -} - -Particle::Particle( - IGameDef *gamedef, - LocalPlayer *player, - ClientEnvironment *env, - v3f pos, - v3f velocity, - v3f acceleration, - float expirationtime, - float size, - bool collisiondetection, - bool collision_removal, - bool object_collision, - bool vertical, - video::ITexture *texture, - v2f texpos, - v2f texsize, - const struct TileAnimationParams &anim, - u8 glow, - video::SColor color -): - scene::ISceneNode(RenderingEngine::get_scene_manager()->getRootSceneNode(), - RenderingEngine::get_scene_manager()) -{ - // Misc - m_gamedef = gamedef; - m_env = env; - - // 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); - m_texpos = texpos; - m_texsize = texsize; - m_animation = anim; - - // Color - m_base_color = color; - m_color = color; - - // Particle related - m_pos = pos; - m_velocity = velocity; - m_acceleration = acceleration; - m_expiration = expirationtime; - m_player = player; - m_size = size; - m_collisiondetection = collisiondetection; - m_collision_removal = collision_removal; - m_object_collision = object_collision; - m_vertical = vertical; - m_glow = glow; - - // Irrlicht stuff - m_collisionbox = aabb3f - (-size/2,-size/2,-size/2,size/2,size/2,size/2); - this->setAutomaticCulling(scene::EAC_OFF); - - // Init lighting - updateLight(); - - // Init model - updateVertices(); -} - -void Particle::OnRegisterSceneNode() -{ - if (IsVisible) - SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT_EFFECT); - - ISceneNode::OnRegisterSceneNode(); -} - -void Particle::render() -{ - video::IVideoDriver* driver = SceneManager->getVideoDriver(); - driver->setMaterial(m_material); - driver->setTransform(video::ETS_WORLD, AbsoluteTransformation); - - u16 indices[] = {0,1,2, 2,3,0}; - driver->drawVertexPrimitiveList(m_vertices, 4, - indices, 2, video::EVT_STANDARD, - scene::EPT_TRIANGLES, video::EIT_16BIT); -} - -void Particle::step(float dtime) -{ - m_time += dtime; - if (m_collisiondetection) { - aabb3f box = m_collisionbox; - v3f p_pos = m_pos * BS; - v3f p_velocity = m_velocity * BS; - 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; - } else { - m_pos = p_pos / BS; - m_velocity = p_velocity / BS; - } - } else { - 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; - m_animation.determineParams( - m_material.getTexture(0)->getSize(), - &frame_count, &frame_length_i, NULL); - float frame_length = frame_length_i / 1000.0; - while (m_animation_time > frame_length) { - m_animation_frame++; - m_animation_time -= frame_length; - } - } - - // Update lighting - updateLight(); - - // Update model - updateVertices(); -} - -void Particle::updateLight() -{ - u8 light = 0; - bool pos_ok; - - v3s16 p = v3s16( - floor(m_pos.X+0.5), - floor(m_pos.Y+0.5), - floor(m_pos.Z+0.5) - ); - MapNode n = m_env->getClientMap().getNodeNoEx(p, &pos_ok); - if (pos_ok) - light = n.getLightBlend(m_env->getDayNightRatio(), m_gamedef->ndef()); - else - light = blend_light(m_env->getDayNightRatio(), LIGHT_SUN, 0); - - u8 m_light = decode_light(light + m_glow); - m_color.set(255, - m_light * m_base_color.getRed() / 255, - m_light * m_base_color.getGreen() / 255, - m_light * m_base_color.getBlue() / 255); -} - -void Particle::updateVertices() -{ - f32 tx0, tx1, ty0, ty1; - - if (m_animation.type != TAT_NONE) { - const v2u32 texsize = m_material.getTexture(0)->getSize(); - v2f texcoord, framesize_f; - v2u32 framesize; - texcoord = m_animation.getTextureCoords(texsize, m_animation_frame); - m_animation.determineParams(texsize, NULL, NULL, &framesize); - framesize_f = v2f(framesize.X / (float) texsize.X, framesize.Y / (float) texsize.Y); - - tx0 = m_texpos.X + texcoord.X; - tx1 = m_texpos.X + texcoord.X + framesize_f.X * m_texsize.X; - ty0 = m_texpos.Y + texcoord.Y; - ty1 = m_texpos.Y + texcoord.Y + framesize_f.Y * m_texsize.Y; - } else { - tx0 = m_texpos.X; - tx1 = m_texpos.X + m_texsize.X; - ty0 = m_texpos.Y; - ty1 = m_texpos.Y + m_texsize.Y; - } - - m_vertices[0] = video::S3DVertex(-m_size / 2, -m_size / 2, - 0, 0, 0, 0, m_color, tx0, ty1); - m_vertices[1] = video::S3DVertex(m_size / 2, -m_size / 2, - 0, 0, 0, 0, m_color, tx1, ty1); - m_vertices[2] = video::S3DVertex(m_size / 2, m_size / 2, - 0, 0, 0, 0, m_color, tx1, ty0); - m_vertices[3] = video::S3DVertex(-m_size / 2, m_size / 2, - 0, 0, 0, 0, m_color, tx0, ty0); - - v3s16 camera_offset = m_env->getCameraOffset(); - for (video::S3DVertex &vertex : m_vertices) { - if (m_vertical) { - v3f ppos = m_player->getPosition()/BS; - vertex.Pos.rotateXZBy(std::atan2(ppos.Z - m_pos.Z, ppos.X - m_pos.X) / - core::DEGTORAD + 90); - } else { - vertex.Pos.rotateYZBy(m_player->getPitch()); - vertex.Pos.rotateXZBy(m_player->getYaw()); - } - m_box.addInternalPoint(vertex.Pos); - vertex.Pos += m_pos*BS - intToFloat(camera_offset, BS); - } -} - -/* - ParticleSpawner -*/ - -ParticleSpawner::ParticleSpawner( - IGameDef *gamedef, - LocalPlayer *player, - u16 amount, - float time, - v3f minpos, v3f maxpos, - v3f minvel, v3f maxvel, - v3f minacc, v3f maxacc, - float minexptime, float maxexptime, - float minsize, float maxsize, - bool collisiondetection, - bool collision_removal, - bool object_collision, - u16 attached_id, - bool vertical, - video::ITexture *texture, - u32 id, - const struct TileAnimationParams &anim, - u8 glow, - ParticleManager *p_manager -): - m_particlemanager(p_manager) -{ - m_gamedef = gamedef; - m_player = player; - m_amount = amount; - m_spawntime = time; - m_minpos = minpos; - m_maxpos = maxpos; - m_minvel = minvel; - m_maxvel = maxvel; - m_minacc = minacc; - m_maxacc = maxacc; - m_minexptime = minexptime; - m_maxexptime = maxexptime; - m_minsize = minsize; - m_maxsize = maxsize; - m_collisiondetection = collisiondetection; - m_collision_removal = collision_removal; - m_object_collision = object_collision; - m_attached_id = attached_id; - m_vertical = vertical; - m_texture = texture; - m_time = 0; - m_animation = anim; - m_glow = glow; - - for (u16 i = 0; i<=m_amount; i++) - { - float spawntime = (float)rand()/(float)RAND_MAX*m_spawntime; - m_spawntimes.push_back(spawntime); - } -} - -void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius, - bool is_attached, const v3f &attached_pos, float attached_yaw) -{ - v3f ppos = m_player->getPosition() / BS; - v3f pos = random_v3f(m_minpos, m_maxpos); - - // Need to apply this first or the following check - // will be wrong for attached spawners - if (is_attached) { - pos.rotateXZBy(attached_yaw); - pos += attached_pos; - } - - if (pos.getDistanceFrom(ppos) > radius) - return; - - v3f vel = random_v3f(m_minvel, m_maxvel); - v3f acc = random_v3f(m_minacc, m_maxacc); - - if (is_attached) { - // Apply attachment yaw - vel.rotateXZBy(attached_yaw); - acc.rotateXZBy(attached_yaw); - } - - float exptime = rand() / (float)RAND_MAX - * (m_maxexptime - m_minexptime) - + m_minexptime; - float size = rand() / (float)RAND_MAX - * (m_maxsize - m_minsize) - + m_minsize; - - m_particlemanager->addParticle(new Particle( - m_gamedef, - m_player, - env, - pos, - vel, - acc, - exptime, - size, - m_collisiondetection, - m_collision_removal, - m_object_collision, - m_vertical, - m_texture, - v2f(0.0, 0.0), - v2f(1.0, 1.0), - m_animation, - m_glow - )); -} - -void ParticleSpawner::step(float dtime, ClientEnvironment* env) -{ - m_time += dtime; - - static thread_local const float radius = - g_settings->getS16("max_block_send_distance") * MAP_BLOCKSIZE; - - bool unloaded = false; - bool is_attached = false; - v3f attached_pos = v3f(0,0,0); - float attached_yaw = 0; - if (m_attached_id != 0) { - if (ClientActiveObject *attached = env->getActiveObject(m_attached_id)) { - attached_pos = attached->getPosition() / BS; - attached_yaw = attached->getYaw(); - is_attached = true; - } else { - unloaded = true; - } - } - - if (m_spawntime != 0) { - // Spawner exists for a predefined timespan - for (std::vector::iterator i = m_spawntimes.begin(); - i != m_spawntimes.end();) { - if ((*i) <= m_time && m_amount > 0) { - m_amount--; - - // Pretend to, but don't actually spawn a particle if it is - // attached to an unloaded object or distant from player. - if (!unloaded) - spawnParticle(env, radius, is_attached, attached_pos, attached_yaw); - - i = m_spawntimes.erase(i); - } else { - ++i; - } - } - } else { - // Spawner exists for an infinity timespan, spawn on a per-second base - - // Skip this step if attached to an unloaded object - if (unloaded) - return; - - for (int i = 0; i <= m_amount; i++) { - if (rand() / (float)RAND_MAX < dtime) - spawnParticle(env, radius, is_attached, attached_pos, attached_yaw); - } - } -} - - -ParticleManager::ParticleManager(ClientEnvironment* env) : - m_env(env) -{} - -ParticleManager::~ParticleManager() -{ - clearAll(); -} - -void ParticleManager::step(float dtime) -{ - stepParticles (dtime); - stepSpawners (dtime); -} - -void ParticleManager::stepSpawners (float dtime) -{ - MutexAutoLock lock(m_spawner_list_lock); - for (std::map::iterator i = - m_particle_spawners.begin(); - i != m_particle_spawners.end();) - { - if (i->second->get_expired()) - { - delete i->second; - m_particle_spawners.erase(i++); - } - else - { - i->second->step(dtime, m_env); - ++i; - } - } -} - -void ParticleManager::stepParticles (float dtime) -{ - MutexAutoLock lock(m_particle_list_lock); - for(std::vector::iterator i = m_particles.begin(); - i != m_particles.end();) - { - if ((*i)->get_expired()) - { - (*i)->remove(); - delete *i; - i = m_particles.erase(i); - } - else - { - (*i)->step(dtime); - ++i; - } - } -} - -void ParticleManager::clearAll () -{ - MutexAutoLock lock(m_spawner_list_lock); - MutexAutoLock lock2(m_particle_list_lock); - for(std::map::iterator i = - m_particle_spawners.begin(); - i != m_particle_spawners.end();) - { - delete i->second; - m_particle_spawners.erase(i++); - } - - for(std::vector::iterator i = - m_particles.begin(); - i != m_particles.end();) - { - (*i)->remove(); - delete *i; - i = m_particles.erase(i); - } -} - -void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client, - LocalPlayer *player) -{ - switch (event->type) { - case CE_DELETE_PARTICLESPAWNER: { - MutexAutoLock lock(m_spawner_list_lock); - if (m_particle_spawners.find(event->delete_particlespawner.id) != - m_particle_spawners.end()) { - delete m_particle_spawners.find(event->delete_particlespawner.id)->second; - m_particle_spawners.erase(event->delete_particlespawner.id); - } - // no allocated memory in delete event - break; - } - case CE_ADD_PARTICLESPAWNER: { - { - MutexAutoLock lock(m_spawner_list_lock); - if (m_particle_spawners.find(event->add_particlespawner.id) != - m_particle_spawners.end()) { - delete m_particle_spawners.find(event->add_particlespawner.id)->second; - m_particle_spawners.erase(event->add_particlespawner.id); - } - } - - video::ITexture *texture = - client->tsrc()->getTextureForMesh(*(event->add_particlespawner.texture)); - - ParticleSpawner *toadd = new ParticleSpawner(client, player, - event->add_particlespawner.amount, - event->add_particlespawner.spawntime, - *event->add_particlespawner.minpos, - *event->add_particlespawner.maxpos, - *event->add_particlespawner.minvel, - *event->add_particlespawner.maxvel, - *event->add_particlespawner.minacc, - *event->add_particlespawner.maxacc, - event->add_particlespawner.minexptime, - event->add_particlespawner.maxexptime, - event->add_particlespawner.minsize, - event->add_particlespawner.maxsize, - event->add_particlespawner.collisiondetection, - event->add_particlespawner.collision_removal, - event->add_particlespawner.object_collision, - event->add_particlespawner.attached_id, - event->add_particlespawner.vertical, - texture, - event->add_particlespawner.id, - event->add_particlespawner.animation, - event->add_particlespawner.glow, - this); - - /* delete allocated content of event */ - delete event->add_particlespawner.minpos; - delete event->add_particlespawner.maxpos; - delete event->add_particlespawner.minvel; - delete event->add_particlespawner.maxvel; - delete event->add_particlespawner.minacc; - delete event->add_particlespawner.texture; - delete event->add_particlespawner.maxacc; - - { - MutexAutoLock lock(m_spawner_list_lock); - m_particle_spawners.insert( - std::pair( - event->add_particlespawner.id, - toadd)); - } - break; - } - case CE_SPAWN_PARTICLE: { - video::ITexture *texture = - client->tsrc()->getTextureForMesh(*(event->spawn_particle.texture)); - - Particle *toadd = new Particle(client, player, m_env, - *event->spawn_particle.pos, - *event->spawn_particle.vel, - *event->spawn_particle.acc, - event->spawn_particle.expirationtime, - event->spawn_particle.size, - event->spawn_particle.collisiondetection, - event->spawn_particle.collision_removal, - event->spawn_particle.object_collision, - event->spawn_particle.vertical, - texture, - v2f(0.0, 0.0), - v2f(1.0, 1.0), - event->spawn_particle.animation, - event->spawn_particle.glow); - - addParticle(toadd); - - delete event->spawn_particle.pos; - delete event->spawn_particle.vel; - delete event->spawn_particle.acc; - delete event->spawn_particle.texture; - - break; - } - default: break; - } -} - -// The final burst of particles when a node is finally dug, *not* particles -// spawned during the digging of a node. - -void ParticleManager::addDiggingParticles(IGameDef* gamedef, - LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f) -{ - // No particles for "airlike" nodes - if (f.drawtype == NDT_AIRLIKE) - return; - - for (u16 j = 0; j < 16; j++) { - addNodeParticle(gamedef, player, pos, n, f); - } -} - -// During the digging of a node particles are spawned individually by this -// function, called from Game::handleDigging() in game.cpp. - -void ParticleManager::addNodeParticle(IGameDef* gamedef, - LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f) -{ - // No particles for "airlike" nodes - if (f.drawtype == NDT_AIRLIKE) - return; - - // Texture - u8 texid = myrand_range(0, 5); - const TileLayer &tile = f.tiles[texid].layers[0]; - video::ITexture *texture; - struct TileAnimationParams anim; - anim.type = TAT_NONE; - - // Only use first frame of animated texture - if (tile.material_flags & MATERIAL_FLAG_ANIMATION) - texture = (*tile.frames)[0].texture; - else - texture = tile.texture; - - float size = (rand() % 8) / 64.0f; - float visual_size = BS * size; - if (tile.scale) - size /= tile.scale; - v2f texsize(size * 2.0f, size * 2.0f); - v2f texpos; - texpos.X = (rand() % 64) / 64.0f - texsize.X; - texpos.Y = (rand() % 64) / 64.0f - texsize.Y; - - // Physics - v3f velocity( - (rand() % 150) / 50.0f - 1.5f, - (rand() % 150) / 50.0f, - (rand() % 150) / 50.0f - 1.5f - ); - v3f acceleration( - 0.0f, - -player->movement_gravity * player->physics_override_gravity / BS, - 0.0f - ); - v3f particlepos = 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 - ); - - video::SColor color; - if (tile.has_color) - color = tile.color; - else - n.getColor(f, &color); - - Particle* toadd = new Particle( - gamedef, - player, - m_env, - particlepos, - velocity, - acceleration, - (rand() % 100) / 100.0f, // expiration time - visual_size, - true, - false, - false, - false, - texture, - texpos, - texsize, - anim, - 0, - color); - - addParticle(toadd); -} - -void ParticleManager::addParticle(Particle* toadd) -{ - MutexAutoLock lock(m_particle_list_lock); - m_particles.push_back(toadd); -} diff --git a/src/particles.h b/src/particles.h deleted file mode 100644 index 3392e7e95..000000000 --- a/src/particles.h +++ /dev/null @@ -1,223 +0,0 @@ -/* -Minetest -Copyright (C) 2013 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#pragma once - -#include -#include "irrlichttypes_extrabloated.h" -#include "client/tile.h" -#include "localplayer.h" -#include "tileanimation.h" - -struct ClientEvent; -class ParticleManager; -class ClientEnvironment; -struct MapNode; -struct ContentFeatures; - -class Particle : public scene::ISceneNode -{ - public: - Particle( - IGameDef* gamedef, - LocalPlayer *player, - ClientEnvironment *env, - v3f pos, - v3f velocity, - v3f acceleration, - float expirationtime, - float size, - bool collisiondetection, - bool collision_removal, - bool object_collision, - bool vertical, - video::ITexture *texture, - v2f texpos, - v2f texsize, - const struct TileAnimationParams &anim, - u8 glow, - video::SColor color = video::SColor(0xFFFFFFFF) - ); - ~Particle() = default; - - virtual const aabb3f &getBoundingBox() const - { - return m_box; - } - - virtual u32 getMaterialCount() const - { - return 1; - } - - virtual video::SMaterial& getMaterial(u32 i) - { - return m_material; - } - - virtual void OnRegisterSceneNode(); - virtual void render(); - - void step(float dtime); - - bool get_expired () - { return m_expiration < m_time; } - -private: - void updateLight(); - void updateVertices(); - - video::S3DVertex m_vertices[4]; - float m_time = 0.0f; - float m_expiration; - - ClientEnvironment *m_env; - IGameDef *m_gamedef; - aabb3f m_box; - aabb3f m_collisionbox; - video::SMaterial m_material; - v2f m_texpos; - v2f m_texsize; - v3f m_pos; - v3f m_velocity; - v3f m_acceleration; - LocalPlayer *m_player; - float m_size; - //! Color without lighting - video::SColor m_base_color; - //! Final rendered color - video::SColor m_color; - bool m_collisiondetection; - bool m_collision_removal; - bool m_object_collision; - bool m_vertical; - v3s16 m_camera_offset; - struct TileAnimationParams m_animation; - float m_animation_time = 0.0f; - int m_animation_frame = 0; - u8 m_glow; -}; - -class ParticleSpawner -{ -public: - ParticleSpawner(IGameDef* gamedef, - LocalPlayer *player, - u16 amount, - float time, - v3f minp, v3f maxp, - v3f minvel, v3f maxvel, - v3f minacc, v3f maxacc, - float minexptime, float maxexptime, - float minsize, float maxsize, - bool collisiondetection, - bool collision_removal, - bool object_collision, - u16 attached_id, - bool vertical, - video::ITexture *texture, - u32 id, - const struct TileAnimationParams &anim, u8 glow, - ParticleManager* p_manager); - - ~ParticleSpawner() = default; - - void step(float dtime, ClientEnvironment *env); - - bool get_expired () - { return (m_amount <= 0) && m_spawntime != 0; } - -private: - void spawnParticle(ClientEnvironment *env, float radius, - bool is_attached, const v3f &attached_pos, - float attached_yaw); - - ParticleManager *m_particlemanager; - float m_time; - IGameDef *m_gamedef; - LocalPlayer *m_player; - u16 m_amount; - float m_spawntime; - v3f m_minpos; - v3f m_maxpos; - v3f m_minvel; - v3f m_maxvel; - v3f m_minacc; - v3f m_maxacc; - float m_minexptime; - float m_maxexptime; - float m_minsize; - float m_maxsize; - video::ITexture *m_texture; - std::vector m_spawntimes; - bool m_collisiondetection; - bool m_collision_removal; - bool m_object_collision; - bool m_vertical; - u16 m_attached_id; - struct TileAnimationParams m_animation; - u8 m_glow; -}; - -/** - * Class doing particle as well as their spawners handling - */ -class ParticleManager -{ -friend class ParticleSpawner; -public: - ParticleManager(ClientEnvironment* env); - ~ParticleManager(); - - void step (float dtime); - - void handleParticleEvent(ClientEvent *event, Client *client, - LocalPlayer *player); - - void addDiggingParticles(IGameDef *gamedef, LocalPlayer *player, v3s16 pos, - const MapNode &n, const ContentFeatures &f); - - void addNodeParticle(IGameDef *gamedef, LocalPlayer *player, v3s16 pos, - const MapNode &n, const ContentFeatures &f); - - u32 getSpawnerId() const - { - for (u32 id = 0;; ++id) { // look for unused particlespawner id - if (m_particle_spawners.find(id) == m_particle_spawners.end()) - return id; - } - } - -protected: - void addParticle(Particle* toadd); - -private: - - void stepParticles (float dtime); - void stepSpawners (float dtime); - - void clearAll (); - - std::vector m_particles; - std::map m_particle_spawners; - - ClientEnvironment* m_env; - std::mutex m_particle_list_lock; - std::mutex m_spawner_list_lock; -}; diff --git a/src/script/cpp_api/s_base.cpp b/src/script/cpp_api/s_base.cpp index d98359d20..bf89f748c 100644 --- a/src/script/cpp_api/s_base.cpp +++ b/src/script/cpp_api/s_base.cpp @@ -29,7 +29,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/string.h" #include "server.h" #ifndef SERVER -#include "client.h" +#include "client/client.h" #endif diff --git a/src/script/cpp_api/s_client.cpp b/src/script/cpp_api/s_client.cpp index 597883c2f..f2cc9730b 100644 --- a/src/script/cpp_api/s_client.cpp +++ b/src/script/cpp_api/s_client.cpp @@ -20,7 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "s_client.h" #include "s_internal.h" -#include "client.h" +#include "client/client.h" #include "common/c_converter.h" #include "common/c_content.h" #include "s_item.h" diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp index e87f16ddd..e9067a54c 100644 --- a/src/script/cpp_api/s_security.cpp +++ b/src/script/cpp_api/s_security.cpp @@ -22,7 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "filesys.h" #include "porting.h" #include "server.h" -#include "client.h" +#include "client/client.h" #include "settings.h" #include diff --git a/src/script/lua_api/l_camera.cpp b/src/script/lua_api/l_camera.cpp index 326cc6d53..462006777 100644 --- a/src/script/lua_api/l_camera.cpp +++ b/src/script/lua_api/l_camera.cpp @@ -21,9 +21,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include "script/common/c_converter.h" #include "l_internal.h" -#include "content_cao.h" -#include "camera.h" -#include "client.h" +#include "client/content_cao.h" +#include "client/camera.h" +#include "client/client.h" LuaCamera::LuaCamera(Camera *m) : m_camera(m) { diff --git a/src/script/lua_api/l_client.cpp b/src/script/lua_api/l_client.cpp index 72826775b..8a5867a32 100644 --- a/src/script/lua_api/l_client.cpp +++ b/src/script/lua_api/l_client.cpp @@ -20,10 +20,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "l_client.h" #include "chatmessage.h" -#include "client.h" +#include "client/client.h" #include "client/clientevent.h" #include "client/sound.h" -#include "clientenvironment.h" +#include "client/clientenvironment.h" #include "common/c_content.h" #include "common/c_converter.h" #include "cpp_api/s_base.h" diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index 1e5149f7a..ba2be0cb5 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -40,7 +40,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "face_position_cache.h" #include "remoteplayer.h" #ifndef SERVER -#include "client.h" +#include "client/client.h" #endif struct EnumString ModApiEnvMod::es_ClearObjectsMode[] = diff --git a/src/script/lua_api/l_localplayer.cpp b/src/script/lua_api/l_localplayer.cpp index 492422d92..7444d0e88 100644 --- a/src/script/lua_api/l_localplayer.cpp +++ b/src/script/lua_api/l_localplayer.cpp @@ -20,7 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "l_localplayer.h" #include "l_internal.h" #include "script/common/c_converter.h" -#include "localplayer.h" +#include "client/localplayer.h" #include "hud.h" #include "common/c_content.h" diff --git a/src/script/lua_api/l_minimap.cpp b/src/script/lua_api/l_minimap.cpp index b59e79095..5fba76eb8 100644 --- a/src/script/lua_api/l_minimap.cpp +++ b/src/script/lua_api/l_minimap.cpp @@ -21,8 +21,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_minimap.h" #include "lua_api/l_internal.h" #include "common/c_converter.h" -#include "client.h" -#include "minimap.h" +#include "client/client.h" +#include "client/minimap.h" #include "settings.h" LuaMinimap::LuaMinimap(Minimap *m) : m_minimap(m) diff --git a/src/script/lua_api/l_particles.cpp b/src/script/lua_api/l_particles.cpp index 7783e5910..340903ebf 100644 --- a/src/script/lua_api/l_particles.cpp +++ b/src/script/lua_api/l_particles.cpp @@ -23,7 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "common/c_converter.h" #include "common/c_content.h" #include "server.h" -#include "particles.h" +#include "client/particles.h" // add_particle({pos=, velocity=, acceleration=, expirationtime=, // size=, collisiondetection=, collision_removal=, object_collision=, diff --git a/src/script/lua_api/l_particles_local.cpp b/src/script/lua_api/l_particles_local.cpp index d5c412556..3c7a821ca 100644 --- a/src/script/lua_api/l_particles_local.cpp +++ b/src/script/lua_api/l_particles_local.cpp @@ -23,8 +23,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "common/c_converter.h" #include "lua_api/l_internal.h" #include "lua_api/l_object.h" -#include "particles.h" -#include "client.h" +#include "client/particles.h" +#include "client/client.h" #include "client/clientevent.h" int ModApiParticlesLocal::l_add_particle(lua_State *L) diff --git a/src/script/scripting_client.cpp b/src/script/scripting_client.cpp index a6511ffd5..86e5f2874 100644 --- a/src/script/scripting_client.cpp +++ b/src/script/scripting_client.cpp @@ -19,7 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "scripting_client.h" -#include "client.h" +#include "client/client.h" #include "cpp_api/s_internal.h" #include "lua_api/l_client.h" #include "lua_api/l_env.h" diff --git a/src/shader.cpp b/src/shader.cpp deleted file mode 100644 index 3b49a36ba..000000000 --- a/src/shader.cpp +++ /dev/null @@ -1,873 +0,0 @@ -/* -Minetest -Copyright (C) 2013 celeron55, Perttu Ahola -Copyright (C) 2013 Kahrl - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include -#include -#include "shader.h" -#include "irrlichttypes_extrabloated.h" -#include "debug.h" -#include "filesys.h" -#include "util/container.h" -#include "util/thread.h" -#include "settings.h" -#include -#include -#include -#include -#include -#include "client/renderingengine.h" -#include "EShaderTypes.h" -#include "log.h" -#include "gamedef.h" -#include "client/tile.h" - -/* - A cache from shader name to shader path -*/ -MutexedMap g_shadername_to_path_cache; - -/* - Gets the path to a shader by first checking if the file - name_of_shader/filename - exists in shader_path and if not, using the data path. - - If not found, returns "". - - Utilizes a thread-safe cache. -*/ -std::string getShaderPath(const std::string &name_of_shader, - const std::string &filename) -{ - std::string combined = name_of_shader + DIR_DELIM + filename; - std::string fullpath; - /* - Check from cache - */ - bool incache = g_shadername_to_path_cache.get(combined, &fullpath); - if(incache) - return fullpath; - - /* - Check from shader_path - */ - std::string shader_path = g_settings->get("shader_path"); - if (!shader_path.empty()) { - std::string testpath = shader_path + DIR_DELIM + combined; - if(fs::PathExists(testpath)) - fullpath = testpath; - } - - /* - Check from default data directory - */ - if (fullpath.empty()) { - std::string rel_path = std::string("client") + DIR_DELIM - + "shaders" + DIR_DELIM - + name_of_shader + DIR_DELIM - + filename; - std::string testpath = porting::path_share + DIR_DELIM + rel_path; - if(fs::PathExists(testpath)) - fullpath = testpath; - } - - // Add to cache (also an empty result is cached) - g_shadername_to_path_cache.set(combined, fullpath); - - // Finally return it - return fullpath; -} - -/* - SourceShaderCache: A cache used for storing source shaders. -*/ - -class SourceShaderCache -{ -public: - void insert(const std::string &name_of_shader, const std::string &filename, - const std::string &program, bool prefer_local) - { - std::string combined = name_of_shader + DIR_DELIM + filename; - // Try to use local shader instead if asked to - if(prefer_local){ - std::string path = getShaderPath(name_of_shader, filename); - if(!path.empty()){ - std::string p = readFile(path); - if (!p.empty()) { - m_programs[combined] = p; - return; - } - } - } - m_programs[combined] = program; - } - - std::string get(const std::string &name_of_shader, - const std::string &filename) - { - std::string combined = name_of_shader + DIR_DELIM + filename; - StringMap::iterator n = m_programs.find(combined); - if (n != m_programs.end()) - return n->second; - return ""; - } - - // Primarily fetches from cache, secondarily tries to read from filesystem - std::string getOrLoad(const std::string &name_of_shader, - const std::string &filename) - { - std::string combined = name_of_shader + DIR_DELIM + filename; - StringMap::iterator n = m_programs.find(combined); - if (n != m_programs.end()) - return n->second; - std::string path = getShaderPath(name_of_shader, filename); - if (path.empty()) { - infostream << "SourceShaderCache::getOrLoad(): No path found for \"" - << combined << "\"" << std::endl; - return ""; - } - infostream << "SourceShaderCache::getOrLoad(): Loading path \"" - << path << "\"" << std::endl; - std::string p = readFile(path); - if (!p.empty()) { - m_programs[combined] = p; - return p; - } - return ""; - } -private: - StringMap m_programs; - - std::string readFile(const std::string &path) - { - std::ifstream is(path.c_str(), std::ios::binary); - if(!is.is_open()) - return ""; - std::ostringstream tmp_os; - tmp_os << is.rdbuf(); - return tmp_os.str(); - } -}; - - -/* - ShaderCallback: Sets constants that can be used in shaders -*/ - -class ShaderCallback : public video::IShaderConstantSetCallBack -{ - std::vector m_setters; - -public: - ShaderCallback(const std::vector &factories) - { - for (IShaderConstantSetterFactory *factory : factories) - m_setters.push_back(factory->create()); - } - - ~ShaderCallback() - { - for (IShaderConstantSetter *setter : m_setters) - delete setter; - } - - virtual void OnSetConstants(video::IMaterialRendererServices *services, s32 userData) - { - video::IVideoDriver *driver = services->getVideoDriver(); - sanity_check(driver != NULL); - - bool is_highlevel = userData; - - for (IShaderConstantSetter *setter : m_setters) - setter->onSetConstants(services, is_highlevel); - } -}; - - -/* - MainShaderConstantSetter: Set basic constants required for almost everything -*/ - -class MainShaderConstantSetter : public IShaderConstantSetter -{ - CachedVertexShaderSetting m_world_view_proj; - CachedVertexShaderSetting m_world; - -public: - MainShaderConstantSetter() : - m_world_view_proj("mWorldViewProj"), - m_world("mWorld") - {} - ~MainShaderConstantSetter() = default; - - virtual void onSetConstants(video::IMaterialRendererServices *services, - bool is_highlevel) - { - video::IVideoDriver *driver = services->getVideoDriver(); - sanity_check(driver); - - // Set clip matrix - core::matrix4 worldViewProj; - worldViewProj = driver->getTransform(video::ETS_PROJECTION); - worldViewProj *= driver->getTransform(video::ETS_VIEW); - worldViewProj *= driver->getTransform(video::ETS_WORLD); - if (is_highlevel) - m_world_view_proj.set(*reinterpret_cast(worldViewProj.pointer()), services); - else - services->setVertexShaderConstant(worldViewProj.pointer(), 0, 4); - - // Set world matrix - core::matrix4 world = driver->getTransform(video::ETS_WORLD); - if (is_highlevel) - m_world.set(*reinterpret_cast(world.pointer()), services); - else - services->setVertexShaderConstant(world.pointer(), 4, 4); - - } -}; - - -class MainShaderConstantSetterFactory : public IShaderConstantSetterFactory -{ -public: - virtual IShaderConstantSetter* create() - { return new MainShaderConstantSetter(); } -}; - - -/* - ShaderSource -*/ - -class ShaderSource : public IWritableShaderSource -{ -public: - ShaderSource(); - ~ShaderSource(); - - /* - - If shader material specified by name is found from cache, - return the cached id. - - Otherwise generate the shader material, add to cache and return id. - - The id 0 points to a null shader. Its material is EMT_SOLID. - */ - u32 getShaderIdDirect(const std::string &name, - const u8 material_type, const u8 drawtype); - - /* - If shader specified by the name pointed by the id doesn't - exist, create it, then return id. - - Can be called from any thread. If called from some other thread - and not found in cache, the call is queued to the main thread - for processing. - */ - - u32 getShader(const std::string &name, - const u8 material_type, const u8 drawtype); - - ShaderInfo getShaderInfo(u32 id); - - // Processes queued shader requests from other threads. - // Shall be called from the main thread. - void processQueue(); - - // Insert a shader program into the cache without touching the - // filesystem. Shall be called from the main thread. - void insertSourceShader(const std::string &name_of_shader, - const std::string &filename, const std::string &program); - - // Rebuild shaders from the current set of source shaders - // Shall be called from the main thread. - void rebuildShaders(); - - void addShaderConstantSetterFactory(IShaderConstantSetterFactory *setter) - { - m_setter_factories.push_back(setter); - } - -private: - - // The id of the thread that is allowed to use irrlicht directly - std::thread::id m_main_thread; - - // Cache of source shaders - // This should be only accessed from the main thread - SourceShaderCache m_sourcecache; - - // A shader id is index in this array. - // The first position contains a dummy shader. - std::vector m_shaderinfo_cache; - // The former container is behind this mutex - std::mutex m_shaderinfo_cache_mutex; - - // Queued shader fetches (to be processed by the main thread) - RequestQueue m_get_shader_queue; - - // Global constant setter factories - std::vector m_setter_factories; - - // Shader callbacks - std::vector m_callbacks; -}; - -IWritableShaderSource *createShaderSource() -{ - return new ShaderSource(); -} - -/* - Generate shader given the shader name. -*/ -ShaderInfo generate_shader(const std::string &name, - u8 material_type, u8 drawtype, std::vector &callbacks, - const std::vector &setter_factories, - SourceShaderCache *sourcecache); - -/* - Load shader programs -*/ -void load_shaders(const std::string &name, SourceShaderCache *sourcecache, - video::E_DRIVER_TYPE drivertype, bool enable_shaders, - std::string &vertex_program, std::string &pixel_program, - std::string &geometry_program, bool &is_highlevel); - -ShaderSource::ShaderSource() -{ - m_main_thread = std::this_thread::get_id(); - - // Add a dummy ShaderInfo as the first index, named "" - m_shaderinfo_cache.emplace_back(); - - // Add main global constant setter - addShaderConstantSetterFactory(new MainShaderConstantSetterFactory()); -} - -ShaderSource::~ShaderSource() -{ - for (ShaderCallback *callback : m_callbacks) { - delete callback; - } - for (IShaderConstantSetterFactory *setter_factorie : m_setter_factories) { - delete setter_factorie; - } -} - -u32 ShaderSource::getShader(const std::string &name, - const u8 material_type, const u8 drawtype) -{ - /* - Get shader - */ - - if (std::this_thread::get_id() == m_main_thread) { - return getShaderIdDirect(name, material_type, drawtype); - } - - /*errorstream<<"getShader(): Queued: name=\""< result_queue; - - // Throw a request in - m_get_shader_queue.add(name, 0, 0, &result_queue); - - /* infostream<<"Waiting for shader from main thread, name=\"" - < - result = result_queue.pop_frontNoEx(); - - if (result.key == name) { - return result.item; - } - - errorstream << "Got shader with invalid name: " << result.key << std::endl; - } - - infostream << "getShader(): Failed" << std::endl; - - return 0; -} - -/* - This method generates all the shaders -*/ -u32 ShaderSource::getShaderIdDirect(const std::string &name, - const u8 material_type, const u8 drawtype) -{ - //infostream<<"getShaderIdDirect(): name=\""<name == name && info->material_type == material_type && - info->drawtype == drawtype) - return i; - } - - /* - Calling only allowed from main thread - */ - if (std::this_thread::get_id() != m_main_thread) { - errorstream<<"ShaderSource::getShaderIdDirect() " - "called not from main thread"<= m_shaderinfo_cache.size()) - return ShaderInfo(); - - return m_shaderinfo_cache[id]; -} - -void ShaderSource::processQueue() -{ - - -} - -void ShaderSource::insertSourceShader(const std::string &name_of_shader, - const std::string &filename, const std::string &program) -{ - /*infostream<<"ShaderSource::insertSourceShader(): " - "name_of_shader=\""<name.empty()) { - *info = generate_shader(info->name, info->material_type, - info->drawtype, m_callbacks, - m_setter_factories, &m_sourcecache); - } - } -} - - -ShaderInfo generate_shader(const std::string &name, u8 material_type, u8 drawtype, - std::vector &callbacks, - const std::vector &setter_factories, - SourceShaderCache *sourcecache) -{ - ShaderInfo shaderinfo; - shaderinfo.name = name; - shaderinfo.material_type = material_type; - shaderinfo.drawtype = drawtype; - shaderinfo.material = video::EMT_SOLID; - switch (material_type) { - case TILE_MATERIAL_OPAQUE: - case TILE_MATERIAL_LIQUID_OPAQUE: - shaderinfo.base_material = video::EMT_SOLID; - break; - case TILE_MATERIAL_ALPHA: - case TILE_MATERIAL_LIQUID_TRANSPARENT: - shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL; - break; - case TILE_MATERIAL_BASIC: - case TILE_MATERIAL_WAVING_LEAVES: - case TILE_MATERIAL_WAVING_PLANTS: - shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; - break; - } - - bool enable_shaders = g_settings->getBool("enable_shaders"); - if (!enable_shaders) - return shaderinfo; - - video::IVideoDriver *driver = RenderingEngine::get_video_driver(); - - video::IGPUProgrammingServices *gpu = driver->getGPUProgrammingServices(); - if(!gpu){ - errorstream<<"generate_shader(): " - "failed to generate \""<getDriverType(), - enable_shaders, vertex_program, pixel_program, - geometry_program, is_highlevel); - // Check hardware/driver support - if (!vertex_program.empty() && - !driver->queryFeature(video::EVDF_VERTEX_SHADER_1_1) && - !driver->queryFeature(video::EVDF_ARB_VERTEX_PROGRAM_1)){ - infostream<<"generate_shader(): vertex shaders disabled " - "because of missing driver/hardware support." - <queryFeature(video::EVDF_PIXEL_SHADER_1_1) && - !driver->queryFeature(video::EVDF_ARB_FRAGMENT_PROGRAM_1)){ - infostream<<"generate_shader(): pixel shaders disabled " - "because of missing driver/hardware support." - <queryFeature(video::EVDF_GEOMETRY_SHADER)){ - infostream<<"generate_shader(): geometry shaders disabled " - "because of missing driver/hardware support." - <getBool("generate_normalmaps")) { - shaders_header += "#define GENERATE_NORMALMAPS 1\n"; - } else { - shaders_header += "#define GENERATE_NORMALMAPS 0\n"; - } - shaders_header += "#define NORMALMAPS_STRENGTH "; - shaders_header += ftos(g_settings->getFloat("normalmaps_strength")); - shaders_header += "\n"; - float sample_step; - int smooth = (int)g_settings->getFloat("normalmaps_smooth"); - switch (smooth){ - case 0: - sample_step = 0.0078125; // 1.0 / 128.0 - break; - case 1: - sample_step = 0.00390625; // 1.0 / 256.0 - break; - case 2: - sample_step = 0.001953125; // 1.0 / 512.0 - break; - default: - sample_step = 0.0078125; - break; - } - shaders_header += "#define SAMPLE_STEP "; - shaders_header += ftos(sample_step); - shaders_header += "\n"; - - if (g_settings->getBool("enable_bumpmapping")) - shaders_header += "#define ENABLE_BUMPMAPPING\n"; - - if (g_settings->getBool("enable_parallax_occlusion")){ - int mode = g_settings->getFloat("parallax_occlusion_mode"); - float scale = g_settings->getFloat("parallax_occlusion_scale"); - float bias = g_settings->getFloat("parallax_occlusion_bias"); - int iterations = g_settings->getFloat("parallax_occlusion_iterations"); - shaders_header += "#define ENABLE_PARALLAX_OCCLUSION\n"; - shaders_header += "#define PARALLAX_OCCLUSION_MODE "; - shaders_header += itos(mode); - shaders_header += "\n"; - shaders_header += "#define PARALLAX_OCCLUSION_SCALE "; - shaders_header += ftos(scale); - shaders_header += "\n"; - shaders_header += "#define PARALLAX_OCCLUSION_BIAS "; - shaders_header += ftos(bias); - shaders_header += "\n"; - shaders_header += "#define PARALLAX_OCCLUSION_ITERATIONS "; - shaders_header += itos(iterations); - shaders_header += "\n"; - } - - shaders_header += "#define USE_NORMALMAPS "; - if (g_settings->getBool("enable_bumpmapping") || g_settings->getBool("enable_parallax_occlusion")) - shaders_header += "1\n"; - else - shaders_header += "0\n"; - - if (g_settings->getBool("enable_waving_water")){ - shaders_header += "#define ENABLE_WAVING_WATER 1\n"; - shaders_header += "#define WATER_WAVE_HEIGHT "; - shaders_header += ftos(g_settings->getFloat("water_wave_height")); - shaders_header += "\n"; - shaders_header += "#define WATER_WAVE_LENGTH "; - shaders_header += ftos(g_settings->getFloat("water_wave_length")); - shaders_header += "\n"; - shaders_header += "#define WATER_WAVE_SPEED "; - shaders_header += ftos(g_settings->getFloat("water_wave_speed")); - shaders_header += "\n"; - } else{ - shaders_header += "#define ENABLE_WAVING_WATER 0\n"; - } - - shaders_header += "#define ENABLE_WAVING_LEAVES "; - if (g_settings->getBool("enable_waving_leaves")) - shaders_header += "1\n"; - else - shaders_header += "0\n"; - - shaders_header += "#define ENABLE_WAVING_PLANTS "; - if (g_settings->getBool("enable_waving_plants")) - shaders_header += "1\n"; - else - shaders_header += "0\n"; - - if (g_settings->getBool("tone_mapping")) - shaders_header += "#define ENABLE_TONE_MAPPING\n"; - - shaders_header += "#define FOG_START "; - shaders_header += ftos(rangelim(g_settings->getFloat("fog_start"), 0.0f, 0.99f)); - shaders_header += "\n"; - - // Call addHighLevelShaderMaterial() or addShaderMaterial() - const c8* vertex_program_ptr = 0; - const c8* pixel_program_ptr = 0; - const c8* geometry_program_ptr = 0; - if (!vertex_program.empty()) { - vertex_program = shaders_header + vertex_program; - vertex_program_ptr = vertex_program.c_str(); - } - if (!pixel_program.empty()) { - pixel_program = shaders_header + pixel_program; - pixel_program_ptr = pixel_program.c_str(); - } - if (!geometry_program.empty()) { - geometry_program = shaders_header + geometry_program; - geometry_program_ptr = geometry_program.c_str(); - } - ShaderCallback *cb = new ShaderCallback(setter_factories); - s32 shadermat = -1; - if(is_highlevel){ - infostream<<"Compiling high level shaders for "<addHighLevelShaderMaterial( - vertex_program_ptr, // Vertex shader program - "vertexMain", // Vertex shader entry point - video::EVST_VS_1_1, // Vertex shader version - pixel_program_ptr, // Pixel shader program - "pixelMain", // Pixel shader entry point - video::EPST_PS_1_2, // Pixel shader version - geometry_program_ptr, // Geometry shader program - "geometryMain", // Geometry shader entry point - video::EGST_GS_4_0, // Geometry shader version - scene::EPT_TRIANGLES, // Geometry shader input - scene::EPT_TRIANGLE_STRIP, // Geometry shader output - 0, // Support maximum number of vertices - cb, // Set-constant callback - shaderinfo.base_material, // Base material - 1 // Userdata passed to callback - ); - if(shadermat == -1){ - errorstream<<"generate_shader(): " - "failed to generate \""<addShaderMaterial( - vertex_program_ptr, // Vertex shader program - pixel_program_ptr, // Pixel shader program - cb, // Set-constant callback - shaderinfo.base_material, // Base material - 0 // Userdata passed to callback - ); - - if(shadermat == -1){ - errorstream<<"generate_shader(): " - "failed to generate \""<getMaterialRenderer(shadermat)->grab(); - - // Apply the newly created material type - shaderinfo.material = (video::E_MATERIAL_TYPE) shadermat; - return shaderinfo; -} - -void load_shaders(const std::string &name, SourceShaderCache *sourcecache, - video::E_DRIVER_TYPE drivertype, bool enable_shaders, - std::string &vertex_program, std::string &pixel_program, - std::string &geometry_program, bool &is_highlevel) -{ - vertex_program = ""; - pixel_program = ""; - geometry_program = ""; - is_highlevel = false; - - if(enable_shaders){ - // Look for high level shaders - if(drivertype == video::EDT_DIRECT3D9){ - // Direct3D 9: HLSL - // (All shaders in one file) - vertex_program = sourcecache->getOrLoad(name, "d3d9.hlsl"); - pixel_program = vertex_program; - geometry_program = vertex_program; - } - else if(drivertype == video::EDT_OPENGL){ - // OpenGL: GLSL - vertex_program = sourcecache->getOrLoad(name, "opengl_vertex.glsl"); - pixel_program = sourcecache->getOrLoad(name, "opengl_fragment.glsl"); - geometry_program = sourcecache->getOrLoad(name, "opengl_geometry.glsl"); - } - if (!vertex_program.empty() || !pixel_program.empty() || !geometry_program.empty()){ - is_highlevel = true; - return; - } - } - -} - -void dumpShaderProgram(std::ostream &output_stream, - const std::string &program_type, const std::string &program) -{ - output_stream << program_type << " shader program:" << std::endl << - "----------------------------------" << std::endl; - size_t pos = 0; - size_t prev = 0; - s16 line = 1; - while ((pos = program.find('\n', prev)) != std::string::npos) { - output_stream << line++ << ": "<< program.substr(prev, pos - prev) << - std::endl; - prev = pos + 1; - } - output_stream << line << ": " << program.substr(prev) << std::endl << - "End of " << program_type << " shader program." << std::endl << - " " << std::endl; -} diff --git a/src/shader.h b/src/shader.h deleted file mode 100644 index 583c776f4..000000000 --- a/src/shader.h +++ /dev/null @@ -1,156 +0,0 @@ -/* -Minetest -Copyright (C) 2013 celeron55, Perttu Ahola -Copyright (C) 2013 Kahrl - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#pragma once - -#include -#include "irrlichttypes_bloated.h" -#include - -class IGameDef; - -/* - shader.{h,cpp}: Shader handling stuff. -*/ - -/* - Gets the path to a shader by first checking if the file - name_of_shader/filename - exists in shader_path and if not, using the data path. - - If not found, returns "". - - Utilizes a thread-safe cache. -*/ -std::string getShaderPath(const std::string &name_of_shader, - const std::string &filename); - -struct ShaderInfo { - std::string name = ""; - video::E_MATERIAL_TYPE base_material = video::EMT_SOLID; - video::E_MATERIAL_TYPE material = video::EMT_SOLID; - u8 drawtype = 0; - u8 material_type = 0; - - ShaderInfo() = default; - virtual ~ShaderInfo() = default; -}; - -/* - Setter of constants for shaders -*/ - -namespace irr { namespace video { - class IMaterialRendererServices; -} } - - -class IShaderConstantSetter { -public: - virtual ~IShaderConstantSetter() = default; - virtual void onSetConstants(video::IMaterialRendererServices *services, - bool is_highlevel) = 0; -}; - - -class IShaderConstantSetterFactory { -public: - virtual ~IShaderConstantSetterFactory() = default; - virtual IShaderConstantSetter* create() = 0; -}; - - -template -class CachedShaderSetting { - const char *m_name; - T m_sent[count]; - bool has_been_set = false; - bool is_pixel; -protected: - CachedShaderSetting(const char *name, bool is_pixel) : - m_name(name), is_pixel(is_pixel) - {} -public: - void set(const T value[count], video::IMaterialRendererServices *services) - { - if (has_been_set && std::equal(m_sent, m_sent + count, value)) - return; - if (is_pixel) - services->setPixelShaderConstant(m_name, value, count); - else - services->setVertexShaderConstant(m_name, value, count); - std::copy(value, value + count, m_sent); - has_been_set = true; - } -}; - -template -class CachedPixelShaderSetting : public CachedShaderSetting { -public: - CachedPixelShaderSetting(const char *name) : - CachedShaderSetting(name, true){} -}; - -template -class CachedVertexShaderSetting : public CachedShaderSetting { -public: - CachedVertexShaderSetting(const char *name) : - CachedShaderSetting(name, false){} -}; - - -/* - ShaderSource creates and caches shaders. -*/ - -class IShaderSource { -public: - IShaderSource() = default; - virtual ~IShaderSource() = default; - - virtual u32 getShaderIdDirect(const std::string &name, - const u8 material_type, const u8 drawtype){return 0;} - virtual ShaderInfo getShaderInfo(u32 id){return ShaderInfo();} - virtual u32 getShader(const std::string &name, - const u8 material_type, const u8 drawtype){return 0;} -}; - -class IWritableShaderSource : public IShaderSource { -public: - IWritableShaderSource() = default; - virtual ~IWritableShaderSource() = default; - - virtual u32 getShaderIdDirect(const std::string &name, - const u8 material_type, const u8 drawtype){return 0;} - virtual ShaderInfo getShaderInfo(u32 id){return ShaderInfo();} - virtual u32 getShader(const std::string &name, - const u8 material_type, const u8 drawtype){return 0;} - - virtual void processQueue()=0; - virtual void insertSourceShader(const std::string &name_of_shader, - const std::string &filename, const std::string &program)=0; - virtual void rebuildShaders()=0; - virtual void addShaderConstantSetterFactory(IShaderConstantSetterFactory *setter) = 0; -}; - -IWritableShaderSource *createShaderSource(); - -void dumpShaderProgram(std::ostream &output_stream, - const std::string &program_type, const std::string &program); diff --git a/src/sky.cpp b/src/sky.cpp deleted file mode 100644 index faf12ba92..000000000 --- a/src/sky.cpp +++ /dev/null @@ -1,755 +0,0 @@ -/* -Minetest -Copyright (C) 2010-2013 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include "sky.h" -#include "IVideoDriver.h" -#include "ISceneManager.h" -#include "ICameraSceneNode.h" -#include "S3DVertex.h" -#include "client/tile.h" -#include "noise.h" // easeCurve -#include "profiler.h" -#include "util/numeric.h" -#include -#include "client/renderingengine.h" -#include "settings.h" -#include "camera.h" // CameraModes - - -Sky::Sky(s32 id, ITextureSource *tsrc): - scene::ISceneNode(RenderingEngine::get_scene_manager()->getRootSceneNode(), - RenderingEngine::get_scene_manager(), id) -{ - setAutomaticCulling(scene::EAC_OFF); - m_box.MaxEdge.set(0, 0, 0); - m_box.MinEdge.set(0, 0, 0); - - // Create material - - video::SMaterial mat; - mat.Lighting = false; -#ifdef __ANDROID__ - mat.ZBuffer = video::ECFN_DISABLED; -#else - mat.ZBuffer = video::ECFN_NEVER; -#endif - mat.ZWriteEnable = false; - mat.AntiAliasing = 0; - mat.TextureLayer[0].TextureWrapU = video::ETC_CLAMP_TO_EDGE; - mat.TextureLayer[0].TextureWrapV = video::ETC_CLAMP_TO_EDGE; - mat.BackfaceCulling = false; - - m_materials[0] = mat; - - m_materials[1] = mat; - //m_materials[1].MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA; - m_materials[1].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; - - m_materials[2] = mat; - m_materials[2].setTexture(0, tsrc->getTextureForMesh("sunrisebg.png")); - m_materials[2].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; - //m_materials[2].MaterialType = video::EMT_TRANSPARENT_ADD_COLOR; - - m_sun_texture = tsrc->isKnownSourceImage("sun.png") ? - tsrc->getTextureForMesh("sun.png") : NULL; - m_moon_texture = tsrc->isKnownSourceImage("moon.png") ? - tsrc->getTextureForMesh("moon.png") : NULL; - m_sun_tonemap = tsrc->isKnownSourceImage("sun_tonemap.png") ? - tsrc->getTexture("sun_tonemap.png") : NULL; - m_moon_tonemap = tsrc->isKnownSourceImage("moon_tonemap.png") ? - tsrc->getTexture("moon_tonemap.png") : NULL; - - if (m_sun_texture) { - m_materials[3] = mat; - m_materials[3].setTexture(0, m_sun_texture); - m_materials[3].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; - if (m_sun_tonemap) - m_materials[3].Lighting = true; - } - - if (m_moon_texture) { - m_materials[4] = mat; - m_materials[4].setTexture(0, m_moon_texture); - m_materials[4].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; - if (m_moon_tonemap) - m_materials[4].Lighting = true; - } - - for (v3f &star : m_stars) { - star = v3f( - myrand_range(-10000, 10000), - myrand_range(-10000, 10000), - myrand_range(-10000, 10000) - ); - star.normalize(); - } - - m_directional_colored_fog = g_settings->getBool("directional_colored_fog"); -} - - -void Sky::OnRegisterSceneNode() -{ - if (IsVisible) - SceneManager->registerNodeForRendering(this, scene::ESNRP_SKY_BOX); - - scene::ISceneNode::OnRegisterSceneNode(); -} - - -void Sky::render() -{ - if (!m_visible) - return; - - video::IVideoDriver* driver = SceneManager->getVideoDriver(); - scene::ICameraSceneNode* camera = SceneManager->getActiveCamera(); - - if (!camera || !driver) - return; - - ScopeProfiler sp(g_profiler, "Sky::render()", SPT_AVG); - - // Draw perspective skybox - - core::matrix4 translate(AbsoluteTransformation); - translate.setTranslation(camera->getAbsolutePosition()); - - // Draw the sky box between the near and far clip plane - const f32 viewDistance = (camera->getNearValue() + camera->getFarValue()) * 0.5f; - core::matrix4 scale; - scale.setScale(core::vector3df(viewDistance, viewDistance, viewDistance)); - - driver->setTransform(video::ETS_WORLD, translate * scale); - - if (m_sunlight_seen) { - float sunsize = 0.07; - video::SColorf suncolor_f(1, 1, 0, 1); - //suncolor_f.r = 1; - //suncolor_f.g = MYMAX(0.3, MYMIN(1.0, 0.7 + m_time_brightness * 0.5)); - //suncolor_f.b = MYMAX(0.0, m_brightness * 0.95); - video::SColorf suncolor2_f(1, 1, 1, 1); - // The values below were probably meant to be suncolor2_f instead of a - // reassignment of suncolor_f. However, the resulting colour was chosen - // and is our long-running classic colour. So preserve, but comment-out - // the unnecessary first assignments above. - suncolor_f.r = 1; - suncolor_f.g = MYMAX(0.3, MYMIN(1.0, 0.85 + m_time_brightness * 0.5)); - suncolor_f.b = MYMAX(0.0, m_brightness); - - float moonsize = 0.04; - video::SColorf mooncolor_f(0.50, 0.57, 0.65, 1); - video::SColorf mooncolor2_f(0.85, 0.875, 0.9, 1); - - float nightlength = 0.415; - float wn = nightlength / 2; - float wicked_time_of_day = 0; - if (m_time_of_day > wn && m_time_of_day < 1.0 - wn) - wicked_time_of_day = (m_time_of_day - wn) / (1.0 - wn * 2) * 0.5 + 0.25; - else if (m_time_of_day < 0.5) - wicked_time_of_day = m_time_of_day / wn * 0.25; - else - wicked_time_of_day = 1.0 - ((1.0 - m_time_of_day) / wn * 0.25); - /*std::cerr<<"time_of_day="< " - <<"wicked_time_of_day="<lock(); - video::SColor* texel = (video::SColor *)(texels + (u32)offset * 4); - video::SColor texel_color (255, texel->getRed(), - texel->getGreen(), texel->getBlue()); - m_sun_tonemap->unlock(); - m_materials[3].EmissiveColor = texel_color; - } - - if (m_moon_tonemap) { - u8 * texels = (u8 *)m_moon_tonemap->lock(); - video::SColor* texel = (video::SColor *)(texels + (u32)offset * 4); - video::SColor texel_color (255, texel->getRed(), - texel->getGreen(), texel->getBlue()); - m_moon_tonemap->unlock(); - m_materials[4].EmissiveColor = texel_color; - } - - const f32 t = 1.0f; - const f32 o = 0.0f; - static const u16 indices[4] = {0, 1, 2, 3}; - video::S3DVertex vertices[4]; - - driver->setMaterial(m_materials[1]); - - video::SColor cloudyfogcolor = m_bgcolor; - - // Draw far cloudy fog thing blended with skycolor - for (u32 j = 0; j < 4; j++) { - video::SColor c = cloudyfogcolor.getInterpolated(m_skycolor, 0.45); - vertices[0] = video::S3DVertex(-1, 0.08, -1, 0, 0, 1, c, t, t); - vertices[1] = video::S3DVertex( 1, 0.08, -1, 0, 0, 1, c, o, t); - vertices[2] = video::S3DVertex( 1, 0.12, -1, 0, 0, 1, c, o, o); - vertices[3] = video::S3DVertex(-1, 0.12, -1, 0, 0, 1, c, t, o); - for (video::S3DVertex &vertex : vertices) { - if (j == 0) - // Don't switch - {} - else if (j == 1) - // Switch from -Z (south) to +X (east) - vertex.Pos.rotateXZBy(90); - else if (j == 2) - // Switch from -Z (south) to -X (west) - vertex.Pos.rotateXZBy(-90); - else - // Switch from -Z (south) to +Z (north) - vertex.Pos.rotateXZBy(-180); - } - driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2); - } - - // Draw far cloudy fog thing - for (u32 j = 0; j < 4; j++) { - video::SColor c = cloudyfogcolor; - vertices[0] = video::S3DVertex(-1, -1.0, -1, 0, 0, 1, c, t, t); - vertices[1] = video::S3DVertex( 1, -1.0, -1, 0, 0, 1, c, o, t); - vertices[2] = video::S3DVertex( 1, 0.08, -1, 0, 0, 1, c, o, o); - vertices[3] = video::S3DVertex(-1, 0.08, -1, 0, 0, 1, c, t, o); - for (video::S3DVertex &vertex : vertices) { - if (j == 0) - // Don't switch - {} - else if (j == 1) - // Switch from -Z (south) to +X (east) - vertex.Pos.rotateXZBy(90); - else if (j == 2) - // Switch from -Z (south) to -X (west) - vertex.Pos.rotateXZBy(-90); - else - // Switch from -Z (south) to +Z (north) - vertex.Pos.rotateXZBy(-180); - } - driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2); - } - - // Draw bottom far cloudy fog thing - video::SColor c = cloudyfogcolor; - vertices[0] = video::S3DVertex(-1, -1.0, -1, 0, 1, 0, c, t, t); - vertices[1] = video::S3DVertex( 1, -1.0, -1, 0, 1, 0, c, o, t); - vertices[2] = video::S3DVertex( 1, -1.0, 1, 0, 1, 0, c, o, o); - vertices[3] = video::S3DVertex(-1, -1.0, 1, 0, 1, 0, c, t, o); - driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2); - - // If sun, moon and stars are (temporarily) disabled, abort here - if (!m_bodies_visible) - return; - - driver->setMaterial(m_materials[2]); - - // Draw sunrise/sunset horizon glow texture (textures/base/pack/sunrisebg.png) - { - float mid1 = 0.25; - float mid = wicked_time_of_day < 0.5 ? mid1 : (1.0 - mid1); - float a_ = 1.0f - std::fabs(wicked_time_of_day - mid) * 35.0f; - float a = easeCurve(MYMAX(0, MYMIN(1, a_))); - //std::cerr<<"a_="< CAMERA_MODE_THIRD) - dir_factor = -1; - f32 pointcolor_blend = wrapDegrees_0_360(yaw * dir_factor + 90); - if (pointcolor_blend > 180) - pointcolor_blend = 360 - pointcolor_blend; - pointcolor_blend /= 180; - // Bound view angle to determine where transition starts and ends - pointcolor_blend = rangelim(1 - pointcolor_blend * 1.375, 0, 1 / 1.375) * - 1.375; - // Combine the colors when looking up or down, otherwise turning looks weird - pointcolor_blend += (0.5 - pointcolor_blend) * - (1 - MYMIN((90 - std::fabs(pitch)) / 90 * 1.5, 1)); - // Invert direction to match where the sun and moon are rising - if (m_time_of_day > 0.5) - pointcolor_blend = 1 - pointcolor_blend; - // Horizon colors of sun and moon - f32 pointcolor_light = rangelim(m_time_brightness * 3, 0.2, 1); - - video::SColorf pointcolor_sun_f(1, 1, 1, 1); - if (m_sun_tonemap) { - pointcolor_sun_f.r = pointcolor_light * - (float)m_materials[3].EmissiveColor.getRed() / 255; - pointcolor_sun_f.b = pointcolor_light * - (float)m_materials[3].EmissiveColor.getBlue() / 255; - pointcolor_sun_f.g = pointcolor_light * - (float)m_materials[3].EmissiveColor.getGreen() / 255; - } else { - pointcolor_sun_f.r = pointcolor_light * 1; - pointcolor_sun_f.b = pointcolor_light * - (0.25 + (rangelim(m_time_brightness, 0.25, 0.75) - 0.25) * 2 * 0.75); - pointcolor_sun_f.g = pointcolor_light * (pointcolor_sun_f.b * 0.375 + - (rangelim(m_time_brightness, 0.05, 0.15) - 0.05) * 10 * 0.625); - } - - video::SColorf pointcolor_moon_f(0.5 * pointcolor_light, - 0.6 * pointcolor_light, 0.8 * pointcolor_light, 1); - if (m_moon_tonemap) { - pointcolor_moon_f.r = pointcolor_light * - (float)m_materials[4].EmissiveColor.getRed() / 255; - pointcolor_moon_f.b = pointcolor_light * - (float)m_materials[4].EmissiveColor.getBlue() / 255; - pointcolor_moon_f.g = pointcolor_light * - (float)m_materials[4].EmissiveColor.getGreen() / 255; - } - - video::SColor pointcolor_sun = pointcolor_sun_f.toSColor(); - video::SColor pointcolor_moon = pointcolor_moon_f.toSColor(); - // Calculate the blend color - pointcolor = m_mix_scolor(pointcolor_moon, pointcolor_sun, pointcolor_blend); - } - m_bgcolor = m_mix_scolor(m_bgcolor, pointcolor, m_horizon_blend() * 0.5); - m_skycolor = m_mix_scolor(m_skycolor, pointcolor, m_horizon_blend() * 0.25); - } - - float cloud_direct_brightness = 0.0f; - if (sunlight_seen) { - if (!m_directional_colored_fog) { - cloud_direct_brightness = time_brightness; - // Boost cloud brightness relative to sky, at dawn, dusk and at night - if (time_brightness < 0.7f) - cloud_direct_brightness *= 1.3f; - } else { - cloud_direct_brightness = std::fmin(m_horizon_blend() * 0.15f + - m_time_brightness, 1.0f); - // Set the same minimum cloud brightness at night - if (time_brightness < 0.5f) - cloud_direct_brightness = std::fmax(cloud_direct_brightness, - time_brightness * 1.3f); - } - } else { - cloud_direct_brightness = direct_brightness; - } - - m_cloud_brightness = m_cloud_brightness * cloud_color_change_fraction + - cloud_direct_brightness * (1.0 - cloud_color_change_fraction); - m_cloudcolor_f = video::SColorf( - m_cloudcolor_bright_f.r * m_cloud_brightness, - m_cloudcolor_bright_f.g * m_cloud_brightness, - m_cloudcolor_bright_f.b * m_cloud_brightness, - 1.0 - ); - if (m_directional_colored_fog) { - m_cloudcolor_f = m_mix_scolorf(m_cloudcolor_f, - video::SColorf(pointcolor), m_horizon_blend() * 0.25); - } -} diff --git a/src/sky.h b/src/sky.h deleted file mode 100644 index b66a4990f..000000000 --- a/src/sky.h +++ /dev/null @@ -1,148 +0,0 @@ -/* -Minetest -Copyright (C) 2013 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include -#include "camera.h" -#include "irrlichttypes_extrabloated.h" - -#pragma once - -#define SKY_MATERIAL_COUNT 5 -#define SKY_STAR_COUNT 200 - -class ITextureSource; - -// Skybox, rendered with zbuffer turned off, before all other nodes. -class Sky : public scene::ISceneNode -{ -public: - //! constructor - Sky(s32 id, ITextureSource *tsrc); - - virtual void OnRegisterSceneNode(); - - //! renders the node. - virtual void render(); - - virtual const aabb3f &getBoundingBox() const { return m_box; } - - // Used by Irrlicht for optimizing rendering - virtual video::SMaterial &getMaterial(u32 i) { return m_materials[i]; } - - // Used by Irrlicht for optimizing rendering - virtual u32 getMaterialCount() const { return SKY_MATERIAL_COUNT; } - - void update(float m_time_of_day, float time_brightness, float direct_brightness, - bool sunlight_seen, CameraMode cam_mode, float yaw, float pitch); - - float getBrightness() { return m_brightness; } - - const video::SColor &getBgColor() const - { - return m_visible ? m_bgcolor : m_fallback_bg_color; - } - - const video::SColor &getSkyColor() const - { - return m_visible ? m_skycolor : m_fallback_bg_color; - } - - bool getCloudsVisible() const { return m_clouds_visible && m_clouds_enabled; } - const video::SColorf &getCloudColor() const { return m_cloudcolor_f; } - - void setVisible(bool visible) { m_visible = visible; } - // Set only from set_sky API - void setCloudsEnabled(bool clouds_enabled) { m_clouds_enabled = clouds_enabled; } - void setFallbackBgColor(const video::SColor &fallback_bg_color) - { - m_fallback_bg_color = fallback_bg_color; - } - void overrideColors(const video::SColor &bgcolor, const video::SColor &skycolor) - { - m_bgcolor = bgcolor; - m_skycolor = skycolor; - } - void setBodiesVisible(bool visible) { m_bodies_visible = visible; } - -private: - aabb3f m_box; - video::SMaterial m_materials[SKY_MATERIAL_COUNT]; - - // How much sun & moon transition should affect horizon color - float m_horizon_blend() - { - if (!m_sunlight_seen) - return 0; - float x = m_time_of_day >= 0.5 ? (1 - m_time_of_day) * 2 - : m_time_of_day * 2; - - if (x <= 0.3) - return 0; - if (x <= 0.4) // when the sun and moon are aligned - return (x - 0.3) * 10; - if (x <= 0.5) - return (0.5 - x) * 10; - return 0; - } - - // Mix two colors by a given amount - video::SColor m_mix_scolor(video::SColor col1, video::SColor col2, f32 factor) - { - video::SColor result = video::SColor( - col1.getAlpha() * (1 - factor) + col2.getAlpha() * factor, - col1.getRed() * (1 - factor) + col2.getRed() * factor, - col1.getGreen() * (1 - factor) + col2.getGreen() * factor, - col1.getBlue() * (1 - factor) + col2.getBlue() * factor); - return result; - } - video::SColorf m_mix_scolorf(video::SColorf col1, video::SColorf col2, f32 factor) - { - video::SColorf result = - video::SColorf(col1.r * (1 - factor) + col2.r * factor, - col1.g * (1 - factor) + col2.g * factor, - col1.b * (1 - factor) + col2.b * factor, - col1.a * (1 - factor) + col2.a * factor); - return result; - } - - bool m_visible = true; - // Used when m_visible=false - video::SColor m_fallback_bg_color = video::SColor(255, 255, 255, 255); - bool m_first_update = true; - float m_time_of_day; - float m_time_brightness; - bool m_sunlight_seen; - float m_brightness = 0.5f; - float m_cloud_brightness = 0.5f; - bool m_clouds_visible; // Whether clouds are disabled due to player underground - bool m_clouds_enabled = true; // Initialised to true, reset only by set_sky API - bool m_directional_colored_fog; - bool m_bodies_visible = true; // sun, moon, stars - video::SColorf m_bgcolor_bright_f = video::SColorf(1.0f, 1.0f, 1.0f, 1.0f); - video::SColorf m_skycolor_bright_f = video::SColorf(1.0f, 1.0f, 1.0f, 1.0f); - video::SColorf m_cloudcolor_bright_f = video::SColorf(1.0f, 1.0f, 1.0f, 1.0f); - video::SColor m_bgcolor; - video::SColor m_skycolor; - video::SColorf m_cloudcolor_f; - v3f m_stars[SKY_STAR_COUNT]; - video::ITexture *m_sun_texture; - video::ITexture *m_moon_texture; - video::ITexture *m_sun_tonemap; - video::ITexture *m_moon_tonemap; -}; diff --git a/src/unittest/test_keycode.cpp b/src/unittest/test_keycode.cpp index dd3d75a5b..3813af949 100644 --- a/src/unittest/test_keycode.cpp +++ b/src/unittest/test_keycode.cpp @@ -21,7 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include "exceptions.h" -#include "keycode.h" +#include "client/keycode.h" class TestKeycode : public TestBase { public: diff --git a/src/wieldmesh.cpp b/src/wieldmesh.cpp deleted file mode 100644 index 7791a5a92..000000000 --- a/src/wieldmesh.cpp +++ /dev/null @@ -1,685 +0,0 @@ -/* -Minetest -Copyright (C) 2010-2014 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include "wieldmesh.h" -#include "settings.h" -#include "shader.h" -#include "inventory.h" -#include "client.h" -#include "itemdef.h" -#include "nodedef.h" -#include "mesh.h" -#include "content_mapblock.h" -#include "mapblock_mesh.h" -#include "client/meshgen/collector.h" -#include "client/tile.h" -#include "log.h" -#include "util/numeric.h" -#include -#include - -#define WIELD_SCALE_FACTOR 30.0 -#define WIELD_SCALE_FACTOR_EXTRUDED 40.0 - -#define MIN_EXTRUSION_MESH_RESOLUTION 16 -#define MAX_EXTRUSION_MESH_RESOLUTION 512 - -static scene::IMesh *createExtrusionMesh(int resolution_x, int resolution_y) -{ - const f32 r = 0.5; - - scene::IMeshBuffer *buf = new scene::SMeshBuffer(); - video::SColor c(255,255,255,255); - v3f scale(1.0, 1.0, 0.1); - - // Front and back - { - video::S3DVertex vertices[8] = { - // z- - video::S3DVertex(-r,+r,-r, 0,0,-1, c, 0,0), - video::S3DVertex(+r,+r,-r, 0,0,-1, c, 1,0), - video::S3DVertex(+r,-r,-r, 0,0,-1, c, 1,1), - video::S3DVertex(-r,-r,-r, 0,0,-1, c, 0,1), - // z+ - video::S3DVertex(-r,+r,+r, 0,0,+1, c, 0,0), - video::S3DVertex(-r,-r,+r, 0,0,+1, c, 0,1), - video::S3DVertex(+r,-r,+r, 0,0,+1, c, 1,1), - video::S3DVertex(+r,+r,+r, 0,0,+1, c, 1,0), - }; - u16 indices[12] = {0,1,2,2,3,0,4,5,6,6,7,4}; - buf->append(vertices, 8, indices, 12); - } - - f32 pixelsize_x = 1 / (f32) resolution_x; - f32 pixelsize_y = 1 / (f32) resolution_y; - - for (int i = 0; i < resolution_x; ++i) { - f32 pixelpos_x = i * pixelsize_x - 0.5; - f32 x0 = pixelpos_x; - f32 x1 = pixelpos_x + pixelsize_x; - f32 tex0 = (i + 0.1) * pixelsize_x; - f32 tex1 = (i + 0.9) * pixelsize_x; - video::S3DVertex vertices[8] = { - // x- - video::S3DVertex(x0,-r,-r, -1,0,0, c, tex0,1), - video::S3DVertex(x0,-r,+r, -1,0,0, c, tex1,1), - video::S3DVertex(x0,+r,+r, -1,0,0, c, tex1,0), - video::S3DVertex(x0,+r,-r, -1,0,0, c, tex0,0), - // x+ - video::S3DVertex(x1,-r,-r, +1,0,0, c, tex0,1), - video::S3DVertex(x1,+r,-r, +1,0,0, c, tex0,0), - video::S3DVertex(x1,+r,+r, +1,0,0, c, tex1,0), - video::S3DVertex(x1,-r,+r, +1,0,0, c, tex1,1), - }; - u16 indices[12] = {0,1,2,2,3,0,4,5,6,6,7,4}; - buf->append(vertices, 8, indices, 12); - } - for (int i = 0; i < resolution_y; ++i) { - f32 pixelpos_y = i * pixelsize_y - 0.5; - f32 y0 = -pixelpos_y - pixelsize_y; - f32 y1 = -pixelpos_y; - f32 tex0 = (i + 0.1) * pixelsize_y; - f32 tex1 = (i + 0.9) * pixelsize_y; - video::S3DVertex vertices[8] = { - // y- - video::S3DVertex(-r,y0,-r, 0,-1,0, c, 0,tex0), - video::S3DVertex(+r,y0,-r, 0,-1,0, c, 1,tex0), - video::S3DVertex(+r,y0,+r, 0,-1,0, c, 1,tex1), - video::S3DVertex(-r,y0,+r, 0,-1,0, c, 0,tex1), - // y+ - video::S3DVertex(-r,y1,-r, 0,+1,0, c, 0,tex0), - video::S3DVertex(-r,y1,+r, 0,+1,0, c, 0,tex1), - video::S3DVertex(+r,y1,+r, 0,+1,0, c, 1,tex1), - video::S3DVertex(+r,y1,-r, 0,+1,0, c, 1,tex0), - }; - u16 indices[12] = {0,1,2,2,3,0,4,5,6,6,7,4}; - buf->append(vertices, 8, indices, 12); - } - - // Create mesh object - scene::SMesh *mesh = new scene::SMesh(); - mesh->addMeshBuffer(buf); - buf->drop(); - scaleMesh(mesh, scale); // also recalculates bounding box - return mesh; -} - -/* - Caches extrusion meshes so that only one of them per resolution - is needed. Also caches one cube (for convenience). - - E.g. there is a single extrusion mesh that is used for all - 16x16 px images, another for all 256x256 px images, and so on. - - WARNING: Not thread safe. This should not be a problem since - rendering related classes (such as WieldMeshSceneNode) will be - used from the rendering thread only. -*/ -class ExtrusionMeshCache: public IReferenceCounted -{ -public: - // Constructor - ExtrusionMeshCache() - { - for (int resolution = MIN_EXTRUSION_MESH_RESOLUTION; - resolution <= MAX_EXTRUSION_MESH_RESOLUTION; - resolution *= 2) { - m_extrusion_meshes[resolution] = - createExtrusionMesh(resolution, resolution); - } - m_cube = createCubeMesh(v3f(1.0, 1.0, 1.0)); - } - // Destructor - virtual ~ExtrusionMeshCache() - { - for (auto &extrusion_meshe : m_extrusion_meshes) { - extrusion_meshe.second->drop(); - } - m_cube->drop(); - } - // Get closest extrusion mesh for given image dimensions - // Caller must drop the returned pointer - scene::IMesh* create(core::dimension2d dim) - { - // handle non-power of two textures inefficiently without cache - if (!is_power_of_two(dim.Width) || !is_power_of_two(dim.Height)) { - return createExtrusionMesh(dim.Width, dim.Height); - } - - int maxdim = MYMAX(dim.Width, dim.Height); - - std::map::iterator - it = m_extrusion_meshes.lower_bound(maxdim); - - if (it == m_extrusion_meshes.end()) { - // no viable resolution found; use largest one - it = m_extrusion_meshes.find(MAX_EXTRUSION_MESH_RESOLUTION); - sanity_check(it != m_extrusion_meshes.end()); - } - - scene::IMesh *mesh = it->second; - mesh->grab(); - return mesh; - } - // Returns a 1x1x1 cube mesh with one meshbuffer (material) per face - // Caller must drop the returned pointer - scene::IMesh* createCube() - { - m_cube->grab(); - return m_cube; - } - -private: - std::map m_extrusion_meshes; - scene::IMesh *m_cube; -}; - -ExtrusionMeshCache *g_extrusion_mesh_cache = NULL; - - -WieldMeshSceneNode::WieldMeshSceneNode(scene::ISceneManager *mgr, s32 id, bool lighting): - scene::ISceneNode(mgr->getRootSceneNode(), mgr, id), - m_material_type(video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF), - m_lighting(lighting) -{ - m_enable_shaders = g_settings->getBool("enable_shaders"); - m_anisotropic_filter = g_settings->getBool("anisotropic_filter"); - m_bilinear_filter = g_settings->getBool("bilinear_filter"); - m_trilinear_filter = g_settings->getBool("trilinear_filter"); - - // If this is the first wield mesh scene node, create a cache - // for extrusion meshes (and a cube mesh), otherwise reuse it - if (!g_extrusion_mesh_cache) - g_extrusion_mesh_cache = new ExtrusionMeshCache(); - else - g_extrusion_mesh_cache->grab(); - - // Disable bounding box culling for this scene node - // since we won't calculate the bounding box. - setAutomaticCulling(scene::EAC_OFF); - - // Create the child scene node - scene::IMesh *dummymesh = g_extrusion_mesh_cache->createCube(); - m_meshnode = SceneManager->addMeshSceneNode(dummymesh, this, -1); - m_meshnode->setReadOnlyMaterials(false); - m_meshnode->setVisible(false); - dummymesh->drop(); // m_meshnode grabbed it -} - -WieldMeshSceneNode::~WieldMeshSceneNode() -{ - sanity_check(g_extrusion_mesh_cache); - if (g_extrusion_mesh_cache->drop()) - g_extrusion_mesh_cache = nullptr; -} - -void WieldMeshSceneNode::setCube(const ContentFeatures &f, - v3f wield_scale) -{ - scene::IMesh *cubemesh = g_extrusion_mesh_cache->createCube(); - scene::SMesh *copy = cloneMesh(cubemesh); - cubemesh->drop(); - postProcessNodeMesh(copy, f, false, true, &m_material_type, &m_colors, true); - changeToMesh(copy); - copy->drop(); - m_meshnode->setScale(wield_scale * WIELD_SCALE_FACTOR); -} - -void WieldMeshSceneNode::setExtruded(const std::string &imagename, - const std::string &overlay_name, v3f wield_scale, ITextureSource *tsrc, - u8 num_frames) -{ - video::ITexture *texture = tsrc->getTexture(imagename); - if (!texture) { - changeToMesh(nullptr); - return; - } - video::ITexture *overlay_texture = - overlay_name.empty() ? NULL : tsrc->getTexture(overlay_name); - - core::dimension2d dim = texture->getSize(); - // Detect animation texture and pull off top frame instead of using entire thing - if (num_frames > 1) { - u32 frame_height = dim.Height / num_frames; - dim = core::dimension2d(dim.Width, frame_height); - } - scene::IMesh *original = g_extrusion_mesh_cache->create(dim); - scene::SMesh *mesh = cloneMesh(original); - original->drop(); - //set texture - mesh->getMeshBuffer(0)->getMaterial().setTexture(0, - tsrc->getTexture(imagename)); - if (overlay_texture) { - scene::IMeshBuffer *copy = cloneMeshBuffer(mesh->getMeshBuffer(0)); - copy->getMaterial().setTexture(0, overlay_texture); - mesh->addMeshBuffer(copy); - copy->drop(); - } - changeToMesh(mesh); - mesh->drop(); - - m_meshnode->setScale(wield_scale * WIELD_SCALE_FACTOR_EXTRUDED); - - // Customize materials - for (u32 layer = 0; layer < m_meshnode->getMaterialCount(); layer++) { - video::SMaterial &material = m_meshnode->getMaterial(layer); - material.TextureLayer[0].TextureWrapU = video::ETC_CLAMP_TO_EDGE; - material.TextureLayer[0].TextureWrapV = video::ETC_CLAMP_TO_EDGE; - material.MaterialType = m_material_type; - material.setFlag(video::EMF_BACK_FACE_CULLING, true); - // Enable bi/trilinear filtering only for high resolution textures - if (dim.Width > 32) { - material.setFlag(video::EMF_BILINEAR_FILTER, m_bilinear_filter); - material.setFlag(video::EMF_TRILINEAR_FILTER, m_trilinear_filter); - } else { - material.setFlag(video::EMF_BILINEAR_FILTER, false); - material.setFlag(video::EMF_TRILINEAR_FILTER, false); - } - material.setFlag(video::EMF_ANISOTROPIC_FILTER, m_anisotropic_filter); - // mipmaps cause "thin black line" artifacts -#if (IRRLICHT_VERSION_MAJOR >= 1 && IRRLICHT_VERSION_MINOR >= 8) || IRRLICHT_VERSION_MAJOR >= 2 - material.setFlag(video::EMF_USE_MIP_MAPS, false); -#endif - if (m_enable_shaders) { - material.setTexture(2, tsrc->getShaderFlagsTexture(false)); - } - } -} - -scene::SMesh *createSpecialNodeMesh(Client *client, content_t id, std::vector *colors) -{ - MeshMakeData mesh_make_data(client, false, false); - MeshCollector collector; - mesh_make_data.setSmoothLighting(false); - MapblockMeshGenerator gen(&mesh_make_data, &collector); - gen.renderSingle(id); - colors->clear(); - scene::SMesh *mesh = new scene::SMesh(); - for (auto &prebuffers : collector.prebuffers) - for (PreMeshBuffer &p : prebuffers) { - if (p.layer.material_flags & MATERIAL_FLAG_ANIMATION) { - const FrameSpec &frame = (*p.layer.frames)[0]; - p.layer.texture = frame.texture; - p.layer.normal_texture = frame.normal_texture; - } - for (video::S3DVertex &v : p.vertices) - v.Color.setAlpha(255); - scene::SMeshBuffer *buf = new scene::SMeshBuffer(); - buf->Material.setTexture(0, p.layer.texture); - p.layer.applyMaterialOptions(buf->Material); - mesh->addMeshBuffer(buf); - buf->append(&p.vertices[0], p.vertices.size(), - &p.indices[0], p.indices.size()); - buf->drop(); - colors->push_back( - ItemPartColor(p.layer.has_color, p.layer.color)); - } - return mesh; -} - -void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client) -{ - ITextureSource *tsrc = client->getTextureSource(); - IItemDefManager *idef = client->getItemDefManager(); - IShaderSource *shdrsrc = client->getShaderSource(); - const NodeDefManager *ndef = client->getNodeDefManager(); - const ItemDefinition &def = item.getDefinition(idef); - const ContentFeatures &f = ndef->get(def.name); - content_t id = ndef->getId(def.name); - - scene::SMesh *mesh = nullptr; - - if (m_enable_shaders) { - u32 shader_id = shdrsrc->getShader("wielded_shader", TILE_MATERIAL_BASIC, NDT_NORMAL); - m_material_type = shdrsrc->getShaderInfo(shader_id).material; - } - - // Color-related - m_colors.clear(); - m_base_color = idef->getItemstackColor(item, client); - - // If wield_image is defined, it overrides everything else - if (!def.wield_image.empty()) { - setExtruded(def.wield_image, def.wield_overlay, def.wield_scale, tsrc, - 1); - m_colors.emplace_back(); - // overlay is white, if present - m_colors.emplace_back(true, video::SColor(0xFFFFFFFF)); - return; - } - - // Handle nodes - // See also CItemDefManager::createClientCached() - if (def.type == ITEM_NODE) { - if (f.mesh_ptr[0]) { - // e.g. mesh nodes and nodeboxes - mesh = cloneMesh(f.mesh_ptr[0]); - postProcessNodeMesh(mesh, f, m_enable_shaders, true, - &m_material_type, &m_colors); - changeToMesh(mesh); - mesh->drop(); - // mesh is pre-scaled by BS * f->visual_scale - m_meshnode->setScale( - def.wield_scale * WIELD_SCALE_FACTOR - / (BS * f.visual_scale)); - } else { - switch (f.drawtype) { - case NDT_AIRLIKE: { - changeToMesh(nullptr); - break; - } - case NDT_PLANTLIKE: { - setExtruded(tsrc->getTextureName(f.tiles[0].layers[0].texture_id), - tsrc->getTextureName(f.tiles[0].layers[1].texture_id), - def.wield_scale, tsrc, - f.tiles[0].layers[0].animation_frame_count); - // Add color - const TileLayer &l0 = f.tiles[0].layers[0]; - m_colors.emplace_back(l0.has_color, l0.color); - const TileLayer &l1 = f.tiles[0].layers[1]; - m_colors.emplace_back(l1.has_color, l1.color); - break; - } - case NDT_PLANTLIKE_ROOTED: { - setExtruded(tsrc->getTextureName(f.special_tiles[0].layers[0].texture_id), - "", def.wield_scale, tsrc, - f.special_tiles[0].layers[0].animation_frame_count); - // Add color - const TileLayer &l0 = f.special_tiles[0].layers[0]; - m_colors.emplace_back(l0.has_color, l0.color); - break; - } - case NDT_NORMAL: - case NDT_ALLFACES: - case NDT_LIQUID: - case NDT_FLOWINGLIQUID: { - setCube(f, def.wield_scale); - break; - } - default: { - mesh = createSpecialNodeMesh(client, id, &m_colors); - changeToMesh(mesh); - mesh->drop(); - m_meshnode->setScale( - def.wield_scale * WIELD_SCALE_FACTOR - / (BS * f.visual_scale)); - } - } - } - u32 material_count = m_meshnode->getMaterialCount(); - for (u32 i = 0; i < material_count; ++i) { - video::SMaterial &material = m_meshnode->getMaterial(i); - material.MaterialType = m_material_type; - material.setFlag(video::EMF_BACK_FACE_CULLING, true); - material.setFlag(video::EMF_BILINEAR_FILTER, m_bilinear_filter); - material.setFlag(video::EMF_TRILINEAR_FILTER, m_trilinear_filter); - } - return; - } - else if (!def.inventory_image.empty()) { - setExtruded(def.inventory_image, def.inventory_overlay, def.wield_scale, - tsrc, 1); - m_colors.emplace_back(); - // overlay is white, if present - m_colors.emplace_back(true, video::SColor(0xFFFFFFFF)); - return; - } - - // no wield mesh found - changeToMesh(nullptr); -} - -void WieldMeshSceneNode::setColor(video::SColor c) -{ - assert(!m_lighting); - scene::IMesh *mesh = m_meshnode->getMesh(); - if (!mesh) - return; - - u8 red = c.getRed(); - u8 green = c.getGreen(); - u8 blue = c.getBlue(); - u32 mc = mesh->getMeshBufferCount(); - for (u32 j = 0; j < mc; j++) { - video::SColor bc(m_base_color); - if ((m_colors.size() > j) && (m_colors[j].override_base)) - bc = m_colors[j].color; - video::SColor buffercolor(255, - bc.getRed() * red / 255, - bc.getGreen() * green / 255, - bc.getBlue() * blue / 255); - scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); - colorizeMeshBuffer(buf, &buffercolor); - } -} - -void WieldMeshSceneNode::render() -{ - // note: if this method is changed to actually do something, - // you probably should implement OnRegisterSceneNode as well -} - -void WieldMeshSceneNode::changeToMesh(scene::IMesh *mesh) -{ - if (!mesh) { - scene::IMesh *dummymesh = g_extrusion_mesh_cache->createCube(); - m_meshnode->setVisible(false); - m_meshnode->setMesh(dummymesh); - dummymesh->drop(); // m_meshnode grabbed it - } else { - m_meshnode->setMesh(mesh); - } - - m_meshnode->setMaterialFlag(video::EMF_LIGHTING, m_lighting); - // need to normalize normals when lighting is enabled (because of setScale()) - m_meshnode->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, m_lighting); - m_meshnode->setVisible(true); -} - -void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result) -{ - ITextureSource *tsrc = client->getTextureSource(); - IItemDefManager *idef = client->getItemDefManager(); - const NodeDefManager *ndef = client->getNodeDefManager(); - const ItemDefinition &def = item.getDefinition(idef); - const ContentFeatures &f = ndef->get(def.name); - content_t id = ndef->getId(def.name); - - FATAL_ERROR_IF(!g_extrusion_mesh_cache, "Extrusion mesh cache is not yet initialized"); - - scene::SMesh *mesh = nullptr; - - // Shading is on by default - result->needs_shading = true; - - // If inventory_image is defined, it overrides everything else - if (!def.inventory_image.empty()) { - mesh = getExtrudedMesh(tsrc, def.inventory_image, - def.inventory_overlay); - result->buffer_colors.emplace_back(); - // overlay is white, if present - result->buffer_colors.emplace_back(true, video::SColor(0xFFFFFFFF)); - // Items with inventory images do not need shading - result->needs_shading = false; - } else if (def.type == ITEM_NODE) { - if (f.mesh_ptr[0]) { - mesh = cloneMesh(f.mesh_ptr[0]); - scaleMesh(mesh, v3f(0.12, 0.12, 0.12)); - postProcessNodeMesh(mesh, f, false, false, nullptr, - &result->buffer_colors); - } else { - switch (f.drawtype) { - case NDT_PLANTLIKE: { - mesh = getExtrudedMesh(tsrc, - tsrc->getTextureName(f.tiles[0].layers[0].texture_id), - tsrc->getTextureName(f.tiles[0].layers[1].texture_id)); - // Add color - const TileLayer &l0 = f.tiles[0].layers[0]; - result->buffer_colors.emplace_back(l0.has_color, l0.color); - const TileLayer &l1 = f.tiles[0].layers[1]; - result->buffer_colors.emplace_back(l1.has_color, l1.color); - break; - } - case NDT_PLANTLIKE_ROOTED: { - mesh = getExtrudedMesh(tsrc, - tsrc->getTextureName(f.special_tiles[0].layers[0].texture_id), ""); - // Add color - const TileLayer &l0 = f.special_tiles[0].layers[0]; - result->buffer_colors.emplace_back(l0.has_color, l0.color); - break; - } - case NDT_NORMAL: - case NDT_ALLFACES: - case NDT_LIQUID: - case NDT_FLOWINGLIQUID: { - scene::IMesh *cube = g_extrusion_mesh_cache->createCube(); - mesh = cloneMesh(cube); - cube->drop(); - scaleMesh(mesh, v3f(1.2, 1.2, 1.2)); - // add overlays - postProcessNodeMesh(mesh, f, false, false, nullptr, - &result->buffer_colors); - break; - } - default: { - mesh = createSpecialNodeMesh(client, id, &result->buffer_colors); - scaleMesh(mesh, v3f(0.12, 0.12, 0.12)); - } - } - } - - u32 mc = mesh->getMeshBufferCount(); - for (u32 i = 0; i < mc; ++i) { - scene::IMeshBuffer *buf = mesh->getMeshBuffer(i); - video::SMaterial &material = buf->getMaterial(); - material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; - material.setFlag(video::EMF_BILINEAR_FILTER, false); - material.setFlag(video::EMF_TRILINEAR_FILTER, false); - material.setFlag(video::EMF_BACK_FACE_CULLING, true); - material.setFlag(video::EMF_LIGHTING, false); - } - - rotateMeshXZby(mesh, -45); - rotateMeshYZby(mesh, -30); - } - result->mesh = mesh; -} - - - -scene::SMesh *getExtrudedMesh(ITextureSource *tsrc, - const std::string &imagename, const std::string &overlay_name) -{ - // check textures - video::ITexture *texture = tsrc->getTextureForMesh(imagename); - if (!texture) { - return NULL; - } - video::ITexture *overlay_texture = - (overlay_name.empty()) ? NULL : tsrc->getTexture(overlay_name); - - // get mesh - core::dimension2d dim = texture->getSize(); - scene::IMesh *original = g_extrusion_mesh_cache->create(dim); - scene::SMesh *mesh = cloneMesh(original); - original->drop(); - - //set texture - mesh->getMeshBuffer(0)->getMaterial().setTexture(0, - tsrc->getTexture(imagename)); - if (overlay_texture) { - scene::IMeshBuffer *copy = cloneMeshBuffer(mesh->getMeshBuffer(0)); - copy->getMaterial().setTexture(0, overlay_texture); - mesh->addMeshBuffer(copy); - copy->drop(); - } - // Customize materials - for (u32 layer = 0; layer < mesh->getMeshBufferCount(); layer++) { - video::SMaterial &material = mesh->getMeshBuffer(layer)->getMaterial(); - material.TextureLayer[0].TextureWrapU = video::ETC_CLAMP_TO_EDGE; - material.TextureLayer[0].TextureWrapV = video::ETC_CLAMP_TO_EDGE; - material.setFlag(video::EMF_BILINEAR_FILTER, false); - material.setFlag(video::EMF_TRILINEAR_FILTER, false); - material.setFlag(video::EMF_BACK_FACE_CULLING, true); - material.setFlag(video::EMF_LIGHTING, false); - material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; - } - scaleMesh(mesh, v3f(2.0, 2.0, 2.0)); - - return mesh; -} - -void postProcessNodeMesh(scene::SMesh *mesh, const ContentFeatures &f, - bool use_shaders, bool set_material, const video::E_MATERIAL_TYPE *mattype, - std::vector *colors, bool apply_scale) -{ - u32 mc = mesh->getMeshBufferCount(); - // Allocate colors for existing buffers - colors->clear(); - for (u32 i = 0; i < mc; ++i) - colors->push_back(ItemPartColor()); - - for (u32 i = 0; i < mc; ++i) { - const TileSpec *tile = &(f.tiles[i]); - scene::IMeshBuffer *buf = mesh->getMeshBuffer(i); - for (int layernum = 0; layernum < MAX_TILE_LAYERS; layernum++) { - const TileLayer *layer = &tile->layers[layernum]; - if (layer->texture_id == 0) - continue; - if (layernum != 0) { - scene::IMeshBuffer *copy = cloneMeshBuffer(buf); - copy->getMaterial() = buf->getMaterial(); - mesh->addMeshBuffer(copy); - copy->drop(); - buf = copy; - colors->push_back( - ItemPartColor(layer->has_color, layer->color)); - } else { - (*colors)[i] = ItemPartColor(layer->has_color, layer->color); - } - video::SMaterial &material = buf->getMaterial(); - if (set_material) - layer->applyMaterialOptions(material); - if (mattype) { - material.MaterialType = *mattype; - } - if (layer->animation_frame_count > 1) { - const FrameSpec &animation_frame = (*layer->frames)[0]; - material.setTexture(0, animation_frame.texture); - } else { - material.setTexture(0, layer->texture); - } - if (use_shaders) { - if (layer->normal_texture) { - if (layer->animation_frame_count > 1) { - const FrameSpec &animation_frame = (*layer->frames)[0]; - material.setTexture(1, animation_frame.normal_texture); - } else - material.setTexture(1, layer->normal_texture); - } - material.setTexture(2, layer->flags_texture); - } - if (apply_scale && tile->world_aligned) { - u32 n = buf->getVertexCount(); - for (u32 k = 0; k != n; ++k) - buf->getTCoords(k) /= layer->scale; - } - } - } -} diff --git a/src/wieldmesh.h b/src/wieldmesh.h deleted file mode 100644 index 0908d3ac2..000000000 --- a/src/wieldmesh.h +++ /dev/null @@ -1,140 +0,0 @@ -/* -Minetest -Copyright (C) 2010-2014 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#pragma once - -#include -#include -#include "irrlichttypes_extrabloated.h" - -struct ItemStack; -class Client; -class ITextureSource; -struct ContentFeatures; - -/*! - * Holds color information of an item mesh's buffer. - */ -struct ItemPartColor -{ - /*! - * If this is false, the global base color of the item - * will be used instead of the specific color of the - * buffer. - */ - bool override_base = false; - /*! - * The color of the buffer. - */ - video::SColor color = 0; - - ItemPartColor() = default; - - ItemPartColor(bool override, video::SColor color) : - override_base(override), color(color) - { - } -}; - -struct ItemMesh -{ - scene::IMesh *mesh = nullptr; - /*! - * Stores the color of each mesh buffer. - */ - std::vector buffer_colors; - /*! - * If false, all faces of the item should have the same brightness. - * Disables shading based on normal vectors. - */ - bool needs_shading = true; - - ItemMesh() = default; -}; - -/* - Wield item scene node, renders the wield mesh of some item -*/ -class WieldMeshSceneNode : public scene::ISceneNode -{ -public: - WieldMeshSceneNode(scene::ISceneManager *mgr, s32 id = -1, bool lighting = false); - virtual ~WieldMeshSceneNode(); - - void setCube(const ContentFeatures &f, v3f wield_scale); - void setExtruded(const std::string &imagename, const std::string &overlay_image, - v3f wield_scale, ITextureSource *tsrc, u8 num_frames); - void setItem(const ItemStack &item, Client *client); - - // Sets the vertex color of the wield mesh. - // Must only be used if the constructor was called with lighting = false - void setColor(video::SColor color); - - scene::IMesh *getMesh() { return m_meshnode->getMesh(); } - - virtual void render(); - - virtual const aabb3f &getBoundingBox() const { return m_bounding_box; } - -private: - void changeToMesh(scene::IMesh *mesh); - - // Child scene node with the current wield mesh - scene::IMeshSceneNode *m_meshnode = nullptr; - video::E_MATERIAL_TYPE m_material_type; - - // True if EMF_LIGHTING should be enabled. - bool m_lighting; - - bool m_enable_shaders; - bool m_anisotropic_filter; - bool m_bilinear_filter; - bool m_trilinear_filter; - /*! - * Stores the colors of the mesh's mesh buffers. - * This does not include lighting. - */ - std::vector m_colors; - /*! - * The base color of this mesh. This is the default - * for all mesh buffers. - */ - video::SColor m_base_color; - - // Bounding box culling is disabled for this type of scene node, - // so this variable is just required so we can implement - // getBoundingBox() and is set to an empty box. - aabb3f m_bounding_box; -}; - -void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result); - -scene::SMesh *getExtrudedMesh(ITextureSource *tsrc, const std::string &imagename, - const std::string &overlay_name); - -/*! - * Applies overlays, textures and optionally materials to the given mesh and - * extracts tile colors for colorization. - * \param mattype overrides the buffer's material type, but can also - * be NULL to leave the original material. - * \param colors returns the colors of the mesh buffers in the mesh. - */ -void postProcessNodeMesh(scene::SMesh *mesh, const ContentFeatures &f, bool use_shaders, - bool set_material, const video::E_MATERIAL_TYPE *mattype, - std::vector *colors, bool apply_scale = false); diff --git a/util/travis/clang-format-whitelist.txt b/util/travis/clang-format-whitelist.txt index 22e688a1f..c89520aad 100644 --- a/util/travis/clang-format-whitelist.txt +++ b/util/travis/clang-format-whitelist.txt @@ -33,6 +33,77 @@ src/client/render/sidebyside.cpp src/client/render/stereo.cpp src/client/tile.cpp src/client/tile.h +src/client/fontengine.h +src/client/clientenvironment.cpp +src/client/mapblock_mesh.cpp +src/client/sound_openal.h +src/client/clouds.cpp +src/client/fontengine.cpp +src/client/camera.h +src/client/hud.cpp +src/client/clientmap.cpp +src/client/sound_openal.cpp +src/client/minimap.h +src/client/content_cao.cpp +src/client/localplayer.h +src/client/mapblock_mesh.h +src/client/mesh.cpp +src/client/sound.cpp +src/client/guiscalingfilter.cpp +src/client/content_cso.cpp +src/client/gameui.cpp +src/client/wieldmesh.cpp +src/client/clientmedia.h +src/client/game.cpp +src/client/keys.h +src/client/client.h +src/client/shader.cpp +src/client/clientmap.h +src/client/inputhandler.h +src/client/content_mapblock.h +src/client/game.h +src/client/mesh.h +src/client/camera.cpp +src/client/sky.h +src/client/mesh_generator_thread.cpp +src/client/guiscalingfilter.h +src/client/clientobject.cpp +src/client/tile.cpp +src/client/hud.h +src/client/inputhandler.cpp +src/client/clientevent.h +src/client/gameui.h +src/client/content_cso.h +src/client/sky.cpp +src/client/localplayer.cpp +src/client/content_mapblock.cpp +src/client/clientobject.h +src/client/filecache.cpp +src/client/particles.h +src/client/clientenvironment.h +src/client/imagefilters.h +src/client/renderingengine.cpp +src/client/tile.h +src/client/clientmedia.cpp +src/client/event_manager.h +src/client/joystick_controller.h +src/client/clouds.h +src/client/clientlauncher.h +src/client/content_cao.h +src/client/minimap.cpp +src/client/sound.h +src/client/keycode.cpp +src/client/particles.cpp +src/client/joystick_controller.cpp +src/client/keycode.h +src/client/wieldmesh.h +src/client/filecache.h +src/client/shader.h +src/client/mesh_generator_thread.h +src/client/renderingengine.h +src/client/client.cpp +src/client/imagefilters.cpp +src/client/clientlauncher.cpp src/clouds.cpp src/clouds.h src/collision.cpp