## Other files generated by minetest
screenshot_*.png
+testbm.txt
## Doxygen files
doc/Doxyfile
cmake_config.h
cmake_config_githash.h
CMakeDoxy*
+compile_commands.json
## Android build files
build/android/src/main/assets
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 \
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 \
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 \
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 \
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 \
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 \
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 \
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 \
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
${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}
)
# Server sources
set(server_SRCS
${common_SRCS}
- main.cpp
)
list(SORT server_SRCS)
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()
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "camera.h"
-#include "debug.h"
-#include "client.h"
-#include "map.h"
-#include "clientmap.h" // MapDrawControl
-#include "player.h"
-#include <cmath>
-#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<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;
- 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(
- 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);
- }
- }
-}
-
-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;
-}
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include "irrlichttypes_extrabloated.h"
-#include "inventory.h"
-#include "client/tile.h"
-#include <ICameraSceneNode.h>
-#include <ISceneNode.h>
-#include <list>
-
-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<Nametag *> &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<Nametag *> m_nametags;
-};
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include <iostream>
-#include <algorithm>
-#include <sstream>
-#include <cmath>
-#include <IFileSystem.h>
-#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<fs::DirListNode> 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<ModSpec>& Client::getMods() const
-{
- static std::vector<ModSpec> 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
- << "):"<<std::endl;
- m_packetcounter.print(infostream);
- m_packetcounter.clear();
- }
- }
-
- // UGLY hack to fix 2 second startup delay caused by non existent
- // server client startup synchronization in local server or singleplayer mode
- static bool initial_step = true;
- if (initial_step) {
- initial_step = false;
- }
- else if(m_state == LC_Created) {
- if (m_is_registration_confirmation_state) {
- // Waiting confirmation
- return;
- }
- float &counter = m_connection_reinit_timer;
- counter -= dtime;
- if(counter <= 0.0) {
- counter = 2.0;
-
- LocalPlayer *myplayer = m_env.getLocalPlayer();
- FATAL_ERROR_IF(myplayer == NULL, "Local player not found in environment.");
-
- sendInit(myplayer->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<v3s16> 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<v3s16>::iterator i = deleted_blocks.begin();
- std::vector<v3s16> 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 <interval> 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<s32> removed_server_ids;
- for (std::unordered_map<s32, int>::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<std::string, ModMetadata *>::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<char> 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 \""<<filename<<"\""<<std::endl;
-
- io::IFileSystem *irrfs = RenderingEngine::get_filesystem();
- video::IVideoDriver *vdrv = RenderingEngine::get_video_driver();
-
- // Create an irrlicht memory file
- io::IReadFile *rfile = irrfs->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 \""<<filename<<"\""<<std::endl;
- rfile->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 \""<<filename<<"\""<<std::endl;
- m_sound->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: "
- <<"\""<<filename<<"\""<<std::endl;
- if(m_mesh_data.count(filename))
- errorstream<<"Multiple models with name \""<<filename.c_str()
- <<"\" found; replacing previous model"<<std::endl;
- m_mesh_data[filename] = data;
- return true;
- }
-
- const char *translate_ext[] = {
- ".tr", NULL
- };
- name = removeStringEnd(filename, translate_ext);
- if (!name.empty()) {
- verbosestream << "Client: Loading translation: "
- << "\"" << filename << "\"" << std::endl;
- g_translations->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<std::string> &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()="
- <<e.what()<<std::endl;
- }
- }
-}
-
-void Client::Receive()
-{
- NetworkPacket pkt;
- m_con->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="<<command<<std::endl;
- m_packetcounter.add((u16)command);
-
- /*
- If this check is removed, be sure to change the queue
- system to know the ids
- */
- if(sender_peer_id != PEER_ID_SERVER) {
- infostream << "Client::ProcessData(): Discarding data not "
- "coming from server: peer_id=" << sender_peer_id
- << std::endl;
- return;
- }
-
- // Command must be handled into ToClientCommandHandler
- if (command >= TOCLIENT_NUM_MSG_TYPES) {
- infostream << "Client: Ignoring unknown command "
- << command << std::endl;
- return;
- }
-
- /*
- * Those packets are handled before m_server_ser_ver is set, it's normal
- * But we must use the new ToClientConnectionState in the future,
- * as a byte mask
- */
- if(toClientCommandTable[command].state == TOCLIENT_STATE_NOT_CONNECTED) {
- handleCommand(pkt);
- return;
- }
-
- if(m_server_ser_ver == SER_FMT_VER_INVALID) {
- infostream << "Client: Server serialization"
- " format invalid or not initialized."
- " Skipping incoming command=" << command << std::endl;
- return;
- }
-
- /*
- Handle runtime commands
- */
-
- handleCommand(pkt);
-}
-
-void Client::Send(NetworkPacket* pkt)
-{
- m_con->Send(PEER_ID_SERVER,
- serverCommandFactoryTable[pkt->getCommand()].channel,
- pkt,
- serverCommandFactoryTable[pkt->getCommand()].reliable);
-}
-
-// Will fill up 12 + 12 + 4 + 4 + 4 bytes
-void writePlayerPos(LocalPlayer *myplayer, ClientMap *clientMap, NetworkPacket *pkt)
-{
- v3f pf = myplayer->getPosition() * 100;
- v3f sf = myplayer->getSpeed() * 100;
- s32 pitch = myplayer->getPitch() * 100;
- s32 yaw = myplayer->getYaw() * 100;
- u32 keyPressed = myplayer->keyPressed;
- // scaled by 80, so that pi can fit into a u8
- u8 fov = clientMap->getCameraFov() * 80;
- u8 wanted_range = MYMIN(255,
- std::ceil(clientMap->getControl().wanted_range / MAP_BLOCKSIZE));
-
- v3s32 position(pf.X, pf.Y, pf.Z);
- v3s32 speed(sf.X, sf.Y, sf.Z);
-
- /*
- Format:
- [0] v3s32 position*100
- [12] v3s32 speed*100
- [12+12] s32 pitch*100
- [12+12+4] s32 yaw*100
- [12+12+4+4] u32 keyPressed
- [12+12+4+4+4] u8 fov*80
- [12+12+4+4+4+1] u8 ceil(wanted_range / MAP_BLOCKSIZE)
- */
- *pkt << position << speed << pitch << yaw << keyPressed;
- *pkt << fov << wanted_range;
-}
-
-void Client::interact(u8 action, const PointedThing& pointed)
-{
- if(m_state != LC_Ready) {
- errorstream << "Client::interact() "
- "Canceled (not connected)"
- << std::endl;
- return;
- }
-
- LocalPlayer *myplayer = m_env.getLocalPlayer();
- if (myplayer == NULL)
- return;
-
- /*
- [0] u16 command
- [2] u8 action
- [3] u16 item
- [5] u32 length of the next item (plen)
- [9] serialized PointedThing
- [9 + plen] player position information
- actions:
- 0: start digging (from undersurface) or use
- 1: stop digging (all parameters ignored)
- 2: digging completed
- 3: place block or item (to abovesurface)
- 4: use item
- 5: perform secondary action of item
- */
-
- NetworkPacket pkt(TOSERVER_INTERACT, 1 + 2 + 0);
-
- pkt << action;
- pkt << (u16)getPlayerItem();
-
- std::ostringstream tmp_os(std::ios::binary);
- pointed.serialize(tmp_os);
-
- pkt.putLongString(tmp_os.str());
-
- writePlayerPos(myplayer, &m_env.getClientMap(), &pkt);
-
- Send(&pkt);
-}
-
-void Client::deleteAuthData()
-{
- if (!m_auth_data)
- return;
-
- switch (m_chosen_auth_mech) {
- case AUTH_MECHANISM_FIRST_SRP:
- break;
- case AUTH_MECHANISM_SRP:
- case AUTH_MECHANISM_LEGACY_PASSWORD:
- srp_user_delete((SRPUser *) m_auth_data);
- m_auth_data = NULL;
- break;
- case AUTH_MECHANISM_NONE:
- break;
- }
- m_chosen_auth_mech = AUTH_MECHANISM_NONE;
-}
-
-
-AuthMechanism Client::choseAuthMech(const u32 mechs)
-{
- if (mechs & AUTH_MECHANISM_SRP)
- return AUTH_MECHANISM_SRP;
-
- if (mechs & AUTH_MECHANISM_FIRST_SRP)
- return AUTH_MECHANISM_FIRST_SRP;
-
- if (mechs & AUTH_MECHANISM_LEGACY_PASSWORD)
- return AUTH_MECHANISM_LEGACY_PASSWORD;
-
- return AUTH_MECHANISM_NONE;
-}
-
-void Client::sendInit(const std::string &playerName)
-{
- NetworkPacket pkt(TOSERVER_INIT, 1 + 2 + 2 + (1 + playerName.size()));
-
- // we don't support network compression yet
- u16 supp_comp_modes = NETPROTO_COMPRESSION_NONE;
-
- pkt << (u8) SER_FMT_VER_HIGHEST_READ << (u16) supp_comp_modes;
- pkt << (u16) CLIENT_PROTOCOL_VERSION_MIN << (u16) CLIENT_PROTOCOL_VERSION_MAX;
- pkt << playerName;
-
- Send(&pkt);
-}
-
-void Client::promptConfirmRegistration(AuthMechanism chosen_auth_mechanism)
-{
- m_chosen_auth_mech = chosen_auth_mechanism;
- m_is_registration_confirmation_state = true;
-}
-
-void Client::confirmRegistration()
-{
- m_is_registration_confirmation_state = false;
- startAuth(m_chosen_auth_mech);
-}
-
-void Client::startAuth(AuthMechanism chosen_auth_mechanism)
-{
- m_chosen_auth_mech = chosen_auth_mechanism;
-
- switch (chosen_auth_mechanism) {
- case AUTH_MECHANISM_FIRST_SRP: {
- // send srp verifier to server
- std::string verifier;
- std::string salt;
- generate_srp_verifier_and_salt(getPlayerName(), m_password,
- &verifier, &salt);
-
- NetworkPacket resp_pkt(TOSERVER_FIRST_SRP, 0);
- resp_pkt << salt << verifier << (u8)((m_password.empty()) ? 1 : 0);
-
- Send(&resp_pkt);
- break;
- }
- case AUTH_MECHANISM_SRP:
- case AUTH_MECHANISM_LEGACY_PASSWORD: {
- u8 based_on = 1;
-
- if (chosen_auth_mechanism == AUTH_MECHANISM_LEGACY_PASSWORD) {
- m_password = translate_password(getPlayerName(), m_password);
- based_on = 0;
- }
-
- std::string playername_u = lowercase(getPlayerName());
- m_auth_data = srp_user_new(SRP_SHA256, SRP_NG_2048,
- getPlayerName().c_str(), playername_u.c_str(),
- (const unsigned char *) m_password.c_str(),
- m_password.length(), NULL, NULL);
- char *bytes_A = 0;
- size_t len_A = 0;
- SRP_Result res = srp_user_start_authentication(
- (struct SRPUser *) m_auth_data, NULL, NULL, 0,
- (unsigned char **) &bytes_A, &len_A);
- FATAL_ERROR_IF(res != SRP_OK, "Creating local SRP user failed.");
-
- NetworkPacket resp_pkt(TOSERVER_SRP_BYTES_A, 0);
- resp_pkt << std::string(bytes_A, len_A) << based_on;
- Send(&resp_pkt);
- break;
- }
- case AUTH_MECHANISM_NONE:
- break; // not handled in this method
- }
-}
-
-void Client::sendDeletedBlocks(std::vector<v3s16> &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<s32> &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<std::wstring>();
-}
-
-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<v3s16, MapBlock*> 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<v3s16, MapBlock*> 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(): "
- <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
- <<std::endl;
- }
-
- v3s16 blockpos = getNodeBlockPos(nodepos);
- v3s16 blockpos_relative = blockpos * MAP_BLOCKSIZE;
-
- try{
- addUpdateMeshTask(blockpos, ack_to_server, urgent);
- }
- catch(InvalidPositionException &e) {}
-
- // Leading edge
- if(nodepos.X == blockpos_relative.X){
- try{
- v3s16 p = blockpos + v3s16(-1,0,0);
- addUpdateMeshTask(p, false, urgent);
- }
- catch(InvalidPositionException &e){}
- }
-
- if(nodepos.Y == blockpos_relative.Y){
- try{
- v3s16 p = blockpos + v3s16(0,-1,0);
- addUpdateMeshTask(p, false, urgent);
- }
- catch(InvalidPositionException &e){}
- }
-
- if(nodepos.Z == blockpos_relative.Z){
- try{
- v3s16 p = blockpos + v3s16(0,0,-1);
- addUpdateMeshTask(p, false, urgent);
- }
- catch(InvalidPositionException &e){}
- }
-}
-
-ClientEvent *Client::getClientEvent()
-{
- FATAL_ERROR_IF(m_client_event_queue.empty(),
- "Cannot getClientEvent, queue is empty.");
-
- ClientEvent *event = m_client_event_queue.front();
- m_client_event_queue.pop();
- return event;
-}
-
-bool Client::connectedToServer()
-{
- return m_con->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<wchar_t> 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"<<std::endl;
- assert(m_itemdef_received); // pre-condition
- assert(m_nodedef_received); // pre-condition
- assert(mediaReceived()); // pre-condition
-
- const wchar_t* text = wgettext("Loading textures...");
-
- // Clear cached pre-scaled 2D GUI images, as this cache
- // might have images with the same name but different
- // content from previous sessions.
- guiScalingCacheClear();
-
- // Rebuild inherited images and recreate textures
- infostream<<"- Rebuilding images and textures"<<std::endl;
- RenderingEngine::draw_load_screen(text, guienv, m_tsrc, 0, 70);
- m_tsrc->rebuildImagesAndTextures();
- delete[] text;
-
- // Rebuild shaders
- infostream<<"- Rebuilding shaders"<<std::endl;
- text = wgettext("Rebuilding shaders...");
- RenderingEngine::draw_load_screen(text, guienv, m_tsrc, 0, 71);
- m_shsrc->rebuildShaders();
- delete[] text;
-
- // Update node aliases
- infostream<<"- Updating node aliases"<<std::endl;
- text = wgettext("Initializing nodes...");
- RenderingEngine::draw_load_screen(text, guienv, m_tsrc, 0, 72);
- m_nodedef->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"<<std::endl;
- TextureUpdateArgs tu_args;
- tu_args.guienv = guienv;
- tu_args.last_time_ms = porting::getTimeMs();
- tu_args.last_percent = 0;
- tu_args.text_base = wgettext("Initializing nodes");
- tu_args.tsrc = m_tsrc;
- m_nodedef->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"<<std::endl;
- m_mesh_update_thread.start();
-
- m_state = LC_Ready;
- sendReady();
-
- if (g_settings->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"<<std::endl;
- delete[] text;
-}
-
-// returns the Round Trip Time
-// if the RTT did not become updated within 2 seconds, e.g. before timing out,
-// it returns the expired time instead
-float Client::getRTT()
-{
- float avg_rtt = m_con->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<char> data_rw(data.c_str(), data.size()); // Const-incorrect Irrlicht
- io::IReadFile *rfile = RenderingEngine::get_filesystem()->createMemoryReadFile(
- *data_rw, data_rw.getSize(), filename.c_str());
- 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<std::string, ModMetadata *>::const_iterator it =
- m_mod_storages.find(name);
- if (it != m_mod_storages.end()) {
- // Save unconditionaly on unregistration
- it->second->save(getModStoragePath());
- 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);
-}
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include "clientenvironment.h"
-#include "irrlichttypes_extrabloated.h"
-#include <ostream>
-#include <map>
-#include <set>
-#include <vector>
-#include <unordered_set>
-#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 <fstream>
-
-#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<u16, u16>::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<u16, u16> 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<ModSpec> &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<std::string> &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<std::string> &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<std::string> &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<u32, u32> &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<v3s16> &blocks);
- void sendGotBlocks(v3s16 block);
- void sendRemovedSounds(std::vector<s32> &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<con::Connection> 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<std::wstring> m_out_chat_queue;
- u32 m_last_chat_message_sent;
- float m_chat_message_allowance = 5.0f;
- std::queue<ChatMessage *> 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<ClientEvent *> 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<s32, int> m_sounds_server_to_client;
- // And the other way!
- std::unordered_map<int, s32> m_sounds_client_to_server;
- // And relations to objects
- std::unordered_map<int, u16> m_sounds_to_objects;
-
- // CSM/client IDs to SSM/server IDs Mapping
- // Map server particle spawner IDs to client IDs
- std::unordered_map<u32, u32> m_particles_server_to_client;
- // Map server hud ids to client hud ids
- std::unordered_map<u32, u32> m_hud_server_to_client;
-
- // Privileges
- std::unordered_set<std::string> m_privileges;
-
- // Detached inventories
- // key = name
- std::unordered_map<std::string, Inventory*> 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<std::string, ModMetadata *> m_mod_storages;
- float m_mod_storage_save_timer = 10.0f;
- std::vector<ModSpec> 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<ModChannelMgr> m_modchannel_mgr;
-};
${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
)
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "camera.h"
+#include "debug.h"
+#include "client.h"
+#include "map.h"
+#include "clientmap.h" // MapDrawControl
+#include "player.h"
+#include <cmath>
+#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<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;
+ 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(
+ 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);
+ }
+ }
+}
+
+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;
+}
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+#include "inventory.h"
+#include "client/tile.h"
+#include <ICameraSceneNode.h>
+#include <ISceneNode.h>
+#include <list>
+
+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<Nametag *> &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<Nametag *> m_nametags;
+};
--- /dev/null
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include <iostream>
+#include <algorithm>
+#include <sstream>
+#include <cmath>
+#include <IFileSystem.h>
+#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<fs::DirListNode> 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<ModSpec>& Client::getMods() const
+{
+ static std::vector<ModSpec> 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
+ << "):"<<std::endl;
+ m_packetcounter.print(infostream);
+ m_packetcounter.clear();
+ }
+ }
+
+ // UGLY hack to fix 2 second startup delay caused by non existent
+ // server client startup synchronization in local server or singleplayer mode
+ static bool initial_step = true;
+ if (initial_step) {
+ initial_step = false;
+ }
+ else if(m_state == LC_Created) {
+ if (m_is_registration_confirmation_state) {
+ // Waiting confirmation
+ return;
+ }
+ float &counter = m_connection_reinit_timer;
+ counter -= dtime;
+ if(counter <= 0.0) {
+ counter = 2.0;
+
+ LocalPlayer *myplayer = m_env.getLocalPlayer();
+ FATAL_ERROR_IF(myplayer == NULL, "Local player not found in environment.");
+
+ sendInit(myplayer->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<v3s16> 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<v3s16>::iterator i = deleted_blocks.begin();
+ std::vector<v3s16> 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 <interval> 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<s32> removed_server_ids;
+ for (std::unordered_map<s32, int>::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<std::string, ModMetadata *>::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<char> 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 \""<<filename<<"\""<<std::endl;
+
+ io::IFileSystem *irrfs = RenderingEngine::get_filesystem();
+ video::IVideoDriver *vdrv = RenderingEngine::get_video_driver();
+
+ // Create an irrlicht memory file
+ io::IReadFile *rfile = irrfs->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 \""<<filename<<"\""<<std::endl;
+ rfile->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 \""<<filename<<"\""<<std::endl;
+ m_sound->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: "
+ <<"\""<<filename<<"\""<<std::endl;
+ if(m_mesh_data.count(filename))
+ errorstream<<"Multiple models with name \""<<filename.c_str()
+ <<"\" found; replacing previous model"<<std::endl;
+ m_mesh_data[filename] = data;
+ return true;
+ }
+
+ const char *translate_ext[] = {
+ ".tr", NULL
+ };
+ name = removeStringEnd(filename, translate_ext);
+ if (!name.empty()) {
+ verbosestream << "Client: Loading translation: "
+ << "\"" << filename << "\"" << std::endl;
+ g_translations->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<std::string> &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()="
+ <<e.what()<<std::endl;
+ }
+ }
+}
+
+void Client::Receive()
+{
+ NetworkPacket pkt;
+ m_con->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="<<command<<std::endl;
+ m_packetcounter.add((u16)command);
+
+ /*
+ If this check is removed, be sure to change the queue
+ system to know the ids
+ */
+ if(sender_peer_id != PEER_ID_SERVER) {
+ infostream << "Client::ProcessData(): Discarding data not "
+ "coming from server: peer_id=" << sender_peer_id
+ << std::endl;
+ return;
+ }
+
+ // Command must be handled into ToClientCommandHandler
+ if (command >= TOCLIENT_NUM_MSG_TYPES) {
+ infostream << "Client: Ignoring unknown command "
+ << command << std::endl;
+ return;
+ }
+
+ /*
+ * Those packets are handled before m_server_ser_ver is set, it's normal
+ * But we must use the new ToClientConnectionState in the future,
+ * as a byte mask
+ */
+ if(toClientCommandTable[command].state == TOCLIENT_STATE_NOT_CONNECTED) {
+ handleCommand(pkt);
+ return;
+ }
+
+ if(m_server_ser_ver == SER_FMT_VER_INVALID) {
+ infostream << "Client: Server serialization"
+ " format invalid or not initialized."
+ " Skipping incoming command=" << command << std::endl;
+ return;
+ }
+
+ /*
+ Handle runtime commands
+ */
+
+ handleCommand(pkt);
+}
+
+void Client::Send(NetworkPacket* pkt)
+{
+ m_con->Send(PEER_ID_SERVER,
+ serverCommandFactoryTable[pkt->getCommand()].channel,
+ pkt,
+ serverCommandFactoryTable[pkt->getCommand()].reliable);
+}
+
+// Will fill up 12 + 12 + 4 + 4 + 4 bytes
+void writePlayerPos(LocalPlayer *myplayer, ClientMap *clientMap, NetworkPacket *pkt)
+{
+ v3f pf = myplayer->getPosition() * 100;
+ v3f sf = myplayer->getSpeed() * 100;
+ s32 pitch = myplayer->getPitch() * 100;
+ s32 yaw = myplayer->getYaw() * 100;
+ u32 keyPressed = myplayer->keyPressed;
+ // scaled by 80, so that pi can fit into a u8
+ u8 fov = clientMap->getCameraFov() * 80;
+ u8 wanted_range = MYMIN(255,
+ std::ceil(clientMap->getControl().wanted_range / MAP_BLOCKSIZE));
+
+ v3s32 position(pf.X, pf.Y, pf.Z);
+ v3s32 speed(sf.X, sf.Y, sf.Z);
+
+ /*
+ Format:
+ [0] v3s32 position*100
+ [12] v3s32 speed*100
+ [12+12] s32 pitch*100
+ [12+12+4] s32 yaw*100
+ [12+12+4+4] u32 keyPressed
+ [12+12+4+4+4] u8 fov*80
+ [12+12+4+4+4+1] u8 ceil(wanted_range / MAP_BLOCKSIZE)
+ */
+ *pkt << position << speed << pitch << yaw << keyPressed;
+ *pkt << fov << wanted_range;
+}
+
+void Client::interact(u8 action, const PointedThing& pointed)
+{
+ if(m_state != LC_Ready) {
+ errorstream << "Client::interact() "
+ "Canceled (not connected)"
+ << std::endl;
+ return;
+ }
+
+ LocalPlayer *myplayer = m_env.getLocalPlayer();
+ if (myplayer == NULL)
+ return;
+
+ /*
+ [0] u16 command
+ [2] u8 action
+ [3] u16 item
+ [5] u32 length of the next item (plen)
+ [9] serialized PointedThing
+ [9 + plen] player position information
+ actions:
+ 0: start digging (from undersurface) or use
+ 1: stop digging (all parameters ignored)
+ 2: digging completed
+ 3: place block or item (to abovesurface)
+ 4: use item
+ 5: perform secondary action of item
+ */
+
+ NetworkPacket pkt(TOSERVER_INTERACT, 1 + 2 + 0);
+
+ pkt << action;
+ pkt << (u16)getPlayerItem();
+
+ std::ostringstream tmp_os(std::ios::binary);
+ pointed.serialize(tmp_os);
+
+ pkt.putLongString(tmp_os.str());
+
+ writePlayerPos(myplayer, &m_env.getClientMap(), &pkt);
+
+ Send(&pkt);
+}
+
+void Client::deleteAuthData()
+{
+ if (!m_auth_data)
+ return;
+
+ switch (m_chosen_auth_mech) {
+ case AUTH_MECHANISM_FIRST_SRP:
+ break;
+ case AUTH_MECHANISM_SRP:
+ case AUTH_MECHANISM_LEGACY_PASSWORD:
+ srp_user_delete((SRPUser *) m_auth_data);
+ m_auth_data = NULL;
+ break;
+ case AUTH_MECHANISM_NONE:
+ break;
+ }
+ m_chosen_auth_mech = AUTH_MECHANISM_NONE;
+}
+
+
+AuthMechanism Client::choseAuthMech(const u32 mechs)
+{
+ if (mechs & AUTH_MECHANISM_SRP)
+ return AUTH_MECHANISM_SRP;
+
+ if (mechs & AUTH_MECHANISM_FIRST_SRP)
+ return AUTH_MECHANISM_FIRST_SRP;
+
+ if (mechs & AUTH_MECHANISM_LEGACY_PASSWORD)
+ return AUTH_MECHANISM_LEGACY_PASSWORD;
+
+ return AUTH_MECHANISM_NONE;
+}
+
+void Client::sendInit(const std::string &playerName)
+{
+ NetworkPacket pkt(TOSERVER_INIT, 1 + 2 + 2 + (1 + playerName.size()));
+
+ // we don't support network compression yet
+ u16 supp_comp_modes = NETPROTO_COMPRESSION_NONE;
+
+ pkt << (u8) SER_FMT_VER_HIGHEST_READ << (u16) supp_comp_modes;
+ pkt << (u16) CLIENT_PROTOCOL_VERSION_MIN << (u16) CLIENT_PROTOCOL_VERSION_MAX;
+ pkt << playerName;
+
+ Send(&pkt);
+}
+
+void Client::promptConfirmRegistration(AuthMechanism chosen_auth_mechanism)
+{
+ m_chosen_auth_mech = chosen_auth_mechanism;
+ m_is_registration_confirmation_state = true;
+}
+
+void Client::confirmRegistration()
+{
+ m_is_registration_confirmation_state = false;
+ startAuth(m_chosen_auth_mech);
+}
+
+void Client::startAuth(AuthMechanism chosen_auth_mechanism)
+{
+ m_chosen_auth_mech = chosen_auth_mechanism;
+
+ switch (chosen_auth_mechanism) {
+ case AUTH_MECHANISM_FIRST_SRP: {
+ // send srp verifier to server
+ std::string verifier;
+ std::string salt;
+ generate_srp_verifier_and_salt(getPlayerName(), m_password,
+ &verifier, &salt);
+
+ NetworkPacket resp_pkt(TOSERVER_FIRST_SRP, 0);
+ resp_pkt << salt << verifier << (u8)((m_password.empty()) ? 1 : 0);
+
+ Send(&resp_pkt);
+ break;
+ }
+ case AUTH_MECHANISM_SRP:
+ case AUTH_MECHANISM_LEGACY_PASSWORD: {
+ u8 based_on = 1;
+
+ if (chosen_auth_mechanism == AUTH_MECHANISM_LEGACY_PASSWORD) {
+ m_password = translate_password(getPlayerName(), m_password);
+ based_on = 0;
+ }
+
+ std::string playername_u = lowercase(getPlayerName());
+ m_auth_data = srp_user_new(SRP_SHA256, SRP_NG_2048,
+ getPlayerName().c_str(), playername_u.c_str(),
+ (const unsigned char *) m_password.c_str(),
+ m_password.length(), NULL, NULL);
+ char *bytes_A = 0;
+ size_t len_A = 0;
+ SRP_Result res = srp_user_start_authentication(
+ (struct SRPUser *) m_auth_data, NULL, NULL, 0,
+ (unsigned char **) &bytes_A, &len_A);
+ FATAL_ERROR_IF(res != SRP_OK, "Creating local SRP user failed.");
+
+ NetworkPacket resp_pkt(TOSERVER_SRP_BYTES_A, 0);
+ resp_pkt << std::string(bytes_A, len_A) << based_on;
+ Send(&resp_pkt);
+ break;
+ }
+ case AUTH_MECHANISM_NONE:
+ break; // not handled in this method
+ }
+}
+
+void Client::sendDeletedBlocks(std::vector<v3s16> &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<s32> &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<std::wstring>();
+}
+
+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<v3s16, MapBlock*> 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<v3s16, MapBlock*> 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(): "
+ <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
+ <<std::endl;
+ }
+
+ v3s16 blockpos = getNodeBlockPos(nodepos);
+ v3s16 blockpos_relative = blockpos * MAP_BLOCKSIZE;
+
+ try{
+ addUpdateMeshTask(blockpos, ack_to_server, urgent);
+ }
+ catch(InvalidPositionException &e) {}
+
+ // Leading edge
+ if(nodepos.X == blockpos_relative.X){
+ try{
+ v3s16 p = blockpos + v3s16(-1,0,0);
+ addUpdateMeshTask(p, false, urgent);
+ }
+ catch(InvalidPositionException &e){}
+ }
+
+ if(nodepos.Y == blockpos_relative.Y){
+ try{
+ v3s16 p = blockpos + v3s16(0,-1,0);
+ addUpdateMeshTask(p, false, urgent);
+ }
+ catch(InvalidPositionException &e){}
+ }
+
+ if(nodepos.Z == blockpos_relative.Z){
+ try{
+ v3s16 p = blockpos + v3s16(0,0,-1);
+ addUpdateMeshTask(p, false, urgent);
+ }
+ catch(InvalidPositionException &e){}
+ }
+}
+
+ClientEvent *Client::getClientEvent()
+{
+ FATAL_ERROR_IF(m_client_event_queue.empty(),
+ "Cannot getClientEvent, queue is empty.");
+
+ ClientEvent *event = m_client_event_queue.front();
+ m_client_event_queue.pop();
+ return event;
+}
+
+bool Client::connectedToServer()
+{
+ return m_con->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<wchar_t> 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"<<std::endl;
+ assert(m_itemdef_received); // pre-condition
+ assert(m_nodedef_received); // pre-condition
+ assert(mediaReceived()); // pre-condition
+
+ const wchar_t* text = wgettext("Loading textures...");
+
+ // Clear cached pre-scaled 2D GUI images, as this cache
+ // might have images with the same name but different
+ // content from previous sessions.
+ guiScalingCacheClear();
+
+ // Rebuild inherited images and recreate textures
+ infostream<<"- Rebuilding images and textures"<<std::endl;
+ RenderingEngine::draw_load_screen(text, guienv, m_tsrc, 0, 70);
+ m_tsrc->rebuildImagesAndTextures();
+ delete[] text;
+
+ // Rebuild shaders
+ infostream<<"- Rebuilding shaders"<<std::endl;
+ text = wgettext("Rebuilding shaders...");
+ RenderingEngine::draw_load_screen(text, guienv, m_tsrc, 0, 71);
+ m_shsrc->rebuildShaders();
+ delete[] text;
+
+ // Update node aliases
+ infostream<<"- Updating node aliases"<<std::endl;
+ text = wgettext("Initializing nodes...");
+ RenderingEngine::draw_load_screen(text, guienv, m_tsrc, 0, 72);
+ m_nodedef->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"<<std::endl;
+ TextureUpdateArgs tu_args;
+ tu_args.guienv = guienv;
+ tu_args.last_time_ms = porting::getTimeMs();
+ tu_args.last_percent = 0;
+ tu_args.text_base = wgettext("Initializing nodes");
+ tu_args.tsrc = m_tsrc;
+ m_nodedef->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"<<std::endl;
+ m_mesh_update_thread.start();
+
+ m_state = LC_Ready;
+ sendReady();
+
+ if (g_settings->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"<<std::endl;
+ delete[] text;
+}
+
+// returns the Round Trip Time
+// if the RTT did not become updated within 2 seconds, e.g. before timing out,
+// it returns the expired time instead
+float Client::getRTT()
+{
+ float avg_rtt = m_con->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<char> data_rw(data.c_str(), data.size()); // Const-incorrect Irrlicht
+ io::IReadFile *rfile = RenderingEngine::get_filesystem()->createMemoryReadFile(
+ *data_rw, data_rw.getSize(), filename.c_str());
+ 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<std::string, ModMetadata *>::const_iterator it =
+ m_mod_storages.find(name);
+ if (it != m_mod_storages.end()) {
+ // Save unconditionaly on unregistration
+ it->second->save(getModStoragePath());
+ 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);
+}
--- /dev/null
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "clientenvironment.h"
+#include "irrlichttypes_extrabloated.h"
+#include <ostream>
+#include <map>
+#include <set>
+#include <vector>
+#include <unordered_set>
+#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 <fstream>
+
+#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<u16, u16>::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<u16, u16> 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<ModSpec> &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<std::string> &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<std::string> &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<std::string> &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<u32, u32> &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<v3s16> &blocks);
+ void sendGotBlocks(v3s16 block);
+ void sendRemovedSounds(std::vector<s32> &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<con::Connection> 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<std::wstring> m_out_chat_queue;
+ u32 m_last_chat_message_sent;
+ float m_chat_message_allowance = 5.0f;
+ std::queue<ChatMessage *> 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<ClientEvent *> 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<s32, int> m_sounds_server_to_client;
+ // And the other way!
+ std::unordered_map<int, s32> m_sounds_client_to_server;
+ // And relations to objects
+ std::unordered_map<int, u16> m_sounds_to_objects;
+
+ // CSM/client IDs to SSM/server IDs Mapping
+ // Map server particle spawner IDs to client IDs
+ std::unordered_map<u32, u32> m_particles_server_to_client;
+ // Map server hud ids to client hud ids
+ std::unordered_map<u32, u32> m_hud_server_to_client;
+
+ // Privileges
+ std::unordered_set<std::string> m_privileges;
+
+ // Detached inventories
+ // key = name
+ std::unordered_map<std::string, Inventory*> 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<std::string, ModMetadata *> m_mod_storages;
+ float m_mod_storage_save_timer = 10.0f;
+ std::vector<ModSpec> 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<ModChannelMgr> m_modchannel_mgr;
+};
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2017 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "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 <algorithm>
+#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<CollisionInfo> 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"<<std::endl;
+ delete object;
+ return 0;
+ }
+ object->setId(new_id);
+ }
+ if (!isFreeClientActiveObjectId(object->getId(), m_active_objects)) {
+ infostream<<"ClientEnvironment::addActiveObject(): "
+ <<"id is not free ("<<object->getId()<<")"<<std::endl;
+ delete object;
+ return 0;
+ }
+ infostream<<"ClientEnvironment::addActiveObject(): "
+ <<"added (id="<<object->getId()<<")"<<std::endl;
+ m_active_objects[object->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="<<id<<" type="<<type<<": Couldn't create object"
+ <<std::endl;
+ return;
+ }
+
+ obj->setId(id);
+
+ try
+ {
+ obj->initialize(init_data);
+ }
+ catch(SerializationError &e)
+ {
+ errorstream<<"ClientEnvironment::addActiveObject():"
+ <<" id="<<id<<" type="<<type
+ <<": SerializationError in initialize(): "
+ <<e.what()
+ <<": init_data="<<serializeJsonString(init_data)
+ <<std::endl;
+ }
+
+ addActiveObject(obj);
+}
+
+void ClientEnvironment::removeActiveObject(u16 id)
+{
+ verbosestream<<"ClientEnvironment::removeActiveObject(): "
+ <<"id="<<id<<std::endl;
+ ClientActiveObject* obj = getActiveObject(id);
+ if (obj == NULL) {
+ infostream<<"ClientEnvironment::removeActiveObject(): "
+ <<"id="<<id<<" not found"<<std::endl;
+ return;
+ }
+ obj->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<DistanceSortedActiveObject> &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<f32> &shootline_on_map,
+ std::vector<PointedThing> &objects)
+{
+ std::vector<DistanceSortedActiveObject> 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());
+ }
+ }
+}
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2017 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "environment.h"
+#include <ISceneManager.h>
+#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<u16, ClientActiveObject*> 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<DistanceSortedActiveObject> &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<f32> &shootline_on_map,
+ std::vector<PointedThing> &objects
+ );
+
+ u16 attachement_parent_ids[USHRT_MAX + 1];
+
+ const std::list<std::string> &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<ClientSimpleObject*> m_simple_objects;
+ std::queue<ClientEnvEvent> m_client_event_queue;
+ IntervalLimiter m_active_object_light_update_interval;
+ std::list<std::string> m_player_names;
+ v3s16 m_camera_offset;
+};
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "clientmap.h"
+#include "client.h"
+#include "mapblock_mesh.h"
+#include <IMaterialRenderer.h>
+#include <matrix4.h>
+#include "mapsector.h"
+#include "mapblock.h"
+#include "profiler.h"
+#include "settings.h"
+#include "camera.h" // CameraModes
+#include "util/basic_macros.h"
+#include <algorithm>
+#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<scene::IMeshBuffer*> 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<MeshBufList> 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<MeshBufList> &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 "<<brightness_count<<" points; result="
+ <<(*result)<<std::endl;*/
+ return true;
+}
+
+int ClientMap::getBackgroundBrightness(float max_d, u32 daylight_factor,
+ int oldvalue, bool *sunlight_seen_result)
+{
+ static v3f z_directions[50] = {
+ v3f(-100, 0, 0)
+ };
+ static f32 z_offsets[sizeof(z_directions)/sizeof(*z_directions)] = {
+ -1000,
+ };
+
+ if(z_directions[0].X < -99){
+ for(u32 i=0; i<sizeof(z_directions)/sizeof(*z_directions); i++){
+ // Assumes FOV of 72 and 16/9 aspect ratio
+ z_directions[i] = v3f(
+ 0.02 * myrand_range(-100, 100),
+ 1.0,
+ 0.01 * myrand_range(-100, 100)
+ ).normalize();
+ z_offsets[i] = 0.01 * myrand_range(0,100);
+ }
+ }
+
+ int sunlight_seen_count = 0;
+ float sunlight_min_d = max_d*0.8;
+ if(sunlight_min_d > 35*BS)
+ sunlight_min_d = 35*BS;
+ std::vector<int> values;
+ for(u32 i=0; i<sizeof(z_directions)/sizeof(*z_directions); i++){
+ v3f z_dir = z_directions[i];
+ core::CMatrix4<f32> a;
+ a.buildRotateFromTo(v3f(0,1,0), z_dir);
+ v3f dir = m_camera_direction;
+ a.rotateVect(dir);
+ int br = 0;
+ float step = BS*1.5;
+ if(max_d > 35*BS)
+ step = max_d / 35 * 1.5;
+ float off = step * z_offsets[i];
+ bool sunlight_seen_now = false;
+ bool ok = getVisibleBrightness(this, m_camera_position, dir,
+ step, 1.0, max_d*0.6+off, max_d, m_nodedef, daylight_factor,
+ sunlight_min_d,
+ &br, &sunlight_seen_now);
+ if(sunlight_seen_now)
+ sunlight_seen_count++;
+ if(!ok)
+ continue;
+ values.push_back(br);
+ // Don't try too much if being in the sun is clear
+ if(sunlight_seen_count >= 20)
+ break;
+ }
+ int brightness_sum = 0;
+ int brightness_count = 0;
+ std::sort(values.begin(), values.end());
+ u32 num_values_to_use = values.size();
+ if(num_values_to_use >= 10)
+ num_values_to_use -= num_values_to_use/2;
+ else if(num_values_to_use >= 7)
+ num_values_to_use -= num_values_to_use/3;
+ u32 first_value_i = (values.size() - num_values_to_use) / 2;
+
+ for (u32 i=first_value_i; i < first_value_i + num_values_to_use; i++) {
+ brightness_sum += values[i];
+ brightness_count++;
+ }
+
+ int ret = 0;
+ if(brightness_count == 0){
+ MapNode n = getNodeNoEx(floatToInt(m_camera_position, BS));
+ if(m_nodedef->get(n).param_type == CPT_LIGHT){
+ ret = decode_light(n.getLightBlend(daylight_factor, m_nodedef));
+ } else {
+ ret = oldvalue;
+ }
+ } else {
+ ret = brightness_sum / brightness_count;
+ }
+
+ *sunlight_seen_result = (sunlight_seen_count > 0);
+ return ret;
+}
+
+void ClientMap::renderPostFx(CameraMode cam_mode)
+{
+ // Sadly ISceneManager has no "post effects" render pass, in that case we
+ // could just register for that and handle it in renderMap().
+
+ MapNode n = getNodeNoEx(floatToInt(m_camera_position, BS));
+
+ // - If the player is in a solid node, make everything black.
+ // - If the player is in liquid, draw a semi-transparent overlay.
+ // - Do not if player is in third person mode
+ const ContentFeatures& features = m_nodedef->get(n);
+ video::SColor post_effect_color = features.post_effect_color;
+ if(features.solidness == 2 && !(g_settings->getBool("noclip") &&
+ m_client->checkLocalPrivilege("noclip")) &&
+ cam_mode == CAMERA_MODE_FIRST)
+ {
+ 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<s32> rect(0,0, ss.X, ss.Y);
+ driver->draw2DRectangle(post_effect_color, rect);
+ }
+}
+
+void ClientMap::PrintInfo(std::ostream &out)
+{
+ out<<"ClientMap: ";
+}
+
+
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+#include "map.h"
+#include "camera.h"
+#include <set>
+#include <map>
+
+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<v3s16, MapBlock*> m_drawlist;
+
+ std::set<v2s16> m_last_drawn_sectors;
+
+ bool m_cache_trilinear_filter;
+ bool m_cache_bilinear_filter;
+ bool m_cache_anistropic_filter;
+};
--- /dev/null
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "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<std::string> 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<std::string, FileStatus*>::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<std::string, FileStatus*>::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<unsigned long, std::string>::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<std::string, FileStatus*>::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<std::string> 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<std::string, FileStatus*>::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<std::string, FileStatus*>::iterator
+ it = m_files.begin();
+ it != m_files.end(); ++it) {
+ if (!it->second->received) {
+ FATAL_ERROR_IF(it->second->sha1.size() != 20, "Invalid SHA1 size");
+ os << it->second->sha1;
+ }
+ }
+
+ return os.str();
+}
+
+void ClientMediaDownloader::deSerializeHashSet(const std::string &data,
+ std::set<std::string> &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));
+ }
+}
--- /dev/null
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "irrlichttypes.h"
+#include "filecache.h"
+#include <ostream>
+#include <map>
+#include <set>
+#include <vector>
+#include <unordered_map>
+
+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<s32> 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<std::string> &result);
+
+ // Maps filename to file status
+ std::map<std::string, FileStatus*> m_files;
+
+ // Array of remote media servers
+ std::vector<RemoteServerStatus*> m_remotes;
+
+ // Filesystem-based media cache
+ FileCache m_media_cache;
+
+ // Has an attempt been made to load media files from the file cache?
+ // Have hash sets been requested from remote servers?
+ bool m_initial_step_done = false;
+
+ // 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<unsigned long, std::string> m_remote_file_transfers;
+
+ // All files up to this name have either been received from a
+ // remote server or failed on all remote servers, so those files
+ // 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 = "";
+
+};
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "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;
+}
+
+
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+#include "activeobject.h"
+#include <unordered_map>
+
+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<u16, Factory> 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;
+ }
+};
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "client/renderingengine.h"
+#include "clouds.h"
+#include "noise.h"
+#include "constants.h"
+#include "debug.h"
+#include "profiler.h"
+#include "settings.h"
+#include <cmath>
+
+
+// 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; i<num_faces_to_draw; i++)
+ {
+ switch(i)
+ {
+ case 0: // top
+ for (video::S3DVertex &vertex : v) {
+ vertex.Normal.set(0,1,0);
+ }
+ v[0].Pos.set(-rx, ry,-rz);
+ v[1].Pos.set(-rx, ry, rz);
+ v[2].Pos.set( rx, ry, rz);
+ v[3].Pos.set( rx, ry,-rz);
+ break;
+ case 1: // back
+ if (INAREA(xi, zi - 1, m_cloud_radius_i)) {
+ u32 j = GETINDEX(xi, zi - 1, m_cloud_radius_i);
+ if(grid[j])
+ continue;
+ }
+ for (video::S3DVertex &vertex : v) {
+ vertex.Color = c_side_1;
+ vertex.Normal.set(0,0,-1);
+ }
+ v[0].Pos.set(-rx, ry,-rz);
+ v[1].Pos.set( rx, ry,-rz);
+ v[2].Pos.set( rx, 0,-rz);
+ v[3].Pos.set(-rx, 0,-rz);
+ break;
+ case 2: //right
+ if (INAREA(xi + 1, zi, m_cloud_radius_i)) {
+ u32 j = GETINDEX(xi+1, zi, m_cloud_radius_i);
+ if(grid[j])
+ continue;
+ }
+ for (video::S3DVertex &vertex : v) {
+ vertex.Color = c_side_2;
+ vertex.Normal.set(1,0,0);
+ }
+ v[0].Pos.set( rx, ry,-rz);
+ v[1].Pos.set( rx, ry, rz);
+ v[2].Pos.set( rx, 0, rz);
+ v[3].Pos.set( rx, 0,-rz);
+ break;
+ case 3: // front
+ if (INAREA(xi, zi + 1, m_cloud_radius_i)) {
+ u32 j = GETINDEX(xi, zi + 1, m_cloud_radius_i);
+ if(grid[j])
+ continue;
+ }
+ for (video::S3DVertex &vertex : v) {
+ vertex.Color = c_side_1;
+ vertex.Normal.set(0,0,-1);
+ }
+ v[0].Pos.set( rx, ry, rz);
+ v[1].Pos.set(-rx, ry, rz);
+ v[2].Pos.set(-rx, 0, rz);
+ v[3].Pos.set( rx, 0, rz);
+ break;
+ case 4: // left
+ if (INAREA(xi-1, zi, m_cloud_radius_i)) {
+ u32 j = GETINDEX(xi-1, zi, m_cloud_radius_i);
+ if(grid[j])
+ continue;
+ }
+ for (video::S3DVertex &vertex : v) {
+ vertex.Color = c_side_2;
+ vertex.Normal.set(-1,0,0);
+ }
+ v[0].Pos.set(-rx, ry, rz);
+ v[1].Pos.set(-rx, ry,-rz);
+ v[2].Pos.set(-rx, 0,-rz);
+ v[3].Pos.set(-rx, 0, rz);
+ break;
+ case 5: // bottom
+ for (video::S3DVertex &vertex : v) {
+ vertex.Color = c_bottom;
+ vertex.Normal.set(0,-1,0);
+ }
+ v[0].Pos.set( rx, 0, rz);
+ v[1].Pos.set(-rx, 0, rz);
+ v[2].Pos.set(-rx, 0,-rz);
+ v[3].Pos.set( rx, 0,-rz);
+ break;
+ }
+
+ v3f pos(p0.X, m_params.height * BS, p0.Y);
+ pos -= intToFloat(m_camera_offset, BS);
+
+ for (video::S3DVertex &vertex : v)
+ vertex.Pos += pos;
+ u16 indices[] = {0,1,2,2,3,0};
+ driver->drawVertexPrimitiveList(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);
+}
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+#include <iostream>
+#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;
+
+};
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include <ICameraSceneNode.h>
+#include <ITextSceneNode.h>
+#include <IBillboardSceneNode.h>
+#include <IMeshManipulator.h>
+#include <IAnimatedMeshSceneNode.h>
+#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 <algorithm>
+#include <cmath>
+#include "client/renderingengine.h"
+
+class Settings;
+struct ToolCapabilities;
+
+std::unordered_map<u16, ClientActiveObject::Factory> ClientActiveObject::m_types;
+
+template<typename T>
+void SmoothTranslator<T>::init(T current)
+{
+ val_old = current;
+ val_current = current;
+ val_target = current;
+ anim_time = 0;
+ anim_time_counter = 0;
+ aim_is_end = true;
+}
+
+template<typename T>
+void SmoothTranslator<T>::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<typename T>
+void SmoothTranslator<T>::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="<<dtime<<", rot.Y="<<rot.Y<<std::endl;
+ rot.Y += dtime * 180;
+ m_node->setRotation(rot);
+ }
+}
+
+void TestCAO::processMessage(const std::string &data)
+{
+ infostream<<"TestCAO: Got data: "<<data<<std::endl;
+ std::istringstream is(data, std::ios::binary);
+ u16 cmd;
+ is>>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"<<std::endl;
+ processInitData(data);
+
+ if (m_is_player) {
+ // Check if it's the current player
+ LocalPlayer *player = m_env->getLocalPlayer();
+ if (player && strcmp(player->getName(), m_name.c_str()) == 0) {
+ m_is_local_player = true;
+ m_is_visible = false;
+ player->setCAO(this);
+ }
+ }
+}
+
+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"
+ <<std::endl;
+ return;
+ }
+
+ for (int i = 0; i < num_messages; i++) {
+ std::string message = deSerializeLongString(is);
+ processMessage(message);
+ }
+
+ m_rotation = wrapDegrees_0_360_v3f(m_rotation);
+ pos_translator.init(m_position);
+ rot_translator.init(m_rotation);
+ updateNodePos();
+}
+
+GenericCAO::~GenericCAO()
+{
+ removeFromScene(true);
+}
+
+bool GenericCAO::getSelectionBox(aabb3f *toset) const
+{
+ if (!m_prop.is_visible || !m_is_visible || m_is_local_player
+ || !m_prop.pointable) {
+ return false;
+ }
+ *toset = m_selection_box;
+ return true;
+}
+
+v3f GenericCAO::getPosition()
+{
+ if (getParent() != NULL) {
+ scene::ISceneNode *node = getSceneNode();
+ if (node)
+ return node->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"<<std::endl;
+ m_spritenode = RenderingEngine::get_scene_manager()->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"<<std::endl;
+ scene::IMesh *mesh = createCubeMesh(v3f(BS,BS,BS));
+ m_meshnode = RenderingEngine::get_scene_manager()->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"<<std::endl;
+ scene::IAnimatedMesh *mesh = m_client->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 "<<m_prop.mesh<<std::endl;
+ } else if (m_prop.visual == "wielditem") {
+ ItemStack item;
+ infostream << "GenericCAO::addToScene(): wielditem" << std::endl;
+ if (m_prop.wield_item.empty()) {
+ // Old format, only textures are specified.
+ infostream << "textures: " << m_prop.textures.size() << std::endl;
+ if (!m_prop.textures.empty()) {
+ infostream << "textures[0]: " << m_prop.textures[0]
+ << std::endl;
+ IItemDefManager *idef = m_client->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(): \""<<m_prop.visual
+ <<"\" not supported"<<std::endl;
+ }
+
+ /* don't update while punch texture modifier is active */
+ if (m_reset_textures_timer < 0)
+ updateTextures(m_current_texture_modifier);
+
+ scene::ISceneNode *node = getSceneNode();
+ if (node && !m_prop.nametag.empty() && !m_is_local_player) {
+ // Add nametag
+ v3f pos;
+ pos.Y = m_prop.selectionbox.MaxEdge.Y + 0.3f;
+ m_nametag = m_client->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 "<<texturestring<<std::endl;
+ continue;
+ }
+
+ // Set material flags and texture
+ video::SMaterial& material = m_animated_meshnode->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<u32> &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<std::string, core::vector2d<v3f>>::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"<<std::endl;
+ std::istringstream is(data, std::ios::binary);
+ // command
+ u8 cmd = readU8(is);
+ if (cmd == GENERIC_CMD_SET_PROPERTIES) {
+ m_prop = gob_read_set_properties(is);
+
+ m_selection_box = m_prop.selectionbox;
+ m_selection_box.MinEdge *= BS;
+ m_selection_box.MaxEdge *= BS;
+
+ m_tx_size.X = 1.0 / m_prop.spritediv.X;
+ m_tx_size.Y = 1.0 / m_prop.spritediv.Y;
+
+ if(!m_initial_tx_basepos_set){
+ m_initial_tx_basepos_set = true;
+ m_tx_basepos = m_prop.initial_sprite_basepos;
+ }
+ if (m_is_local_player) {
+ LocalPlayer *player = m_env->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<v3f>(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; i<armor_groups_size; i++)
+ {
+ std::string name = deSerializeString(is);
+ int rating = readS16(is);
+ m_armor_groups[name] = rating;
+ }
+ } else if (cmd == GENERIC_CMD_UPDATE_NAMETAG_ATTRIBUTES) {
+ // Deprecated, for backwards compatibility only.
+ readU8(is); // version
+ m_prop.nametag_color = readARGB8(is);
+ if (m_nametag != NULL) {
+ m_nametag->nametag_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="<<m_hp<<"\n";
+ os<<"armor={";
+ for(ItemGroupList::const_iterator i = m_armor_groups.begin();
+ i != m_armor_groups.end(); ++i)
+ {
+ os<<i->first<<"="<<i->second<<", ";
+ }
+ os<<"}";
+ return os.str();
+}
+
+// Prototype
+GenericCAO proto_GenericCAO(NULL, NULL);
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include <map>
+#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<typename T>
+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<f32>
+{
+ void translate(f32 dtime);
+};
+
+struct SmoothTranslatorWrappedv3f : SmoothTranslator<v3f>
+{
+ 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<v3f> 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<std::string, core::vector2d<v3f>> 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<u16> 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;
+ }
+};
--- /dev/null
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "content_cso.h"
+#include <IBillboardSceneNode.h>
+#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"<<std::endl;
+ m_spritenode = smgr->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"<<std::endl;
+ m_spritenode->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);
+}
+
--- /dev/null
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+#include "clientsimpleobject.h"
+
+ClientSimpleObject* createSmokePuff(scene::ISceneManager *smgr,
+ ClientEnvironment *env, v3f pos, v2f size);
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "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 <IMeshManipulator.h>
+#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<aabb3f> 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();
+}
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "nodedef.h"
+#include <IMeshManipulator.h>
+
+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);
+};
--- /dev/null
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2013 Jonathan Neuschäfer <j.neuschaefer@gmx.net>
+
+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 <string>
+#include <iostream>
+#include <fstream>
+#include <cstdlib>
+
+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: "
+ <<path<<std::endl;
+ return false;
+ }
+
+ bool bad = false;
+ for(;;){
+ char buf[1024];
+ fis.read(buf, 1024);
+ std::streamsize len = fis.gcount();
+ os.write(buf, len);
+ if(fis.eof())
+ break;
+ if(!fis.good()){
+ bad = true;
+ break;
+ }
+ }
+ if(bad){
+ errorstream<<"FileCache: Failed to read file from cache: \""
+ <<path<<"\""<<std::endl;
+ }
+
+ return !bad;
+}
+
+bool FileCache::updateByPath(const std::string &path, const std::string &data)
+{
+ std::ofstream file(path.c_str(), std::ios_base::binary |
+ std::ios_base::trunc);
+
+ if(!file.good())
+ {
+ errorstream<<"FileCache: Can't write to file at "
+ <<path<<std::endl;
+ return false;
+ }
+
+ file.write(data.c_str(), data.length());
+ file.close();
+
+ return !file.fail();
+}
+
+bool FileCache::update(const std::string &name, const std::string &data)
+{
+ std::string path = m_dir + DIR_DELIM + name;
+ return updateByPath(path, data);
+}
+bool FileCache::load(const std::string &name, std::ostream &os)
+{
+ std::string path = m_dir + DIR_DELIM + name;
+ return loadByPath(path, os);
+}
--- /dev/null
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2013 Jonathan Neuschäfer <j.neuschaefer@gmx.net>
+
+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 <iostream>
+#include <string>
+
+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);
+};
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2014 sapier <sapier at gmx dot net>
+
+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 <cmath>
+#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;
+ }
+}
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2014 sapier <sapier at gmx dot net>
+
+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 <map>
+#include <vector>
+#include "util/basic_macros.h"
+#include <IGUIFont.h>
+#include <IGUISkin.h>
+#include <IGUIEnvironment.h>
+#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<unsigned int, irr::gui::IGUIFont*> 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;
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "game.h"
+
+#include <iomanip>
+#include <cmath>
+#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<std::string> m_fetched;
+private:
+ void paths_insert(std::set<std::string> &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<std::string> &dst_paths,
+ std::set<std::string> &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<float, 4> m_sky_bg_color;
+ CachedPixelShaderSetting<float> m_fog_distance;
+ CachedVertexShaderSetting<float> m_animation_timer_vertex;
+ CachedPixelShaderSetting<float> m_animation_timer_pixel;
+ CachedPixelShaderSetting<float, 3> m_day_light;
+ CachedPixelShaderSetting<float, 3> m_eye_position_pixel;
+ CachedVertexShaderSetting<float, 3> m_eye_position_vertex;
+ CachedPixelShaderSetting<float, 3> m_minimap_yaw;
+ CachedPixelShaderSetting<SamplerLayer_t> m_base_texture;
+ CachedPixelShaderSetting<SamplerLayer_t> m_normal_texture;
+ CachedPixelShaderSetting<SamplerLayer_t> 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<GameGlobalShaderConstantSetter*>(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<GameGlobalShaderConstantSetter *> 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<f32> &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<GameUI> 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<u32> 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<u32> ¤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<u32>(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<ClientEvent> 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<f32> shootline;
+
+ if (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT) {
+ shootline = core::line3d<f32>(camera_position,
+ camera_position + camera_direction * BS * d);
+ } else {
+ // prevent player pointing anything in front-view
+ shootline = core::line3d<f32>(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<f32> &shootline,
+ bool liquids_pointable,
+ bool look_for_object,
+ const v3s16 &camera_offset)
+{
+ std::vector<aabb3f> *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<aabb3f> boxes;
+ n.getSelectionBoxes(nodedef, &boxes,
+ n.getNeighbors(result.node_undersurface, &map));
+
+ f32 d = 0.002 * BS;
+ for (std::vector<aabb3f>::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="<<dig_index<<std::endl;
+ client->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"<<std::endl;
+ client->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<s32>(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;
+ }
+}
--- /dev/null
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "irrlichttypes.h"
+#include <string>
+
+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);
--- /dev/null
+/*
+Copyright (C) 2015 Aaron Suen <warr1024@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "guiscalingfilter.h"
+#include "imagefilters.h"
+#include "porting.h"
+#include "settings.h"
+#include "util/numeric.h"
+#include <cstdio>
+#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<io::path, video::IImage *> g_imgCache;
+
+/* Maintain a static cache of all pre-scaled textures. These need to be
+ * cleared as well when the cached images.
+ */
+std::map<io::path, video::ITexture *> 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<s32> &srcrect,
+ const core::rect<s32> &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>((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<u32>(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<s32>(0, 0, src->getSize().Width, src->getSize().Height),
+ core::rect<s32>(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<s32> &destrect, const core::rect<s32> &srcrect,
+ const core::rect<s32> *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<s32> mysrcrect = (scaled != txr)
+ ? core::rect<s32>(0, 0, destrect.getWidth(), destrect.getHeight())
+ : srcrect;
+
+ driver->draw2DImage(scaled, destrect, mysrcrect, cliprect, colors, usealpha);
+}
--- /dev/null
+/*
+Copyright (C) 2015 Aaron Suen <warr1024@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+
+/* 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<s32> &srcrect, const core::rect<s32> &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<s32> &destrect, const core::rect<s32> &srcrect,
+ const core::rect<s32> *cliprect = 0, const video::SColor *const colors = 0,
+ bool usealpha = false);
--- /dev/null
+/*
+Copyright (C) 2015 Aaron Suen <warr1024@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "imagefilters.h"
+#include "util/numeric.h"
+#include <cmath>
+
+/* 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<u32> 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<s32> &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<u32> 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);
+ }
+}
--- /dev/null
+/*
+Copyright (C) 2015 Aaron Suen <warr1024@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+
+/* 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<s32> &srcrect, video::IImage *dest);
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "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 << "<Keycode " << (int) key << ">";
+ 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 << "<Char " << hex_encode((char*) &Char, sizeof(wchar_t)) << ">";
+ 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 : "<Unnamed key>";
+}
+
+const KeyPress EscapeKey("KEY_ESCAPE");
+const KeyPress CancelKey("KEY_CANCEL");
+
+/*
+ Key config
+*/
+
+// A simple cache for quicker lookup
+std::unordered_map<std::string, KeyPress> g_key_setting_cache;
+
+KeyPress getKeySetting(const char *settingname)
+{
+ std::unordered_map<std::string, KeyPress>::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;
+}
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "irrlichttypes.h"
+#include "Keycodes.h"
+#include <IEventReceiver.h>
+#include <string>
+
+/* 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);
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "localplayer.h"
+#include <cmath>
+#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<aabb3f> &nodeboxes)
+{
+ if (nodeboxes.empty())
+ return aabb3f(0, 0, 0, 0, 0, 0);
+
+ aabb3f b_max;
+
+ std::vector<aabb3f>::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<aabb3f> 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<CollisionInfo> *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<CollisionInfo> *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<aabb3f> 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;
+ }
+}
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "player.h"
+#include "environment.h"
+#include "constants.h"
+#include "settings.h"
+#include <list>
+
+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<CollisionInfo> *collision_info);
+ // Temporary option for old move code
+ void old_move(f32 dtime, Environment *env, f32 pos_max_d,
+ std::vector<CollisionInfo> *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;
+};
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "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 <array>
+
+/*
+ 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<v3s16,8> &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<bool, 4> 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<v3s16,8> 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<FastFace> &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<f32>(x0 + w * abs_scale, y0 + h),
+ core::vector2d<f32>(x0, y0 + h),
+ core::vector2d<f32>(x0, y0),
+ core::vector2d<f32>(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<FastFace> &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<FastFace> &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<FastFace> 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<u8, u32>(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<u8, u32>(layer, i)] = p.layer;
+ m_animation_frames[std::pair<u8, u32>(layer, i)] = 0;
+ if (g_settings->getBool(
+ "desynchronize_mapblock_texture_animation")) {
+ // Get starting position from noise
+ m_animation_frame_offsets[std::pair<u8, u32>(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<u8, u32>(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<u8, u32>(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 "<<fastfaces.getSize()<<" faces."<<std::endl;
+
+ // Check if animation is required for this mesh
+ m_has_animation =
+ !m_crack_materials.empty() ||
+ !m_daynight_diffs.empty() ||
+ !m_animation_tiles.empty();
+}
+
+MapBlockMesh::~MapBlockMesh()
+{
+ for (scene::IMesh *m : m_mesh) {
+ if (m_enable_vbo && m)
+ for (u32 i = 0; i < m->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);
+}
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+#include "client/tile.h"
+#include "voxel.h"
+#include <array>
+#include <map>
+
+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::pair<u8, u32>, 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<std::pair<u8, u32>, TileLayer> m_animation_tiles;
+ std::map<std::pair<u8, u32>, int> m_animation_frames; // last animation frame
+ std::map<std::pair<u8, u32>, int> m_animation_frame_offsets;
+
+ // 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::pair<u8, u32>, std::map<u32, video::SColor > > 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);
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "mesh.h"
+#include "debug.h"
+#include "log.h"
+#include "irrMap.h"
+#include <iostream>
+#include <IAnimatedMesh.h>
+#include <SAnimatedMesh.h>
+#include <IAnimatedMeshSceneNode.h>
+
+// In Irrlicht 1.8 the signature of ITexture::lock was changed from
+// (bool, u32) to (E_TEXTURE_LOCK_MODE, u32).
+#if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 7
+#define MY_ETLM_READ_ONLY true
+#else
+#define MY_ETLM_READ_ONLY video::ETLM_READ_ONLY
+#endif
+
+inline static void applyShadeFactor(video::SColor& color, float factor)
+{
+ color.setRed(core::clamp(core::round32(color.getRed()*factor), 0, 255));
+ 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<aabb3f> &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<u32> tris;
+ float score;
+ s16 cachepos;
+ u16 NumActiveTris;
+};
+
+struct tcache
+{
+ u16 ind[3];
+ float score;
+ bool drawn;
+};
+
+const u16 cachesize = 32;
+
+float FindVertexScore(vcache *v)
+{
+ const float CacheDecayPower = 1.5f;
+ const float LastTriScore = 0.75f;
+ const float ValenceBoostScale = 2.0f;
+ const float ValenceBoostPower = 0.5f;
+ const float MaxSizeVertexCache = 32.0f;
+
+ if (v->NumActiveTris == 0)
+ {
+ // No tri needs this vertex!
+ return -1.0f;
+ }
+
+ float Score = 0.0f;
+ int CachePosition = v->cachepos;
+ if (CachePosition < 0)
+ {
+ // Vertex is not in FIFO cache - no score.
+ }
+ else
+ {
+ if (CachePosition < 3)
+ {
+ // This vertex was used in the last triangle,
+ // so it has a fixed score.
+ Score = LastTriScore;
+ }
+ else
+ {
+ // Points for being high in the cache.
+ const float Scaler = 1.0f / (MaxSizeVertexCache - 3);
+ Score = 1.0f - (CachePosition - 3) * Scaler;
+ Score = powf(Score, CacheDecayPower);
+ }
+ }
+
+ // Bonus points for having a low number of tris still to
+ // use the vert, so we get rid of lone verts quickly.
+ float ValenceBoost = powf(v->NumActiveTris,
+ -ValenceBoostPower);
+ Score += ValenceBoostScale * ValenceBoost;
+
+ return Score;
+}
+
+/*
+ A specialized LRU cache for the Forsyth algorithm.
+*/
+
+class f_lru
+{
+
+public:
+ f_lru(vcache *v, tcache *t): vc(v), tc(t)
+ {
+ for (int &i : cache) {
+ i = -1;
+ }
+ }
+
+ // Adds this vertex index and returns the highest-scoring triangle index
+ u32 add(u16 vert, bool updatetris = false)
+ {
+ bool found = false;
+
+ // Mark existing pos as empty
+ for (u16 i = 0; i < cachesize; i++)
+ {
+ if (cache[i] == vert)
+ {
+ // Move everything down
+ for (u16 j = i; j; j--)
+ {
+ cache[j] = cache[j - 1];
+ }
+
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ {
+ if (cache[cachesize-1] != -1)
+ vc[cache[cachesize-1]].cachepos = -1;
+
+ // Move everything down
+ for (u16 i = cachesize - 1; i; i--)
+ {
+ cache[i] = cache[i - 1];
+ }
+ }
+
+ cache[0] = vert;
+
+ u32 highest = 0;
+ float hiscore = 0;
+
+ if (updatetris)
+ {
+ // Update cache positions
+ for (u16 i = 0; i < cachesize; i++)
+ {
+ if (cache[i] == -1)
+ break;
+
+ vc[cache[i]].cachepos = i;
+ vc[cache[i]].score = FindVertexScore(&vc[cache[i]]);
+ }
+
+ // Update triangle scores
+ for (int i : cache) {
+ if (i == -1)
+ break;
+
+ const u16 trisize = vc[i].tris.size();
+ for (u16 t = 0; t < trisize; t++)
+ {
+ tcache *tri = &tc[vc[i].tris[t]];
+
+ tri->score =
+ vc[tri->ind[0]].score +
+ vc[tri->ind[1]].score +
+ vc[tri->ind[2]].score;
+
+ if (tri->score > hiscore)
+ {
+ hiscore = tri->score;
+ highest = vc[i].tris[t];
+ }
+ }
+ }
+ }
+
+ return highest;
+ }
+
+private:
+ s32 cache[cachesize];
+ vcache *vc;
+ tcache *tc;
+};
+
+/**
+Vertex cache optimization according to the Forsyth paper:
+http://home.comcast.net/~tom_forsyth/papers/fast_vert_cache_opt.html
+
+The function is thread-safe (read: you can optimize several meshes in different threads)
+
+\param mesh Source mesh for the operation. */
+scene::IMesh* createForsythOptimizedMesh(const scene::IMesh *mesh)
+{
+ if (!mesh)
+ return 0;
+
+ scene::SMesh *newmesh = new scene::SMesh();
+ newmesh->BoundingBox = mesh->getBoundingBox();
+
+ const u32 mbcount = mesh->getMeshBufferCount();
+
+ for (u32 b = 0; b < mbcount; ++b)
+ {
+ const scene::IMeshBuffer *mb = mesh->getMeshBuffer(b);
+
+ if (mb->getIndexType() != video::EIT_16BIT)
+ {
+ //os::Printer::log("Cannot optimize a mesh with 32bit indices", ELL_ERROR);
+ newmesh->drop();
+ return 0;
+ }
+
+ const u32 icount = mb->getIndexCount();
+ const u32 tcount = icount / 3;
+ const u32 vcount = mb->getVertexCount();
+ const u16 *ind = mb->getIndices();
+
+ vcache *vc = new vcache[vcount];
+ tcache *tc = new tcache[tcount];
+
+ f_lru lru(vc, tc);
+
+ // init
+ for (u16 i = 0; i < vcount; i++)
+ {
+ vc[i].score = 0;
+ vc[i].cachepos = -1;
+ vc[i].NumActiveTris = 0;
+ }
+
+ // First pass: count how many times a vert is used
+ for (u32 i = 0; i < icount; i += 3)
+ {
+ vc[ind[i]].NumActiveTris++;
+ vc[ind[i + 1]].NumActiveTris++;
+ vc[ind[i + 2]].NumActiveTris++;
+
+ const u32 tri_ind = i/3;
+ tc[tri_ind].ind[0] = ind[i];
+ tc[tri_ind].ind[1] = ind[i + 1];
+ tc[tri_ind].ind[2] = ind[i + 2];
+ }
+
+ // Second pass: list of each triangle
+ for (u32 i = 0; i < tcount; i++)
+ {
+ vc[tc[i].ind[0]].tris.push_back(i);
+ vc[tc[i].ind[1]].tris.push_back(i);
+ vc[tc[i].ind[2]].tris.push_back(i);
+
+ tc[i].drawn = false;
+ }
+
+ // Give initial scores
+ for (u16 i = 0; i < vcount; i++)
+ {
+ vc[i].score = FindVertexScore(&vc[i]);
+ }
+ for (u32 i = 0; i < tcount; i++)
+ {
+ tc[i].score =
+ vc[tc[i].ind[0]].score +
+ vc[tc[i].ind[1]].score +
+ vc[tc[i].ind[2]].score;
+ }
+
+ switch(mb->getVertexType())
+ {
+ case video::EVT_STANDARD:
+ {
+ video::S3DVertex *v = (video::S3DVertex *) mb->getVertices();
+
+ scene::SMeshBuffer *buf = new scene::SMeshBuffer();
+ buf->Material = mb->getMaterial();
+
+ buf->Vertices.reallocate(vcount);
+ buf->Indices.reallocate(icount);
+
+ core::map<const video::S3DVertex, const u16> sind; // search index for fast operation
+ typedef core::map<const video::S3DVertex, const u16>::Node snode;
+
+ // Main algorithm
+ u32 highest = 0;
+ u32 drawcalls = 0;
+ for (;;)
+ {
+ if (tc[highest].drawn)
+ {
+ bool found = false;
+ float hiscore = 0;
+ for (u32 t = 0; t < tcount; t++)
+ {
+ if (!tc[t].drawn)
+ {
+ if (tc[t].score > hiscore)
+ {
+ highest = t;
+ hiscore = tc[t].score;
+ found = true;
+ }
+ }
+ }
+ if (!found)
+ break;
+ }
+
+ // Output the best triangle
+ u16 newind = buf->Vertices.size();
+
+ snode *s = sind.find(v[tc[highest].ind[0]]);
+
+ if (!s)
+ {
+ buf->Vertices.push_back(v[tc[highest].ind[0]]);
+ buf->Indices.push_back(newind);
+ sind.insert(v[tc[highest].ind[0]], newind);
+ newind++;
+ }
+ else
+ {
+ buf->Indices.push_back(s->getValue());
+ }
+
+ s = sind.find(v[tc[highest].ind[1]]);
+
+ if (!s)
+ {
+ buf->Vertices.push_back(v[tc[highest].ind[1]]);
+ buf->Indices.push_back(newind);
+ sind.insert(v[tc[highest].ind[1]], newind);
+ newind++;
+ }
+ else
+ {
+ buf->Indices.push_back(s->getValue());
+ }
+
+ s = sind.find(v[tc[highest].ind[2]]);
+
+ if (!s)
+ {
+ buf->Vertices.push_back(v[tc[highest].ind[2]]);
+ buf->Indices.push_back(newind);
+ sind.insert(v[tc[highest].ind[2]], newind);
+ }
+ else
+ {
+ buf->Indices.push_back(s->getValue());
+ }
+
+ vc[tc[highest].ind[0]].NumActiveTris--;
+ vc[tc[highest].ind[1]].NumActiveTris--;
+ vc[tc[highest].ind[2]].NumActiveTris--;
+
+ tc[highest].drawn = true;
+
+ for (u16 j : tc[highest].ind) {
+ vcache *vert = &vc[j];
+ for (u16 t = 0; t < vert->tris.size(); t++)
+ {
+ if (highest == vert->tris[t])
+ {
+ vert->tris.erase(t);
+ break;
+ }
+ }
+ }
+
+ lru.add(tc[highest].ind[0]);
+ lru.add(tc[highest].ind[1]);
+ highest = lru.add(tc[highest].ind[2], true);
+ drawcalls++;
+ }
+
+ buf->setBoundingBox(mb->getBoundingBox());
+ newmesh->addMeshBuffer(buf);
+ buf->drop();
+ }
+ break;
+ case video::EVT_2TCOORDS:
+ {
+ video::S3DVertex2TCoords *v = (video::S3DVertex2TCoords *) mb->getVertices();
+
+ scene::SMeshBufferLightMap *buf = new scene::SMeshBufferLightMap();
+ buf->Material = mb->getMaterial();
+
+ buf->Vertices.reallocate(vcount);
+ buf->Indices.reallocate(icount);
+
+ core::map<const video::S3DVertex2TCoords, const u16> sind; // search index for fast operation
+ typedef core::map<const video::S3DVertex2TCoords, const u16>::Node snode;
+
+ // Main algorithm
+ u32 highest = 0;
+ u32 drawcalls = 0;
+ for (;;)
+ {
+ if (tc[highest].drawn)
+ {
+ bool found = false;
+ float hiscore = 0;
+ for (u32 t = 0; t < tcount; t++)
+ {
+ if (!tc[t].drawn)
+ {
+ if (tc[t].score > hiscore)
+ {
+ highest = t;
+ hiscore = tc[t].score;
+ found = true;
+ }
+ }
+ }
+ if (!found)
+ break;
+ }
+
+ // Output the best triangle
+ u16 newind = buf->Vertices.size();
+
+ snode *s = sind.find(v[tc[highest].ind[0]]);
+
+ if (!s)
+ {
+ buf->Vertices.push_back(v[tc[highest].ind[0]]);
+ buf->Indices.push_back(newind);
+ sind.insert(v[tc[highest].ind[0]], newind);
+ newind++;
+ }
+ else
+ {
+ buf->Indices.push_back(s->getValue());
+ }
+
+ s = sind.find(v[tc[highest].ind[1]]);
+
+ if (!s)
+ {
+ buf->Vertices.push_back(v[tc[highest].ind[1]]);
+ buf->Indices.push_back(newind);
+ sind.insert(v[tc[highest].ind[1]], newind);
+ newind++;
+ }
+ else
+ {
+ buf->Indices.push_back(s->getValue());
+ }
+
+ s = sind.find(v[tc[highest].ind[2]]);
+
+ if (!s)
+ {
+ buf->Vertices.push_back(v[tc[highest].ind[2]]);
+ buf->Indices.push_back(newind);
+ sind.insert(v[tc[highest].ind[2]], newind);
+ }
+ else
+ {
+ buf->Indices.push_back(s->getValue());
+ }
+
+ vc[tc[highest].ind[0]].NumActiveTris--;
+ vc[tc[highest].ind[1]].NumActiveTris--;
+ vc[tc[highest].ind[2]].NumActiveTris--;
+
+ tc[highest].drawn = true;
+
+ for (u16 j : tc[highest].ind) {
+ vcache *vert = &vc[j];
+ for (u16 t = 0; t < vert->tris.size(); t++)
+ {
+ if (highest == vert->tris[t])
+ {
+ vert->tris.erase(t);
+ break;
+ }
+ }
+ }
+
+ lru.add(tc[highest].ind[0]);
+ lru.add(tc[highest].ind[1]);
+ highest = lru.add(tc[highest].ind[2]);
+ drawcalls++;
+ }
+
+ buf->setBoundingBox(mb->getBoundingBox());
+ newmesh->addMeshBuffer(buf);
+ buf->drop();
+
+ }
+ break;
+ case video::EVT_TANGENTS:
+ {
+ video::S3DVertexTangents *v = (video::S3DVertexTangents *) mb->getVertices();
+
+ scene::SMeshBufferTangents *buf = new scene::SMeshBufferTangents();
+ buf->Material = mb->getMaterial();
+
+ buf->Vertices.reallocate(vcount);
+ buf->Indices.reallocate(icount);
+
+ core::map<const video::S3DVertexTangents, const u16> sind; // search index for fast operation
+ typedef core::map<const video::S3DVertexTangents, const u16>::Node snode;
+
+ // Main algorithm
+ u32 highest = 0;
+ u32 drawcalls = 0;
+ for (;;)
+ {
+ if (tc[highest].drawn)
+ {
+ bool found = false;
+ float hiscore = 0;
+ for (u32 t = 0; t < tcount; t++)
+ {
+ if (!tc[t].drawn)
+ {
+ if (tc[t].score > hiscore)
+ {
+ highest = t;
+ hiscore = tc[t].score;
+ found = true;
+ }
+ }
+ }
+ if (!found)
+ break;
+ }
+
+ // Output the best triangle
+ u16 newind = buf->Vertices.size();
+
+ snode *s = sind.find(v[tc[highest].ind[0]]);
+
+ if (!s)
+ {
+ buf->Vertices.push_back(v[tc[highest].ind[0]]);
+ buf->Indices.push_back(newind);
+ sind.insert(v[tc[highest].ind[0]], newind);
+ newind++;
+ }
+ else
+ {
+ buf->Indices.push_back(s->getValue());
+ }
+
+ s = sind.find(v[tc[highest].ind[1]]);
+
+ if (!s)
+ {
+ buf->Vertices.push_back(v[tc[highest].ind[1]]);
+ buf->Indices.push_back(newind);
+ sind.insert(v[tc[highest].ind[1]], newind);
+ newind++;
+ }
+ else
+ {
+ buf->Indices.push_back(s->getValue());
+ }
+
+ s = sind.find(v[tc[highest].ind[2]]);
+
+ if (!s)
+ {
+ buf->Vertices.push_back(v[tc[highest].ind[2]]);
+ buf->Indices.push_back(newind);
+ sind.insert(v[tc[highest].ind[2]], newind);
+ }
+ else
+ {
+ buf->Indices.push_back(s->getValue());
+ }
+
+ vc[tc[highest].ind[0]].NumActiveTris--;
+ vc[tc[highest].ind[1]].NumActiveTris--;
+ vc[tc[highest].ind[2]].NumActiveTris--;
+
+ tc[highest].drawn = true;
+
+ for (u16 j : tc[highest].ind) {
+ vcache *vert = &vc[j];
+ for (u16 t = 0; t < vert->tris.size(); t++)
+ {
+ if (highest == vert->tris[t])
+ {
+ vert->tris.erase(t);
+ break;
+ }
+ }
+ }
+
+ lru.add(tc[highest].ind[0]);
+ lru.add(tc[highest].ind[1]);
+ highest = lru.add(tc[highest].ind[2]);
+ drawcalls++;
+ }
+
+ buf->setBoundingBox(mb->getBoundingBox());
+ newmesh->addMeshBuffer(buf);
+ buf->drop();
+ }
+ break;
+ }
+
+ delete [] vc;
+ delete [] tc;
+
+ } // for each meshbuffer
+
+ return newmesh;
+}
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+#include "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<aabb3f> &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);
--- /dev/null
+/*
+Minetest
+Copyright (C) 2013, 2017 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "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<CachedMapBlockData*> 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<QueuedMeshUpdate*>::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<v3s16, CachedMapBlockData*>::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<v3s16, CachedMapBlockData*>::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<v3s16, CachedMapBlockData*>::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;
+ }
+}
--- /dev/null
+/*
+Minetest
+Copyright (C) 2013, 2017 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include <ctime>
+#include <mutex>
+#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<QueuedMeshUpdate *> m_queue;
+ std::set<v3s16> m_urgents;
+ std::map<v3s16, CachedMapBlockData *> 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<MeshUpdateResult> 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();
+};
#include "collector.h"
#include <stdexcept>
#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)
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2015 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "minimap.h"
+#include <cmath>
+#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<std::map<v3s16, MinimapMapblock*>::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<v3s16, MinimapMapblock *>::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<v3s16, MinimapMapblock *>::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<s32>(0, 0),
+ core::dimension2d<u32>(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<s32>(0, 0),
+ core::dimension2d<u32>(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<u32> 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<u32>(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<s32> oldViewPort = driver->getViewPort();
+ core::matrix4 oldProjMat = driver->getTransform(video::ETS_PROJECTION);
+ core::matrix4 oldViewMat = driver->getTransform(video::ETS_VIEW);
+
+ driver->setViewPort(core::rect<s32>(
+ 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<s32> 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<v2f>::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<s32> 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<Nametag *> &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;
+ }
+}
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2015 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+#include "util/thread.h"
+#include "voxel.h"
+#include <map>
+#include <string>
+#include <vector>
+
+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<QueuedMinimapUpdate> m_update_queue;
+ std::map<v3s16, MinimapMapblock *> 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<v2f> m_active_markers;
+};
--- /dev/null
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "particles.h"
+#include <cmath>
+#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<float>::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<u32, ParticleSpawner*>::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<Particle*>::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<u32, ParticleSpawner*>::iterator i =
+ m_particle_spawners.begin();
+ i != m_particle_spawners.end();)
+ {
+ delete i->second;
+ m_particle_spawners.erase(i++);
+ }
+
+ for(std::vector<Particle*>::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<u32, ParticleSpawner*>(
+ 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);
+}
--- /dev/null
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include <iostream>
+#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<float> 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<Particle*> m_particles;
+ std::map<u32, ParticleSpawner*> m_particle_spawners;
+
+ ClientEnvironment* m_env;
+ std::mutex m_particle_list_lock;
+ std::mutex m_spawner_list_lock;
+};
*/
#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()),
*/
#include "interlaced.h"
-#include "client.h"
-#include "shader.h"
+#include "client/client.h"
+#include "client/shader.h"
#include "client/tile.h"
RenderingCoreInterlaced::RenderingCoreInterlaced(
*/
#include "stereo.h"
-#include "camera.h"
+#include "client/camera.h"
#include "constants.h"
#include "settings.h"
--- /dev/null
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2013 Kahrl <kahrl@gmx.net>
+
+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 <fstream>
+#include <iterator>
+#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 <ICameraSceneNode.h>
+#include <IGPUProgrammingServices.h>
+#include <IMaterialRenderer.h>
+#include <IMaterialRendererServices.h>
+#include <IShaderConstantSetCallBack.h>
+#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<std::string, std::string> 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<IShaderConstantSetter*> m_setters;
+
+public:
+ ShaderCallback(const std::vector<IShaderConstantSetterFactory *> &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<float, 16> m_world_view_proj;
+ CachedVertexShaderSetting<float, 16> 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<float(*)[16]>(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<float(*)[16]>(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<ShaderInfo> 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<std::string, u32, u8, u8> m_get_shader_queue;
+
+ // Global constant setter factories
+ std::vector<IShaderConstantSetterFactory *> m_setter_factories;
+
+ // Shader callbacks
+ std::vector<ShaderCallback *> 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<ShaderCallback *> &callbacks,
+ const std::vector<IShaderConstantSetterFactory *> &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=\""<<name<<"\""<<std::endl;*/
+
+ // We're gonna ask the result to be put into here
+
+ static ResultQueue<std::string, u32, u8, u8> result_queue;
+
+ // Throw a request in
+ m_get_shader_queue.add(name, 0, 0, &result_queue);
+
+ /* infostream<<"Waiting for shader from main thread, name=\""
+ <<name<<"\""<<std::endl;*/
+
+ while(true) {
+ GetResult<std::string, u32, u8, u8>
+ 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<<"\""<<std::endl;
+
+ // Empty name means shader 0
+ if (name.empty()) {
+ infostream<<"getShaderIdDirect(): name is empty"<<std::endl;
+ return 0;
+ }
+
+ // Check if already have such instance
+ for(u32 i=0; i<m_shaderinfo_cache.size(); i++){
+ ShaderInfo *info = &m_shaderinfo_cache[i];
+ if(info->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"<<std::endl;
+ return 0;
+ }
+
+ ShaderInfo info = generate_shader(name, material_type, drawtype,
+ m_callbacks, m_setter_factories, &m_sourcecache);
+
+ /*
+ Add shader to caches (add dummy shaders too)
+ */
+
+ MutexAutoLock lock(m_shaderinfo_cache_mutex);
+
+ u32 id = m_shaderinfo_cache.size();
+ m_shaderinfo_cache.push_back(info);
+
+ infostream<<"getShaderIdDirect(): "
+ <<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;
+
+ return id;
+}
+
+
+ShaderInfo ShaderSource::getShaderInfo(u32 id)
+{
+ MutexAutoLock lock(m_shaderinfo_cache_mutex);
+
+ if(id >= 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_of_shader<<"\", "
+ "filename=\""<<filename<<"\""<<std::endl;*/
+
+ sanity_check(std::this_thread::get_id() == m_main_thread);
+
+ m_sourcecache.insert(name_of_shader, filename, program, true);
+}
+
+void ShaderSource::rebuildShaders()
+{
+ MutexAutoLock lock(m_shaderinfo_cache_mutex);
+
+ /*// Oh well... just clear everything, they'll load sometime.
+ m_shaderinfo_cache.clear();
+ m_name_to_id.clear();*/
+
+ /*
+ FIXME: Old shader materials can't be deleted in Irrlicht,
+ or can they?
+ (This would be nice to do in the destructor too)
+ */
+
+ // Recreate shaders
+ for (ShaderInfo &i : m_shaderinfo_cache) {
+ ShaderInfo *info = &i;
+ if (!info->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<ShaderCallback *> &callbacks,
+ const std::vector<IShaderConstantSetterFactory *> &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 \""<<name<<"\", "
+ "GPU programming not supported."
+ <<std::endl;
+ return shaderinfo;
+ }
+
+ // Choose shader language depending on driver type and settings
+ // Then load shaders
+ std::string vertex_program;
+ std::string pixel_program;
+ std::string geometry_program;
+ bool is_highlevel;
+ load_shaders(name, sourcecache, driver->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."
+ <<std::endl;
+ vertex_program = "";
+ }
+ if (!pixel_program.empty() &&
+ !driver->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."
+ <<std::endl;
+ pixel_program = "";
+ }
+ if (!geometry_program.empty() &&
+ !driver->queryFeature(video::EVDF_GEOMETRY_SHADER)){
+ infostream<<"generate_shader(): geometry shaders disabled "
+ "because of missing driver/hardware support."
+ <<std::endl;
+ geometry_program = "";
+ }
+
+ // If no shaders are used, don't make a separate material type
+ if (vertex_program.empty() && pixel_program.empty() && geometry_program.empty())
+ return shaderinfo;
+
+ // Create shaders header
+ std::string shaders_header = "#version 120\n";
+
+ static const char* drawTypes[] = {
+ "NDT_NORMAL",
+ "NDT_AIRLIKE",
+ "NDT_LIQUID",
+ "NDT_FLOWINGLIQUID",
+ "NDT_GLASSLIKE",
+ "NDT_ALLFACES",
+ "NDT_ALLFACES_OPTIONAL",
+ "NDT_TORCHLIKE",
+ "NDT_SIGNLIKE",
+ "NDT_PLANTLIKE",
+ "NDT_FENCELIKE",
+ "NDT_RAILLIKE",
+ "NDT_NODEBOX",
+ "NDT_GLASSLIKE_FRAMED",
+ "NDT_FIRELIKE",
+ "NDT_GLASSLIKE_FRAMED_OPTIONAL",
+ "NDT_PLANTLIKE_ROOTED",
+ };
+
+ for (int i = 0; i < 14; i++){
+ shaders_header += "#define ";
+ shaders_header += drawTypes[i];
+ shaders_header += " ";
+ shaders_header += itos(i);
+ shaders_header += "\n";
+ }
+
+ static const char* materialTypes[] = {
+ "TILE_MATERIAL_BASIC",
+ "TILE_MATERIAL_ALPHA",
+ "TILE_MATERIAL_LIQUID_TRANSPARENT",
+ "TILE_MATERIAL_LIQUID_OPAQUE",
+ "TILE_MATERIAL_WAVING_LEAVES",
+ "TILE_MATERIAL_WAVING_PLANTS",
+ "TILE_MATERIAL_OPAQUE"
+ };
+
+ for (int i = 0; i < 7; i++){
+ shaders_header += "#define ";
+ shaders_header += materialTypes[i];
+ shaders_header += " ";
+ shaders_header += itos(i);
+ shaders_header += "\n";
+ }
+
+ shaders_header += "#define MATERIAL_TYPE ";
+ shaders_header += itos(material_type);
+ shaders_header += "\n";
+ shaders_header += "#define DRAW_TYPE ";
+ shaders_header += itos(drawtype);
+ shaders_header += "\n";
+
+ if (g_settings->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 "<<name<<std::endl;
+ shadermat = gpu->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 \""<<name<<"\", "
+ "addHighLevelShaderMaterial failed."
+ <<std::endl;
+ dumpShaderProgram(warningstream, "Vertex", vertex_program);
+ dumpShaderProgram(warningstream, "Pixel", pixel_program);
+ dumpShaderProgram(warningstream, "Geometry", geometry_program);
+ delete cb;
+ return shaderinfo;
+ }
+ }
+ else{
+ infostream<<"Compiling assembly shaders for "<<name<<std::endl;
+ shadermat = gpu->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 \""<<name<<"\", "
+ "addShaderMaterial failed."
+ <<std::endl;
+ dumpShaderProgram(warningstream, "Vertex", vertex_program);
+ dumpShaderProgram(warningstream,"Pixel", pixel_program);
+ delete cb;
+ return shaderinfo;
+ }
+ }
+ callbacks.push_back(cb);
+
+ // HACK, TODO: investigate this better
+ // Grab the material renderer once more so minetest doesn't crash on exit
+ driver->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;
+}
--- /dev/null
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2013 Kahrl <kahrl@gmx.net>
+
+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 <IMaterialRendererServices.h>
+#include "irrlichttypes_bloated.h"
+#include <string>
+
+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 <typename T, std::size_t count=1>
+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 <typename T, std::size_t count = 1>
+class CachedPixelShaderSetting : public CachedShaderSetting<T, count> {
+public:
+ CachedPixelShaderSetting(const char *name) :
+ CachedShaderSetting<T, count>(name, true){}
+};
+
+template <typename T, std::size_t count = 1>
+class CachedVertexShaderSetting : public CachedShaderSetting<T, count> {
+public:
+ CachedVertexShaderSetting(const char *name) :
+ CachedShaderSetting<T, count>(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);
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "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 <cmath>
+#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="<<m_time_of_day<<" -> "
+ <<"wicked_time_of_day="<<wicked_time_of_day<<std::endl;*/
+
+ video::SColor suncolor = suncolor_f.toSColor();
+ video::SColor suncolor2 = suncolor2_f.toSColor();
+ video::SColor mooncolor = mooncolor_f.toSColor();
+ video::SColor mooncolor2 = mooncolor2_f.toSColor();
+
+ // Calculate offset normalized to the X dimension of a 512x1 px tonemap
+ float offset = (1.0 - fabs(sin((m_time_of_day - 0.5) * irr::core::PI))) * 511;
+
+ if (m_sun_tonemap) {
+ u8 * texels = (u8 *)m_sun_tonemap->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_="<<a_<<" a="<<a<<std::endl;
+ video::SColor c(255, 255, 255, 255);
+ float y = -(1.0 - a) * 0.22;
+ vertices[0] = video::S3DVertex(-1, -0.05 + y, -1, 0, 0, 1, c, t, t);
+ vertices[1] = video::S3DVertex( 1, -0.05 + y, -1, 0, 0, 1, c, o, t);
+ vertices[2] = video::S3DVertex( 1, 0.2 + y, -1, 0, 0, 1, c, o, o);
+ vertices[3] = video::S3DVertex(-1, 0.2 + y, -1, 0, 0, 1, c, t, o);
+ for (video::S3DVertex &vertex : vertices) {
+ if (wicked_time_of_day < 0.5)
+ // Switch from -Z (south) to +X (east)
+ vertex.Pos.rotateXZBy(90);
+ else
+ // Switch from -Z (south) to -X (west)
+ vertex.Pos.rotateXZBy(-90);
+ }
+ driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
+ }
+
+ // Draw stars
+ do {
+ driver->setMaterial(m_materials[1]);
+ float starbrightness = MYMAX(0, MYMIN(1,
+ (0.285 - fabs(wicked_time_of_day < 0.5 ?
+ wicked_time_of_day : (1.0 - wicked_time_of_day))) * 10));
+ float f = starbrightness;
+ float d = 0.007/2;
+ video::SColor starcolor(255, f * 90, f * 90, f * 90);
+ if (starcolor.getBlue() < m_skycolor.getBlue())
+ break;
+#ifdef __ANDROID__
+ u16 indices[SKY_STAR_COUNT * 3];
+ video::S3DVertex vertices[SKY_STAR_COUNT * 3];
+ for (u32 i = 0; i < SKY_STAR_COUNT; i++) {
+ indices[i * 3 + 0] = i * 3 + 0;
+ indices[i * 3 + 1] = i * 3 + 1;
+ indices[i * 3 + 2] = i * 3 + 2;
+ v3f r = m_stars[i];
+ core::CMatrix4<f32> a;
+ a.buildRotateFromTo(v3f(0, 1, 0), r);
+ v3f p = v3f(-d, 1, -d);
+ v3f p1 = v3f(d, 1, 0);
+ v3f p2 = v3f(-d, 1, d);
+ a.rotateVect(p);
+ a.rotateVect(p1);
+ a.rotateVect(p2);
+ p.rotateXYBy(wicked_time_of_day * 360 - 90);
+ p1.rotateXYBy(wicked_time_of_day * 360 - 90);
+ p2.rotateXYBy(wicked_time_of_day * 360 - 90);
+ vertices[i * 3 + 0].Pos = p;
+ vertices[i * 3 + 0].Color = starcolor;
+ vertices[i * 3 + 1].Pos = p1;
+ vertices[i * 3 + 1].Color = starcolor;
+ vertices[i * 3 + 2].Pos = p2;
+ vertices[i * 3 + 2].Color = starcolor;
+ }
+ driver->drawIndexedTriangleList(vertices, SKY_STAR_COUNT * 3,
+ indices, SKY_STAR_COUNT);
+#else
+ u16 indices[SKY_STAR_COUNT * 4];
+ video::S3DVertex vertices[SKY_STAR_COUNT * 4];
+ for (u32 i = 0; i < SKY_STAR_COUNT; i++) {
+ indices[i * 4 + 0] = i * 4 + 0;
+ indices[i * 4 + 1] = i * 4 + 1;
+ indices[i * 4 + 2] = i * 4 + 2;
+ indices[i * 4 + 3] = i * 4 + 3;
+ v3f r = m_stars[i];
+ core::CMatrix4<f32> a;
+ a.buildRotateFromTo(v3f(0, 1, 0), r);
+ v3f p = v3f(-d, 1, -d);
+ v3f p1 = v3f( d, 1, -d);
+ v3f p2 = v3f( d, 1, d);
+ v3f p3 = v3f(-d, 1, d);
+ a.rotateVect(p);
+ a.rotateVect(p1);
+ a.rotateVect(p2);
+ a.rotateVect(p3);
+ p.rotateXYBy(wicked_time_of_day * 360 - 90);
+ p1.rotateXYBy(wicked_time_of_day * 360 - 90);
+ p2.rotateXYBy(wicked_time_of_day * 360 - 90);
+ p3.rotateXYBy(wicked_time_of_day * 360 - 90);
+ vertices[i * 4 + 0].Pos = p;
+ vertices[i * 4 + 0].Color = starcolor;
+ vertices[i * 4 + 1].Pos = p1;
+ vertices[i * 4 + 1].Color = starcolor;
+ vertices[i * 4 + 2].Pos = p2;
+ vertices[i * 4 + 2].Color = starcolor;
+ vertices[i * 4 + 3].Pos = p3;
+ vertices[i * 4 + 3].Color = starcolor;
+ }
+ driver->drawVertexPrimitiveList(vertices, SKY_STAR_COUNT * 4,
+ indices, SKY_STAR_COUNT, video::EVT_STANDARD,
+ scene::EPT_QUADS, video::EIT_16BIT);
+#endif
+ } while(false);
+
+ // Draw sun
+ if (wicked_time_of_day > 0.15 && wicked_time_of_day < 0.85) {
+ if (!m_sun_texture) {
+ driver->setMaterial(m_materials[1]);
+ float d = sunsize * 1.7;
+ video::SColor c = suncolor;
+ c.setAlpha(0.05 * 255);
+ vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, c, t, t);
+ vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, c, o, t);
+ vertices[2] = video::S3DVertex( d, d, -1, 0, 0, 1, c, o, o);
+ vertices[3] = video::S3DVertex(-d, d, -1, 0, 0, 1, c, t, o);
+ for (video::S3DVertex &vertex : vertices) {
+ // Switch from -Z (south) to +X (east)
+ vertex.Pos.rotateXZBy(90);
+ vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
+ }
+ driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
+
+ d = sunsize * 1.2;
+ c = suncolor;
+ c.setAlpha(0.15 * 255);
+ vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, c, t, t);
+ vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, c, o, t);
+ vertices[2] = video::S3DVertex( d, d, -1, 0, 0, 1, c, o, o);
+ vertices[3] = video::S3DVertex(-d, d, -1, 0, 0, 1, c, t, o);
+ for (video::S3DVertex &vertex : vertices) {
+ // Switch from -Z (south) to +X (east)
+ vertex.Pos.rotateXZBy(90);
+ vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
+ }
+ driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
+
+ d = sunsize;
+ vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, suncolor, t, t);
+ vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, suncolor, o, t);
+ vertices[2] = video::S3DVertex( d, d, -1, 0, 0, 1, suncolor, o, o);
+ vertices[3] = video::S3DVertex(-d, d, -1, 0, 0, 1, suncolor, t, o);
+ for (video::S3DVertex &vertex : vertices) {
+ // Switch from -Z (south) to +X (east)
+ vertex.Pos.rotateXZBy(90);
+ vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
+ }
+ driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
+
+ d = sunsize * 0.7;
+ vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, suncolor2, t, t);
+ vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, suncolor2, o, t);
+ vertices[2] = video::S3DVertex( d, d, -1, 0, 0, 1, suncolor2, o, o);
+ vertices[3] = video::S3DVertex(-d, d, -1, 0, 0, 1, suncolor2, t, o);
+ for (video::S3DVertex &vertex : vertices) {
+ // Switch from -Z (south) to +X (east)
+ vertex.Pos.rotateXZBy(90);
+ vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
+ }
+ driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
+ } else {
+ driver->setMaterial(m_materials[3]);
+ float d = sunsize * 1.7;
+ video::SColor c;
+ if (m_sun_tonemap)
+ c = video::SColor (0, 0, 0, 0);
+ else
+ c = video::SColor (255, 255, 255, 255);
+ vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, c, t, t);
+ vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, c, o, t);
+ vertices[2] = video::S3DVertex( d, d, -1, 0, 0, 1, c, o, o);
+ vertices[3] = video::S3DVertex(-d, d, -1, 0, 0, 1, c, t, o);
+ for (video::S3DVertex &vertex : vertices) {
+ // Switch from -Z (south) to +X (east)
+ vertex.Pos.rotateXZBy(90);
+ vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
+ }
+ driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
+ }
+ }
+
+ // Draw moon
+ if (wicked_time_of_day < 0.3 || wicked_time_of_day > 0.7) {
+ if (!m_moon_texture) {
+ driver->setMaterial(m_materials[1]);
+ float d = moonsize * 1.9;
+ video::SColor c = mooncolor;
+ c.setAlpha(0.05 * 255);
+ vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, c, t, t);
+ vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, c, o, t);
+ vertices[2] = video::S3DVertex( d, d, -1, 0, 0, 1, c, o, o);
+ vertices[3] = video::S3DVertex(-d, d, -1, 0, 0, 1, c, t, o);
+ for (video::S3DVertex &vertex : vertices) {
+ // Switch from -Z (south) to -X (west)
+ vertex.Pos.rotateXZBy(-90);
+ vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
+ }
+ driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
+
+ d = moonsize * 1.3;
+ c = mooncolor;
+ c.setAlpha(0.15 * 255);
+ vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, c, t, t);
+ vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, c, o, t);
+ vertices[2] = video::S3DVertex( d, d, -1, 0, 0, 1, c, o, o);
+ vertices[3] = video::S3DVertex(-d, d, -1, 0, 0, 1, c, t, o);
+ for (video::S3DVertex &vertex : vertices) {
+ // Switch from -Z (south) to -X (west)
+ vertex.Pos.rotateXZBy(-90);
+ vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
+ }
+ driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
+
+ d = moonsize;
+ vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, mooncolor, t, t);
+ vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, mooncolor, o, t);
+ vertices[2] = video::S3DVertex( d, d, -1, 0, 0, 1, mooncolor, o, o);
+ vertices[3] = video::S3DVertex(-d, d, -1, 0, 0, 1, mooncolor, t, o);
+ for (video::S3DVertex &vertex : vertices) {
+ // Switch from -Z (south) to -X (west)
+ vertex.Pos.rotateXZBy(-90);
+ vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
+ }
+ driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
+
+ float d2 = moonsize * 0.6;
+ vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, mooncolor2, t, t);
+ vertices[1] = video::S3DVertex( d2,-d, -1, 0, 0, 1, mooncolor2, o, t);
+ vertices[2] = video::S3DVertex( d2, d2, -1, 0, 0, 1, mooncolor2, o, o);
+ vertices[3] = video::S3DVertex(-d, d2, -1, 0, 0, 1, mooncolor2, t, o);
+ for (video::S3DVertex &vertex : vertices) {
+ // Switch from -Z (south) to -X (west)
+ vertex.Pos.rotateXZBy(-90);
+ vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
+ }
+ driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
+ } else {
+ driver->setMaterial(m_materials[4]);
+ float d = moonsize * 1.9;
+ video::SColor c;
+ if (m_moon_tonemap)
+ c = video::SColor (0, 0, 0, 0);
+ else
+ c = video::SColor (255, 255, 255, 255);
+ vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, c, t, t);
+ vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, c, o, t);
+ vertices[2] = video::S3DVertex( d, d, -1, 0, 0, 1, c, o, o);
+ vertices[3] = video::S3DVertex(-d, d, -1, 0, 0, 1, c, t, o);
+ for (video::S3DVertex &vertex : vertices) {
+ // Switch from -Z (south) to -X (west)
+ vertex.Pos.rotateXZBy(-90);
+ vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
+ }
+ driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
+ }
+ }
+
+ // Draw far cloudy fog thing below east and west horizons
+ for (u32 j = 0; j < 2; 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.02, -1, 0, 0, 1, c, o, o);
+ vertices[3] = video::S3DVertex(-1, -0.02, -1, 0, 0, 1, c, t, o);
+ for (video::S3DVertex &vertex : vertices) {
+ //if (wicked_time_of_day < 0.5)
+ if (j == 0)
+ // Switch from -Z (south) to +X (east)
+ vertex.Pos.rotateXZBy(90);
+ else
+ // Switch from -Z (south) to -X (west)
+ vertex.Pos.rotateXZBy(-90);
+ }
+ driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
+ }
+ }
+}
+
+
+void Sky::update(float time_of_day, float time_brightness,
+ float direct_brightness, bool sunlight_seen,
+ CameraMode cam_mode, float yaw, float pitch)
+{
+ // Stabilize initial brightness and color values by flooding updates
+ if (m_first_update) {
+ /*dstream<<"First update with time_of_day="<<time_of_day
+ <<" time_brightness="<<time_brightness
+ <<" direct_brightness="<<direct_brightness
+ <<" sunlight_seen="<<sunlight_seen<<std::endl;*/
+ m_first_update = false;
+ for (u32 i = 0; i < 100; i++) {
+ update(time_of_day, time_brightness, direct_brightness,
+ sunlight_seen, cam_mode, yaw, pitch);
+ }
+ return;
+ }
+
+ m_time_of_day = time_of_day;
+ m_time_brightness = time_brightness;
+ m_sunlight_seen = sunlight_seen;
+ m_bodies_visible = true;
+
+ bool is_dawn = (time_brightness >= 0.20 && time_brightness < 0.35);
+
+ /*
+ Development colours
+
+ video::SColorf bgcolor_bright_normal_f(170. / 255, 200. / 255, 230. / 255, 1.0);
+ video::SColorf bgcolor_bright_dawn_f(0.666, 200. / 255 * 0.7, 230. / 255 * 0.5, 1.0);
+ video::SColorf bgcolor_bright_dawn_f(0.666, 0.549, 0.220, 1.0);
+ video::SColorf bgcolor_bright_dawn_f(0.666 * 1.2, 0.549 * 1.0, 0.220 * 1.0, 1.0);
+ video::SColorf bgcolor_bright_dawn_f(0.666 * 1.2, 0.549 * 1.0, 0.220 * 1.2, 1.0);
+
+ video::SColorf cloudcolor_bright_dawn_f(1.0, 0.591, 0.4);
+ video::SColorf cloudcolor_bright_dawn_f(1.0, 0.65, 0.44);
+ video::SColorf cloudcolor_bright_dawn_f(1.0, 0.7, 0.5);
+ */
+
+ video::SColorf bgcolor_bright_normal_f = video::SColor(255, 155, 193, 240);
+ video::SColorf bgcolor_bright_indoor_f = video::SColor(255, 100, 100, 100);
+ video::SColorf bgcolor_bright_dawn_f = video::SColor(255, 186, 193, 240);
+ video::SColorf bgcolor_bright_night_f = video::SColor(255, 64, 144, 255);
+
+ video::SColorf skycolor_bright_normal_f = video::SColor(255, 140, 186, 250);
+ video::SColorf skycolor_bright_dawn_f = video::SColor(255, 180, 186, 250);
+ video::SColorf skycolor_bright_night_f = video::SColor(255, 0, 107, 255);
+
+ // pure white: becomes "diffuse light component" for clouds
+ video::SColorf cloudcolor_bright_normal_f = video::SColor(255, 255, 255, 255);
+ // dawn-factoring version of pure white (note: R is above 1.0)
+ video::SColorf cloudcolor_bright_dawn_f(255.0f/240.0f, 223.0f/240.0f, 191.0f/255.0f);
+
+ float cloud_color_change_fraction = 0.95;
+ if (sunlight_seen) {
+ if (std::fabs(time_brightness - m_brightness) < 0.2f) {
+ m_brightness = m_brightness * 0.95 + time_brightness * 0.05;
+ } else {
+ m_brightness = m_brightness * 0.80 + time_brightness * 0.20;
+ cloud_color_change_fraction = 0.0;
+ }
+ } else {
+ if (direct_brightness < m_brightness)
+ m_brightness = m_brightness * 0.95 + direct_brightness * 0.05;
+ else
+ m_brightness = m_brightness * 0.98 + direct_brightness * 0.02;
+ }
+
+ m_clouds_visible = true;
+ float color_change_fraction = 0.98f;
+ if (sunlight_seen) {
+ if (is_dawn) { // Dawn
+ m_bgcolor_bright_f = m_bgcolor_bright_f.getInterpolated(
+ bgcolor_bright_dawn_f, color_change_fraction);
+ m_skycolor_bright_f = m_skycolor_bright_f.getInterpolated(
+ skycolor_bright_dawn_f, color_change_fraction);
+ m_cloudcolor_bright_f = m_cloudcolor_bright_f.getInterpolated(
+ cloudcolor_bright_dawn_f, color_change_fraction);
+ } else {
+ if (time_brightness < 0.13f) { // Night
+ m_bgcolor_bright_f = m_bgcolor_bright_f.getInterpolated(
+ bgcolor_bright_night_f, color_change_fraction);
+ m_skycolor_bright_f = m_skycolor_bright_f.getInterpolated(
+ skycolor_bright_night_f, color_change_fraction);
+ } else { // Day
+ m_bgcolor_bright_f = m_bgcolor_bright_f.getInterpolated(
+ bgcolor_bright_normal_f, color_change_fraction);
+ m_skycolor_bright_f = m_skycolor_bright_f.getInterpolated(
+ skycolor_bright_normal_f, color_change_fraction);
+ }
+
+ m_cloudcolor_bright_f = m_cloudcolor_bright_f.getInterpolated(
+ cloudcolor_bright_normal_f, color_change_fraction);
+ }
+ } else {
+ m_bgcolor_bright_f = m_bgcolor_bright_f.getInterpolated(
+ bgcolor_bright_indoor_f, color_change_fraction);
+ m_skycolor_bright_f = m_skycolor_bright_f.getInterpolated(
+ bgcolor_bright_indoor_f, color_change_fraction);
+ m_cloudcolor_bright_f = m_cloudcolor_bright_f.getInterpolated(
+ cloudcolor_bright_normal_f, color_change_fraction);
+ m_clouds_visible = false;
+ }
+
+ video::SColor bgcolor_bright = m_bgcolor_bright_f.toSColor();
+ m_bgcolor = video::SColor(
+ 255,
+ bgcolor_bright.getRed() * m_brightness,
+ bgcolor_bright.getGreen() * m_brightness,
+ bgcolor_bright.getBlue() * m_brightness
+ );
+
+ video::SColor skycolor_bright = m_skycolor_bright_f.toSColor();
+ m_skycolor = video::SColor(
+ 255,
+ skycolor_bright.getRed() * m_brightness,
+ skycolor_bright.getGreen() * m_brightness,
+ skycolor_bright.getBlue() * m_brightness
+ );
+
+ // Horizon coloring based on sun and moon direction during sunset and sunrise
+ video::SColor pointcolor = video::SColor(m_bgcolor.getAlpha(), 255, 255, 255);
+ if (m_directional_colored_fog) {
+ if (m_horizon_blend() != 0) {
+ // Calculate hemisphere value from yaw, (inverted in third person front view)
+ s8 dir_factor = 1;
+ if (cam_mode > 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);
+ }
+}
--- /dev/null
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include <ISceneNode.h>
+#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;
+};
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2014 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "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 <map>
+#include <IMeshManipulator.h>
+
+#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<u32> 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<int, scene::IMesh*>::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<int, scene::IMesh*> 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<u32> 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<u32>(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<ItemPartColor> *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<u32> 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<ItemPartColor> *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;
+ }
+ }
+ }
+}
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2014 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include <string>
+#include <vector>
+#include "irrlichttypes_extrabloated.h"
+
+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<ItemPartColor> 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<ItemPartColor> 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<ItemPartColor> *colors, bool apply_scale = false);
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2017 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "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 <algorithm>
-#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<CollisionInfo> 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"<<std::endl;
- delete object;
- return 0;
- }
- object->setId(new_id);
- }
- if (!isFreeClientActiveObjectId(object->getId(), m_active_objects)) {
- infostream<<"ClientEnvironment::addActiveObject(): "
- <<"id is not free ("<<object->getId()<<")"<<std::endl;
- delete object;
- return 0;
- }
- infostream<<"ClientEnvironment::addActiveObject(): "
- <<"added (id="<<object->getId()<<")"<<std::endl;
- m_active_objects[object->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="<<id<<" type="<<type<<": Couldn't create object"
- <<std::endl;
- return;
- }
-
- obj->setId(id);
-
- try
- {
- obj->initialize(init_data);
- }
- catch(SerializationError &e)
- {
- errorstream<<"ClientEnvironment::addActiveObject():"
- <<" id="<<id<<" type="<<type
- <<": SerializationError in initialize(): "
- <<e.what()
- <<": init_data="<<serializeJsonString(init_data)
- <<std::endl;
- }
-
- addActiveObject(obj);
-}
-
-void ClientEnvironment::removeActiveObject(u16 id)
-{
- verbosestream<<"ClientEnvironment::removeActiveObject(): "
- <<"id="<<id<<std::endl;
- ClientActiveObject* obj = getActiveObject(id);
- if (obj == NULL) {
- infostream<<"ClientEnvironment::removeActiveObject(): "
- <<"id="<<id<<" not found"<<std::endl;
- return;
- }
- obj->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<DistanceSortedActiveObject> &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<f32> &shootline_on_map,
- std::vector<PointedThing> &objects)
-{
- std::vector<DistanceSortedActiveObject> 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());
- }
- }
-}
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2017 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include "environment.h"
-#include <ISceneManager.h>
-#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<u16, ClientActiveObject*> 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<DistanceSortedActiveObject> &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<f32> &shootline_on_map,
- std::vector<PointedThing> &objects
- );
-
- u16 attachement_parent_ids[USHRT_MAX + 1];
-
- const std::list<std::string> &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<ClientSimpleObject*> m_simple_objects;
- std::queue<ClientEnvEvent> m_client_event_queue;
- IntervalLimiter m_active_object_light_update_interval;
- std::list<std::string> m_player_names;
- v3s16 m_camera_offset;
-};
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "clientmap.h"
-#include "client.h"
-#include "mapblock_mesh.h"
-#include <IMaterialRenderer.h>
-#include <matrix4.h>
-#include "mapsector.h"
-#include "mapblock.h"
-#include "profiler.h"
-#include "settings.h"
-#include "camera.h" // CameraModes
-#include "util/basic_macros.h"
-#include <algorithm>
-#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<scene::IMeshBuffer*> 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<MeshBufList> 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<MeshBufList> &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 "<<brightness_count<<" points; result="
- <<(*result)<<std::endl;*/
- return true;
-}
-
-int ClientMap::getBackgroundBrightness(float max_d, u32 daylight_factor,
- int oldvalue, bool *sunlight_seen_result)
-{
- static v3f z_directions[50] = {
- v3f(-100, 0, 0)
- };
- static f32 z_offsets[sizeof(z_directions)/sizeof(*z_directions)] = {
- -1000,
- };
-
- if(z_directions[0].X < -99){
- for(u32 i=0; i<sizeof(z_directions)/sizeof(*z_directions); i++){
- // Assumes FOV of 72 and 16/9 aspect ratio
- z_directions[i] = v3f(
- 0.02 * myrand_range(-100, 100),
- 1.0,
- 0.01 * myrand_range(-100, 100)
- ).normalize();
- z_offsets[i] = 0.01 * myrand_range(0,100);
- }
- }
-
- int sunlight_seen_count = 0;
- float sunlight_min_d = max_d*0.8;
- if(sunlight_min_d > 35*BS)
- sunlight_min_d = 35*BS;
- std::vector<int> values;
- for(u32 i=0; i<sizeof(z_directions)/sizeof(*z_directions); i++){
- v3f z_dir = z_directions[i];
- core::CMatrix4<f32> a;
- a.buildRotateFromTo(v3f(0,1,0), z_dir);
- v3f dir = m_camera_direction;
- a.rotateVect(dir);
- int br = 0;
- float step = BS*1.5;
- if(max_d > 35*BS)
- step = max_d / 35 * 1.5;
- float off = step * z_offsets[i];
- bool sunlight_seen_now = false;
- bool ok = getVisibleBrightness(this, m_camera_position, dir,
- step, 1.0, max_d*0.6+off, max_d, m_nodedef, daylight_factor,
- sunlight_min_d,
- &br, &sunlight_seen_now);
- if(sunlight_seen_now)
- sunlight_seen_count++;
- if(!ok)
- continue;
- values.push_back(br);
- // Don't try too much if being in the sun is clear
- if(sunlight_seen_count >= 20)
- break;
- }
- int brightness_sum = 0;
- int brightness_count = 0;
- std::sort(values.begin(), values.end());
- u32 num_values_to_use = values.size();
- if(num_values_to_use >= 10)
- num_values_to_use -= num_values_to_use/2;
- else if(num_values_to_use >= 7)
- num_values_to_use -= num_values_to_use/3;
- u32 first_value_i = (values.size() - num_values_to_use) / 2;
-
- for (u32 i=first_value_i; i < first_value_i + num_values_to_use; i++) {
- brightness_sum += values[i];
- brightness_count++;
- }
-
- int ret = 0;
- if(brightness_count == 0){
- MapNode n = getNodeNoEx(floatToInt(m_camera_position, BS));
- if(m_nodedef->get(n).param_type == CPT_LIGHT){
- ret = decode_light(n.getLightBlend(daylight_factor, m_nodedef));
- } else {
- ret = oldvalue;
- }
- } else {
- ret = brightness_sum / brightness_count;
- }
-
- *sunlight_seen_result = (sunlight_seen_count > 0);
- return ret;
-}
-
-void ClientMap::renderPostFx(CameraMode cam_mode)
-{
- // Sadly ISceneManager has no "post effects" render pass, in that case we
- // could just register for that and handle it in renderMap().
-
- MapNode n = getNodeNoEx(floatToInt(m_camera_position, BS));
-
- // - If the player is in a solid node, make everything black.
- // - If the player is in liquid, draw a semi-transparent overlay.
- // - Do not if player is in third person mode
- const ContentFeatures& features = m_nodedef->get(n);
- video::SColor post_effect_color = features.post_effect_color;
- if(features.solidness == 2 && !(g_settings->getBool("noclip") &&
- m_client->checkLocalPrivilege("noclip")) &&
- cam_mode == CAMERA_MODE_FIRST)
- {
- 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<s32> rect(0,0, ss.X, ss.Y);
- driver->draw2DRectangle(post_effect_color, rect);
- }
-}
-
-void ClientMap::PrintInfo(std::ostream &out)
-{
- out<<"ClientMap: ";
-}
-
-
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include "irrlichttypes_extrabloated.h"
-#include "map.h"
-#include "camera.h"
-#include <set>
-#include <map>
-
-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<v3s16, MapBlock*> m_drawlist;
-
- std::set<v2s16> m_last_drawn_sectors;
-
- bool m_cache_trilinear_filter;
- bool m_cache_bilinear_filter;
- bool m_cache_anistropic_filter;
-};
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "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<std::string> 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<std::string, FileStatus*>::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<std::string, FileStatus*>::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<unsigned long, std::string>::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<std::string, FileStatus*>::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<std::string> 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<std::string, FileStatus*>::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<std::string, FileStatus*>::iterator
- it = m_files.begin();
- it != m_files.end(); ++it) {
- if (!it->second->received) {
- FATAL_ERROR_IF(it->second->sha1.size() != 20, "Invalid SHA1 size");
- os << it->second->sha1;
- }
- }
-
- return os.str();
-}
-
-void ClientMediaDownloader::deSerializeHashSet(const std::string &data,
- std::set<std::string> &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));
- }
-}
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include "irrlichttypes.h"
-#include "filecache.h"
-#include <ostream>
-#include <map>
-#include <set>
-#include <vector>
-#include <unordered_map>
-
-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<s32> 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<std::string> &result);
-
- // Maps filename to file status
- std::map<std::string, FileStatus*> m_files;
-
- // Array of remote media servers
- std::vector<RemoteServerStatus*> m_remotes;
-
- // Filesystem-based media cache
- FileCache m_media_cache;
-
- // Has an attempt been made to load media files from the file cache?
- // Have hash sets been requested from remote servers?
- bool m_initial_step_done = false;
-
- // 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<unsigned long, std::string> m_remote_file_transfers;
-
- // All files up to this name have either been received from a
- // remote server or failed on all remote servers, so those files
- // 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 = "";
-
-};
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "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;
-}
-
-
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include "irrlichttypes_extrabloated.h"
-#include "activeobject.h"
-#include <unordered_map>
-
-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<u16, Factory> 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;
- }
-};
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "client/renderingengine.h"
-#include "clouds.h"
-#include "noise.h"
-#include "constants.h"
-#include "debug.h"
-#include "profiler.h"
-#include "settings.h"
-#include <cmath>
-
-
-// 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; i<num_faces_to_draw; i++)
- {
- switch(i)
- {
- case 0: // top
- for (video::S3DVertex &vertex : v) {
- vertex.Normal.set(0,1,0);
- }
- v[0].Pos.set(-rx, ry,-rz);
- v[1].Pos.set(-rx, ry, rz);
- v[2].Pos.set( rx, ry, rz);
- v[3].Pos.set( rx, ry,-rz);
- break;
- case 1: // back
- if (INAREA(xi, zi - 1, m_cloud_radius_i)) {
- u32 j = GETINDEX(xi, zi - 1, m_cloud_radius_i);
- if(grid[j])
- continue;
- }
- for (video::S3DVertex &vertex : v) {
- vertex.Color = c_side_1;
- vertex.Normal.set(0,0,-1);
- }
- v[0].Pos.set(-rx, ry,-rz);
- v[1].Pos.set( rx, ry,-rz);
- v[2].Pos.set( rx, 0,-rz);
- v[3].Pos.set(-rx, 0,-rz);
- break;
- case 2: //right
- if (INAREA(xi + 1, zi, m_cloud_radius_i)) {
- u32 j = GETINDEX(xi+1, zi, m_cloud_radius_i);
- if(grid[j])
- continue;
- }
- for (video::S3DVertex &vertex : v) {
- vertex.Color = c_side_2;
- vertex.Normal.set(1,0,0);
- }
- v[0].Pos.set( rx, ry,-rz);
- v[1].Pos.set( rx, ry, rz);
- v[2].Pos.set( rx, 0, rz);
- v[3].Pos.set( rx, 0,-rz);
- break;
- case 3: // front
- if (INAREA(xi, zi + 1, m_cloud_radius_i)) {
- u32 j = GETINDEX(xi, zi + 1, m_cloud_radius_i);
- if(grid[j])
- continue;
- }
- for (video::S3DVertex &vertex : v) {
- vertex.Color = c_side_1;
- vertex.Normal.set(0,0,-1);
- }
- v[0].Pos.set( rx, ry, rz);
- v[1].Pos.set(-rx, ry, rz);
- v[2].Pos.set(-rx, 0, rz);
- v[3].Pos.set( rx, 0, rz);
- break;
- case 4: // left
- if (INAREA(xi-1, zi, m_cloud_radius_i)) {
- u32 j = GETINDEX(xi-1, zi, m_cloud_radius_i);
- if(grid[j])
- continue;
- }
- for (video::S3DVertex &vertex : v) {
- vertex.Color = c_side_2;
- vertex.Normal.set(-1,0,0);
- }
- v[0].Pos.set(-rx, ry, rz);
- v[1].Pos.set(-rx, ry,-rz);
- v[2].Pos.set(-rx, 0,-rz);
- v[3].Pos.set(-rx, 0, rz);
- break;
- case 5: // bottom
- for (video::S3DVertex &vertex : v) {
- vertex.Color = c_bottom;
- vertex.Normal.set(0,-1,0);
- }
- v[0].Pos.set( rx, 0, rz);
- v[1].Pos.set(-rx, 0, rz);
- v[2].Pos.set(-rx, 0,-rz);
- v[3].Pos.set( rx, 0,-rz);
- break;
- }
-
- v3f pos(p0.X, m_params.height * BS, p0.Y);
- pos -= intToFloat(m_camera_offset, BS);
-
- for (video::S3DVertex &vertex : v)
- vertex.Pos += pos;
- u16 indices[] = {0,1,2,2,3,0};
- driver->drawVertexPrimitiveList(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);
-}
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include "irrlichttypes_extrabloated.h"
-#include <iostream>
-#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;
-
-};
#include "nodedef.h"
#include "gamedef.h"
#ifndef SERVER
-#include "clientenvironment.h"
+#include "client/clientenvironment.h"
#endif
#include "serverenvironment.h"
#include "serverobject.h"
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include <ICameraSceneNode.h>
-#include <ITextSceneNode.h>
-#include <IBillboardSceneNode.h>
-#include <IMeshManipulator.h>
-#include <IAnimatedMeshSceneNode.h>
-#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 <algorithm>
-#include <cmath>
-#include "client/renderingengine.h"
-
-class Settings;
-struct ToolCapabilities;
-
-std::unordered_map<u16, ClientActiveObject::Factory> ClientActiveObject::m_types;
-
-template<typename T>
-void SmoothTranslator<T>::init(T current)
-{
- val_old = current;
- val_current = current;
- val_target = current;
- anim_time = 0;
- anim_time_counter = 0;
- aim_is_end = true;
-}
-
-template<typename T>
-void SmoothTranslator<T>::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<typename T>
-void SmoothTranslator<T>::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="<<dtime<<", rot.Y="<<rot.Y<<std::endl;
- rot.Y += dtime * 180;
- m_node->setRotation(rot);
- }
-}
-
-void TestCAO::processMessage(const std::string &data)
-{
- infostream<<"TestCAO: Got data: "<<data<<std::endl;
- std::istringstream is(data, std::ios::binary);
- u16 cmd;
- is>>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"<<std::endl;
- processInitData(data);
-
- if (m_is_player) {
- // Check if it's the current player
- LocalPlayer *player = m_env->getLocalPlayer();
- if (player && strcmp(player->getName(), m_name.c_str()) == 0) {
- m_is_local_player = true;
- m_is_visible = false;
- player->setCAO(this);
- }
- }
-}
-
-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"
- <<std::endl;
- return;
- }
-
- for (int i = 0; i < num_messages; i++) {
- std::string message = deSerializeLongString(is);
- processMessage(message);
- }
-
- m_rotation = wrapDegrees_0_360_v3f(m_rotation);
- pos_translator.init(m_position);
- rot_translator.init(m_rotation);
- updateNodePos();
-}
-
-GenericCAO::~GenericCAO()
-{
- removeFromScene(true);
-}
-
-bool GenericCAO::getSelectionBox(aabb3f *toset) const
-{
- if (!m_prop.is_visible || !m_is_visible || m_is_local_player
- || !m_prop.pointable) {
- return false;
- }
- *toset = m_selection_box;
- return true;
-}
-
-v3f GenericCAO::getPosition()
-{
- if (getParent() != NULL) {
- scene::ISceneNode *node = getSceneNode();
- if (node)
- return node->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"<<std::endl;
- m_spritenode = RenderingEngine::get_scene_manager()->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"<<std::endl;
- scene::IMesh *mesh = createCubeMesh(v3f(BS,BS,BS));
- m_meshnode = RenderingEngine::get_scene_manager()->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"<<std::endl;
- scene::IAnimatedMesh *mesh = m_client->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 "<<m_prop.mesh<<std::endl;
- } else if (m_prop.visual == "wielditem") {
- ItemStack item;
- infostream << "GenericCAO::addToScene(): wielditem" << std::endl;
- if (m_prop.wield_item.empty()) {
- // Old format, only textures are specified.
- infostream << "textures: " << m_prop.textures.size() << std::endl;
- if (!m_prop.textures.empty()) {
- infostream << "textures[0]: " << m_prop.textures[0]
- << std::endl;
- IItemDefManager *idef = m_client->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(): \""<<m_prop.visual
- <<"\" not supported"<<std::endl;
- }
-
- /* don't update while punch texture modifier is active */
- if (m_reset_textures_timer < 0)
- updateTextures(m_current_texture_modifier);
-
- scene::ISceneNode *node = getSceneNode();
- if (node && !m_prop.nametag.empty() && !m_is_local_player) {
- // Add nametag
- v3f pos;
- pos.Y = m_prop.selectionbox.MaxEdge.Y + 0.3f;
- m_nametag = m_client->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 "<<texturestring<<std::endl;
- continue;
- }
-
- // Set material flags and texture
- video::SMaterial& material = m_animated_meshnode->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<u32> &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<std::string, core::vector2d<v3f>>::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"<<std::endl;
- std::istringstream is(data, std::ios::binary);
- // command
- u8 cmd = readU8(is);
- if (cmd == GENERIC_CMD_SET_PROPERTIES) {
- m_prop = gob_read_set_properties(is);
-
- m_selection_box = m_prop.selectionbox;
- m_selection_box.MinEdge *= BS;
- m_selection_box.MaxEdge *= BS;
-
- m_tx_size.X = 1.0 / m_prop.spritediv.X;
- m_tx_size.Y = 1.0 / m_prop.spritediv.Y;
-
- if(!m_initial_tx_basepos_set){
- m_initial_tx_basepos_set = true;
- m_tx_basepos = m_prop.initial_sprite_basepos;
- }
- if (m_is_local_player) {
- LocalPlayer *player = m_env->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<v3f>(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; i<armor_groups_size; i++)
- {
- std::string name = deSerializeString(is);
- int rating = readS16(is);
- m_armor_groups[name] = rating;
- }
- } else if (cmd == GENERIC_CMD_UPDATE_NAMETAG_ATTRIBUTES) {
- // Deprecated, for backwards compatibility only.
- readU8(is); // version
- m_prop.nametag_color = readARGB8(is);
- if (m_nametag != NULL) {
- m_nametag->nametag_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="<<m_hp<<"\n";
- os<<"armor={";
- for(ItemGroupList::const_iterator i = m_armor_groups.begin();
- i != m_armor_groups.end(); ++i)
- {
- os<<i->first<<"="<<i->second<<", ";
- }
- os<<"}";
- return os.str();
-}
-
-// Prototype
-GenericCAO proto_GenericCAO(NULL, NULL);
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include <map>
-#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<typename T>
-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<f32>
-{
- void translate(f32 dtime);
-};
-
-struct SmoothTranslatorWrappedv3f : SmoothTranslator<v3f>
-{
- 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<v3f> 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<std::string, core::vector2d<v3f>> 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<u16> 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;
- }
-};
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "content_cso.h"
-#include <IBillboardSceneNode.h>
-#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"<<std::endl;
- m_spritenode = smgr->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"<<std::endl;
- m_spritenode->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);
-}
-
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include "irrlichttypes_extrabloated.h"
-#include "clientsimpleobject.h"
-
-ClientSimpleObject* createSmokePuff(scene::ISceneManager *smgr,
- ClientEnvironment *env, v3f pos, v2f size);
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "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 <IMeshManipulator.h>
-#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<aabb3f> 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();
-}
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include "nodedef.h"
-#include <IMeshManipulator.h>
-
-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);
-};
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-Copyright (C) 2013 Jonathan Neuschäfer <j.neuschaefer@gmx.net>
-
-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 <string>
-#include <iostream>
-#include <fstream>
-#include <cstdlib>
-
-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: "
- <<path<<std::endl;
- return false;
- }
-
- bool bad = false;
- for(;;){
- char buf[1024];
- fis.read(buf, 1024);
- std::streamsize len = fis.gcount();
- os.write(buf, len);
- if(fis.eof())
- break;
- if(!fis.good()){
- bad = true;
- break;
- }
- }
- if(bad){
- errorstream<<"FileCache: Failed to read file from cache: \""
- <<path<<"\""<<std::endl;
- }
-
- return !bad;
-}
-
-bool FileCache::updateByPath(const std::string &path, const std::string &data)
-{
- std::ofstream file(path.c_str(), std::ios_base::binary |
- std::ios_base::trunc);
-
- if(!file.good())
- {
- errorstream<<"FileCache: Can't write to file at "
- <<path<<std::endl;
- return false;
- }
-
- file.write(data.c_str(), data.length());
- file.close();
-
- return !file.fail();
-}
-
-bool FileCache::update(const std::string &name, const std::string &data)
-{
- std::string path = m_dir + DIR_DELIM + name;
- return updateByPath(path, data);
-}
-bool FileCache::load(const std::string &name, std::ostream &os)
-{
- std::string path = m_dir + DIR_DELIM + name;
- return loadByPath(path, os);
-}
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-Copyright (C) 2013 Jonathan Neuschäfer <j.neuschaefer@gmx.net>
-
-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 <iostream>
-#include <string>
-
-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);
-};
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2014 sapier <sapier at gmx dot net>
-
-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 <cmath>
-#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;
- }
-}
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2014 sapier <sapier at gmx dot net>
-
-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 <map>
-#include <vector>
-#include "util/basic_macros.h"
-#include <IGUIFont.h>
-#include <IGUISkin.h>
-#include <IGUIEnvironment.h>
-#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<unsigned int, irr::gui::IGUIFont*> 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;
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "game.h"
-
-#include <iomanip>
-#include <cmath>
-#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<std::string> m_fetched;
-private:
- void paths_insert(std::set<std::string> &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<std::string> &dst_paths,
- std::set<std::string> &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<float, 4> m_sky_bg_color;
- CachedPixelShaderSetting<float> m_fog_distance;
- CachedVertexShaderSetting<float> m_animation_timer_vertex;
- CachedPixelShaderSetting<float> m_animation_timer_pixel;
- CachedPixelShaderSetting<float, 3> m_day_light;
- CachedPixelShaderSetting<float, 3> m_eye_position_pixel;
- CachedVertexShaderSetting<float, 3> m_eye_position_vertex;
- CachedPixelShaderSetting<float, 3> m_minimap_yaw;
- CachedPixelShaderSetting<SamplerLayer_t> m_base_texture;
- CachedPixelShaderSetting<SamplerLayer_t> m_normal_texture;
- CachedPixelShaderSetting<SamplerLayer_t> 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<GameGlobalShaderConstantSetter*>(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<GameGlobalShaderConstantSetter *> 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<f32> &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<GameUI> 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<u32> 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<u32> ¤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<u32>(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<ClientEvent> 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<f32> shootline;
-
- if (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT) {
- shootline = core::line3d<f32>(camera_position,
- camera_position + camera_direction * BS * d);
- } else {
- // prevent player pointing anything in front-view
- shootline = core::line3d<f32>(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<f32> &shootline,
- bool liquids_pointable,
- bool look_for_object,
- const v3s16 &camera_offset)
-{
- std::vector<aabb3f> *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<aabb3f> boxes;
- n.getSelectionBoxes(nodedef, &boxes,
- n.getNeighbors(result.node_undersurface, &map));
-
- f32 d = 0.002 * BS;
- for (std::vector<aabb3f>::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="<<dig_index<<std::endl;
- client->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"<<std::endl;
- client->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<s32>(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;
- }
-}
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include "irrlichttypes.h"
-#include <string>
-
-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);
#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 <string>
*/
#include "guiConfirmRegistration.h"
-#include "client.h"
+#include "client/client.h"
#include <IGUICheckBox.h>
#include <IGUIButton.h>
#include <IGUIStaticText.h>
#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__
#include "guiTable.h"
#include "constants.h"
#include "gamedef.h"
-#include "keycode.h"
+#include "client/keycode.h"
#include "util/strfnd.h"
#include <IGUICheckBox.h>
#include <IGUIEditBox.h>
#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"
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;
#include "irrlichttypes_extrabloated.h"
#include "modalMenu.h"
#include "gettext.h"
-#include "keycode.h"
+#include "client/keycode.h"
#include <string>
#include <vector>
*/
#include "guiPasswordChange.h"
-#include "client.h"
+#include "client/client.h"
#include <IGUICheckBox.h>
#include <IGUIEditBox.h>
#include <IGUIButton.h>
#include "util/string.h" // for parseColorString()
#include "settings.h" // for settings
#include "porting.h" // for dpi
-#include "guiscalingfilter.h"
+#include "client/guiscalingfilter.h"
/*
GUITable
+++ /dev/null
-/*
-Copyright (C) 2015 Aaron Suen <warr1024@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "guiscalingfilter.h"
-#include "imagefilters.h"
-#include "porting.h"
-#include "settings.h"
-#include "util/numeric.h"
-#include <cstdio>
-#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<io::path, video::IImage *> g_imgCache;
-
-/* Maintain a static cache of all pre-scaled textures. These need to be
- * cleared as well when the cached images.
- */
-std::map<io::path, video::ITexture *> 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<s32> &srcrect,
- const core::rect<s32> &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>((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<u32>(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<s32>(0, 0, src->getSize().Width, src->getSize().Height),
- core::rect<s32>(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<s32> &destrect, const core::rect<s32> &srcrect,
- const core::rect<s32> *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<s32> mysrcrect = (scaled != txr)
- ? core::rect<s32>(0, 0, destrect.getWidth(), destrect.getHeight())
- : srcrect;
-
- driver->draw2DImage(scaled, destrect, mysrcrect, cliprect, colors, usealpha);
-}
+++ /dev/null
-/*
-Copyright (C) 2015 Aaron Suen <warr1024@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include "irrlichttypes_extrabloated.h"
-
-/* 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<s32> &srcrect, const core::rect<s32> &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<s32> &destrect, const core::rect<s32> &srcrect,
- const core::rect<s32> *cliprect = 0, const video::SColor *const colors = 0,
- bool usealpha = false);
+++ /dev/null
-/*
-Copyright (C) 2015 Aaron Suen <warr1024@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "imagefilters.h"
-#include "util/numeric.h"
-#include <cmath>
-
-/* 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<u32> 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<s32> &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<u32> 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);
- }
-}
+++ /dev/null
-/*
-Copyright (C) 2015 Aaron Suen <warr1024@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include "irrlichttypes_extrabloated.h"
-
-/* 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<s32> &srcrect, video::IImage *dest);
#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"
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "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 << "<Keycode " << (int) key << ">";
- 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 << "<Char " << hex_encode((char*) &Char, sizeof(wchar_t)) << ">";
- 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 : "<Unnamed key>";
-}
-
-const KeyPress EscapeKey("KEY_ESCAPE");
-const KeyPress CancelKey("KEY_CANCEL");
-
-/*
- Key config
-*/
-
-// A simple cache for quicker lookup
-std::unordered_map<std::string, KeyPress> g_key_setting_cache;
-
-KeyPress getKeySetting(const char *settingname)
-{
- std::unordered_map<std::string, KeyPress>::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;
-}
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include "irrlichttypes.h"
-#include "Keycodes.h"
-#include <IEventReceiver.h>
-#include <string>
-
-/* 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);
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "localplayer.h"
-#include <cmath>
-#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<aabb3f> &nodeboxes)
-{
- if (nodeboxes.empty())
- return aabb3f(0, 0, 0, 0, 0, 0);
-
- aabb3f b_max;
-
- std::vector<aabb3f>::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<aabb3f> 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<CollisionInfo> *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<CollisionInfo> *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<aabb3f> 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;
- }
-}
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include "player.h"
-#include "environment.h"
-#include "constants.h"
-#include "settings.h"
-#include <list>
-
-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<CollisionInfo> *collision_info);
- // Temporary option for old move code
- void old_move(f32 dtime, Environment *env, f32 pos_max_d,
- std::vector<CollisionInfo> *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;
-};
#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"
#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"
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "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 <array>
-
-/*
- 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<v3s16,8> &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<bool, 4> 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<v3s16,8> 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<FastFace> &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<f32>(x0 + w * abs_scale, y0 + h),
- core::vector2d<f32>(x0, y0 + h),
- core::vector2d<f32>(x0, y0),
- core::vector2d<f32>(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<FastFace> &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<FastFace> &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<FastFace> 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<u8, u32>(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<u8, u32>(layer, i)] = p.layer;
- m_animation_frames[std::pair<u8, u32>(layer, i)] = 0;
- if (g_settings->getBool(
- "desynchronize_mapblock_texture_animation")) {
- // Get starting position from noise
- m_animation_frame_offsets[std::pair<u8, u32>(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<u8, u32>(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<u8, u32>(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 "<<fastfaces.getSize()<<" faces."<<std::endl;
-
- // Check if animation is required for this mesh
- m_has_animation =
- !m_crack_materials.empty() ||
- !m_daynight_diffs.empty() ||
- !m_animation_tiles.empty();
-}
-
-MapBlockMesh::~MapBlockMesh()
-{
- for (scene::IMesh *m : m_mesh) {
- if (m_enable_vbo && m)
- for (u32 i = 0; i < m->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);
-}
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include "irrlichttypes_extrabloated.h"
-#include "client/tile.h"
-#include "voxel.h"
-#include <array>
-#include <map>
-
-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::pair<u8, u32>, 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<std::pair<u8, u32>, TileLayer> m_animation_tiles;
- std::map<std::pair<u8, u32>, int> m_animation_frames; // last animation frame
- std::map<std::pair<u8, u32>, int> m_animation_frame_offsets;
-
- // 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::pair<u8, u32>, std::map<u32, video::SColor > > 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);
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "mesh.h"
-#include "debug.h"
-#include "log.h"
-#include "irrMap.h"
-#include <iostream>
-#include <IAnimatedMesh.h>
-#include <SAnimatedMesh.h>
-#include <IAnimatedMeshSceneNode.h>
-
-// In Irrlicht 1.8 the signature of ITexture::lock was changed from
-// (bool, u32) to (E_TEXTURE_LOCK_MODE, u32).
-#if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 7
-#define MY_ETLM_READ_ONLY true
-#else
-#define MY_ETLM_READ_ONLY video::ETLM_READ_ONLY
-#endif
-
-inline static void applyShadeFactor(video::SColor& color, float factor)
-{
- color.setRed(core::clamp(core::round32(color.getRed()*factor), 0, 255));
- 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<aabb3f> &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<u32> tris;
- float score;
- s16 cachepos;
- u16 NumActiveTris;
-};
-
-struct tcache
-{
- u16 ind[3];
- float score;
- bool drawn;
-};
-
-const u16 cachesize = 32;
-
-float FindVertexScore(vcache *v)
-{
- const float CacheDecayPower = 1.5f;
- const float LastTriScore = 0.75f;
- const float ValenceBoostScale = 2.0f;
- const float ValenceBoostPower = 0.5f;
- const float MaxSizeVertexCache = 32.0f;
-
- if (v->NumActiveTris == 0)
- {
- // No tri needs this vertex!
- return -1.0f;
- }
-
- float Score = 0.0f;
- int CachePosition = v->cachepos;
- if (CachePosition < 0)
- {
- // Vertex is not in FIFO cache - no score.
- }
- else
- {
- if (CachePosition < 3)
- {
- // This vertex was used in the last triangle,
- // so it has a fixed score.
- Score = LastTriScore;
- }
- else
- {
- // Points for being high in the cache.
- const float Scaler = 1.0f / (MaxSizeVertexCache - 3);
- Score = 1.0f - (CachePosition - 3) * Scaler;
- Score = powf(Score, CacheDecayPower);
- }
- }
-
- // Bonus points for having a low number of tris still to
- // use the vert, so we get rid of lone verts quickly.
- float ValenceBoost = powf(v->NumActiveTris,
- -ValenceBoostPower);
- Score += ValenceBoostScale * ValenceBoost;
-
- return Score;
-}
-
-/*
- A specialized LRU cache for the Forsyth algorithm.
-*/
-
-class f_lru
-{
-
-public:
- f_lru(vcache *v, tcache *t): vc(v), tc(t)
- {
- for (int &i : cache) {
- i = -1;
- }
- }
-
- // Adds this vertex index and returns the highest-scoring triangle index
- u32 add(u16 vert, bool updatetris = false)
- {
- bool found = false;
-
- // Mark existing pos as empty
- for (u16 i = 0; i < cachesize; i++)
- {
- if (cache[i] == vert)
- {
- // Move everything down
- for (u16 j = i; j; j--)
- {
- cache[j] = cache[j - 1];
- }
-
- found = true;
- break;
- }
- }
-
- if (!found)
- {
- if (cache[cachesize-1] != -1)
- vc[cache[cachesize-1]].cachepos = -1;
-
- // Move everything down
- for (u16 i = cachesize - 1; i; i--)
- {
- cache[i] = cache[i - 1];
- }
- }
-
- cache[0] = vert;
-
- u32 highest = 0;
- float hiscore = 0;
-
- if (updatetris)
- {
- // Update cache positions
- for (u16 i = 0; i < cachesize; i++)
- {
- if (cache[i] == -1)
- break;
-
- vc[cache[i]].cachepos = i;
- vc[cache[i]].score = FindVertexScore(&vc[cache[i]]);
- }
-
- // Update triangle scores
- for (int i : cache) {
- if (i == -1)
- break;
-
- const u16 trisize = vc[i].tris.size();
- for (u16 t = 0; t < trisize; t++)
- {
- tcache *tri = &tc[vc[i].tris[t]];
-
- tri->score =
- vc[tri->ind[0]].score +
- vc[tri->ind[1]].score +
- vc[tri->ind[2]].score;
-
- if (tri->score > hiscore)
- {
- hiscore = tri->score;
- highest = vc[i].tris[t];
- }
- }
- }
- }
-
- return highest;
- }
-
-private:
- s32 cache[cachesize];
- vcache *vc;
- tcache *tc;
-};
-
-/**
-Vertex cache optimization according to the Forsyth paper:
-http://home.comcast.net/~tom_forsyth/papers/fast_vert_cache_opt.html
-
-The function is thread-safe (read: you can optimize several meshes in different threads)
-
-\param mesh Source mesh for the operation. */
-scene::IMesh* createForsythOptimizedMesh(const scene::IMesh *mesh)
-{
- if (!mesh)
- return 0;
-
- scene::SMesh *newmesh = new scene::SMesh();
- newmesh->BoundingBox = mesh->getBoundingBox();
-
- const u32 mbcount = mesh->getMeshBufferCount();
-
- for (u32 b = 0; b < mbcount; ++b)
- {
- const scene::IMeshBuffer *mb = mesh->getMeshBuffer(b);
-
- if (mb->getIndexType() != video::EIT_16BIT)
- {
- //os::Printer::log("Cannot optimize a mesh with 32bit indices", ELL_ERROR);
- newmesh->drop();
- return 0;
- }
-
- const u32 icount = mb->getIndexCount();
- const u32 tcount = icount / 3;
- const u32 vcount = mb->getVertexCount();
- const u16 *ind = mb->getIndices();
-
- vcache *vc = new vcache[vcount];
- tcache *tc = new tcache[tcount];
-
- f_lru lru(vc, tc);
-
- // init
- for (u16 i = 0; i < vcount; i++)
- {
- vc[i].score = 0;
- vc[i].cachepos = -1;
- vc[i].NumActiveTris = 0;
- }
-
- // First pass: count how many times a vert is used
- for (u32 i = 0; i < icount; i += 3)
- {
- vc[ind[i]].NumActiveTris++;
- vc[ind[i + 1]].NumActiveTris++;
- vc[ind[i + 2]].NumActiveTris++;
-
- const u32 tri_ind = i/3;
- tc[tri_ind].ind[0] = ind[i];
- tc[tri_ind].ind[1] = ind[i + 1];
- tc[tri_ind].ind[2] = ind[i + 2];
- }
-
- // Second pass: list of each triangle
- for (u32 i = 0; i < tcount; i++)
- {
- vc[tc[i].ind[0]].tris.push_back(i);
- vc[tc[i].ind[1]].tris.push_back(i);
- vc[tc[i].ind[2]].tris.push_back(i);
-
- tc[i].drawn = false;
- }
-
- // Give initial scores
- for (u16 i = 0; i < vcount; i++)
- {
- vc[i].score = FindVertexScore(&vc[i]);
- }
- for (u32 i = 0; i < tcount; i++)
- {
- tc[i].score =
- vc[tc[i].ind[0]].score +
- vc[tc[i].ind[1]].score +
- vc[tc[i].ind[2]].score;
- }
-
- switch(mb->getVertexType())
- {
- case video::EVT_STANDARD:
- {
- video::S3DVertex *v = (video::S3DVertex *) mb->getVertices();
-
- scene::SMeshBuffer *buf = new scene::SMeshBuffer();
- buf->Material = mb->getMaterial();
-
- buf->Vertices.reallocate(vcount);
- buf->Indices.reallocate(icount);
-
- core::map<const video::S3DVertex, const u16> sind; // search index for fast operation
- typedef core::map<const video::S3DVertex, const u16>::Node snode;
-
- // Main algorithm
- u32 highest = 0;
- u32 drawcalls = 0;
- for (;;)
- {
- if (tc[highest].drawn)
- {
- bool found = false;
- float hiscore = 0;
- for (u32 t = 0; t < tcount; t++)
- {
- if (!tc[t].drawn)
- {
- if (tc[t].score > hiscore)
- {
- highest = t;
- hiscore = tc[t].score;
- found = true;
- }
- }
- }
- if (!found)
- break;
- }
-
- // Output the best triangle
- u16 newind = buf->Vertices.size();
-
- snode *s = sind.find(v[tc[highest].ind[0]]);
-
- if (!s)
- {
- buf->Vertices.push_back(v[tc[highest].ind[0]]);
- buf->Indices.push_back(newind);
- sind.insert(v[tc[highest].ind[0]], newind);
- newind++;
- }
- else
- {
- buf->Indices.push_back(s->getValue());
- }
-
- s = sind.find(v[tc[highest].ind[1]]);
-
- if (!s)
- {
- buf->Vertices.push_back(v[tc[highest].ind[1]]);
- buf->Indices.push_back(newind);
- sind.insert(v[tc[highest].ind[1]], newind);
- newind++;
- }
- else
- {
- buf->Indices.push_back(s->getValue());
- }
-
- s = sind.find(v[tc[highest].ind[2]]);
-
- if (!s)
- {
- buf->Vertices.push_back(v[tc[highest].ind[2]]);
- buf->Indices.push_back(newind);
- sind.insert(v[tc[highest].ind[2]], newind);
- }
- else
- {
- buf->Indices.push_back(s->getValue());
- }
-
- vc[tc[highest].ind[0]].NumActiveTris--;
- vc[tc[highest].ind[1]].NumActiveTris--;
- vc[tc[highest].ind[2]].NumActiveTris--;
-
- tc[highest].drawn = true;
-
- for (u16 j : tc[highest].ind) {
- vcache *vert = &vc[j];
- for (u16 t = 0; t < vert->tris.size(); t++)
- {
- if (highest == vert->tris[t])
- {
- vert->tris.erase(t);
- break;
- }
- }
- }
-
- lru.add(tc[highest].ind[0]);
- lru.add(tc[highest].ind[1]);
- highest = lru.add(tc[highest].ind[2], true);
- drawcalls++;
- }
-
- buf->setBoundingBox(mb->getBoundingBox());
- newmesh->addMeshBuffer(buf);
- buf->drop();
- }
- break;
- case video::EVT_2TCOORDS:
- {
- video::S3DVertex2TCoords *v = (video::S3DVertex2TCoords *) mb->getVertices();
-
- scene::SMeshBufferLightMap *buf = new scene::SMeshBufferLightMap();
- buf->Material = mb->getMaterial();
-
- buf->Vertices.reallocate(vcount);
- buf->Indices.reallocate(icount);
-
- core::map<const video::S3DVertex2TCoords, const u16> sind; // search index for fast operation
- typedef core::map<const video::S3DVertex2TCoords, const u16>::Node snode;
-
- // Main algorithm
- u32 highest = 0;
- u32 drawcalls = 0;
- for (;;)
- {
- if (tc[highest].drawn)
- {
- bool found = false;
- float hiscore = 0;
- for (u32 t = 0; t < tcount; t++)
- {
- if (!tc[t].drawn)
- {
- if (tc[t].score > hiscore)
- {
- highest = t;
- hiscore = tc[t].score;
- found = true;
- }
- }
- }
- if (!found)
- break;
- }
-
- // Output the best triangle
- u16 newind = buf->Vertices.size();
-
- snode *s = sind.find(v[tc[highest].ind[0]]);
-
- if (!s)
- {
- buf->Vertices.push_back(v[tc[highest].ind[0]]);
- buf->Indices.push_back(newind);
- sind.insert(v[tc[highest].ind[0]], newind);
- newind++;
- }
- else
- {
- buf->Indices.push_back(s->getValue());
- }
-
- s = sind.find(v[tc[highest].ind[1]]);
-
- if (!s)
- {
- buf->Vertices.push_back(v[tc[highest].ind[1]]);
- buf->Indices.push_back(newind);
- sind.insert(v[tc[highest].ind[1]], newind);
- newind++;
- }
- else
- {
- buf->Indices.push_back(s->getValue());
- }
-
- s = sind.find(v[tc[highest].ind[2]]);
-
- if (!s)
- {
- buf->Vertices.push_back(v[tc[highest].ind[2]]);
- buf->Indices.push_back(newind);
- sind.insert(v[tc[highest].ind[2]], newind);
- }
- else
- {
- buf->Indices.push_back(s->getValue());
- }
-
- vc[tc[highest].ind[0]].NumActiveTris--;
- vc[tc[highest].ind[1]].NumActiveTris--;
- vc[tc[highest].ind[2]].NumActiveTris--;
-
- tc[highest].drawn = true;
-
- for (u16 j : tc[highest].ind) {
- vcache *vert = &vc[j];
- for (u16 t = 0; t < vert->tris.size(); t++)
- {
- if (highest == vert->tris[t])
- {
- vert->tris.erase(t);
- break;
- }
- }
- }
-
- lru.add(tc[highest].ind[0]);
- lru.add(tc[highest].ind[1]);
- highest = lru.add(tc[highest].ind[2]);
- drawcalls++;
- }
-
- buf->setBoundingBox(mb->getBoundingBox());
- newmesh->addMeshBuffer(buf);
- buf->drop();
-
- }
- break;
- case video::EVT_TANGENTS:
- {
- video::S3DVertexTangents *v = (video::S3DVertexTangents *) mb->getVertices();
-
- scene::SMeshBufferTangents *buf = new scene::SMeshBufferTangents();
- buf->Material = mb->getMaterial();
-
- buf->Vertices.reallocate(vcount);
- buf->Indices.reallocate(icount);
-
- core::map<const video::S3DVertexTangents, const u16> sind; // search index for fast operation
- typedef core::map<const video::S3DVertexTangents, const u16>::Node snode;
-
- // Main algorithm
- u32 highest = 0;
- u32 drawcalls = 0;
- for (;;)
- {
- if (tc[highest].drawn)
- {
- bool found = false;
- float hiscore = 0;
- for (u32 t = 0; t < tcount; t++)
- {
- if (!tc[t].drawn)
- {
- if (tc[t].score > hiscore)
- {
- highest = t;
- hiscore = tc[t].score;
- found = true;
- }
- }
- }
- if (!found)
- break;
- }
-
- // Output the best triangle
- u16 newind = buf->Vertices.size();
-
- snode *s = sind.find(v[tc[highest].ind[0]]);
-
- if (!s)
- {
- buf->Vertices.push_back(v[tc[highest].ind[0]]);
- buf->Indices.push_back(newind);
- sind.insert(v[tc[highest].ind[0]], newind);
- newind++;
- }
- else
- {
- buf->Indices.push_back(s->getValue());
- }
-
- s = sind.find(v[tc[highest].ind[1]]);
-
- if (!s)
- {
- buf->Vertices.push_back(v[tc[highest].ind[1]]);
- buf->Indices.push_back(newind);
- sind.insert(v[tc[highest].ind[1]], newind);
- newind++;
- }
- else
- {
- buf->Indices.push_back(s->getValue());
- }
-
- s = sind.find(v[tc[highest].ind[2]]);
-
- if (!s)
- {
- buf->Vertices.push_back(v[tc[highest].ind[2]]);
- buf->Indices.push_back(newind);
- sind.insert(v[tc[highest].ind[2]], newind);
- }
- else
- {
- buf->Indices.push_back(s->getValue());
- }
-
- vc[tc[highest].ind[0]].NumActiveTris--;
- vc[tc[highest].ind[1]].NumActiveTris--;
- vc[tc[highest].ind[2]].NumActiveTris--;
-
- tc[highest].drawn = true;
-
- for (u16 j : tc[highest].ind) {
- vcache *vert = &vc[j];
- for (u16 t = 0; t < vert->tris.size(); t++)
- {
- if (highest == vert->tris[t])
- {
- vert->tris.erase(t);
- break;
- }
- }
- }
-
- lru.add(tc[highest].ind[0]);
- lru.add(tc[highest].ind[1]);
- highest = lru.add(tc[highest].ind[2]);
- drawcalls++;
- }
-
- buf->setBoundingBox(mb->getBoundingBox());
- newmesh->addMeshBuffer(buf);
- buf->drop();
- }
- break;
- }
-
- delete [] vc;
- delete [] tc;
-
- } // for each meshbuffer
-
- return newmesh;
-}
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include "irrlichttypes_extrabloated.h"
-#include "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<aabb3f> &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);
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2013, 2017 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "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<CachedMapBlockData*> 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<QueuedMeshUpdate*>::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<v3s16, CachedMapBlockData*>::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<v3s16, CachedMapBlockData*>::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<v3s16, CachedMapBlockData*>::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;
- }
-}
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2013, 2017 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include <ctime>
-#include <mutex>
-#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<QueuedMeshUpdate *> m_queue;
- std::set<v3s16> m_urgents;
- std::map<v3s16, CachedMapBlockData *> 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<MeshUpdateResult> 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();
-};
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2015 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "minimap.h"
-#include <cmath>
-#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<std::map<v3s16, MinimapMapblock*>::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<v3s16, MinimapMapblock *>::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<v3s16, MinimapMapblock *>::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<s32>(0, 0),
- core::dimension2d<u32>(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<s32>(0, 0),
- core::dimension2d<u32>(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<u32> 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<u32>(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<s32> oldViewPort = driver->getViewPort();
- core::matrix4 oldProjMat = driver->getTransform(video::ETS_PROJECTION);
- core::matrix4 oldViewMat = driver->getTransform(video::ETS_VIEW);
-
- driver->setViewPort(core::rect<s32>(
- 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<s32> 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<v2f>::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<s32> 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<Nametag *> &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;
- }
-}
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2015 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include "irrlichttypes_extrabloated.h"
-#include "util/thread.h"
-#include "voxel.h"
-#include <map>
-#include <string>
-#include <vector>
-
-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<QueuedMinimapUpdate> m_update_queue;
- std::map<v3s16, MinimapMapblock *> 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<v2f> m_active_markers;
-};
#pragma once
-#include "client.h"
+#include "client/client.h"
#include "networkprotocol.h"
class NetworkPacket;
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"
#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 <IMeshManipulator.h>
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "particles.h"
-#include <cmath>
-#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<float>::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<u32, ParticleSpawner*>::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<Particle*>::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<u32, ParticleSpawner*>::iterator i =
- m_particle_spawners.begin();
- i != m_particle_spawners.end();)
- {
- delete i->second;
- m_particle_spawners.erase(i++);
- }
-
- for(std::vector<Particle*>::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<u32, ParticleSpawner*>(
- 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);
-}
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include <iostream>
-#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<float> 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<Particle*> m_particles;
- std::map<u32, ParticleSpawner*> m_particle_spawners;
-
- ClientEnvironment* m_env;
- std::mutex m_particle_list_lock;
- std::mutex m_spawner_list_lock;
-};
#include "util/string.h"
#include "server.h"
#ifndef SERVER
-#include "client.h"
+#include "client/client.h"
#endif
#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"
#include "filesys.h"
#include "porting.h"
#include "server.h"
-#include "client.h"
+#include "client/client.h"
#include "settings.h"
#include <cerrno>
#include <cmath>
#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)
{
#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"
#include "face_position_cache.h"
#include "remoteplayer.h"
#ifndef SERVER
-#include "client.h"
+#include "client/client.h"
#endif
struct EnumString ModApiEnvMod::es_ClearObjectsMode[] =
#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"
#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)
#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=,
#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)
*/
#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"
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-Copyright (C) 2013 Kahrl <kahrl@gmx.net>
-
-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 <fstream>
-#include <iterator>
-#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 <ICameraSceneNode.h>
-#include <IGPUProgrammingServices.h>
-#include <IMaterialRenderer.h>
-#include <IMaterialRendererServices.h>
-#include <IShaderConstantSetCallBack.h>
-#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<std::string, std::string> 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<IShaderConstantSetter*> m_setters;
-
-public:
- ShaderCallback(const std::vector<IShaderConstantSetterFactory *> &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<float, 16> m_world_view_proj;
- CachedVertexShaderSetting<float, 16> 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<float(*)[16]>(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<float(*)[16]>(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<ShaderInfo> 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<std::string, u32, u8, u8> m_get_shader_queue;
-
- // Global constant setter factories
- std::vector<IShaderConstantSetterFactory *> m_setter_factories;
-
- // Shader callbacks
- std::vector<ShaderCallback *> 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<ShaderCallback *> &callbacks,
- const std::vector<IShaderConstantSetterFactory *> &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=\""<<name<<"\""<<std::endl;*/
-
- // We're gonna ask the result to be put into here
-
- static ResultQueue<std::string, u32, u8, u8> result_queue;
-
- // Throw a request in
- m_get_shader_queue.add(name, 0, 0, &result_queue);
-
- /* infostream<<"Waiting for shader from main thread, name=\""
- <<name<<"\""<<std::endl;*/
-
- while(true) {
- GetResult<std::string, u32, u8, u8>
- 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<<"\""<<std::endl;
-
- // Empty name means shader 0
- if (name.empty()) {
- infostream<<"getShaderIdDirect(): name is empty"<<std::endl;
- return 0;
- }
-
- // Check if already have such instance
- for(u32 i=0; i<m_shaderinfo_cache.size(); i++){
- ShaderInfo *info = &m_shaderinfo_cache[i];
- if(info->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"<<std::endl;
- return 0;
- }
-
- ShaderInfo info = generate_shader(name, material_type, drawtype,
- m_callbacks, m_setter_factories, &m_sourcecache);
-
- /*
- Add shader to caches (add dummy shaders too)
- */
-
- MutexAutoLock lock(m_shaderinfo_cache_mutex);
-
- u32 id = m_shaderinfo_cache.size();
- m_shaderinfo_cache.push_back(info);
-
- infostream<<"getShaderIdDirect(): "
- <<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;
-
- return id;
-}
-
-
-ShaderInfo ShaderSource::getShaderInfo(u32 id)
-{
- MutexAutoLock lock(m_shaderinfo_cache_mutex);
-
- if(id >= 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_of_shader<<"\", "
- "filename=\""<<filename<<"\""<<std::endl;*/
-
- sanity_check(std::this_thread::get_id() == m_main_thread);
-
- m_sourcecache.insert(name_of_shader, filename, program, true);
-}
-
-void ShaderSource::rebuildShaders()
-{
- MutexAutoLock lock(m_shaderinfo_cache_mutex);
-
- /*// Oh well... just clear everything, they'll load sometime.
- m_shaderinfo_cache.clear();
- m_name_to_id.clear();*/
-
- /*
- FIXME: Old shader materials can't be deleted in Irrlicht,
- or can they?
- (This would be nice to do in the destructor too)
- */
-
- // Recreate shaders
- for (ShaderInfo &i : m_shaderinfo_cache) {
- ShaderInfo *info = &i;
- if (!info->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<ShaderCallback *> &callbacks,
- const std::vector<IShaderConstantSetterFactory *> &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 \""<<name<<"\", "
- "GPU programming not supported."
- <<std::endl;
- return shaderinfo;
- }
-
- // Choose shader language depending on driver type and settings
- // Then load shaders
- std::string vertex_program;
- std::string pixel_program;
- std::string geometry_program;
- bool is_highlevel;
- load_shaders(name, sourcecache, driver->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."
- <<std::endl;
- vertex_program = "";
- }
- if (!pixel_program.empty() &&
- !driver->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."
- <<std::endl;
- pixel_program = "";
- }
- if (!geometry_program.empty() &&
- !driver->queryFeature(video::EVDF_GEOMETRY_SHADER)){
- infostream<<"generate_shader(): geometry shaders disabled "
- "because of missing driver/hardware support."
- <<std::endl;
- geometry_program = "";
- }
-
- // If no shaders are used, don't make a separate material type
- if (vertex_program.empty() && pixel_program.empty() && geometry_program.empty())
- return shaderinfo;
-
- // Create shaders header
- std::string shaders_header = "#version 120\n";
-
- static const char* drawTypes[] = {
- "NDT_NORMAL",
- "NDT_AIRLIKE",
- "NDT_LIQUID",
- "NDT_FLOWINGLIQUID",
- "NDT_GLASSLIKE",
- "NDT_ALLFACES",
- "NDT_ALLFACES_OPTIONAL",
- "NDT_TORCHLIKE",
- "NDT_SIGNLIKE",
- "NDT_PLANTLIKE",
- "NDT_FENCELIKE",
- "NDT_RAILLIKE",
- "NDT_NODEBOX",
- "NDT_GLASSLIKE_FRAMED",
- "NDT_FIRELIKE",
- "NDT_GLASSLIKE_FRAMED_OPTIONAL",
- "NDT_PLANTLIKE_ROOTED",
- };
-
- for (int i = 0; i < 14; i++){
- shaders_header += "#define ";
- shaders_header += drawTypes[i];
- shaders_header += " ";
- shaders_header += itos(i);
- shaders_header += "\n";
- }
-
- static const char* materialTypes[] = {
- "TILE_MATERIAL_BASIC",
- "TILE_MATERIAL_ALPHA",
- "TILE_MATERIAL_LIQUID_TRANSPARENT",
- "TILE_MATERIAL_LIQUID_OPAQUE",
- "TILE_MATERIAL_WAVING_LEAVES",
- "TILE_MATERIAL_WAVING_PLANTS",
- "TILE_MATERIAL_OPAQUE"
- };
-
- for (int i = 0; i < 7; i++){
- shaders_header += "#define ";
- shaders_header += materialTypes[i];
- shaders_header += " ";
- shaders_header += itos(i);
- shaders_header += "\n";
- }
-
- shaders_header += "#define MATERIAL_TYPE ";
- shaders_header += itos(material_type);
- shaders_header += "\n";
- shaders_header += "#define DRAW_TYPE ";
- shaders_header += itos(drawtype);
- shaders_header += "\n";
-
- if (g_settings->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 "<<name<<std::endl;
- shadermat = gpu->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 \""<<name<<"\", "
- "addHighLevelShaderMaterial failed."
- <<std::endl;
- dumpShaderProgram(warningstream, "Vertex", vertex_program);
- dumpShaderProgram(warningstream, "Pixel", pixel_program);
- dumpShaderProgram(warningstream, "Geometry", geometry_program);
- delete cb;
- return shaderinfo;
- }
- }
- else{
- infostream<<"Compiling assembly shaders for "<<name<<std::endl;
- shadermat = gpu->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 \""<<name<<"\", "
- "addShaderMaterial failed."
- <<std::endl;
- dumpShaderProgram(warningstream, "Vertex", vertex_program);
- dumpShaderProgram(warningstream,"Pixel", pixel_program);
- delete cb;
- return shaderinfo;
- }
- }
- callbacks.push_back(cb);
-
- // HACK, TODO: investigate this better
- // Grab the material renderer once more so minetest doesn't crash on exit
- driver->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;
-}
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-Copyright (C) 2013 Kahrl <kahrl@gmx.net>
-
-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 <IMaterialRendererServices.h>
-#include "irrlichttypes_bloated.h"
-#include <string>
-
-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 <typename T, std::size_t count=1>
-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 <typename T, std::size_t count = 1>
-class CachedPixelShaderSetting : public CachedShaderSetting<T, count> {
-public:
- CachedPixelShaderSetting(const char *name) :
- CachedShaderSetting<T, count>(name, true){}
-};
-
-template <typename T, std::size_t count = 1>
-class CachedVertexShaderSetting : public CachedShaderSetting<T, count> {
-public:
- CachedVertexShaderSetting(const char *name) :
- CachedShaderSetting<T, count>(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);
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "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 <cmath>
-#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="<<m_time_of_day<<" -> "
- <<"wicked_time_of_day="<<wicked_time_of_day<<std::endl;*/
-
- video::SColor suncolor = suncolor_f.toSColor();
- video::SColor suncolor2 = suncolor2_f.toSColor();
- video::SColor mooncolor = mooncolor_f.toSColor();
- video::SColor mooncolor2 = mooncolor2_f.toSColor();
-
- // Calculate offset normalized to the X dimension of a 512x1 px tonemap
- float offset = (1.0 - fabs(sin((m_time_of_day - 0.5) * irr::core::PI))) * 511;
-
- if (m_sun_tonemap) {
- u8 * texels = (u8 *)m_sun_tonemap->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_="<<a_<<" a="<<a<<std::endl;
- video::SColor c(255, 255, 255, 255);
- float y = -(1.0 - a) * 0.22;
- vertices[0] = video::S3DVertex(-1, -0.05 + y, -1, 0, 0, 1, c, t, t);
- vertices[1] = video::S3DVertex( 1, -0.05 + y, -1, 0, 0, 1, c, o, t);
- vertices[2] = video::S3DVertex( 1, 0.2 + y, -1, 0, 0, 1, c, o, o);
- vertices[3] = video::S3DVertex(-1, 0.2 + y, -1, 0, 0, 1, c, t, o);
- for (video::S3DVertex &vertex : vertices) {
- if (wicked_time_of_day < 0.5)
- // Switch from -Z (south) to +X (east)
- vertex.Pos.rotateXZBy(90);
- else
- // Switch from -Z (south) to -X (west)
- vertex.Pos.rotateXZBy(-90);
- }
- driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
- }
-
- // Draw stars
- do {
- driver->setMaterial(m_materials[1]);
- float starbrightness = MYMAX(0, MYMIN(1,
- (0.285 - fabs(wicked_time_of_day < 0.5 ?
- wicked_time_of_day : (1.0 - wicked_time_of_day))) * 10));
- float f = starbrightness;
- float d = 0.007/2;
- video::SColor starcolor(255, f * 90, f * 90, f * 90);
- if (starcolor.getBlue() < m_skycolor.getBlue())
- break;
-#ifdef __ANDROID__
- u16 indices[SKY_STAR_COUNT * 3];
- video::S3DVertex vertices[SKY_STAR_COUNT * 3];
- for (u32 i = 0; i < SKY_STAR_COUNT; i++) {
- indices[i * 3 + 0] = i * 3 + 0;
- indices[i * 3 + 1] = i * 3 + 1;
- indices[i * 3 + 2] = i * 3 + 2;
- v3f r = m_stars[i];
- core::CMatrix4<f32> a;
- a.buildRotateFromTo(v3f(0, 1, 0), r);
- v3f p = v3f(-d, 1, -d);
- v3f p1 = v3f(d, 1, 0);
- v3f p2 = v3f(-d, 1, d);
- a.rotateVect(p);
- a.rotateVect(p1);
- a.rotateVect(p2);
- p.rotateXYBy(wicked_time_of_day * 360 - 90);
- p1.rotateXYBy(wicked_time_of_day * 360 - 90);
- p2.rotateXYBy(wicked_time_of_day * 360 - 90);
- vertices[i * 3 + 0].Pos = p;
- vertices[i * 3 + 0].Color = starcolor;
- vertices[i * 3 + 1].Pos = p1;
- vertices[i * 3 + 1].Color = starcolor;
- vertices[i * 3 + 2].Pos = p2;
- vertices[i * 3 + 2].Color = starcolor;
- }
- driver->drawIndexedTriangleList(vertices, SKY_STAR_COUNT * 3,
- indices, SKY_STAR_COUNT);
-#else
- u16 indices[SKY_STAR_COUNT * 4];
- video::S3DVertex vertices[SKY_STAR_COUNT * 4];
- for (u32 i = 0; i < SKY_STAR_COUNT; i++) {
- indices[i * 4 + 0] = i * 4 + 0;
- indices[i * 4 + 1] = i * 4 + 1;
- indices[i * 4 + 2] = i * 4 + 2;
- indices[i * 4 + 3] = i * 4 + 3;
- v3f r = m_stars[i];
- core::CMatrix4<f32> a;
- a.buildRotateFromTo(v3f(0, 1, 0), r);
- v3f p = v3f(-d, 1, -d);
- v3f p1 = v3f( d, 1, -d);
- v3f p2 = v3f( d, 1, d);
- v3f p3 = v3f(-d, 1, d);
- a.rotateVect(p);
- a.rotateVect(p1);
- a.rotateVect(p2);
- a.rotateVect(p3);
- p.rotateXYBy(wicked_time_of_day * 360 - 90);
- p1.rotateXYBy(wicked_time_of_day * 360 - 90);
- p2.rotateXYBy(wicked_time_of_day * 360 - 90);
- p3.rotateXYBy(wicked_time_of_day * 360 - 90);
- vertices[i * 4 + 0].Pos = p;
- vertices[i * 4 + 0].Color = starcolor;
- vertices[i * 4 + 1].Pos = p1;
- vertices[i * 4 + 1].Color = starcolor;
- vertices[i * 4 + 2].Pos = p2;
- vertices[i * 4 + 2].Color = starcolor;
- vertices[i * 4 + 3].Pos = p3;
- vertices[i * 4 + 3].Color = starcolor;
- }
- driver->drawVertexPrimitiveList(vertices, SKY_STAR_COUNT * 4,
- indices, SKY_STAR_COUNT, video::EVT_STANDARD,
- scene::EPT_QUADS, video::EIT_16BIT);
-#endif
- } while(false);
-
- // Draw sun
- if (wicked_time_of_day > 0.15 && wicked_time_of_day < 0.85) {
- if (!m_sun_texture) {
- driver->setMaterial(m_materials[1]);
- float d = sunsize * 1.7;
- video::SColor c = suncolor;
- c.setAlpha(0.05 * 255);
- vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, c, t, t);
- vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, c, o, t);
- vertices[2] = video::S3DVertex( d, d, -1, 0, 0, 1, c, o, o);
- vertices[3] = video::S3DVertex(-d, d, -1, 0, 0, 1, c, t, o);
- for (video::S3DVertex &vertex : vertices) {
- // Switch from -Z (south) to +X (east)
- vertex.Pos.rotateXZBy(90);
- vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
- }
- driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
-
- d = sunsize * 1.2;
- c = suncolor;
- c.setAlpha(0.15 * 255);
- vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, c, t, t);
- vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, c, o, t);
- vertices[2] = video::S3DVertex( d, d, -1, 0, 0, 1, c, o, o);
- vertices[3] = video::S3DVertex(-d, d, -1, 0, 0, 1, c, t, o);
- for (video::S3DVertex &vertex : vertices) {
- // Switch from -Z (south) to +X (east)
- vertex.Pos.rotateXZBy(90);
- vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
- }
- driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
-
- d = sunsize;
- vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, suncolor, t, t);
- vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, suncolor, o, t);
- vertices[2] = video::S3DVertex( d, d, -1, 0, 0, 1, suncolor, o, o);
- vertices[3] = video::S3DVertex(-d, d, -1, 0, 0, 1, suncolor, t, o);
- for (video::S3DVertex &vertex : vertices) {
- // Switch from -Z (south) to +X (east)
- vertex.Pos.rotateXZBy(90);
- vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
- }
- driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
-
- d = sunsize * 0.7;
- vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, suncolor2, t, t);
- vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, suncolor2, o, t);
- vertices[2] = video::S3DVertex( d, d, -1, 0, 0, 1, suncolor2, o, o);
- vertices[3] = video::S3DVertex(-d, d, -1, 0, 0, 1, suncolor2, t, o);
- for (video::S3DVertex &vertex : vertices) {
- // Switch from -Z (south) to +X (east)
- vertex.Pos.rotateXZBy(90);
- vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
- }
- driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
- } else {
- driver->setMaterial(m_materials[3]);
- float d = sunsize * 1.7;
- video::SColor c;
- if (m_sun_tonemap)
- c = video::SColor (0, 0, 0, 0);
- else
- c = video::SColor (255, 255, 255, 255);
- vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, c, t, t);
- vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, c, o, t);
- vertices[2] = video::S3DVertex( d, d, -1, 0, 0, 1, c, o, o);
- vertices[3] = video::S3DVertex(-d, d, -1, 0, 0, 1, c, t, o);
- for (video::S3DVertex &vertex : vertices) {
- // Switch from -Z (south) to +X (east)
- vertex.Pos.rotateXZBy(90);
- vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
- }
- driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
- }
- }
-
- // Draw moon
- if (wicked_time_of_day < 0.3 || wicked_time_of_day > 0.7) {
- if (!m_moon_texture) {
- driver->setMaterial(m_materials[1]);
- float d = moonsize * 1.9;
- video::SColor c = mooncolor;
- c.setAlpha(0.05 * 255);
- vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, c, t, t);
- vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, c, o, t);
- vertices[2] = video::S3DVertex( d, d, -1, 0, 0, 1, c, o, o);
- vertices[3] = video::S3DVertex(-d, d, -1, 0, 0, 1, c, t, o);
- for (video::S3DVertex &vertex : vertices) {
- // Switch from -Z (south) to -X (west)
- vertex.Pos.rotateXZBy(-90);
- vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
- }
- driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
-
- d = moonsize * 1.3;
- c = mooncolor;
- c.setAlpha(0.15 * 255);
- vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, c, t, t);
- vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, c, o, t);
- vertices[2] = video::S3DVertex( d, d, -1, 0, 0, 1, c, o, o);
- vertices[3] = video::S3DVertex(-d, d, -1, 0, 0, 1, c, t, o);
- for (video::S3DVertex &vertex : vertices) {
- // Switch from -Z (south) to -X (west)
- vertex.Pos.rotateXZBy(-90);
- vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
- }
- driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
-
- d = moonsize;
- vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, mooncolor, t, t);
- vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, mooncolor, o, t);
- vertices[2] = video::S3DVertex( d, d, -1, 0, 0, 1, mooncolor, o, o);
- vertices[3] = video::S3DVertex(-d, d, -1, 0, 0, 1, mooncolor, t, o);
- for (video::S3DVertex &vertex : vertices) {
- // Switch from -Z (south) to -X (west)
- vertex.Pos.rotateXZBy(-90);
- vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
- }
- driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
-
- float d2 = moonsize * 0.6;
- vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, mooncolor2, t, t);
- vertices[1] = video::S3DVertex( d2,-d, -1, 0, 0, 1, mooncolor2, o, t);
- vertices[2] = video::S3DVertex( d2, d2, -1, 0, 0, 1, mooncolor2, o, o);
- vertices[3] = video::S3DVertex(-d, d2, -1, 0, 0, 1, mooncolor2, t, o);
- for (video::S3DVertex &vertex : vertices) {
- // Switch from -Z (south) to -X (west)
- vertex.Pos.rotateXZBy(-90);
- vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
- }
- driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
- } else {
- driver->setMaterial(m_materials[4]);
- float d = moonsize * 1.9;
- video::SColor c;
- if (m_moon_tonemap)
- c = video::SColor (0, 0, 0, 0);
- else
- c = video::SColor (255, 255, 255, 255);
- vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, c, t, t);
- vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, c, o, t);
- vertices[2] = video::S3DVertex( d, d, -1, 0, 0, 1, c, o, o);
- vertices[3] = video::S3DVertex(-d, d, -1, 0, 0, 1, c, t, o);
- for (video::S3DVertex &vertex : vertices) {
- // Switch from -Z (south) to -X (west)
- vertex.Pos.rotateXZBy(-90);
- vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
- }
- driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
- }
- }
-
- // Draw far cloudy fog thing below east and west horizons
- for (u32 j = 0; j < 2; 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.02, -1, 0, 0, 1, c, o, o);
- vertices[3] = video::S3DVertex(-1, -0.02, -1, 0, 0, 1, c, t, o);
- for (video::S3DVertex &vertex : vertices) {
- //if (wicked_time_of_day < 0.5)
- if (j == 0)
- // Switch from -Z (south) to +X (east)
- vertex.Pos.rotateXZBy(90);
- else
- // Switch from -Z (south) to -X (west)
- vertex.Pos.rotateXZBy(-90);
- }
- driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
- }
- }
-}
-
-
-void Sky::update(float time_of_day, float time_brightness,
- float direct_brightness, bool sunlight_seen,
- CameraMode cam_mode, float yaw, float pitch)
-{
- // Stabilize initial brightness and color values by flooding updates
- if (m_first_update) {
- /*dstream<<"First update with time_of_day="<<time_of_day
- <<" time_brightness="<<time_brightness
- <<" direct_brightness="<<direct_brightness
- <<" sunlight_seen="<<sunlight_seen<<std::endl;*/
- m_first_update = false;
- for (u32 i = 0; i < 100; i++) {
- update(time_of_day, time_brightness, direct_brightness,
- sunlight_seen, cam_mode, yaw, pitch);
- }
- return;
- }
-
- m_time_of_day = time_of_day;
- m_time_brightness = time_brightness;
- m_sunlight_seen = sunlight_seen;
- m_bodies_visible = true;
-
- bool is_dawn = (time_brightness >= 0.20 && time_brightness < 0.35);
-
- /*
- Development colours
-
- video::SColorf bgcolor_bright_normal_f(170. / 255, 200. / 255, 230. / 255, 1.0);
- video::SColorf bgcolor_bright_dawn_f(0.666, 200. / 255 * 0.7, 230. / 255 * 0.5, 1.0);
- video::SColorf bgcolor_bright_dawn_f(0.666, 0.549, 0.220, 1.0);
- video::SColorf bgcolor_bright_dawn_f(0.666 * 1.2, 0.549 * 1.0, 0.220 * 1.0, 1.0);
- video::SColorf bgcolor_bright_dawn_f(0.666 * 1.2, 0.549 * 1.0, 0.220 * 1.2, 1.0);
-
- video::SColorf cloudcolor_bright_dawn_f(1.0, 0.591, 0.4);
- video::SColorf cloudcolor_bright_dawn_f(1.0, 0.65, 0.44);
- video::SColorf cloudcolor_bright_dawn_f(1.0, 0.7, 0.5);
- */
-
- video::SColorf bgcolor_bright_normal_f = video::SColor(255, 155, 193, 240);
- video::SColorf bgcolor_bright_indoor_f = video::SColor(255, 100, 100, 100);
- video::SColorf bgcolor_bright_dawn_f = video::SColor(255, 186, 193, 240);
- video::SColorf bgcolor_bright_night_f = video::SColor(255, 64, 144, 255);
-
- video::SColorf skycolor_bright_normal_f = video::SColor(255, 140, 186, 250);
- video::SColorf skycolor_bright_dawn_f = video::SColor(255, 180, 186, 250);
- video::SColorf skycolor_bright_night_f = video::SColor(255, 0, 107, 255);
-
- // pure white: becomes "diffuse light component" for clouds
- video::SColorf cloudcolor_bright_normal_f = video::SColor(255, 255, 255, 255);
- // dawn-factoring version of pure white (note: R is above 1.0)
- video::SColorf cloudcolor_bright_dawn_f(255.0f/240.0f, 223.0f/240.0f, 191.0f/255.0f);
-
- float cloud_color_change_fraction = 0.95;
- if (sunlight_seen) {
- if (std::fabs(time_brightness - m_brightness) < 0.2f) {
- m_brightness = m_brightness * 0.95 + time_brightness * 0.05;
- } else {
- m_brightness = m_brightness * 0.80 + time_brightness * 0.20;
- cloud_color_change_fraction = 0.0;
- }
- } else {
- if (direct_brightness < m_brightness)
- m_brightness = m_brightness * 0.95 + direct_brightness * 0.05;
- else
- m_brightness = m_brightness * 0.98 + direct_brightness * 0.02;
- }
-
- m_clouds_visible = true;
- float color_change_fraction = 0.98f;
- if (sunlight_seen) {
- if (is_dawn) { // Dawn
- m_bgcolor_bright_f = m_bgcolor_bright_f.getInterpolated(
- bgcolor_bright_dawn_f, color_change_fraction);
- m_skycolor_bright_f = m_skycolor_bright_f.getInterpolated(
- skycolor_bright_dawn_f, color_change_fraction);
- m_cloudcolor_bright_f = m_cloudcolor_bright_f.getInterpolated(
- cloudcolor_bright_dawn_f, color_change_fraction);
- } else {
- if (time_brightness < 0.13f) { // Night
- m_bgcolor_bright_f = m_bgcolor_bright_f.getInterpolated(
- bgcolor_bright_night_f, color_change_fraction);
- m_skycolor_bright_f = m_skycolor_bright_f.getInterpolated(
- skycolor_bright_night_f, color_change_fraction);
- } else { // Day
- m_bgcolor_bright_f = m_bgcolor_bright_f.getInterpolated(
- bgcolor_bright_normal_f, color_change_fraction);
- m_skycolor_bright_f = m_skycolor_bright_f.getInterpolated(
- skycolor_bright_normal_f, color_change_fraction);
- }
-
- m_cloudcolor_bright_f = m_cloudcolor_bright_f.getInterpolated(
- cloudcolor_bright_normal_f, color_change_fraction);
- }
- } else {
- m_bgcolor_bright_f = m_bgcolor_bright_f.getInterpolated(
- bgcolor_bright_indoor_f, color_change_fraction);
- m_skycolor_bright_f = m_skycolor_bright_f.getInterpolated(
- bgcolor_bright_indoor_f, color_change_fraction);
- m_cloudcolor_bright_f = m_cloudcolor_bright_f.getInterpolated(
- cloudcolor_bright_normal_f, color_change_fraction);
- m_clouds_visible = false;
- }
-
- video::SColor bgcolor_bright = m_bgcolor_bright_f.toSColor();
- m_bgcolor = video::SColor(
- 255,
- bgcolor_bright.getRed() * m_brightness,
- bgcolor_bright.getGreen() * m_brightness,
- bgcolor_bright.getBlue() * m_brightness
- );
-
- video::SColor skycolor_bright = m_skycolor_bright_f.toSColor();
- m_skycolor = video::SColor(
- 255,
- skycolor_bright.getRed() * m_brightness,
- skycolor_bright.getGreen() * m_brightness,
- skycolor_bright.getBlue() * m_brightness
- );
-
- // Horizon coloring based on sun and moon direction during sunset and sunrise
- video::SColor pointcolor = video::SColor(m_bgcolor.getAlpha(), 255, 255, 255);
- if (m_directional_colored_fog) {
- if (m_horizon_blend() != 0) {
- // Calculate hemisphere value from yaw, (inverted in third person front view)
- s8 dir_factor = 1;
- if (cam_mode > 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);
- }
-}
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include <ISceneNode.h>
-#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;
-};
#include <string>
#include "exceptions.h"
-#include "keycode.h"
+#include "client/keycode.h"
class TestKeycode : public TestBase {
public:
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2014 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "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 <map>
-#include <IMeshManipulator.h>
-
-#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<u32> 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<int, scene::IMesh*>::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<int, scene::IMesh*> 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<u32> 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<u32>(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<ItemPartColor> *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<u32> 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<ItemPartColor> *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;
- }
- }
- }
-}
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2014 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include <string>
-#include <vector>
-#include "irrlichttypes_extrabloated.h"
-
-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<ItemPartColor> 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<ItemPartColor> 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<ItemPartColor> *colors, bool apply_scale = false);
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