mirror of
https://github.com/luanti-org/luanti.git
synced 2025-11-29 12:15:18 +01:00
Move client-specific files to 'src/client' (#7902)
Update Android.mk Remove 'src/client' from include_directories
This commit is contained in:
committed by
SmallJoker
parent
ddd9317b73
commit
5f1cd555cd
@@ -25,12 +25,37 @@ set(client_SRCS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/render/plain.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/render/sidebyside.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/render/stereo.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/renderingengine.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/camera.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/client.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/clientenvironment.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/clientlauncher.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/clientmap.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/clientmedia.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/clientobject.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/clouds.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/content_cao.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/content_cso.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/content_mapblock.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/filecache.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/fontengine.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/game.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/gameui.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/inputhandler.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tile.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/joystick_controller.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}/joystick_controller.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
|
||||
)
|
||||
|
||||
658
src/client/camera.cpp
Normal file
658
src/client/camera.cpp
Normal file
@@ -0,0 +1,658 @@
|
||||
/*
|
||||
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;
|
||||
}
|
||||
231
src/client/camera.h
Normal file
231
src/client/camera.h
Normal file
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
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;
|
||||
};
|
||||
1970
src/client/client.cpp
Normal file
1970
src/client/client.cpp
Normal file
File diff suppressed because it is too large
Load Diff
608
src/client/client.h
Normal file
608
src/client/client.h
Normal file
@@ -0,0 +1,608 @@
|
||||
/*
|
||||
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;
|
||||
};
|
||||
540
src/client/clientenvironment.cpp
Normal file
540
src/client/clientenvironment.cpp
Normal file
@@ -0,0 +1,540 @@
|
||||
/*
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
151
src/client/clientenvironment.h
Normal file
151
src/client/clientenvironment.h
Normal file
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
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;
|
||||
};
|
||||
671
src/client/clientmap.cpp
Normal file
671
src/client/clientmap.cpp
Normal file
@@ -0,0 +1,671 @@
|
||||
/*
|
||||
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: ";
|
||||
}
|
||||
|
||||
|
||||
138
src/client/clientmap.h
Normal file
138
src/client/clientmap.h
Normal file
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
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;
|
||||
};
|
||||
639
src/client/clientmedia.cpp
Normal file
639
src/client/clientmedia.cpp
Normal file
@@ -0,0 +1,639 @@
|
||||
/*
|
||||
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));
|
||||
}
|
||||
}
|
||||
148
src/client/clientmedia.h
Normal file
148
src/client/clientmedia.h
Normal file
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
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 = "";
|
||||
|
||||
};
|
||||
66
src/client/clientobject.cpp
Normal file
66
src/client/clientobject.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
108
src/client/clientobject.h
Normal file
108
src/client/clientobject.h
Normal file
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
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;
|
||||
}
|
||||
};
|
||||
386
src/client/clouds.cpp
Normal file
386
src/client/clouds.cpp
Normal file
@@ -0,0 +1,386 @@
|
||||
/*
|
||||
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);
|
||||
}
|
||||
144
src/client/clouds.h
Normal file
144
src/client/clouds.h
Normal file
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
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;
|
||||
|
||||
};
|
||||
1611
src/client/content_cao.cpp
Normal file
1611
src/client/content_cao.cpp
Normal file
File diff suppressed because it is too large
Load Diff
236
src/client/content_cao.h
Normal file
236
src/client/content_cao.h
Normal file
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
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;
|
||||
}
|
||||
};
|
||||
77
src/client/content_cso.cpp
Normal file
77
src/client/content_cso.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
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);
|
||||
}
|
||||
|
||||
26
src/client/content_cso.h
Normal file
26
src/client/content_cso.h
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
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);
|
||||
1430
src/client/content_mapblock.cpp
Normal file
1430
src/client/content_mapblock.cpp
Normal file
File diff suppressed because it is too large
Load Diff
178
src/client/content_mapblock.h
Normal file
178
src/client/content_mapblock.h
Normal file
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
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);
|
||||
};
|
||||
89
src/client/filecache.cpp
Normal file
89
src/client/filecache.cpp
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
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);
|
||||
}
|
||||
42
src/client/filecache.h
Normal file
42
src/client/filecache.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
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);
|
||||
};
|
||||
505
src/client/fontengine.cpp
Normal file
505
src/client/fontengine.cpp
Normal file
@@ -0,0 +1,505 @@
|
||||
/*
|
||||
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;
|
||||
}
|
||||
}
|
||||
128
src/client/fontengine.h
Normal file
128
src/client/fontengine.h
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
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;
|
||||
4218
src/client/game.cpp
Normal file
4218
src/client/game.cpp
Normal file
File diff suppressed because it is too large
Load Diff
56
src/client/game.h
Normal file
56
src/client/game.h
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
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);
|
||||
169
src/client/guiscalingfilter.cpp
Normal file
169
src/client/guiscalingfilter.cpp
Normal file
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
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);
|
||||
}
|
||||
50
src/client/guiscalingfilter.h
Normal file
50
src/client/guiscalingfilter.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
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);
|
||||
172
src/client/imagefilters.cpp
Normal file
172
src/client/imagefilters.cpp
Normal file
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
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);
|
||||
}
|
||||
}
|
||||
43
src/client/imagefilters.h
Normal file
43
src/client/imagefilters.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
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);
|
||||
384
src/client/keycode.cpp
Normal file
384
src/client/keycode.cpp
Normal file
@@ -0,0 +1,384 @@
|
||||
/*
|
||||
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;
|
||||
}
|
||||
67
src/client/keycode.h
Normal file
67
src/client/keycode.h
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
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);
|
||||
1158
src/client/localplayer.cpp
Normal file
1158
src/client/localplayer.cpp
Normal file
File diff suppressed because it is too large
Load Diff
198
src/client/localplayer.h
Normal file
198
src/client/localplayer.h
Normal file
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
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;
|
||||
};
|
||||
1389
src/client/mapblock_mesh.cpp
Normal file
1389
src/client/mapblock_mesh.cpp
Normal file
File diff suppressed because it is too large
Load Diff
228
src/client/mapblock_mesh.h
Normal file
228
src/client/mapblock_mesh.h
Normal file
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
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);
|
||||
1135
src/client/mesh.cpp
Normal file
1135
src/client/mesh.cpp
Normal file
File diff suppressed because it is too large
Load Diff
129
src/client/mesh.h
Normal file
129
src/client/mesh.h
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
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);
|
||||
308
src/client/mesh_generator_thread.cpp
Normal file
308
src/client/mesh_generator_thread.cpp
Normal file
@@ -0,0 +1,308 @@
|
||||
/*
|
||||
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;
|
||||
}
|
||||
}
|
||||
131
src/client/mesh_generator_thread.h
Normal file
131
src/client/mesh_generator_thread.h
Normal file
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
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();
|
||||
};
|
||||
@@ -20,7 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
#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)
|
||||
|
||||
624
src/client/minimap.cpp
Normal file
624
src/client/minimap.cpp
Normal file
@@ -0,0 +1,624 @@
|
||||
/*
|
||||
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;
|
||||
}
|
||||
}
|
||||
163
src/client/minimap.h
Normal file
163
src/client/minimap.h
Normal file
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
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;
|
||||
};
|
||||
684
src/client/particles.cpp
Normal file
684
src/client/particles.cpp
Normal file
@@ -0,0 +1,684 @@
|
||||
/*
|
||||
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);
|
||||
}
|
||||
223
src/client/particles.h
Normal file
223
src/client/particles.h
Normal file
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
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;
|
||||
};
|
||||
@@ -19,11 +19,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
*/
|
||||
|
||||
#include "core.h"
|
||||
#include "camera.h"
|
||||
#include "client.h"
|
||||
#include "clientmap.h"
|
||||
#include "hud.h"
|
||||
#include "minimap.h"
|
||||
#include "client/camera.h"
|
||||
#include "client/client.h"
|
||||
#include "client/clientmap.h"
|
||||
#include "client/hud.h"
|
||||
#include "client/minimap.h"
|
||||
|
||||
RenderingCore::RenderingCore(IrrlichtDevice *_device, Client *_client, Hud *_hud)
|
||||
: device(_device), driver(device->getVideoDriver()), smgr(device->getSceneManager()),
|
||||
|
||||
@@ -19,8 +19,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
*/
|
||||
|
||||
#include "interlaced.h"
|
||||
#include "client.h"
|
||||
#include "shader.h"
|
||||
#include "client/client.h"
|
||||
#include "client/shader.h"
|
||||
#include "client/tile.h"
|
||||
|
||||
RenderingCoreInterlaced::RenderingCoreInterlaced(
|
||||
|
||||
@@ -19,7 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
*/
|
||||
|
||||
#include "stereo.h"
|
||||
#include "camera.h"
|
||||
#include "client/camera.h"
|
||||
#include "constants.h"
|
||||
#include "settings.h"
|
||||
|
||||
|
||||
873
src/client/shader.cpp
Normal file
873
src/client/shader.cpp
Normal file
@@ -0,0 +1,873 @@
|
||||
/*
|
||||
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;
|
||||
}
|
||||
156
src/client/shader.h
Normal file
156
src/client/shader.h
Normal file
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
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);
|
||||
755
src/client/sky.cpp
Normal file
755
src/client/sky.cpp
Normal file
@@ -0,0 +1,755 @@
|
||||
/*
|
||||
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);
|
||||
}
|
||||
}
|
||||
148
src/client/sky.h
Normal file
148
src/client/sky.h
Normal file
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
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;
|
||||
};
|
||||
685
src/client/wieldmesh.cpp
Normal file
685
src/client/wieldmesh.cpp
Normal file
@@ -0,0 +1,685 @@
|
||||
/*
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
140
src/client/wieldmesh.h
Normal file
140
src/client/wieldmesh.h
Normal file
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
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);
|
||||
Reference in New Issue
Block a user