#include "camera.h"
#include "debug.h"
#include "client.h"
+#include "config.h"
#include "map.h"
#include "clientmap.h" // MapDrawControl
#include "player.h"
#include "wieldmesh.h"
#include "noise.h" // easeCurve
#include "sound.h"
-#include "event.h"
+#include "mtevent.h"
#include "nodedef.h"
#include "util/numeric.h"
#include "constants.h"
#define CAMERA_OFFSET_STEP 200
#define WIELDMESH_OFFSET_X 55.0f
#define WIELDMESH_OFFSET_Y -35.0f
+#define WIELDMESH_AMPLITUDE_X 7.0f
+#define WIELDMESH_AMPLITUDE_Y 10.0f
-Camera::Camera(MapDrawControl &draw_control, Client *client):
+Camera::Camera(MapDrawControl &draw_control, Client *client, RenderingEngine *rendering_engine):
m_draw_control(draw_control),
m_client(client)
{
- scene::ISceneManager *smgr = RenderingEngine::get_scene_manager();
+ auto smgr = rendering_engine->get_scene_manager();
// note: making the camera node a child of the player node
// would lead to unexpected behaviour, so we don't do that.
m_playernode = smgr->addEmptySceneNode(smgr->getRootSceneNode());
m_cache_fov = std::fmax(g_settings->getFloat("fov"), 45.0f);
m_arm_inertia = g_settings->getBool("arm_inertia");
m_nametags.clear();
+ m_show_nametag_backgrounds = g_settings->getBool("show_nametag_backgrounds");
}
Camera::~Camera()
m_wieldmgr->drop();
}
+void Camera::notifyFovChange()
+{
+ LocalPlayer *player = m_client->getEnv().getLocalPlayer();
+ assert(player);
+
+ PlayerFovSpec spec = player->getFov();
+
+ /*
+ * Update m_old_fov_degrees first - it serves as the starting point of the
+ * upcoming transition.
+ *
+ * If an FOV transition is already active, mark current FOV as the start of
+ * the new transition. If not, set it to the previous transition's target FOV.
+ */
+ if (m_fov_transition_active)
+ m_old_fov_degrees = m_curr_fov_degrees;
+ else
+ m_old_fov_degrees = m_server_sent_fov ? m_target_fov_degrees : m_cache_fov;
+
+ /*
+ * Update m_server_sent_fov next - it corresponds to the target FOV of the
+ * upcoming transition.
+ *
+ * Set it to m_cache_fov, if server-sent FOV is 0. Otherwise check if
+ * server-sent FOV is a multiplier, and multiply it with m_cache_fov instead
+ * of overriding.
+ */
+ if (spec.fov == 0.0f) {
+ m_server_sent_fov = false;
+ m_target_fov_degrees = m_cache_fov;
+ } else {
+ m_server_sent_fov = true;
+ m_target_fov_degrees = spec.is_multiplier ? m_cache_fov * spec.fov : spec.fov;
+ }
+
+ if (spec.transition_time > 0.0f)
+ m_fov_transition_active = true;
+
+ // If FOV smooth transition is active, initialize required variables
+ if (m_fov_transition_active) {
+ m_transition_time = spec.transition_time;
+ m_fov_diff = m_target_fov_degrees - m_old_fov_degrees;
+ }
+}
+
bool Camera::successfullyCreated(std::string &error_message)
{
if (!m_playernode) {
error_message.clear();
}
- if (g_settings->getBool("enable_client_modding")) {
+ if (m_client->modsLoaded())
m_client->getScript()->on_camera_ready(this);
- }
+
return error_message.empty();
}
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);
+ WIELDMESH_OFFSET_X - (WIELDMESH_AMPLITUDE_X * 0.5f),
+ WIELDMESH_OFFSET_X + (WIELDMESH_AMPLITUDE_X * 0.5f));
}
if (m_cam_vel.Y > 1.0f) {
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);
+ WIELDMESH_OFFSET_Y - (WIELDMESH_AMPLITUDE_Y * 0.5f),
+ WIELDMESH_OFFSET_Y + (WIELDMESH_AMPLITUDE_Y * 0.5f));
}
m_arm_dir = dir(m_wieldmesh_offset);
following a vector, with a smooth deceleration factor.
*/
- f32 dec_X = 0.12f * (m_cam_vel_old.X * (1.0f +
+ f32 dec_X = 0.35f * (std::min(15.0f, 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 +
+ f32 dec_Y = 0.25f * (std::min(15.0f, m_cam_vel_old.Y) * (1.0f +
(1.0f - m_arm_dir.Y))) * (gap_Y / 15.0f);
if (gap_X < 0.1f)
// 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)
- {
+
+ // This is worse than `LocalPlayer::getPosition()` but
+ // mods expect the player head to be at the parent's position
+ // plus eye height.
+ if (player->getParent())
+ player_position = player->getParent()->getPosition();
+
+ // Smooth the camera movement when the player instantly moves upward due to stepheight.
+ // To smooth the 'not touching_ground' stepheight, smoothing is necessary when jumping
+ // or swimming (for when moving from liquid to land).
+ // Disable smoothing if climbing or flying, to avoid upwards offset of player model
+ // when seen in 3rd person view.
+ bool flying = g_settings->getBool("free_move") && m_client->checkLocalPrivilege("fly");
+ if (player_position.Y > old_player_position.Y && !player->is_climbing && !flying) {
f32 oldy = old_player_position.Y;
f32 newy = player_position.Y;
f32 t = std::exp(-23 * frametime);
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();
+ // Calculate and translate the head SceneNode offsets
+ {
+ v3f eye_offset = player->getEyeOffset();
+ if (m_camera_mode == CAMERA_MODE_FIRST)
+ eye_offset += player->eye_offset_first;
+ else
+ eye_offset += player->eye_offset_third;
+
+ // Set head node transformation
+ eye_offset.Y += cameratilt * -player->hurt_tilt_strength + fall_bobbing;
+ m_headnode->setPosition(eye_offset);
+ 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);
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();
+ /*
+ * Apply server-sent FOV, instantaneous or smooth transition.
+ * If not, check for zoom and set to zoom FOV.
+ * Otherwise, default to m_cache_fov.
+ */
+ if (m_fov_transition_active) {
+ // Smooth FOV transition
+ // Dynamically calculate FOV delta based on frametimes
+ f32 delta = (frametime / m_transition_time) * m_fov_diff;
+ m_curr_fov_degrees += delta;
+
+ // Mark transition as complete if target FOV has been reached
+ if ((m_fov_diff > 0.0f && m_curr_fov_degrees >= m_target_fov_degrees) ||
+ (m_fov_diff < 0.0f && m_curr_fov_degrees <= m_target_fov_degrees)) {
+ m_fov_transition_active = false;
+ m_curr_fov_degrees = m_target_fov_degrees;
+ }
+ } else if (m_server_sent_fov) {
+ // Instantaneous FOV change
+ m_curr_fov_degrees = m_target_fov_degrees;
+ } else if (player->getPlayerControl().zoom && player->getZoomFOV() > 0.001f) {
+ // Player requests zoom, apply zoom FOV
+ m_curr_fov_degrees = player->getZoomFOV();
} else {
- fov_degrees = m_cache_fov;
+ // Set to client's selected FOV
+ m_curr_fov_degrees = m_cache_fov;
}
- fov_degrees = rangelim(fov_degrees, 1.0f, 160.0f);
+ m_curr_fov_degrees = rangelim(m_curr_fov_degrees, 1.0f, 160.0f);
// FOV and aspect ratio
- const v2u32 &window_size = RenderingEngine::get_instance()->getWindowSize();
+ const v2u32 &window_size = RenderingEngine::getWindowSize();
m_aspect = (f32) window_size.X / (f32) window_size.Y;
- m_fov_y = fov_degrees * M_PI / 180.0;
+ m_fov_y = m_curr_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_y *= core::clamp(sqrt(16./10. / m_aspect), 1.0, 1.4);
m_fov_x = 2 * atan(m_aspect * tan(0.5 * m_fov_y));
m_cameranode->setAspectRatio(m_aspect);
m_cameranode->setFOV(m_fov_y);
m_wieldnode->setPosition(wield_position);
m_wieldnode->setRotation(wield_rotation);
- m_wieldnode->setColor(player->light_color);
+ m_wieldnode->setNodeLightColor(player->light_color);
// Set render distance
updateViewingRange();
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"))) {
+ if ((walking || swimming || climbing) && !flying) {
// Start animation
m_view_bobbing_state = 1;
m_view_bobbing_speed = MYMIN(speed.getLength(), 70);
- }
- else if (m_view_bobbing_state == 1)
- {
+ } 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");
+
+ // Ignore near_plane setting on all other platforms to prevent abuse
+#if ENABLE_GLES
+ m_cameranode->setNearValue(rangelim(
+ g_settings->getFloat("near_plane"), 0.0f, 0.25f) * BS);
+#else
+ m_cameranode->setNearValue(0.1f * BS);
+#endif
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;
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();
+ // Clear Z buffer so that the wielded tool stays in front of world geometry
+ m_wieldmgr->getVideoDriver()->clearBuffers(video::ECBF_DEPTH);
// Draw the wielded node (in a separate scene manager)
scene::ICameraSceneNode* cam = m_wieldmgr->getActiveCamera();
core::matrix4 trans = m_cameranode->getProjectionMatrix();
trans *= m_cameranode->getViewMatrix();
- for (std::list<Nametag *>::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;
+ gui::IGUIFont *font = g_fontengine->getFont();
+ video::IVideoDriver *driver = RenderingEngine::get_video_driver();
+ v2u32 screensize = driver->getScreenSize();
+
+ for (const Nametag *nametag : m_nametags) {
+ // Nametags are hidden in GenericCAO::updateNametag()
+
+ v3f pos = nametag->parent_node->getAbsolutePosition() + 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<u32> textsize =
- g_fontengine->getFont()->getDimension(
+ unescape_translate(utf8_to_wide(nametag->text));
+ core::dimension2d<u32> textsize = font->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<s32> 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);
+ core::rect<s32> bg_size(-2, 0, textsize.Width+2, textsize.Height);
+
+ auto bgcolor = nametag->getBgColor(m_show_nametag_backgrounds);
+ if (bgcolor.getAlpha() != 0)
+ driver->draw2DRectangle(bgcolor, bg_size + screen_pos);
+
+ font->draw(
+ translate_string(utf8_to_wide(nametag->text)).c_str(),
+ size + screen_pos, nametag->textcolor);
}
}
}
Nametag *Camera::addNametag(scene::ISceneNode *parent_node,
- const std::string &nametag_text, video::SColor nametag_color,
- const v3f &pos)
+ const std::string &text, video::SColor textcolor,
+ Optional<video::SColor> bgcolor, const v3f &pos)
{
- Nametag *nametag = new Nametag(parent_node, nametag_text, nametag_color, pos);
+ Nametag *nametag = new Nametag(parent_node, text, textcolor, bgcolor, pos);
m_nametags.push_back(nametag);
return nametag;
}