Initial commit

This commit is contained in:
2022-10-08 17:16:13 -04:00
commit 385638c5e1
1925 changed files with 872504 additions and 0 deletions

910
src/CMakeLists.txt Normal file
View File

@@ -0,0 +1,910 @@
project(minetest)
INCLUDE(CheckIncludeFiles)
INCLUDE(CheckLibraryExists)
# Add custom SemiDebug build mode
set(CMAKE_CXX_FLAGS_SEMIDEBUG "-O1 -g -Wall" CACHE STRING
"Flags used by the C++ compiler during semidebug builds."
FORCE
)
set(CMAKE_C_FLAGS_SEMIDEBUG "-O1 -g -Wall -pedantic" CACHE STRING
"Flags used by the C compiler during semidebug builds."
FORCE
)
mark_as_advanced(
CMAKE_CXX_FLAGS_SEMIDEBUG
CMAKE_C_FLAGS_SEMIDEBUG
)
set(CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}" CACHE STRING
"Choose the type of build. Options are: None Debug SemiDebug RelWithDebInfo MinSizeRel."
FORCE
)
# Set some random things default to not being visible in the GUI
mark_as_advanced(EXECUTABLE_OUTPUT_PATH LIBRARY_OUTPUT_PATH)
if(NOT (BUILD_CLIENT OR BUILD_SERVER))
message(WARNING "Neither BUILD_CLIENT nor BUILD_SERVER is set! Setting BUILD_SERVER=true")
set(BUILD_SERVER TRUE)
endif()
option(ENABLE_CURL "Enable cURL support for fetching media" TRUE)
set(USE_CURL FALSE)
if(ENABLE_CURL)
find_package(CURL)
if (CURL_FOUND)
message(STATUS "cURL support enabled.")
set(USE_CURL TRUE)
endif()
else()
mark_as_advanced(CLEAR CURL_LIBRARY CURL_INCLUDE_DIR)
endif()
if(NOT USE_CURL)
if(BUILD_CLIENT)
message(WARNING "cURL is required to load the server list")
endif()
if(BUILD_SERVER)
message(WARNING "cURL is required to announce to the server list")
endif()
endif()
option(ENABLE_GETTEXT "Use GetText for internationalization" ${BUILD_CLIENT})
set(USE_GETTEXT FALSE)
if(ENABLE_GETTEXT)
find_package(GettextLib)
if(GETTEXTLIB_FOUND)
if(WIN32)
message(STATUS "GetText library: ${GETTEXT_LIBRARY}")
message(STATUS "GetText DLL(s): ${GETTEXT_DLL}")
endif()
set(USE_GETTEXT TRUE)
message(STATUS "GetText enabled; locales found: ${GETTEXT_AVAILABLE_LOCALES}")
endif(GETTEXTLIB_FOUND)
else()
mark_as_advanced(GETTEXT_INCLUDE_DIR GETTEXT_LIBRARY GETTEXT_MSGFMT)
message(STATUS "GetText disabled.")
endif()
option(ENABLE_SOUND "Enable sound" TRUE)
set(USE_SOUND FALSE)
if(BUILD_CLIENT AND ENABLE_SOUND)
# Sound libraries
find_package(OpenAL)
find_package(Vorbis)
if(NOT OPENAL_FOUND)
message(STATUS "Sound enabled, but OpenAL not found!")
mark_as_advanced(CLEAR OPENAL_LIBRARY OPENAL_INCLUDE_DIR)
endif()
if(NOT VORBIS_FOUND)
message(STATUS "Sound enabled, but Vorbis libraries not found!")
mark_as_advanced(CLEAR OGG_INCLUDE_DIR VORBIS_INCLUDE_DIR OGG_LIBRARY VORBIS_LIBRARY VORBISFILE_LIBRARY)
endif()
if(OPENAL_FOUND AND VORBIS_FOUND)
set(USE_SOUND TRUE)
message(STATUS "Sound enabled.")
else()
message(FATAL_ERROR "Sound enabled, but cannot be used.\n"
"To continue, either fill in the required paths or disable sound. (-DENABLE_SOUND=0)")
endif()
endif()
# TODO: this should be removed one day, we can enable it unconditionally
option(ENABLE_GLES "Enable extra support code for OpenGL ES" FALSE)
mark_as_advanced(ENABLE_GLES)
option(ENABLE_TOUCH "Enable Touchscreen support" FALSE)
if(ENABLE_TOUCH)
add_definitions(-DHAVE_TOUCHSCREENGUI)
endif()
if(BUILD_CLIENT)
find_package(Freetype REQUIRED)
endif()
option(ENABLE_CURSES "Enable ncurses console" TRUE)
set(USE_CURSES FALSE)
if(ENABLE_CURSES)
find_package(Ncursesw)
if(CURSES_FOUND)
set(USE_CURSES TRUE)
message(STATUS "ncurses console enabled.")
include_directories(${CURSES_INCLUDE_DIRS})
else()
message(STATUS "ncurses not found!")
endif()
endif(ENABLE_CURSES)
option(ENABLE_POSTGRESQL "Enable PostgreSQL backend" TRUE)
set(USE_POSTGRESQL FALSE)
if(ENABLE_POSTGRESQL)
if(CMAKE_VERSION VERSION_LESS "3.20")
find_package(PostgreSQL QUIET)
# Before CMake 3.20 FindPostgreSQL.cmake always looked for server includes
# but we don't need them, so continue anyway if only those are missing.
if(PostgreSQL_INCLUDE_DIR AND PostgreSQL_LIBRARY)
set(PostgreSQL_FOUND TRUE)
set(PostgreSQL_INCLUDE_DIRS ${PostgreSQL_INCLUDE_DIR})
set(PostgreSQL_LIBRARIES ${PostgreSQL_LIBRARY})
endif()
else()
find_package(PostgreSQL)
endif()
if(PostgreSQL_FOUND)
set(USE_POSTGRESQL TRUE)
message(STATUS "PostgreSQL backend enabled")
# This variable is case sensitive, don't try to change it to POSTGRESQL_INCLUDE_DIR
message(STATUS "PostgreSQL includes: ${PostgreSQL_INCLUDE_DIRS}")
include_directories(${PostgreSQL_INCLUDE_DIRS})
else()
message(STATUS "PostgreSQL not found!")
endif()
endif(ENABLE_POSTGRESQL)
option(ENABLE_LEVELDB "Enable LevelDB backend" TRUE)
set(USE_LEVELDB FALSE)
if(ENABLE_LEVELDB)
find_library(LEVELDB_LIBRARY NAMES leveldb libleveldb)
find_path(LEVELDB_INCLUDE_DIR db.h PATH_SUFFIXES leveldb)
if(LEVELDB_LIBRARY AND LEVELDB_INCLUDE_DIR)
set(USE_LEVELDB TRUE)
message(STATUS "LevelDB backend enabled.")
include_directories(${LEVELDB_INCLUDE_DIR})
else()
message(STATUS "LevelDB not found!")
endif()
endif(ENABLE_LEVELDB)
OPTION(ENABLE_REDIS "Enable Redis backend" TRUE)
set(USE_REDIS FALSE)
if(ENABLE_REDIS)
find_library(REDIS_LIBRARY hiredis)
find_path(REDIS_INCLUDE_DIR hiredis.h PATH_SUFFIXES hiredis)
if(REDIS_LIBRARY AND REDIS_INCLUDE_DIR)
set(USE_REDIS TRUE)
message(STATUS "Redis backend enabled.")
include_directories(${REDIS_INCLUDE_DIR})
else(REDIS_LIBRARY AND REDIS_INCLUDE_DIR)
message(STATUS "Redis not found!")
endif(REDIS_LIBRARY AND REDIS_INCLUDE_DIR)
endif(ENABLE_REDIS)
find_package(SQLite3 REQUIRED)
OPTION(ENABLE_PROMETHEUS "Enable prometheus client support" FALSE)
set(USE_PROMETHEUS FALSE)
if(ENABLE_PROMETHEUS)
find_path(PROMETHEUS_CPP_INCLUDE_DIR NAMES prometheus/counter.h)
find_library(PROMETHEUS_PULL_LIBRARY NAMES prometheus-cpp-pull)
find_library(PROMETHEUS_CORE_LIBRARY NAMES prometheus-cpp-core)
if(PROMETHEUS_CPP_INCLUDE_DIR AND PROMETHEUS_PULL_LIBRARY AND PROMETHEUS_CORE_LIBRARY)
set(PROMETHEUS_LIBRARIES ${PROMETHEUS_PULL_LIBRARY} ${PROMETHEUS_CORE_LIBRARY})
set(USE_PROMETHEUS TRUE)
include_directories(${PROMETHEUS_CPP_INCLUDE_DIR})
endif(PROMETHEUS_CPP_INCLUDE_DIR AND PROMETHEUS_PULL_LIBRARY AND PROMETHEUS_CORE_LIBRARY)
endif(ENABLE_PROMETHEUS)
if(USE_PROMETHEUS)
message(STATUS "Prometheus client enabled.")
else(USE_PROMETHEUS)
message(STATUS "Prometheus client disabled.")
endif(USE_PROMETHEUS)
OPTION(ENABLE_SPATIAL "Enable SpatialIndex AreaStore backend" TRUE)
set(USE_SPATIAL FALSE)
if(ENABLE_SPATIAL)
find_library(SPATIAL_LIBRARY spatialindex)
find_path(SPATIAL_INCLUDE_DIR spatialindex/SpatialIndex.h)
if(SPATIAL_LIBRARY AND SPATIAL_INCLUDE_DIR)
set(USE_SPATIAL TRUE)
message(STATUS "SpatialIndex AreaStore backend enabled.")
include_directories(${SPATIAL_INCLUDE_DIR})
else(SPATIAL_LIBRARY AND SPATIAL_INCLUDE_DIR)
message(STATUS "SpatialIndex not found!")
endif(SPATIAL_LIBRARY AND SPATIAL_INCLUDE_DIR)
endif(ENABLE_SPATIAL)
find_package(ZLIB REQUIRED)
find_package(Zstd REQUIRED)
if(NOT MSVC)
set(USE_GPROF FALSE CACHE BOOL "Use -pg flag for g++")
endif()
# Haiku endian support
if(HAIKU)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_BSD_SOURCE")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_BSD_SOURCE")
endif()
# Use cmake_config.h
add_definitions(-DUSE_CMAKE_CONFIG_H)
if(WIN32)
# Windows
if(MSVC) # MSVC Specifics
set(PLATFORM_LIBS dbghelp.lib ${PLATFORM_LIBS})
# Surpress some useless warnings
add_definitions ( /D "_CRT_SECURE_NO_DEPRECATE" /W1 )
# Get M_PI to work
add_definitions(/D "_USE_MATH_DEFINES")
# Dont define min/max macros in minwindef.h
add_definitions(/D "NOMINMAX")
else() # Probably MinGW = GCC
set(PLATFORM_LIBS "")
endif()
set(PLATFORM_LIBS ws2_32.lib version.lib shlwapi.lib winmm.lib ${PLATFORM_LIBS})
set(EXTRA_DLL "" CACHE FILEPATH "Optional paths to additional DLLs that should be packaged")
# DLLs are automatically copied to the output directory by vcpkg when VCPKG_APPLOCAL_DEPS=ON
if(NOT VCPKG_APPLOCAL_DEPS)
set(ZLIB_DLL "" CACHE FILEPATH "Path to Zlib DLL for installation (optional)")
set(ZSTD_DLL "" CACHE FILEPATH "Path to Zstd DLL for installation (optional)")
if(ENABLE_SOUND)
set(OPENAL_DLL "" CACHE FILEPATH "Path to OpenAL32.dll for installation (optional)")
set(OGG_DLL "" CACHE FILEPATH "Path to libogg.dll for installation (optional)")
set(VORBIS_DLL "" CACHE FILEPATH "Path to Vorbis DLLs for installation (optional)")
endif()
if(USE_GETTEXT)
set(GETTEXT_DLL "" CACHE FILEPATH "Path to Intl/Iconv DLLs for installation (optional)")
endif()
if(USE_LUAJIT)
set(LUA_DLL "" CACHE FILEPATH "Path to luajit-5.1.dll for installation (optional)")
endif()
endif()
else()
# Unix probably
if(BUILD_CLIENT)
if(NOT HAIKU AND NOT APPLE)
find_package(X11 REQUIRED)
endif(NOT HAIKU AND NOT APPLE)
endif()
set(PLATFORM_LIBS -lpthread ${CMAKE_DL_LIBS})
if(APPLE)
set(PLATFORM_LIBS "-framework CoreFoundation" ${PLATFORM_LIBS})
else()
check_library_exists(rt clock_gettime "" HAVE_LIBRT)
if (HAVE_LIBRT)
set(PLATFORM_LIBS -lrt ${PLATFORM_LIBS})
endif(HAVE_LIBRT)
endif(APPLE)
# Prefer local iconv if installed
find_library(ICONV_LIBRARY iconv)
mark_as_advanced(ICONV_LIBRARY)
if (ICONV_LIBRARY)
set(PLATFORM_LIBS ${PLATFORM_LIBS} ${ICONV_LIBRARY})
endif()
if (HAIKU)
set(PLATFORM_LIBS ${PLATFORM_LIBS} intl network)
endif()
endif()
check_include_files(endian.h HAVE_ENDIAN_H)
configure_file(
"${PROJECT_SOURCE_DIR}/cmake_config.h.in"
"${PROJECT_BINARY_DIR}/cmake_config.h"
)
# Add a target that always rebuilds cmake_config_githash.h
add_custom_target(GenerateVersion
COMMAND ${CMAKE_COMMAND}
-D "GENERATE_VERSION_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}"
-D "GENERATE_VERSION_BINARY_DIR=${CMAKE_CURRENT_BINARY_DIR}"
-D "VERSION_STRING=${VERSION_STRING}"
-D "DEVELOPMENT_BUILD=${DEVELOPMENT_BUILD}"
-P "${CMAKE_SOURCE_DIR}/cmake/Modules/GenerateVersion.cmake"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
add_subdirectory(threading)
add_subdirectory(content)
add_subdirectory(database)
add_subdirectory(gui)
add_subdirectory(mapgen)
add_subdirectory(network)
add_subdirectory(script)
add_subdirectory(unittest)
add_subdirectory(benchmark)
add_subdirectory(util)
add_subdirectory(irrlicht_changes)
add_subdirectory(server)
set(common_SRCS
${database_SRCS}
${mapgen_SRCS}
${server_SRCS}
${content_SRCS}
ban.cpp
chat.cpp
clientiface.cpp
collision.cpp
content_mapnode.cpp
content_nodemeta.cpp
convert_json.cpp
craftdef.cpp
debug.cpp
defaultsettings.cpp
emerge.cpp
environment.cpp
face_position_cache.cpp
filesys.cpp
gettext.cpp
httpfetch.cpp
hud.cpp
inventory.cpp
inventorymanager.cpp
itemdef.cpp
itemstackmetadata.cpp
light.cpp
log.cpp
main.cpp
map.cpp
map_settings_manager.cpp
mapblock.cpp
mapnode.cpp
mapsector.cpp
metadata.cpp
modchannels.cpp
nameidmapping.cpp
nodedef.cpp
nodemetadata.cpp
nodetimer.cpp
noise.cpp
objdef.cpp
object_properties.cpp
particles.cpp
pathfinder.cpp
player.cpp
porting.cpp
profiler.cpp
raycast.cpp
reflowscan.cpp
remoteplayer.cpp
rollback.cpp
rollback_interface.cpp
serialization.cpp
server.cpp
serverenvironment.cpp
serverlist.cpp
settings.cpp
staticobject.cpp
terminal_chat_console.cpp
texture_override.cpp
tileanimation.cpp
tool.cpp
translation.cpp
version.cpp
voxel.cpp
voxelalgorithms.cpp
hud.cpp
${common_network_SRCS}
${JTHREAD_SRCS}
${common_SCRIPT_SRCS}
${UTIL_SRCS}
)
if(BUILD_UNITTESTS)
set(common_SRCS ${common_SRCS} ${UNITTEST_SRCS})
endif()
if(BUILD_BENCHMARKS)
set(common_SRCS ${common_SRCS} ${BENCHMARK_SRCS})
endif()
# This gives us the icon and file version information
if(WIN32)
set(WINRESOURCE_FILE "${CMAKE_CURRENT_SOURCE_DIR}/../misc/winresource.rc")
set(MINETEST_EXE_MANIFEST_FILE "${CMAKE_CURRENT_SOURCE_DIR}/../misc/minetest.exe.manifest")
if(MINGW)
if(NOT CMAKE_RC_COMPILER)
set(CMAKE_RC_COMPILER "windres.exe")
endif()
ADD_CUSTOM_COMMAND(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/winresource_rc.o
COMMAND ${CMAKE_RC_COMPILER} -I${CMAKE_CURRENT_SOURCE_DIR} -I${CMAKE_CURRENT_BINARY_DIR}
-i${WINRESOURCE_FILE}
-o ${CMAKE_CURRENT_BINARY_DIR}/winresource_rc.o
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
DEPENDS ${WINRESOURCE_FILE} ${MINETEST_EXE_MANIFEST_FILE})
SET(extra_windows_SRCS ${CMAKE_CURRENT_BINARY_DIR}/winresource_rc.o)
else(MINGW) # Probably MSVC
set(extra_windows_SRCS ${WINRESOURCE_FILE} ${MINETEST_EXE_MANIFEST_FILE})
endif(MINGW)
endif()
# Client sources
if (BUILD_CLIENT)
add_subdirectory(client)
endif(BUILD_CLIENT)
set(client_SRCS
${client_SRCS}
${common_SRCS}
${gui_SRCS}
${client_network_SRCS}
${client_irrlicht_changes_SRCS}
${client_SCRIPT_SRCS}
)
if(BUILD_UNITTESTS)
set(client_SRCS ${client_SRCS} ${UNITTEST_CLIENT_SRCS})
endif()
if(BUILD_BENCHMARKS)
set(client_SRCS ${client_SRCS} ${BENCHMARK_CLIENT_SRCS})
endif()
list(SORT client_SRCS)
# Server sources
set(server_SRCS
${common_SRCS}
)
list(SORT server_SRCS)
# Avoid source_group on broken CMake version.
# see issue #7074 #7075
if (CMAKE_VERSION VERSION_GREATER 3.8.1)
source_group(TREE ${PROJECT_SOURCE_DIR} PREFIX "Source Files" FILES ${client_SRCS})
source_group(TREE ${PROJECT_SOURCE_DIR} PREFIX "Source Files" FILES ${server_SRCS})
endif()
include_directories(
${PROJECT_BINARY_DIR}
${PROJECT_SOURCE_DIR}
${PROJECT_SOURCE_DIR}/script
)
include_directories(SYSTEM
${ZLIB_INCLUDE_DIR}
${ZSTD_INCLUDE_DIR}
${SQLITE3_INCLUDE_DIR}
${LUA_INCLUDE_DIR}
${GMP_INCLUDE_DIR}
${JSON_INCLUDE_DIR}
${LUA_BIT_INCLUDE_DIR}
)
if(USE_GETTEXT)
include_directories(${GETTEXT_INCLUDE_DIR})
endif()
if(BUILD_CLIENT)
include_directories(SYSTEM
${FREETYPE_INCLUDE_DIRS}
${SOUND_INCLUDE_DIRS}
${X11_INCLUDE_DIR}
)
endif()
if(USE_CURL)
include_directories(${CURL_INCLUDE_DIR})
endif()
# When cross-compiling assume the user doesn't want to run the executable anyway,
# otherwise place it in <source dir>/bin/ since Minetest can only run from there.
if(NOT CMAKE_CROSSCOMPILING)
set(EXECUTABLE_OUTPUT_PATH "${CMAKE_SOURCE_DIR}/bin")
endif()
if(BUILD_CLIENT)
add_executable(${PROJECT_NAME} ${client_SRCS} ${extra_windows_SRCS})
add_dependencies(${PROJECT_NAME} GenerateVersion)
target_link_libraries(
${PROJECT_NAME}
${ZLIB_LIBRARIES}
IrrlichtMt::IrrlichtMt
${ZSTD_LIBRARY}
${X11_LIBRARIES}
${SOUND_LIBRARIES}
${SQLITE3_LIBRARY}
${LUA_LIBRARY}
${GMP_LIBRARY}
${JSON_LIBRARY}
${LUA_BIT_LIBRARY}
${FREETYPE_LIBRARY}
${PLATFORM_LIBS}
)
if(NOT USE_LUAJIT)
set_target_properties(${PROJECT_NAME} PROPERTIES
# This is necessary for dynamic Lua modules
# to work when Lua is statically linked (issue #10806)
ENABLE_EXPORTS 1
)
endif()
if(USE_GETTEXT)
target_link_libraries(
${PROJECT_NAME}
${GETTEXT_LIBRARY}
)
endif()
if(USE_CURL)
target_link_libraries(
${PROJECT_NAME}
${CURL_LIBRARY}
)
endif()
if(FREETYPE_PKGCONFIG_FOUND)
set_target_properties(${PROJECT_NAME}
PROPERTIES
COMPILE_FLAGS "${FREETYPE_CFLAGS_STR}"
)
endif()
if (USE_CURSES)
target_link_libraries(${PROJECT_NAME} ${CURSES_LIBRARIES})
endif()
if (USE_POSTGRESQL)
target_link_libraries(${PROJECT_NAME} ${PostgreSQL_LIBRARIES})
endif()
if (USE_LEVELDB)
target_link_libraries(${PROJECT_NAME} ${LEVELDB_LIBRARY})
endif()
if (USE_REDIS)
target_link_libraries(${PROJECT_NAME} ${REDIS_LIBRARY})
endif()
if (USE_PROMETHEUS)
target_link_libraries(${PROJECT_NAME} ${PROMETHEUS_LIBRARIES})
endif()
if (USE_SPATIAL)
target_link_libraries(${PROJECT_NAME} ${SPATIAL_LIBRARY})
endif()
if(BUILD_BENCHMARKS)
target_link_libraries(${PROJECT_NAME} catch2)
endif()
endif(BUILD_CLIENT)
if(BUILD_SERVER)
add_executable(${PROJECT_NAME}server ${server_SRCS} ${extra_windows_SRCS})
add_dependencies(${PROJECT_NAME}server GenerateVersion)
get_target_property(
IRRLICHT_INCLUDES IrrlichtMt::IrrlichtMt INTERFACE_INCLUDE_DIRECTORIES)
# Doesn't work without PRIVATE/PUBLIC/INTERFACE mode specified.
target_include_directories(${PROJECT_NAME}server PRIVATE ${IRRLICHT_INCLUDES})
target_link_libraries(
${PROJECT_NAME}server
${ZLIB_LIBRARIES}
${ZSTD_LIBRARY}
${SQLITE3_LIBRARY}
${JSON_LIBRARY}
${LUA_LIBRARY}
${LUA_BIT_LIBRARY}
${GMP_LIBRARY}
${PLATFORM_LIBS}
)
set_target_properties(${PROJECT_NAME}server PROPERTIES
COMPILE_DEFINITIONS "SERVER")
if(NOT USE_LUAJIT)
set_target_properties(${PROJECT_NAME}server PROPERTIES
# This is necessary for dynamic Lua modules
# to work when Lua is statically linked (issue #10806)
ENABLE_EXPORTS 1
)
endif()
if (USE_GETTEXT)
target_link_libraries(${PROJECT_NAME}server ${GETTEXT_LIBRARY})
endif()
if (USE_CURSES)
target_link_libraries(${PROJECT_NAME}server ${CURSES_LIBRARIES})
endif()
if (USE_POSTGRESQL)
target_link_libraries(${PROJECT_NAME}server ${PostgreSQL_LIBRARIES})
endif()
if (USE_LEVELDB)
target_link_libraries(${PROJECT_NAME}server ${LEVELDB_LIBRARY})
endif()
if (USE_REDIS)
target_link_libraries(${PROJECT_NAME}server ${REDIS_LIBRARY})
endif()
if (USE_PROMETHEUS)
target_link_libraries(${PROJECT_NAME}server ${PROMETHEUS_LIBRARIES})
endif()
if (USE_SPATIAL)
target_link_libraries(${PROJECT_NAME}server ${SPATIAL_LIBRARY})
endif()
if(USE_CURL)
target_link_libraries(
${PROJECT_NAME}server
${CURL_LIBRARY}
)
endif()
if(BUILD_BENCHMARKS)
target_link_libraries(${PROJECT_NAME}server catch2)
endif()
endif(BUILD_SERVER)
# Blacklisted locales that don't work.
# see issue #4638
set(GETTEXT_BLACKLISTED_LOCALES
ar
dv
he
hi
kn
ms_Arab
th
)
option(APPLY_LOCALE_BLACKLIST "Use a blacklist to avoid known broken locales" TRUE)
if (GETTEXTLIB_FOUND AND APPLY_LOCALE_BLACKLIST)
set(GETTEXT_USED_LOCALES "")
foreach(LOCALE ${GETTEXT_AVAILABLE_LOCALES})
if (NOT "${LOCALE}" IN_LIST GETTEXT_BLACKLISTED_LOCALES)
list(APPEND GETTEXT_USED_LOCALES ${LOCALE})
endif()
endforeach()
message(STATUS "Locale blacklist applied; Locales used: ${GETTEXT_USED_LOCALES}")
elseif (GETTEXTLIB_FOUND)
set(GETTEXT_USED_LOCALES ${GETTEXT_AVAILABLE_LOCALES})
endif()
# Set some optimizations and tweaks
include(CheckCSourceCompiles)
if(MSVC)
# Visual Studio
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /D WIN32_LEAN_AND_MEAN")
# EHa enables SEH exceptions (used for catching segfaults)
set(CMAKE_CXX_FLAGS_RELEASE "/EHa /Ox /MD /GS- /Zi /fp:fast /D NDEBUG /D _HAS_ITERATOR_DEBUGGING=0")
if(CMAKE_SIZEOF_VOID_P EQUAL 4)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /arch:SSE")
endif()
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/INCREMENTAL:NO /DEBUG /OPT:REF /OPT:ICF /SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup")
set(CMAKE_CXX_FLAGS_SEMIDEBUG "/MDd /Zi /Ob0 /O1 /RTC1")
# Debug build doesn't catch exceptions by itself
# Add some optimizations because otherwise it's VERY slow
set(CMAKE_CXX_FLAGS_DEBUG "/MDd /Zi /Ob0 /Od /RTC1")
# Flags for C files (sqlite)
# /MD = dynamically link to MSVCRxxx.dll
set(CMAKE_C_FLAGS_RELEASE "/O2 /Ob2 /MD")
# Flags that cannot be shared between cl and clang-cl
# https://clang.llvm.org/docs/UsersManual.html#clang-cl
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fuse-ld=lld")
# Disable pragma-pack warning
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Wno-pragma-pack")
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /TP /FD /GL")
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /LTCG")
endif()
else()
# GCC or compatible compilers such as Clang
set(WARNING_FLAGS "-Wall -Wextra")
set(WARNING_FLAGS "${WARNING_FLAGS} -Wno-unused-parameter -Wno-implicit-fallthrough")
if(WARN_ALL)
set(RELEASE_WARNING_FLAGS "${WARNING_FLAGS}")
else()
set(RELEASE_WARNING_FLAGS "")
endif()
if(APPLE AND USE_LUAJIT)
# required per http://luajit.org/install.html
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pagezero_size 10000 -image_base 100000000")
elseif(UNIX AND USE_LUAJIT)
check_c_source_compiles("#ifndef __aarch64__\n#error\n#endif\nint main(){}" IS_AARCH64)
if(IS_AARCH64)
# Move text segment below LuaJIT's 47-bit limit (see issue #9367)
if(CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
# FreeBSD uses lld, and lld does not support -Ttext-segment, suggesting
# --image-base instead. Not sure if it's equivalent change for the purpose
# but at least if fixes build on FreeBSD/aarch64
# XXX: the condition should also be changed to check for lld regardless of
# os, bit CMake doesn't have anything like CMAKE_LINKER_IS_LLD yet
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--image-base=0x200000000")
else()
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-Ttext-segment=0x200000000")
endif()
endif()
endif()
if(MINGW)
set(OTHER_FLAGS "${OTHER_FLAGS} -mthreads -fexceptions")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DWIN32_LEAN_AND_MEAN")
endif()
# Use a safe subset of flags to speed up math calculations:
# - we don't need errno or math exceptions
# - we don't deal with Inf/NaN or signed zero
set(MATH_FLAGS "-fno-math-errno -fno-trapping-math -ffinite-math-only -fno-signed-zeros")
# Enable SSE for floating point math on 32-bit x86 by default
# reasoning see minetest issue #11810 and https://gcc.gnu.org/wiki/FloatingPointMath
if(CMAKE_SIZEOF_VOID_P EQUAL 4)
check_c_source_compiles("#ifndef __i686__\n#error\n#endif\nint main(){}" IS_I686)
if(IS_I686)
message(STATUS "Detected Intel x86: using SSE instead of x87 FPU")
set(OTHER_FLAGS "${OTHER_FLAGS} -mfpmath=sse -msse")
endif()
endif()
set(CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG ${RELEASE_WARNING_FLAGS} ${OTHER_FLAGS} -pipe -funroll-loops")
if(CMAKE_SYSTEM_NAME MATCHES "(Darwin|BSD|DragonFly)")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Os")
else()
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -fomit-frame-pointer")
if(CMAKE_SYSTEM_NAME STREQUAL "Linux"
AND CMAKE_CXX_COMPILER_ID STREQUAL "Clang"
AND CMAKE_CXX_COMPILER_VERSION MATCHES "^9\\.")
# Clang 9 has broken -ffast-math on glibc
else()
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${MATH_FLAGS}")
endif()
endif()
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELEASE} -g")
set(CMAKE_CXX_FLAGS_SEMIDEBUG "-g -O1 ${WARNING_FLAGS} ${OTHER_FLAGS}")
set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 ${WARNING_FLAGS} ${OTHER_FLAGS}")
if(USE_GPROF)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -pg")
endif()
if(MINGW)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -mwindows")
endif()
endif()
# Installation
if(WIN32)
if(EXTRA_DLL)
install(FILES ${EXTRA_DLL} DESTINATION ${BINDIR})
endif()
if(VCPKG_APPLOCAL_DEPS)
# Collect the dll's from the output path
install(DIRECTORY ${EXECUTABLE_OUTPUT_PATH}/Release/
DESTINATION ${BINDIR}
CONFIGURATIONS Release
FILES_MATCHING PATTERN "*.dll")
install(DIRECTORY ${EXECUTABLE_OUTPUT_PATH}/Debug/
DESTINATION ${BINDIR}
CONFIGURATIONS Debug
FILES_MATCHING PATTERN "*.dll")
install(DIRECTORY ${EXECUTABLE_OUTPUT_PATH}/RelWithDebInfo/
DESTINATION ${BINDIR}
CONFIGURATIONS RelWithDebInfo
FILES_MATCHING PATTERN "*.dll")
install(DIRECTORY ${EXECUTABLE_OUTPUT_PATH}/MinSizeRel/
DESTINATION ${BINDIR}
CONFIGURATIONS MinSizeRel
FILES_MATCHING PATTERN "*.dll")
else()
# Use the old-style way to install dll's
if(BUILD_CLIENT AND USE_SOUND)
if(OPENAL_DLL)
install(FILES ${OPENAL_DLL} DESTINATION ${BINDIR})
endif()
if(OGG_DLL)
install(FILES ${OGG_DLL} DESTINATION ${BINDIR})
endif()
if(VORBIS_DLL)
install(FILES ${VORBIS_DLL} DESTINATION ${BINDIR})
endif()
endif()
if(CURL_DLL)
install(FILES ${CURL_DLL} DESTINATION ${BINDIR})
endif()
if(ZLIB_DLL)
install(FILES ${ZLIB_DLL} DESTINATION ${BINDIR})
endif()
if(ZSTD_DLL)
install(FILES ${ZSTD_DLL} DESTINATION ${BINDIR})
endif()
if(BUILD_CLIENT AND FREETYPE_DLL)
install(FILES ${FREETYPE_DLL} DESTINATION ${BINDIR})
endif()
if(SQLITE3_DLL)
install(FILES ${SQLITE3_DLL} DESTINATION ${BINDIR})
endif()
if(LEVELDB_DLL)
install(FILES ${LEVELDB_DLL} DESTINATION ${BINDIR})
endif()
if(LUA_DLL)
install(FILES ${LUA_DLL} DESTINATION ${BINDIR})
endif()
if(BUILD_CLIENT AND USE_GETTEXT AND GETTEXT_DLL)
install(FILES ${GETTEXT_DLL} DESTINATION ${BINDIR})
endif()
endif()
if(BUILD_CLIENT AND IRRLICHT_DLL)
install(FILES ${IRRLICHT_DLL} DESTINATION ${BINDIR})
endif()
endif()
if(BUILD_CLIENT)
install(TARGETS ${PROJECT_NAME}
RUNTIME DESTINATION ${BINDIR}
LIBRARY DESTINATION ${BINDIR}
ARCHIVE DESTINATION ${BINDIR}
BUNDLE DESTINATION .
)
if(APPLE)
install(CODE "
set(BU_CHMOD_BUNDLE_ITEMS ON)
include(BundleUtilities)
fixup_bundle(\"\${CMAKE_INSTALL_PREFIX}/${BUNDLE_PATH}\" \"\" \"\${CMAKE_INSTALL_PREFIX}/${BINDIR}\")
" COMPONENT Runtime)
endif()
if(USE_GETTEXT)
foreach(LOCALE ${GETTEXT_USED_LOCALES})
set_mo_paths(MO_BUILD_PATH MO_DEST_PATH ${LOCALE})
set(MO_BUILD_PATH "${MO_BUILD_PATH}/${PROJECT_NAME}.mo")
install(FILES ${MO_BUILD_PATH} DESTINATION ${MO_DEST_PATH})
endforeach()
endif()
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../fonts" DESTINATION "${SHAREDIR}"
FILES_MATCHING PATTERN "*.ttf" PATTERN "*.txt")
endif(BUILD_CLIENT)
if(BUILD_SERVER)
install(TARGETS ${PROJECT_NAME}server DESTINATION ${BINDIR})
endif()
if (USE_GETTEXT)
set(MO_FILES)
foreach(LOCALE ${GETTEXT_USED_LOCALES})
set(PO_FILE_PATH "${GETTEXT_PO_PATH}/${LOCALE}/${PROJECT_NAME}.po")
set_mo_paths(MO_BUILD_PATH MO_DEST_PATH ${LOCALE})
set(MO_FILE_PATH "${MO_BUILD_PATH}/${PROJECT_NAME}.mo")
add_custom_command(OUTPUT ${MO_BUILD_PATH}
COMMAND ${CMAKE_COMMAND} -E make_directory ${MO_BUILD_PATH}
COMMENT "mo-update [${LOCALE}]: Creating locale directory.")
add_custom_command(
OUTPUT ${MO_FILE_PATH}
COMMAND ${GETTEXT_MSGFMT} -o ${MO_FILE_PATH} ${PO_FILE_PATH}
DEPENDS ${MO_BUILD_PATH} ${PO_FILE_PATH}
WORKING_DIRECTORY "${GETTEXT_PO_PATH}/${LOCALE}"
COMMENT "mo-update [${LOCALE}]: Creating mo file."
)
set(MO_FILES ${MO_FILES} ${MO_FILE_PATH})
endforeach()
add_custom_target(translations ALL COMMENT "mo update" DEPENDS ${MO_FILES})
endif()

132
src/activeobject.h Normal file
View File

@@ -0,0 +1,132 @@
/*
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 "irr_aabb3d.h"
#include "irr_v3d.h"
#include <string>
enum ActiveObjectType {
ACTIVEOBJECT_TYPE_INVALID = 0,
ACTIVEOBJECT_TYPE_TEST = 1,
// Obsolete stuff
// ACTIVEOBJECT_TYPE_ITEM = 2,
// ACTIVEOBJECT_TYPE_RAT = 3,
// ACTIVEOBJECT_TYPE_OERKKI1 = 4,
// ACTIVEOBJECT_TYPE_FIREFLY = 5,
// ACTIVEOBJECT_TYPE_MOBV2 = 6,
// End obsolete stuff
ACTIVEOBJECT_TYPE_LUAENTITY = 7,
// Special type, not stored as a static object
ACTIVEOBJECT_TYPE_PLAYER = 100,
// Special type, only exists as CAO
ACTIVEOBJECT_TYPE_GENERIC = 101,
};
// Other types are defined in content_object.h
struct ActiveObjectMessage
{
ActiveObjectMessage(u16 id_, bool reliable_=true, const std::string &data_ = "") :
id(id_),
reliable(reliable_),
datastring(data_)
{}
u16 id;
bool reliable;
std::string datastring;
};
enum ActiveObjectCommand {
AO_CMD_SET_PROPERTIES,
AO_CMD_UPDATE_POSITION,
AO_CMD_SET_TEXTURE_MOD,
AO_CMD_SET_SPRITE,
AO_CMD_PUNCHED,
AO_CMD_UPDATE_ARMOR_GROUPS,
AO_CMD_SET_ANIMATION,
AO_CMD_SET_BONE_POSITION,
AO_CMD_ATTACH_TO,
AO_CMD_SET_PHYSICS_OVERRIDE,
AO_CMD_OBSOLETE1,
// ^ UPDATE_NAMETAG_ATTRIBUTES deprecated since 0.4.14, removed in 5.3.0
AO_CMD_SPAWN_INFANT,
AO_CMD_SET_ANIMATION_SPEED
};
/*
Parent class for ServerActiveObject and ClientActiveObject
*/
class ActiveObject
{
public:
ActiveObject(u16 id):
m_id(id)
{
}
u16 getId() const
{
return m_id;
}
void setId(u16 id)
{
m_id = id;
}
virtual ActiveObjectType getType() const = 0;
/*!
* Returns the collision box of the object.
* This box is translated by the object's
* location.
* The box's coordinates are world coordinates.
* @returns true if the object has a collision box.
*/
virtual bool getCollisionBox(aabb3f *toset) const = 0;
/*!
* Returns the selection box of the object.
* This box is not translated when the
* object moves.
* The box's coordinates are world coordinates.
* @returns true if the object has a selection box.
*/
virtual bool getSelectionBox(aabb3f *toset) const = 0;
virtual bool collideWithObjects() const = 0;
virtual void setAttachment(int parent_id, const std::string &bone, v3f position,
v3f rotation, bool force_visible) {}
virtual void getAttachment(int *parent_id, std::string *bone, v3f *position,
v3f *rotation, bool *force_visible) const {}
virtual void clearChildAttachments() {}
virtual void clearParentAttachment() {}
virtual void addAttachmentChild(int child_id) {}
virtual void removeAttachmentChild(int child_id) {}
protected:
u16 m_id; // 0 is invalid, "no id"
};

66
src/activeobjectmgr.h Normal file
View File

@@ -0,0 +1,66 @@
/*
Minetest
Copyright (C) 2010-2018 nerzhul, Loic BLOT <loic.blot@unix-experience.fr>
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 <unordered_map>
#include "irrlichttypes.h"
class TestClientActiveObjectMgr;
class TestServerActiveObjectMgr;
template <typename T>
class ActiveObjectMgr
{
friend class ::TestClientActiveObjectMgr;
friend class ::TestServerActiveObjectMgr;
public:
virtual void step(float dtime, const std::function<void(T *)> &f) = 0;
virtual bool registerObject(T *obj) = 0;
virtual void removeObject(u16 id) = 0;
T *getActiveObject(u16 id)
{
typename std::unordered_map<u16, T *>::const_iterator n =
m_active_objects.find(id);
return (n != m_active_objects.end() ? n->second : nullptr);
}
protected:
u16 getFreeId() const
{
// try to reuse id's as late as possible
static thread_local u16 last_used_id = 0;
u16 startid = last_used_id;
while (!isFreeId(++last_used_id)) {
if (last_used_id == startid)
return 0;
}
return last_used_id;
}
bool isFreeId(u16 id) const
{
return id != 0 && m_active_objects.find(id) == m_active_objects.end();
}
std::unordered_map<u16, T *> m_active_objects;
};

142
src/ban.cpp Normal file
View File

@@ -0,0 +1,142 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2018 nerzhul, Loic BLOT <loic.blot@unix-experience.fr>
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 "ban.h"
#include <fstream>
#include "threading/mutex_auto_lock.h"
#include <sstream>
#include <set>
#include "util/strfnd.h"
#include "util/string.h"
#include "log.h"
#include "filesys.h"
BanManager::BanManager(const std::string &banfilepath):
m_banfilepath(banfilepath)
{
try {
load();
} catch(SerializationError &e) {
infostream << "BanManager: creating "
<< m_banfilepath << std::endl;
}
}
BanManager::~BanManager()
{
save();
}
void BanManager::load()
{
MutexAutoLock lock(m_mutex);
infostream<<"BanManager: loading from "<<m_banfilepath<<std::endl;
std::ifstream is(m_banfilepath.c_str(), std::ios::binary);
if (!is.good()) {
infostream<<"BanManager: failed loading from "<<m_banfilepath<<std::endl;
throw SerializationError("BanManager::load(): Couldn't open file");
}
while (!is.eof() && is.good()) {
std::string line;
std::getline(is, line, '\n');
Strfnd f(line);
std::string ip = trim(f.next("|"));
std::string name = trim(f.next("|"));
if(!ip.empty()) {
m_ips[ip] = name;
}
}
m_modified = false;
}
void BanManager::save()
{
MutexAutoLock lock(m_mutex);
infostream << "BanManager: saving to " << m_banfilepath << std::endl;
std::ostringstream ss(std::ios_base::binary);
for (const auto &ip : m_ips)
ss << ip.first << "|" << ip.second << "\n";
if (!fs::safeWriteToFile(m_banfilepath, ss.str())) {
infostream << "BanManager: failed saving to " << m_banfilepath << std::endl;
throw SerializationError("BanManager::save(): Couldn't write file");
}
m_modified = false;
}
bool BanManager::isIpBanned(const std::string &ip)
{
MutexAutoLock lock(m_mutex);
return m_ips.find(ip) != m_ips.end();
}
std::string BanManager::getBanDescription(const std::string &ip_or_name)
{
MutexAutoLock lock(m_mutex);
std::string s;
for (const auto &ip : m_ips) {
if (ip.first == ip_or_name || ip.second == ip_or_name
|| ip_or_name.empty()) {
s += ip.first + "|" + ip.second + ", ";
}
}
s = s.substr(0, s.size() - 2);
return s;
}
std::string BanManager::getBanName(const std::string &ip)
{
MutexAutoLock lock(m_mutex);
StringMap::iterator it = m_ips.find(ip);
if (it == m_ips.end())
return "";
return it->second;
}
void BanManager::add(const std::string &ip, const std::string &name)
{
MutexAutoLock lock(m_mutex);
m_ips[ip] = name;
m_modified = true;
}
void BanManager::remove(const std::string &ip_or_name)
{
MutexAutoLock lock(m_mutex);
for (StringMap::iterator it = m_ips.begin(); it != m_ips.end();) {
if ((it->first == ip_or_name) || (it->second == ip_or_name)) {
m_ips.erase(it++);
m_modified = true;
} else {
++it;
}
}
}
bool BanManager::isModified()
{
MutexAutoLock lock(m_mutex);
return m_modified;
}

49
src/ban.h Normal file
View File

@@ -0,0 +1,49 @@
/*
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 "util/string.h"
#include "threading/thread.h"
#include "exceptions.h"
#include <map>
#include <string>
#include <mutex>
class BanManager
{
public:
BanManager(const std::string &banfilepath);
~BanManager();
void load();
void save();
bool isIpBanned(const std::string &ip);
// Supplying ip_or_name = "" lists all bans.
std::string getBanDescription(const std::string &ip_or_name);
std::string getBanName(const std::string &ip);
void add(const std::string &ip, const std::string &name);
void remove(const std::string &ip_or_name);
bool isModified();
private:
std::mutex m_mutex;
std::string m_banfilepath = "";
StringMap m_ips;
bool m_modified = false;
};

View File

@@ -0,0 +1,7 @@
set (BENCHMARK_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/benchmark.cpp
${CMAKE_CURRENT_SOURCE_DIR}/benchmark_serialize.cpp
PARENT_SCOPE)
set (BENCHMARK_CLIENT_SRCS
PARENT_SCOPE)

View File

@@ -0,0 +1,32 @@
/*
Minetest
Copyright (C) 2022 Minetest Authors
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 "benchmark/benchmark.h"
// This must be set in just this file
#define CATCH_CONFIG_RUNNER
#include "benchmark_setup.h"
int run_benchmarks()
{
int argc = 1;
const char *argv[] = { "MinetestBenchmark", NULL };
int errCount = Catch::Session().run(argc, argv);
return errCount ? 1 : 0;
}

26
src/benchmark/benchmark.h Normal file
View File

@@ -0,0 +1,26 @@
/*
Minetest
Copyright (C) 2022 Minetest Authors
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 "config.h"
#if BUILD_BENCHMARKS
extern int run_benchmarks();
#endif

View File

@@ -0,0 +1,71 @@
/*
Minetest
Copyright (C) 2022 Minetest Authors
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 "benchmark_setup.h"
#include "util/serialize.h"
#include <sstream>
#include <ios>
// Builds a string of exactly `length` characters by repeating `s` (rest cut off)
static std::string makeRepeatTo(const std::string &s, size_t length)
{
std::string v;
v.reserve(length + s.size());
for (size_t i = 0; i < length; i += s.size()) {
v += s;
}
v.resize(length);
return v;
}
#define BENCH3(_label, _chars, _length, _lengthlabel) \
BENCHMARK_ADVANCED("serializeJsonStringIfNeeded_" _lengthlabel "_" _label)(Catch::Benchmark::Chronometer meter) { \
std::string s = makeRepeatTo(_chars, _length); \
meter.measure([&] { return serializeJsonStringIfNeeded(s); }); \
}; \
BENCHMARK_ADVANCED("deSerializeJsonStringIfNeeded_" _lengthlabel "_" _label)(Catch::Benchmark::Chronometer meter) { \
std::string s = makeRepeatTo(_chars, _length); \
std::string serialized = serializeJsonStringIfNeeded(s); \
std::istringstream is(serialized, std::ios::binary); \
meter.measure([&] { \
is.clear(); \
is.seekg(0, std::ios::beg); \
return deSerializeJsonStringIfNeeded(is); \
}); \
};
/* Both with and without a space character (' ') */
#define BENCH2(_label, _chars, _length, _lengthlabel) \
BENCH3(_label, _chars, _length, _lengthlabel) \
BENCH3(_label "_with_space", " " _chars, _length, _lengthlabel) \
/* Iterate over input lengths */
#define BENCH1(_label, _chars) \
BENCH2(_label, _chars, 10, "small") \
BENCH2(_label, _chars, 10000, "large")
/* Iterate over character sets */
#define BENCH_ALL() \
BENCH1("alpha", "abcdefghijklmnopqrstuvwxyz") \
BENCH1("escaped", "\"\\/\b\f\n\r\t") \
BENCH1("nonascii", "\xf0\xff")
TEST_CASE("benchmark_serialize") {
BENCH_ALL()
}

View File

@@ -0,0 +1,22 @@
/*
Minetest
Copyright (C) 2022 Minetest Authors
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.
*/
#define CATCH_CONFIG_ENABLE_BENCHMARKING
#define CATCH_CONFIG_CONSOLE_WIDTH 160
#include <catch.hpp>

845
src/chat.cpp Normal file
View File

@@ -0,0 +1,845 @@
/*
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 "chat.h"
#include <algorithm>
#include <cctype>
#include <sstream>
#include "config.h"
#include "debug.h"
#include "util/strfnd.h"
#include "util/string.h"
#include "util/numeric.h"
ChatBuffer::ChatBuffer(u32 scrollback):
m_scrollback(scrollback)
{
if (m_scrollback == 0)
m_scrollback = 1;
m_empty_formatted_line.first = true;
m_cache_clickable_chat_weblinks = false;
// Curses mode cannot access g_settings here
if (g_settings != nullptr) {
m_cache_clickable_chat_weblinks = g_settings->getBool("clickable_chat_weblinks");
if (m_cache_clickable_chat_weblinks) {
std::string colorval = g_settings->get("chat_weblink_color");
parseColorString(colorval, m_cache_chat_weblink_color, false, 255);
m_cache_chat_weblink_color.setAlpha(255);
}
}
}
void ChatBuffer::addLine(const std::wstring &name, const std::wstring &text)
{
m_lines_modified = true;
ChatLine line(name, text);
m_unformatted.push_back(line);
if (m_rows > 0) {
// m_formatted is valid and must be kept valid
bool scrolled_at_bottom = (m_scroll == getBottomScrollPos());
u32 num_added = formatChatLine(line, m_cols, m_formatted);
if (scrolled_at_bottom)
m_scroll += num_added;
}
// Limit number of lines by m_scrollback
if (m_unformatted.size() > m_scrollback) {
deleteOldest(m_unformatted.size() - m_scrollback);
}
}
void ChatBuffer::clear()
{
m_unformatted.clear();
m_formatted.clear();
m_scroll = 0;
m_lines_modified = true;
}
u32 ChatBuffer::getLineCount() const
{
return m_unformatted.size();
}
const ChatLine& ChatBuffer::getLine(u32 index) const
{
assert(index < getLineCount()); // pre-condition
return m_unformatted[index];
}
void ChatBuffer::step(f32 dtime)
{
for (ChatLine &line : m_unformatted) {
line.age += dtime;
}
}
void ChatBuffer::deleteOldest(u32 count)
{
bool at_bottom = (m_scroll == getBottomScrollPos());
u32 del_unformatted = 0;
u32 del_formatted = 0;
while (count > 0 && del_unformatted < m_unformatted.size()) {
++del_unformatted;
// keep m_formatted in sync
if (del_formatted < m_formatted.size()) {
sanity_check(m_formatted[del_formatted].first);
++del_formatted;
while (del_formatted < m_formatted.size() &&
!m_formatted[del_formatted].first)
++del_formatted;
}
--count;
}
m_unformatted.erase(m_unformatted.begin(), m_unformatted.begin() + del_unformatted);
m_formatted.erase(m_formatted.begin(), m_formatted.begin() + del_formatted);
if (del_unformatted > 0)
m_lines_modified = true;
if (at_bottom)
m_scroll = getBottomScrollPos();
else
scrollAbsolute(m_scroll - del_formatted);
}
void ChatBuffer::deleteByAge(f32 maxAge)
{
u32 count = 0;
while (count < m_unformatted.size() && m_unformatted[count].age > maxAge)
++count;
deleteOldest(count);
}
u32 ChatBuffer::getRows() const
{
return m_rows;
}
void ChatBuffer::reformat(u32 cols, u32 rows)
{
if (cols == 0 || rows == 0)
{
// Clear formatted buffer
m_cols = 0;
m_rows = 0;
m_scroll = 0;
m_formatted.clear();
}
else if (cols != m_cols || rows != m_rows)
{
// TODO: Avoid reformatting ALL lines (even invisible ones)
// each time the console size changes.
// Find out the scroll position in *unformatted* lines
u32 restore_scroll_unformatted = 0;
u32 restore_scroll_formatted = 0;
bool at_bottom = (m_scroll == getBottomScrollPos());
if (!at_bottom)
{
for (s32 i = 0; i < m_scroll; ++i)
{
if (m_formatted[i].first)
++restore_scroll_unformatted;
}
}
// If number of columns change, reformat everything
if (cols != m_cols)
{
m_formatted.clear();
for (u32 i = 0; i < m_unformatted.size(); ++i)
{
if (i == restore_scroll_unformatted)
restore_scroll_formatted = m_formatted.size();
formatChatLine(m_unformatted[i], cols, m_formatted);
}
}
// Update the console size
m_cols = cols;
m_rows = rows;
// Restore the scroll position
if (at_bottom)
{
scrollBottom();
}
else
{
scrollAbsolute(restore_scroll_formatted);
}
}
}
const ChatFormattedLine& ChatBuffer::getFormattedLine(u32 row) const
{
s32 index = m_scroll + (s32) row;
if (index >= 0 && index < (s32) m_formatted.size())
return m_formatted[index];
return m_empty_formatted_line;
}
void ChatBuffer::scroll(s32 rows)
{
scrollAbsolute(m_scroll + rows);
}
void ChatBuffer::scrollAbsolute(s32 scroll)
{
s32 top = getTopScrollPos();
s32 bottom = getBottomScrollPos();
m_scroll = scroll;
if (m_scroll < top)
m_scroll = top;
if (m_scroll > bottom)
m_scroll = bottom;
}
void ChatBuffer::scrollBottom()
{
m_scroll = getBottomScrollPos();
}
u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols,
std::vector<ChatFormattedLine>& destination) const
{
u32 num_added = 0;
std::vector<ChatFormattedFragment> next_frags;
ChatFormattedLine next_line;
ChatFormattedFragment temp_frag;
u32 out_column = 0;
u32 in_pos = 0;
u32 hanging_indentation = 0;
// Format the sender name and produce fragments
if (!line.name.empty()) {
temp_frag.text = L"<";
temp_frag.column = 0;
//temp_frag.bold = 0;
next_frags.push_back(temp_frag);
temp_frag.text = line.name;
temp_frag.column = 0;
//temp_frag.bold = 1;
next_frags.push_back(temp_frag);
temp_frag.text = L"> ";
temp_frag.column = 0;
//temp_frag.bold = 0;
next_frags.push_back(temp_frag);
}
std::wstring name_sanitized = line.name.c_str();
// Choose an indentation level
if (line.name.empty()) {
// Server messages
hanging_indentation = 0;
} else if (name_sanitized.size() + 3 <= cols/2) {
// Names shorter than about half the console width
hanging_indentation = line.name.size() + 3;
} else {
// Very long names
hanging_indentation = 2;
}
//EnrichedString line_text(line.text);
next_line.first = true;
// Set/use forced newline after the last frag in each line
bool mark_newline = false;
// Produce fragments and layout them into lines
while (!next_frags.empty() || in_pos < line.text.size()) {
mark_newline = false; // now using this to USE line-end frag
// Layout fragments into lines
while (!next_frags.empty()) {
ChatFormattedFragment& frag = next_frags[0];
// Force newline after this frag, if marked
if (frag.column == INT_MAX)
mark_newline = true;
if (frag.text.size() <= cols - out_column) {
// Fragment fits into current line
frag.column = out_column;
next_line.fragments.push_back(frag);
out_column += frag.text.size();
next_frags.erase(next_frags.begin());
} else {
// Fragment does not fit into current line
// So split it up
temp_frag.text = frag.text.substr(0, cols - out_column);
temp_frag.column = out_column;
temp_frag.weblink = frag.weblink;
next_line.fragments.push_back(temp_frag);
frag.text = frag.text.substr(cols - out_column);
frag.column = 0;
out_column = cols;
}
if (out_column == cols || mark_newline) {
// End the current line
destination.push_back(next_line);
num_added++;
next_line.fragments.clear();
next_line.first = false;
out_column = hanging_indentation;
mark_newline = false;
}
}
// Produce fragment(s) for next formatted line
if (!(in_pos < line.text.size()))
continue;
const std::wstring &linestring = line.text.getString();
u32 remaining_in_output = cols - out_column;
size_t http_pos = std::wstring::npos;
mark_newline = false; // now using this to SET line-end frag
// Construct all frags for next output line
while (!mark_newline) {
// Determine a fragment length <= the minimum of
// remaining_in_{in,out}put. Try to end the fragment
// on a word boundary.
u32 frag_length = 0, space_pos = 0;
u32 remaining_in_input = line.text.size() - in_pos;
if (m_cache_clickable_chat_weblinks) {
// Note: unsigned(-1) on fail
http_pos = linestring.find(L"https://", in_pos);
if (http_pos == std::wstring::npos)
http_pos = linestring.find(L"http://", in_pos);
if (http_pos != std::wstring::npos)
http_pos -= in_pos;
}
while (frag_length < remaining_in_input &&
frag_length < remaining_in_output) {
if (iswspace(linestring[in_pos + frag_length]))
space_pos = frag_length;
++frag_length;
}
if (http_pos >= remaining_in_output) {
// Http not in range, grab until space or EOL, halt as normal.
// Note this works because (http_pos = npos) is unsigned(-1)
mark_newline = true;
} else if (http_pos == 0) {
// At http, grab ALL until FIRST whitespace or end marker. loop.
// If at end of string, next loop will be empty string to mark end of weblink.
frag_length = 6; // Frag is at least "http://"
// Chars to mark end of weblink
// TODO? replace this with a safer (slower) regex whitelist?
static const std::wstring delim_chars = L"\'\";";
wchar_t tempchar = linestring[in_pos+frag_length];
while (frag_length < remaining_in_input &&
!iswspace(tempchar) &&
delim_chars.find(tempchar) == std::wstring::npos) {
++frag_length;
tempchar = linestring[in_pos+frag_length];
}
space_pos = frag_length - 1;
// This frag may need to be force-split. That's ok, urls aren't "words"
if (frag_length >= remaining_in_output) {
mark_newline = true;
}
} else {
// Http in range, grab until http, loop
space_pos = http_pos - 1;
frag_length = http_pos;
}
// Include trailing space in current frag
if (space_pos != 0 && frag_length < remaining_in_input)
frag_length = space_pos + 1;
temp_frag.text = line.text.substr(in_pos, frag_length);
// A hack so this frag remembers mark_newline for the layout phase
temp_frag.column = mark_newline ? INT_MAX : 0;
if (http_pos == 0) {
// Discard color stuff from the source frag
temp_frag.text = EnrichedString(temp_frag.text.getString());
temp_frag.text.setDefaultColor(m_cache_chat_weblink_color);
// Set weblink in the frag meta
temp_frag.weblink = wide_to_utf8(temp_frag.text.getString());
} else {
temp_frag.weblink.clear();
}
next_frags.push_back(temp_frag);
in_pos += frag_length;
remaining_in_output -= std::min(frag_length, remaining_in_output);
}
}
// End the last line
if (num_added == 0 || !next_line.fragments.empty()) {
destination.push_back(next_line);
num_added++;
}
return num_added;
}
s32 ChatBuffer::getTopScrollPos() const
{
s32 formatted_count = (s32) m_formatted.size();
s32 rows = (s32) m_rows;
if (rows == 0)
return 0;
if (formatted_count <= rows)
return formatted_count - rows;
return 0;
}
s32 ChatBuffer::getBottomScrollPos() const
{
s32 formatted_count = (s32) m_formatted.size();
s32 rows = (s32) m_rows;
if (rows == 0)
return 0;
return formatted_count - rows;
}
void ChatBuffer::resize(u32 scrollback)
{
m_scrollback = scrollback;
if (m_unformatted.size() > m_scrollback)
deleteOldest(m_unformatted.size() - m_scrollback);
}
ChatPrompt::ChatPrompt(const std::wstring &prompt, u32 history_limit):
m_prompt(prompt),
m_history_limit(history_limit)
{
}
void ChatPrompt::input(wchar_t ch)
{
m_line.insert(m_cursor, 1, ch);
m_cursor++;
clampView();
m_nick_completion_start = 0;
m_nick_completion_end = 0;
}
void ChatPrompt::input(const std::wstring &str)
{
m_line.insert(m_cursor, str);
m_cursor += str.size();
clampView();
m_nick_completion_start = 0;
m_nick_completion_end = 0;
}
void ChatPrompt::addToHistory(const std::wstring &line)
{
if (!line.empty() &&
(m_history.size() == 0 || m_history.back() != line)) {
// Remove all duplicates
m_history.erase(std::remove(m_history.begin(), m_history.end(),
line), m_history.end());
// Push unique line
m_history.push_back(line);
}
if (m_history.size() > m_history_limit)
m_history.erase(m_history.begin());
m_history_index = m_history.size();
}
void ChatPrompt::clear()
{
m_line.clear();
m_view = 0;
m_cursor = 0;
m_nick_completion_start = 0;
m_nick_completion_end = 0;
}
std::wstring ChatPrompt::replace(const std::wstring &line)
{
std::wstring old_line = m_line;
m_line = line;
m_view = m_cursor = line.size();
clampView();
m_nick_completion_start = 0;
m_nick_completion_end = 0;
return old_line;
}
void ChatPrompt::historyPrev()
{
if (m_history_index != 0)
{
--m_history_index;
replace(m_history[m_history_index]);
}
}
void ChatPrompt::historyNext()
{
if (m_history_index + 1 >= m_history.size())
{
m_history_index = m_history.size();
replace(L"");
}
else
{
++m_history_index;
replace(m_history[m_history_index]);
}
}
void ChatPrompt::nickCompletion(const std::list<std::string>& names, bool backwards)
{
// Two cases:
// (a) m_nick_completion_start == m_nick_completion_end == 0
// Then no previous nick completion is active.
// Get the word around the cursor and replace with any nick
// that has that word as a prefix.
// (b) else, continue a previous nick completion.
// m_nick_completion_start..m_nick_completion_end are the
// interval where the originally used prefix was. Cycle
// through the list of completions of that prefix.
u32 prefix_start = m_nick_completion_start;
u32 prefix_end = m_nick_completion_end;
bool initial = (prefix_end == 0);
if (initial)
{
// no previous nick completion is active
prefix_start = prefix_end = m_cursor;
while (prefix_start > 0 && !iswspace(m_line[prefix_start-1]))
--prefix_start;
while (prefix_end < m_line.size() && !iswspace(m_line[prefix_end]))
++prefix_end;
if (prefix_start == prefix_end)
return;
}
std::wstring prefix = m_line.substr(prefix_start, prefix_end - prefix_start);
// find all names that start with the selected prefix
std::vector<std::wstring> completions;
for (const std::string &name : names) {
std::wstring completion = utf8_to_wide(name);
if (str_starts_with(completion, prefix, true)) {
if (prefix_start == 0)
completion += L": ";
completions.push_back(completion);
}
}
if (completions.empty())
return;
// find a replacement string and the word that will be replaced
u32 word_end = prefix_end;
u32 replacement_index = 0;
if (!initial)
{
while (word_end < m_line.size() && !iswspace(m_line[word_end]))
++word_end;
std::wstring word = m_line.substr(prefix_start, word_end - prefix_start);
// cycle through completions
for (u32 i = 0; i < completions.size(); ++i)
{
if (str_equal(word, completions[i], true))
{
if (backwards)
replacement_index = i + completions.size() - 1;
else
replacement_index = i + 1;
replacement_index %= completions.size();
break;
}
}
}
std::wstring replacement = completions[replacement_index];
if (word_end < m_line.size() && iswspace(m_line[word_end]))
++word_end;
// replace existing word with replacement word,
// place the cursor at the end and record the completion prefix
m_line.replace(prefix_start, word_end - prefix_start, replacement);
m_cursor = prefix_start + replacement.size();
clampView();
m_nick_completion_start = prefix_start;
m_nick_completion_end = prefix_end;
}
void ChatPrompt::reformat(u32 cols)
{
if (cols <= m_prompt.size())
{
m_cols = 0;
m_view = m_cursor;
}
else
{
s32 length = m_line.size();
bool was_at_end = (m_view + m_cols >= length + 1);
m_cols = cols - m_prompt.size();
if (was_at_end)
m_view = length;
clampView();
}
}
std::wstring ChatPrompt::getVisiblePortion() const
{
return m_prompt + m_line.substr(m_view, m_cols);
}
s32 ChatPrompt::getVisibleCursorPosition() const
{
return m_cursor - m_view + m_prompt.size();
}
void ChatPrompt::cursorOperation(CursorOp op, CursorOpDir dir, CursorOpScope scope)
{
s32 old_cursor = m_cursor;
s32 new_cursor = m_cursor;
s32 length = m_line.size();
s32 increment = (dir == CURSOROP_DIR_RIGHT) ? 1 : -1;
switch (scope) {
case CURSOROP_SCOPE_CHARACTER:
new_cursor += increment;
break;
case CURSOROP_SCOPE_WORD:
if (dir == CURSOROP_DIR_RIGHT) {
// skip one word to the right
while (new_cursor < length && iswspace(m_line[new_cursor]))
new_cursor++;
while (new_cursor < length && !iswspace(m_line[new_cursor]))
new_cursor++;
while (new_cursor < length && iswspace(m_line[new_cursor]))
new_cursor++;
} else {
// skip one word to the left
while (new_cursor >= 1 && iswspace(m_line[new_cursor - 1]))
new_cursor--;
while (new_cursor >= 1 && !iswspace(m_line[new_cursor - 1]))
new_cursor--;
}
break;
case CURSOROP_SCOPE_LINE:
new_cursor += increment * length;
break;
case CURSOROP_SCOPE_SELECTION:
break;
}
new_cursor = MYMAX(MYMIN(new_cursor, length), 0);
switch (op) {
case CURSOROP_MOVE:
m_cursor = new_cursor;
m_cursor_len = 0;
break;
case CURSOROP_DELETE:
if (m_cursor_len > 0) { // Delete selected text first
m_line.erase(m_cursor, m_cursor_len);
} else {
m_cursor = MYMIN(new_cursor, old_cursor);
m_line.erase(m_cursor, abs(new_cursor - old_cursor));
}
m_cursor_len = 0;
break;
case CURSOROP_SELECT:
if (scope == CURSOROP_SCOPE_LINE) {
m_cursor = 0;
m_cursor_len = length;
} else {
m_cursor = MYMIN(new_cursor, old_cursor);
m_cursor_len += abs(new_cursor - old_cursor);
m_cursor_len = MYMIN(m_cursor_len, length - m_cursor);
}
break;
}
clampView();
m_nick_completion_start = 0;
m_nick_completion_end = 0;
}
void ChatPrompt::clampView()
{
s32 length = m_line.size();
if (length + 1 <= m_cols)
{
m_view = 0;
}
else
{
m_view = MYMIN(m_view, length + 1 - m_cols);
m_view = MYMIN(m_view, m_cursor);
m_view = MYMAX(m_view, m_cursor - m_cols + 1);
m_view = MYMAX(m_view, 0);
}
}
ChatBackend::ChatBackend():
m_console_buffer(500),
m_recent_buffer(6),
m_prompt(L"]", 500)
{
}
void ChatBackend::addMessage(const std::wstring &name, std::wstring text)
{
// Note: A message may consist of multiple lines, for example the MOTD.
text = translate_string(text);
WStrfnd fnd(text);
while (!fnd.at_end())
{
std::wstring line = fnd.next(L"\n");
m_console_buffer.addLine(name, line);
m_recent_buffer.addLine(name, line);
}
}
void ChatBackend::addUnparsedMessage(std::wstring message)
{
// TODO: Remove the need to parse chat messages client-side, by sending
// separate name and text fields in TOCLIENT_CHAT_MESSAGE.
if (message.size() >= 2 && message[0] == L'<')
{
std::size_t closing = message.find_first_of(L'>', 1);
if (closing != std::wstring::npos &&
closing + 2 <= message.size() &&
message[closing+1] == L' ')
{
std::wstring name = message.substr(1, closing - 1);
std::wstring text = message.substr(closing + 2);
addMessage(name, text);
return;
}
}
// Unable to parse, probably a server message.
addMessage(L"", message);
}
ChatBuffer& ChatBackend::getConsoleBuffer()
{
return m_console_buffer;
}
ChatBuffer& ChatBackend::getRecentBuffer()
{
return m_recent_buffer;
}
EnrichedString ChatBackend::getRecentChat() const
{
EnrichedString result;
for (u32 i = 0; i < m_recent_buffer.getLineCount(); ++i) {
const ChatLine& line = m_recent_buffer.getLine(i);
if (i != 0)
result += L"\n";
if (!line.name.empty()) {
result += L"<";
result += line.name;
result += L"> ";
}
result += line.text;
}
return result;
}
ChatPrompt& ChatBackend::getPrompt()
{
return m_prompt;
}
void ChatBackend::reformat(u32 cols, u32 rows)
{
m_console_buffer.reformat(cols, rows);
// no need to reformat m_recent_buffer, its formatted lines
// are not used
m_prompt.reformat(cols);
}
void ChatBackend::clearRecentChat()
{
m_recent_buffer.clear();
}
void ChatBackend::applySettings()
{
u32 recent_lines = g_settings->getU32("recent_chat_messages");
recent_lines = rangelim(recent_lines, 2, 20);
m_recent_buffer.resize(recent_lines);
}
void ChatBackend::step(float dtime)
{
m_recent_buffer.step(dtime);
m_recent_buffer.deleteByAge(60.0);
// no need to age messages in anything but m_recent_buffer
}
void ChatBackend::scroll(s32 rows)
{
m_console_buffer.scroll(rows);
}
void ChatBackend::scrollPageDown()
{
m_console_buffer.scroll(m_console_buffer.getRows());
}
void ChatBackend::scrollPageUp()
{
m_console_buffer.scroll(-(s32)m_console_buffer.getRows());
}

309
src/chat.h Normal file
View File

@@ -0,0 +1,309 @@
/*
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 <string>
#include <vector>
#include <list>
#include "irrlichttypes.h"
#include "util/enriched_string.h"
#include "settings.h"
// Chat console related classes
struct ChatLine
{
// age in seconds
f32 age = 0.0f;
// name of sending player, or empty if sent by server
EnrichedString name;
// message text
EnrichedString text;
ChatLine(const std::wstring &a_name, const std::wstring &a_text):
name(a_name),
text(a_text)
{
}
ChatLine(const EnrichedString &a_name, const EnrichedString &a_text):
name(a_name),
text(a_text)
{
}
};
struct ChatFormattedFragment
{
// text string
EnrichedString text;
// starting column
u32 column;
// web link is empty for most frags
std::string weblink;
// formatting
//u8 bold:1;
};
struct ChatFormattedLine
{
// Array of text fragments
std::vector<ChatFormattedFragment> fragments;
// true if first line of one formatted ChatLine
bool first;
};
class ChatBuffer
{
public:
ChatBuffer(u32 scrollback);
~ChatBuffer() = default;
// Append chat line
// Removes oldest chat line if scrollback size is reached
void addLine(const std::wstring &name, const std::wstring &text);
// Remove all chat lines
void clear();
// Get number of lines currently in buffer.
u32 getLineCount() const;
// Get reference to i-th chat line.
const ChatLine& getLine(u32 index) const;
// Increase each chat line's age by dtime.
void step(f32 dtime);
// Delete oldest N chat lines.
void deleteOldest(u32 count);
// Delete lines older than maxAge.
void deleteByAge(f32 maxAge);
// Get number of rows, 0 if reformat has not been called yet.
u32 getRows() const;
// Update console size and reformat all formatted lines.
void reformat(u32 cols, u32 rows);
// Get formatted line for a given row (0 is top of screen).
// Only valid after reformat has been called at least once
const ChatFormattedLine& getFormattedLine(u32 row) const;
// Scrolling in formatted buffer (relative)
// positive rows == scroll up, negative rows == scroll down
void scroll(s32 rows);
// Scrolling in formatted buffer (absolute)
void scrollAbsolute(s32 scroll);
// Scroll to bottom of buffer (newest)
void scrollBottom();
// Functions for keeping track of whether the lines were modified by any
// preceding operations
// If they were not changed, getLineCount() and getLine() output the same as
// before
bool getLinesModified() const { return m_lines_modified; }
void resetLinesModified() { m_lines_modified = false; }
// Format a chat line for the given number of columns.
// Appends the formatted lines to the destination array and
// returns the number of formatted lines.
u32 formatChatLine(const ChatLine& line, u32 cols,
std::vector<ChatFormattedLine>& destination) const;
void resize(u32 scrollback);
protected:
s32 getTopScrollPos() const;
s32 getBottomScrollPos() const;
private:
// Scrollback size
u32 m_scrollback;
// Array of unformatted chat lines
std::vector<ChatLine> m_unformatted;
// Number of character columns in console
u32 m_cols = 0;
// Number of character rows in console
u32 m_rows = 0;
// Scroll position (console's top line index into m_formatted)
s32 m_scroll = 0;
// Array of formatted lines
std::vector<ChatFormattedLine> m_formatted;
// Empty formatted line, for error returns
ChatFormattedLine m_empty_formatted_line;
// Enable clickable chat weblinks
bool m_cache_clickable_chat_weblinks;
// Color of clickable chat weblinks
irr::video::SColor m_cache_chat_weblink_color;
// Whether the lines were modified since last markLinesUnchanged()
// Is always set to true when m_unformatted is modified, because that's what
// determines the output of getLineCount() and getLine()
bool m_lines_modified = true;
};
class ChatPrompt
{
public:
ChatPrompt(const std::wstring &prompt, u32 history_limit);
~ChatPrompt() = default;
// Input character or string
void input(wchar_t ch);
void input(const std::wstring &str);
// Add a string to the history
void addToHistory(const std::wstring &line);
// Get current line
std::wstring getLine() const { return m_line; }
// Get section of line that is currently selected
std::wstring getSelection() const { return m_line.substr(m_cursor, m_cursor_len); }
// Clear the current line
void clear();
// Replace the current line with the given text
std::wstring replace(const std::wstring &line);
// Select previous command from history
void historyPrev();
// Select next command from history
void historyNext();
// Nick completion
void nickCompletion(const std::list<std::string>& names, bool backwards);
// Update console size and reformat the visible portion of the prompt
void reformat(u32 cols);
// Get visible portion of the prompt.
std::wstring getVisiblePortion() const;
// Get cursor position (relative to visible portion). -1 if invalid
s32 getVisibleCursorPosition() const;
// Get length of cursor selection
s32 getCursorLength() const { return m_cursor_len; }
// Cursor operations
enum CursorOp {
CURSOROP_MOVE,
CURSOROP_SELECT,
CURSOROP_DELETE
};
// Cursor operation direction
enum CursorOpDir {
CURSOROP_DIR_LEFT,
CURSOROP_DIR_RIGHT
};
// Cursor operation scope
enum CursorOpScope {
CURSOROP_SCOPE_CHARACTER,
CURSOROP_SCOPE_WORD,
CURSOROP_SCOPE_LINE,
CURSOROP_SCOPE_SELECTION
};
// Cursor operation
// op specifies whether it's a move or delete operation
// dir specifies whether the operation goes left or right
// scope specifies how far the operation will reach (char/word/line)
// Examples:
// cursorOperation(CURSOROP_MOVE, CURSOROP_DIR_RIGHT, CURSOROP_SCOPE_LINE)
// moves the cursor to the end of the line.
// cursorOperation(CURSOROP_DELETE, CURSOROP_DIR_LEFT, CURSOROP_SCOPE_WORD)
// deletes the word to the left of the cursor.
void cursorOperation(CursorOp op, CursorOpDir dir, CursorOpScope scope);
protected:
// set m_view to ensure that 0 <= m_view <= m_cursor < m_view + m_cols
// if line can be fully shown, set m_view to zero
// else, also ensure m_view <= m_line.size() + 1 - m_cols
void clampView();
private:
// Prompt prefix
std::wstring m_prompt = L"";
// Currently edited line
std::wstring m_line = L"";
// History buffer
std::vector<std::wstring> m_history;
// History index (0 <= m_history_index <= m_history.size())
u32 m_history_index = 0;
// Maximum number of history entries
u32 m_history_limit;
// Number of columns excluding columns reserved for the prompt
s32 m_cols = 0;
// Start of visible portion (index into m_line)
s32 m_view = 0;
// Cursor (index into m_line)
s32 m_cursor = 0;
// Cursor length (length of selected portion of line)
s32 m_cursor_len = 0;
// Last nick completion start (index into m_line)
s32 m_nick_completion_start = 0;
// Last nick completion start (index into m_line)
s32 m_nick_completion_end = 0;
};
class ChatBackend
{
public:
ChatBackend();
~ChatBackend() = default;
// Add chat message
void addMessage(const std::wstring &name, std::wstring text);
// Parse and add unparsed chat message
void addUnparsedMessage(std::wstring line);
// Get the console buffer
ChatBuffer& getConsoleBuffer();
// Get the recent messages buffer
ChatBuffer& getRecentBuffer();
// Concatenate all recent messages
EnrichedString getRecentChat() const;
// Get the console prompt
ChatPrompt& getPrompt();
// Reformat all buffers
void reformat(u32 cols, u32 rows);
// Clear all recent messages
void clearRecentChat();
// Age recent messages
void step(float dtime);
// Scrolling
void scroll(s32 rows);
void scrollPageDown();
void scrollPageUp();
// Resize recent buffer based on settings
void applySettings();
private:
ChatBuffer m_console_buffer;
ChatBuffer m_recent_buffer;
ChatPrompt m_prompt;
};

79
src/chat_interface.h Normal file
View File

@@ -0,0 +1,79 @@
/*
Minetest
Copyright (C) 2015 est31 <MTest31@outlook.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 "util/container.h"
#include <string>
#include <queue>
#include "irrlichttypes.h"
enum ChatEventType {
CET_CHAT,
CET_NICK_ADD,
CET_NICK_REMOVE,
CET_TIME_INFO,
};
class ChatEvent {
protected:
ChatEvent(ChatEventType a_type) { type = a_type; }
public:
ChatEventType type;
};
struct ChatEventTimeInfo : public ChatEvent {
ChatEventTimeInfo(
u64 a_game_time,
u32 a_time) :
ChatEvent(CET_TIME_INFO),
game_time(a_game_time),
time(a_time)
{}
u64 game_time;
u32 time;
};
struct ChatEventNick : public ChatEvent {
ChatEventNick(ChatEventType a_type,
const std::string &a_nick) :
ChatEvent(a_type), // one of CET_NICK_ADD, CET_NICK_REMOVE
nick(a_nick)
{}
std::string nick;
};
struct ChatEventChat : public ChatEvent {
ChatEventChat(const std::string &a_nick,
const std::wstring &an_evt_msg) :
ChatEvent(CET_CHAT),
nick(a_nick),
evt_msg(an_evt_msg)
{}
std::string nick;
std::wstring evt_msg;
};
struct ChatInterface {
MutexedQueue<ChatEvent *> command_queue; // chat backend --> server
MutexedQueue<ChatEvent *> outgoing_queue; // server --> chat backend
};

49
src/chatmessage.h Normal file
View File

@@ -0,0 +1,49 @@
/*
Minetest
Copyright (C) 2017 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
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 <ctime>
enum ChatMessageType
{
CHATMESSAGE_TYPE_RAW = 0,
CHATMESSAGE_TYPE_NORMAL = 1,
CHATMESSAGE_TYPE_ANNOUNCE = 2,
CHATMESSAGE_TYPE_SYSTEM = 3,
CHATMESSAGE_TYPE_MAX = 4,
};
struct ChatMessage
{
ChatMessage(const std::wstring &m = L"") : message(m) {}
ChatMessage(ChatMessageType t, const std::wstring &m, const std::wstring &s = L"",
std::time_t ts = std::time(0)) :
type(t),
message(m), sender(s), timestamp(ts)
{
}
ChatMessageType type = CHATMESSAGE_TYPE_RAW;
std::wstring message = L"";
std::wstring sender = L"";
std::time_t timestamp = std::time(0);
};

66
src/client/CMakeLists.txt Normal file
View File

@@ -0,0 +1,66 @@
set(sound_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/sound.cpp)
if(USE_SOUND)
set(sound_SRCS ${sound_SRCS}
${CMAKE_CURRENT_SOURCE_DIR}/sound_openal.cpp)
set(SOUND_INCLUDE_DIRS
${OPENAL_INCLUDE_DIR}
${VORBIS_INCLUDE_DIR}
${OGG_INCLUDE_DIR}
PARENT_SCOPE)
set(SOUND_LIBRARIES
${OPENAL_LIBRARY}
${VORBIS_LIBRARIES}
PARENT_SCOPE)
endif()
set(client_SRCS
${sound_SRCS}
${CMAKE_CURRENT_SOURCE_DIR}/meshgen/collector.cpp
${CMAKE_CURRENT_SOURCE_DIR}/render/anaglyph.cpp
${CMAKE_CURRENT_SOURCE_DIR}/render/core.cpp
${CMAKE_CURRENT_SOURCE_DIR}/render/factory.cpp
${CMAKE_CURRENT_SOURCE_DIR}/render/interlaced.cpp
${CMAKE_CURRENT_SOURCE_DIR}/render/pageflip.cpp
${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}/activeobjectmgr.cpp
${CMAKE_CURRENT_SOURCE_DIR}/camera.cpp
${CMAKE_CURRENT_SOURCE_DIR}/client.cpp
${CMAKE_CURRENT_SOURCE_DIR}/clientenvironment.cpp
${CMAKE_CURRENT_SOURCE_DIR}/clientlauncher.cpp
${CMAKE_CURRENT_SOURCE_DIR}/clientmap.cpp
${CMAKE_CURRENT_SOURCE_DIR}/clientmedia.cpp
${CMAKE_CURRENT_SOURCE_DIR}/clientobject.cpp
${CMAKE_CURRENT_SOURCE_DIR}/clouds.cpp
${CMAKE_CURRENT_SOURCE_DIR}/content_cao.cpp
${CMAKE_CURRENT_SOURCE_DIR}/content_cso.cpp
${CMAKE_CURRENT_SOURCE_DIR}/content_mapblock.cpp
${CMAKE_CURRENT_SOURCE_DIR}/filecache.cpp
${CMAKE_CURRENT_SOURCE_DIR}/fontengine.cpp
${CMAKE_CURRENT_SOURCE_DIR}/game.cpp
${CMAKE_CURRENT_SOURCE_DIR}/gameui.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiscalingfilter.cpp
${CMAKE_CURRENT_SOURCE_DIR}/hud.cpp
${CMAKE_CURRENT_SOURCE_DIR}/imagefilters.cpp
${CMAKE_CURRENT_SOURCE_DIR}/inputhandler.cpp
${CMAKE_CURRENT_SOURCE_DIR}/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
${CMAKE_CURRENT_SOURCE_DIR}/shadows/dynamicshadows.cpp
${CMAKE_CURRENT_SOURCE_DIR}/shadows/dynamicshadowsrender.cpp
${CMAKE_CURRENT_SOURCE_DIR}/shadows/shadowsshadercallbacks.cpp
${CMAKE_CURRENT_SOURCE_DIR}/shadows/shadowsScreenQuad.cpp
PARENT_SCOPE
)

View File

@@ -0,0 +1,109 @@
/*
Minetest
Copyright (C) 2010-2018 nerzhul, Loic BLOT <loic.blot@unix-experience.fr>
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 <log.h>
#include "profiler.h"
#include "activeobjectmgr.h"
namespace client
{
void ActiveObjectMgr::clear()
{
// delete active objects
for (auto &active_object : m_active_objects) {
delete active_object.second;
// Object must be marked as gone when children try to detach
active_object.second = nullptr;
}
m_active_objects.clear();
}
void ActiveObjectMgr::step(
float dtime, const std::function<void(ClientActiveObject *)> &f)
{
g_profiler->avg("ActiveObjectMgr: CAO count [#]", m_active_objects.size());
for (auto &ao_it : m_active_objects) {
f(ao_it.second);
}
}
// clang-format off
bool ActiveObjectMgr::registerObject(ClientActiveObject *obj)
{
assert(obj); // Pre-condition
if (obj->getId() == 0) {
u16 new_id = getFreeId();
if (new_id == 0) {
infostream << "Client::ActiveObjectMgr::registerObject(): "
<< "no free id available" << std::endl;
delete obj;
return false;
}
obj->setId(new_id);
}
if (!isFreeId(obj->getId())) {
infostream << "Client::ActiveObjectMgr::registerObject(): "
<< "id is not free (" << obj->getId() << ")" << std::endl;
delete obj;
return false;
}
infostream << "Client::ActiveObjectMgr::registerObject(): "
<< "added (id=" << obj->getId() << ")" << std::endl;
m_active_objects[obj->getId()] = obj;
return true;
}
void ActiveObjectMgr::removeObject(u16 id)
{
verbosestream << "Client::ActiveObjectMgr::removeObject(): "
<< "id=" << id << std::endl;
ClientActiveObject *obj = getActiveObject(id);
if (!obj) {
infostream << "Client::ActiveObjectMgr::removeObject(): "
<< "id=" << id << " not found" << std::endl;
return;
}
m_active_objects.erase(id);
obj->removeFromScene(true);
delete obj;
}
// clang-format on
void ActiveObjectMgr::getActiveObjects(const v3f &origin, f32 max_d,
std::vector<DistanceSortedActiveObject> &dest)
{
f32 max_d2 = max_d * max_d;
for (auto &ao_it : m_active_objects) {
ClientActiveObject *obj = ao_it.second;
f32 d2 = (obj->getPosition() - origin).getLengthSQ();
if (d2 > max_d2)
continue;
dest.emplace_back(obj, d2);
}
}
} // namespace client

View File

@@ -0,0 +1,41 @@
/*
Minetest
Copyright (C) 2010-2018 nerzhul, Loic BLOT <loic.blot@unix-experience.fr>
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 <functional>
#include <vector>
#include "../activeobjectmgr.h"
#include "clientobject.h"
namespace client
{
class ActiveObjectMgr : public ::ActiveObjectMgr<ClientActiveObject>
{
public:
void clear();
void step(float dtime,
const std::function<void(ClientActiveObject *)> &f) override;
bool registerObject(ClientActiveObject *obj) override;
void removeObject(u16 id) override;
void getActiveObjects(const v3f &origin, f32 max_d,
std::vector<DistanceSortedActiveObject> &dest);
};
} // namespace client

708
src/client/camera.cpp Normal file
View File

@@ -0,0 +1,708 @@
/*
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 "config.h"
#include "map.h"
#include "clientmap.h" // MapDrawControl
#include "player.h"
#include <cmath>
#include "client/renderingengine.h"
#include "client/content_cao.h"
#include "settings.h"
#include "wieldmesh.h"
#include "noise.h" // easeCurve
#include "sound.h"
#include "mtevent.h"
#include "nodedef.h"
#include "util/numeric.h"
#include "constants.h"
#include "fontengine.h"
#include "script/scripting_client.h"
#include "gettext.h"
#define CAMERA_OFFSET_STEP 200
#define WIELDMESH_OFFSET_X 55.0f
#define WIELDMESH_OFFSET_Y -35.0f
#define WIELDMESH_AMPLITUDE_X 7.0f
#define WIELDMESH_AMPLITUDE_Y 10.0f
Camera::Camera(MapDrawControl &draw_control, Client *client, RenderingEngine *rendering_engine):
m_draw_control(draw_control),
m_client(client),
m_player_light_color(0xFFFFFFFF)
{
auto smgr = rendering_engine->get_scene_manager();
// note: making the camera node a child of the player node
// 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", 0.0f, 100.0f);
m_cache_view_bobbing_amount = g_settings->getFloat("view_bobbing_amount", 0.0f, 7.9f);
// 45 degrees is the lowest FOV that doesn't cause the server to treat this
// as a zoom FOV and load world beyond the set server limits.
m_cache_fov = g_settings->getFloat("fov", 45.0f, 160.0f);
m_arm_inertia = g_settings->getBool("arm_inertia");
m_nametags.clear();
m_show_nametag_backgrounds = g_settings->getBool("show_nametag_backgrounds");
}
Camera::~Camera()
{
m_wieldmgr->drop();
}
void Camera::notifyFovChange()
{
LocalPlayer *player = m_client->getEnv().getLocalPlayer();
assert(player);
PlayerFovSpec spec = player->getFov();
/*
* Update m_old_fov_degrees first - it serves as the starting point of the
* upcoming transition.
*
* If an FOV transition is already active, mark current FOV as the start of
* the new transition. If not, set it to the previous transition's target FOV.
*/
if (m_fov_transition_active)
m_old_fov_degrees = m_curr_fov_degrees;
else
m_old_fov_degrees = m_server_sent_fov ? m_target_fov_degrees : m_cache_fov;
/*
* Update m_server_sent_fov next - it corresponds to the target FOV of the
* upcoming transition.
*
* Set it to m_cache_fov, if server-sent FOV is 0. Otherwise check if
* server-sent FOV is a multiplier, and multiply it with m_cache_fov instead
* of overriding.
*/
if (spec.fov == 0.0f) {
m_server_sent_fov = false;
m_target_fov_degrees = m_cache_fov;
} else {
m_server_sent_fov = true;
m_target_fov_degrees = spec.is_multiplier ? m_cache_fov * spec.fov : spec.fov;
}
if (spec.transition_time > 0.0f)
m_fov_transition_active = true;
// If FOV smooth transition is active, initialize required variables
if (m_fov_transition_active) {
m_transition_time = spec.transition_time;
m_fov_diff = m_target_fov_degrees - m_old_fov_degrees;
}
}
// 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);
m_wieldnode->setNodeLightColor(m_player_light_color);
}
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;
} else 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 - (WIELDMESH_AMPLITUDE_X * 0.5f),
WIELDMESH_OFFSET_X + (WIELDMESH_AMPLITUDE_X * 0.5f));
}
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 - (WIELDMESH_AMPLITUDE_Y * 0.5f),
WIELDMESH_OFFSET_Y + (WIELDMESH_AMPLITUDE_Y * 0.5f));
}
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.35f * (std::min(15.0f, m_cam_vel_old.X) * (1.0f +
(1.0f - m_arm_dir.X))) * (gap_X / 20.0f);
f32 dec_Y = 0.25f * (std::min(15.0f, m_cam_vel_old.Y) * (1.0f +
(1.0f - m_arm_dir.Y))) * (gap_Y / 15.0f);
if (gap_X < 0.1f)
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 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();
// This is worse than `LocalPlayer::getPosition()` but
// mods expect the player head to be at the parent's position
// plus eye height.
if (player->getParent())
player_position = player->getParent()->getPosition();
// Smooth the camera movement after the player instantly moves upward due to stepheight.
// The smoothing usually continues until the camera position reaches the player position.
float player_stepheight = player->getCAO() ? player->getCAO()->getStepHeight() : HUGE_VALF;
float upward_movement = player_position.Y - old_player_position.Y;
if (upward_movement < 0.01f || upward_movement > player_stepheight) {
m_stepheight_smooth_active = false;
} else if (player->touching_ground) {
m_stepheight_smooth_active = true;
}
if (m_stepheight_smooth_active) {
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
if (player->camera_impact > 0.0f)
fall_bobbing *= (1 - rangelim(50 / player->camera_impact, 0, 1)) * 5;
fall_bobbing *= m_cache_fall_bobbing_amount;
}
// Calculate and translate the head SceneNode offsets
{
v3f eye_offset = player->getEyeOffset();
if (m_camera_mode == CAMERA_MODE_FIRST)
eye_offset += player->eye_offset_first;
else
eye_offset += player->eye_offset_third;
// Set head node transformation
eye_offset.Y += cameratilt * -player->hurt_tilt_strength + fall_bobbing;
m_headnode->setPosition(eye_offset);
m_headnode->setRotation(v3f(player->getPitch(), 0,
cameratilt * player->hurt_tilt_strength));
m_headnode->updateAbsolutePosition();
}
// Compute relative camera position and target
v3f rel_cam_pos = v3f(0,0,0);
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;
f32 bobknob = 1.2;
f32 bobtmp = sin(pow(bobfrac, bobknob) * M_PI);
v3f bobvec = v3f(
0.3 * bobdir * sin(bobfrac * M_PI),
-0.28 * bobtmp * bobtmp,
0.);
rel_cam_pos += bobvec * m_cache_view_bobbing_amount;
rel_cam_target += bobvec * m_cache_view_bobbing_amount;
rel_cam_up.rotateXYBy(-0.03 * bobdir * bobtmp * M_PI * m_cache_view_bobbing_amount);
}
// 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()
.getNode(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;
/*
* Apply server-sent FOV, instantaneous or smooth transition.
* If not, check for zoom and set to zoom FOV.
* Otherwise, default to m_cache_fov.
*/
if (m_fov_transition_active) {
// Smooth FOV transition
// Dynamically calculate FOV delta based on frametimes
f32 delta = (frametime / m_transition_time) * m_fov_diff;
m_curr_fov_degrees += delta;
// Mark transition as complete if target FOV has been reached
if ((m_fov_diff > 0.0f && m_curr_fov_degrees >= m_target_fov_degrees) ||
(m_fov_diff < 0.0f && m_curr_fov_degrees <= m_target_fov_degrees)) {
m_fov_transition_active = false;
m_curr_fov_degrees = m_target_fov_degrees;
}
} else if (m_server_sent_fov) {
// Instantaneous FOV change
m_curr_fov_degrees = m_target_fov_degrees;
} else if (player->getPlayerControl().zoom && player->getZoomFOV() > 0.001f) {
// Player requests zoom, apply zoom FOV
m_curr_fov_degrees = player->getZoomFOV();
} else {
// Set to client's selected FOV
m_curr_fov_degrees = m_cache_fov;
}
m_curr_fov_degrees = rangelim(m_curr_fov_degrees, 1.0f, 160.0f);
// FOV and aspect ratio
const v2u32 &window_size = RenderingEngine::getWindowSize();
m_aspect = (f32) window_size.X / (f32) window_size.Y;
m_fov_y = m_curr_fov_degrees * M_PI / 180.0;
// Increase vertical FOV on lower aspect ratios (<16:10)
m_fov_y *= core::clamp(sqrt(16./10. / m_aspect), 1.0, 1.4);
m_fov_x = 2 * atan(m_aspect * tan(0.5 * m_fov_y));
m_cameranode->setAspectRatio(m_aspect);
m_cameranode->setFOV(m_fov_y);
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_player_light_color = player->light_color;
m_wieldnode->setNodeLightColor(m_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;
const bool flying = g_settings->getBool("free_move")
&& m_client->checkLocalPrivilege("fly");
if ((walking || swimming || climbing) && !flying) {
// Start animation
m_view_bobbing_state = 1;
m_view_bobbing_speed = MYMIN(speed.getLength(), 70);
} else if (m_view_bobbing_state == 1) {
// Stop animation
m_view_bobbing_state = 2;
m_view_bobbing_speed = 60;
}
}
void Camera::updateViewingRange()
{
f32 viewing_range = g_settings->getFloat("viewing_range");
// Ignore near_plane setting on all other platforms to prevent abuse
#if ENABLE_GLES
m_cameranode->setNearValue(rangelim(
g_settings->getFloat("near_plane"), 0.0f, 0.25f) * BS);
#else
m_cameranode->setNearValue(0.1f * BS);
#endif
m_draw_control.wanted_range = std::fmin(adjustDist(viewing_range, getFovMax()), 4000);
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 stays in front of world geometry
m_wieldmgr->getVideoDriver()->clearBuffers(video::ECBF_DEPTH);
// Draw the wielded node (in a separate scene manager)
scene::ICameraSceneNode* cam = m_wieldmgr->getActiveCamera();
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();
gui::IGUIFont *font = g_fontengine->getFont();
video::IVideoDriver *driver = RenderingEngine::get_video_driver();
v2u32 screensize = driver->getScreenSize();
for (const Nametag *nametag : m_nametags) {
// Nametags are hidden in GenericCAO::updateNametag()
v3f pos = nametag->parent_node->getAbsolutePosition() + nametag->pos * BS;
f32 transformed_pos[4] = { pos.X, pos.Y, pos.Z, 1.0f };
trans.multiplyWith1x4Matrix(transformed_pos);
if (transformed_pos[3] > 0) {
std::wstring nametag_colorless =
unescape_translate(utf8_to_wide(nametag->text));
core::dimension2d<u32> textsize = font->getDimension(
nametag_colorless.c_str());
f32 zDiv = transformed_pos[3] == 0.0f ? 1.0f :
core::reciprocal(transformed_pos[3]);
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);
core::rect<s32> bg_size(-2, 0, textsize.Width+2, textsize.Height);
auto bgcolor = nametag->getBgColor(m_show_nametag_backgrounds);
if (bgcolor.getAlpha() != 0)
driver->draw2DRectangle(bgcolor, bg_size + screen_pos);
font->draw(
translate_string(utf8_to_wide(nametag->text)).c_str(),
size + screen_pos, nametag->textcolor);
}
}
}
Nametag *Camera::addNametag(scene::ISceneNode *parent_node,
const std::string &text, video::SColor textcolor,
Optional<video::SColor> bgcolor, const v3f &pos)
{
Nametag *nametag = new Nametag(parent_node, text, textcolor, bgcolor, pos);
m_nametags.push_back(nametag);
return nametag;
}
void Camera::removeNametag(Nametag *nametag)
{
m_nametags.remove(nametag);
delete nametag;
}

270
src/client/camera.h Normal file
View File

@@ -0,0 +1,270 @@
/*
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>
#include "util/Optional.h"
class LocalPlayer;
struct MapDrawControl;
class Client;
class RenderingEngine;
class WieldMeshSceneNode;
struct Nametag
{
scene::ISceneNode *parent_node;
std::string text;
video::SColor textcolor;
Optional<video::SColor> bgcolor;
v3f pos;
Nametag(scene::ISceneNode *a_parent_node,
const std::string &text,
const video::SColor &textcolor,
const Optional<video::SColor> &bgcolor,
const v3f &pos):
parent_node(a_parent_node),
text(text),
textcolor(textcolor),
bgcolor(bgcolor),
pos(pos)
{
}
video::SColor getBgColor(bool use_fallback) const
{
if (bgcolor)
return bgcolor.value();
else if (!use_fallback)
return video::SColor(0, 0, 0, 0);
else if (textcolor.getLuminance() > 186)
// Dark background for light text
return video::SColor(50, 50, 50, 50);
else
// Light background for dark text
return video::SColor(50, 255, 255, 255);
}
};
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, RenderingEngine *rendering_engine);
~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;
}
// Returns the absolute position of the head SceneNode in the world
inline v3f getHeadPosition() const
{
return m_headnode->getAbsolutePosition();
}
// 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);
}
// Notify about new server-sent FOV and initialize smooth FOV transition
void notifyFovChange();
// Step the camera: updates the viewing range and view bobbing.
void step(f32 dtime);
// Update the camera from the local player's position.
void update(LocalPlayer* player, f32 frametime, 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 &text, video::SColor textcolor,
Optional<video::SColor> bgcolor, const v3f &pos);
void removeNametag(Nametag *nametag);
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;
// Default Client FOV (as defined by the "fov" setting)
f32 m_cache_fov;
// Absolute camera position
v3f m_camera_position;
// Absolute camera direction
v3f m_camera_direction;
// Camera offset
v3s16 m_camera_offset;
bool m_stepheight_smooth_active = false;
// Server-sent FOV variables
bool m_server_sent_fov = false;
f32 m_curr_fov_degrees, m_old_fov_degrees, m_target_fov_degrees;
// FOV transition variables
bool m_fov_transition_active = false;
f32 m_fov_diff, m_transition_time;
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;
bool m_arm_inertia;
std::list<Nametag *> m_nametags;
bool m_show_nametag_backgrounds;
// Last known light color of the player
video::SColor m_player_light_color;
};

2065
src/client/client.cpp Normal file

File diff suppressed because it is too large Load Diff

609
src/client/client.h Normal file
View File

@@ -0,0 +1,609 @@
/*
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 "gameparams.h"
#include <fstream>
#define CLIENT_CHAT_MESSAGE_LIMIT_PER_10S 10.0f
struct ClientEvent;
struct MeshMakeData;
struct ChatMessage;
class MapBlockMesh;
class RenderingEngine;
class IWritableTextureSource;
class IWritableShaderSource;
class IWritableItemDefManager;
class ISoundManager;
class NodeDefManager;
//class IWritableCraftDefManager;
class ClientMediaDownloader;
class SingleMediaDownloader;
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)
{
auto n = m_packets.find(command);
if (n == m_packets.end())
m_packets[command] = 1;
else
n->second++;
}
void clear()
{
m_packets.clear();
}
u32 sum() const;
void print(std::ostream &o) const;
private:
// command, count
std::map<u16, u32> 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,
RenderingEngine *rendering_engine,
bool ipv6,
GameUI *game_ui,
ELoginRegister allow_login_or_register
);
~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_NodemetaChanged(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_Fov(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_HudSetSun(NetworkPacket* pkt);
void handleCommand_HudSetMoon(NetworkPacket* pkt);
void handleCommand_HudSetStars(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 handleCommand_PlayerSpeed(NetworkPacket *pkt);
void handleCommand_MediaPush(NetworkPacket *pkt);
void handleCommand_MinimapModes(NetworkPacket *pkt);
void handleCommand_SetLighting(NetworkPacket *pkt);
void ProcessData(NetworkPacket *pkt);
void Send(NetworkPacket* pkt);
void interact(InteractAction 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(u16 damage);
void sendRespawn();
void sendReady();
void sendHaveMedia(const std::vector<u32> &tokens);
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);
// helpers to enforce CSM restrictions
MapNode CSMGetNode(v3s16 p, bool *is_valid_position);
int CSMClampRadius(v3s16 pos, int radius);
v3s16 CSMClampPos(v3s16 pos);
void addNode(v3s16 p, MapNode n, bool remove_metadata = true);
void setPlayerControl(PlayerControl &control);
// 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 updateWieldedItem();
/* InventoryManager interface */
Inventory* getInventory(const InventoryLocation &loc) override;
void inventoryAction(InventoryAction *a) override;
// Send the item number 'item' as player item to the server
void setPlayerItem(u16 item);
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;
}
inline void setFatalError(const LuaError &e)
{
setFatalError(std::string("Lua: ") + e.what());
}
// 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() const
{ return m_itemdef_received; }
bool nodedefReceived() const
{ return m_nodedef_received; }
bool mediaReceived() const
{ return !m_media_downloader; }
bool activeObjectsReceived() const
{ return m_activeobjects_received; }
u16 getProtoVersion()
{ return m_proto_ver; }
bool m_simple_singleplayer_mode;
float mediaReceiveProgress();
void afterContentReceived();
void showUpdateProgressTexture(void *args, u32 progress, u32 max_progress);
float getRTT();
float getCurRate();
Minimap* getMinimap() { return m_minimap; }
void setCamera(Camera* camera) { m_camera = camera; }
Camera* getCamera () { return m_camera; }
scene::ISceneManager *getSceneManager();
bool shouldShowMinimap() const;
// IGameDef interface
IItemDefManager* getItemDefManager() override;
const NodeDefManager* getNodeDefManager() override;
ICraftDefManager* getCraftDefManager() override;
ITextureSource* getTextureSource();
virtual IWritableShaderSource* 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(std::string filename);
ModMetadataDatabase *getModStorageDatabase() override { return m_mod_storage_database; }
bool registerModStorage(ModMetadata *meta) override;
void unregisterModStorage(const std::string &name) override;
// Migrates away old files-based mod storage if necessary
void migrateModStorage();
// 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,
bool from_media_push = false);
// 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; }
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 u64 getCSMRestrictionFlags() const
{
return m_csm_restriction_flags;
}
inline bool checkCSMRestrictionFlag(CSMRestrictionFlags flag) const
{
return m_csm_restriction_flags & flag;
}
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();
// 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 sendPlayerPos();
void deleteAuthData();
// helper method shared with clientpackethandler
static AuthMechanism choseAuthMech(const u32 mechs);
void sendInit(const std::string &playerName);
void startAuth(AuthMechanism chosen_auth_mechanism);
void sendDeletedBlocks(std::vector<v3s16> &blocks);
void sendGotBlocks(const std::vector<v3s16> &blocks);
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;
RenderingEngine *m_rendering_engine;
MeshUpdateThread m_mesh_update_thread;
ClientEnvironment m_env;
ParticleManager m_particle_manager;
std::unique_ptr<con::Connection> m_con;
std::string m_address_name;
ELoginRegister m_allow_login_or_register = ELoginRegister::Any;
Camera *m_camera = nullptr;
Minimap *m_minimap = nullptr;
bool m_minimap_disabled_by_server = false;
// 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.
u16 m_proto_ver = 0;
bool m_update_wielded_item = 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_activeobjects_received = false;
bool m_mods_loaded = false;
std::vector<std::string> m_remote_media_servers;
// Media downloader, only exists during init
ClientMediaDownloader *m_media_downloader;
// Set of media filenames pushed by server at runtime
std::unordered_set<std::string> m_media_pushed_files;
// Pending downloads of dynamic media (key: token)
std::vector<std::pair<u32, std::shared_ptr<SingleMediaDownloader>>> m_pending_media_downloads;
// 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;
// Relation of client id to object id
std::unordered_map<int, u16> m_sounds_to_objects;
// 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;
// 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;
// Client modding
ClientScripting *m_script = nullptr;
std::unordered_map<std::string, ModMetadata *> m_mod_storages;
ModMetadataDatabase *m_mod_storage_database = nullptr;
float m_mod_storage_save_timer = 10.0f;
std::vector<ModSpec> m_mods;
StringMap m_mod_vfs;
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;
};

View File

@@ -0,0 +1,515 @@
/*
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 "mtevent.h"
#include "collision.h"
#include "nodedef.h"
#include "profiler.h"
#include "raycast.h"
#include "voxelalgorithms.h"
#include "settings.h"
#include "shader.h"
#include "content_cao.h"
#include <algorithm>
#include "client/renderingengine.h"
/*
CAOShaderConstantSetter
*/
//! Shader constant setter for passing material emissive color to the CAO object_shader
class CAOShaderConstantSetter : public IShaderConstantSetter
{
public:
CAOShaderConstantSetter():
m_emissive_color_setting("emissiveColor")
{}
~CAOShaderConstantSetter() override = default;
void onSetConstants(video::IMaterialRendererServices *services) override
{
// Ambient color
video::SColorf emissive_color(m_emissive_color);
float as_array[4] = {
emissive_color.r,
emissive_color.g,
emissive_color.b,
emissive_color.a,
};
m_emissive_color_setting.set(as_array, services);
}
void onSetMaterial(const video::SMaterial& material) override
{
m_emissive_color = material.EmissiveColor;
}
private:
video::SColor m_emissive_color;
CachedPixelShaderSetting<float, 4> m_emissive_color_setting;
};
class CAOShaderConstantSetterFactory : public IShaderConstantSetterFactory
{
public:
CAOShaderConstantSetterFactory()
{}
virtual IShaderConstantSetter* create()
{
return new CAOShaderConstantSetter();
}
};
/*
ClientEnvironment
*/
ClientEnvironment::ClientEnvironment(ClientMap *map,
ITextureSource *texturesource, Client *client):
Environment(client),
m_map(map),
m_texturesource(texturesource),
m_client(client)
{
auto *shdrsrc = m_client->getShaderSource();
shdrsrc->addShaderConstantSetterFactory(new CAOShaderConstantSetterFactory());
}
ClientEnvironment::~ClientEnvironment()
{
m_ao_manager.clear();
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;
/*
Stuff that has a maximum time increment
*/
u32 steps = ceil(dtime / dtime_max_increment);
f32 dtime_part = dtime / steps;
for (; steps > 0; --steps) {
/*
Local player handling
*/
// Control local player
lplayer->applyControl(dtime_part, this);
// Apply physics
if (!free_move) {
// Gravity
v3f speed = lplayer->getSpeed();
if (!is_climbing && !lplayer->in_liquid)
speed.Y -= lplayer->movement_gravity *
lplayer->physics_override_gravity * dtime_part * 2.0f;
// Liquid floating / sinking
if (!is_climbing && lplayer->in_liquid &&
!lplayer->swimming_vertical &&
!lplayer->swimming_pitch)
speed.Y -= lplayer->movement_liquid_sink * dtime_part * 2.0f;
// Movement resistance
if (lplayer->move_resistance > 0) {
// How much the node's move_resistance blocks movement, ranges
// between 0 and 1. Should match the scale at which liquid_viscosity
// increase affects other liquid attributes.
static const f32 resistance_factor = 0.3f;
v3f d_wanted;
bool in_liquid_stable = lplayer->in_liquid_stable || lplayer->in_liquid;
if (in_liquid_stable) {
d_wanted = -speed / lplayer->movement_liquid_fluidity;
} else {
d_wanted = -speed / BS;
}
f32 dl = d_wanted.getLength();
if (in_liquid_stable) {
if (dl > lplayer->movement_liquid_fluidity_smooth)
dl = lplayer->movement_liquid_fluidity_smooth;
}
dl *= (lplayer->move_resistance * resistance_factor) +
(1 - resistance_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);
}
bool player_immortal = false;
f32 player_fall_factor = 1.0f;
GenericCAO *playercao = lplayer->getCAO();
if (playercao) {
player_immortal = playercao->isImmortal();
int addp_p = itemgroup_get(playercao->getGroups(),
"fall_damage_add_percent");
// convert armor group into an usable fall damage factor
player_fall_factor = 1.0f + (float)addp_p / 100.0f;
}
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
if (info.type == COLLISION_NODE) {
const ContentFeatures &f = m_client->ndef()->
get(m_map->getNode(info.node_p));
// Determine fall damage modifier
int addp_n = itemgroup_get(f.groups, "fall_damage_add_percent");
// convert node group to an usable fall damage factor
f32 node_fall_factor = 1.0f + (float)addp_n / 100.0f;
// combine both player fall damage modifiers
pre_factor = node_fall_factor * player_fall_factor;
}
float speed = pre_factor * speed_diff.getLength();
if (speed > tolerance && !player_immortal && pre_factor > 0.0f) {
f32 damage_f = (speed - tolerance) / BS;
u16 damage = (u16)MYMIN(damage_f + 0.5, U16_MAX);
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->getNode(p);
u16 light = getInteriorLight(node_at_lplayer, 0, m_client->ndef());
lplayer->light_color = encode_light(light, 0); // this transfers light.alpha
final_color_blend(&lplayer->light_color, light, day_night_ratio);
}
/*
Step active objects and update lighting of them
*/
bool update_lighting = m_active_object_light_update_interval.step(dtime, 0.21);
auto cb_state = [this, dtime, update_lighting, day_night_ratio] (ClientActiveObject *cao) {
// Step object
cao->step(dtime, this);
if (update_lighting)
cao->updateLight(day_night_ratio);
};
m_ao_manager.step(dtime, cb_state);
/*
Step and handle simple objects
*/
g_profiler->avg("ClientEnv: CSO count [#]", m_simple_objects.size());
for (auto i = m_simple_objects.begin(); i != m_simple_objects.end();) {
ClientSimpleObject *simple = *i;
simple->step(dtime);
if(simple->m_to_be_removed) {
delete simple;
i = m_simple_objects.erase(i);
}
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;
}
u16 ClientEnvironment::addActiveObject(ClientActiveObject *object)
{
// Register object. If failed return zero id
if (!m_ao_manager.registerObject(object))
return 0;
object->addToScene(m_texturesource, m_client->getSceneManager());
// Update lighting immediately
object->updateLight(getDayNightRatio());
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;
}
u16 new_id = addActiveObject(obj);
// Object initialized:
if ((obj = getActiveObject(new_id))) {
// Final step is to update all children which are already known
// Data provided by AO_CMD_SPAWN_INFANT
const auto &children = obj->getAttachmentChildIds();
for (auto c_id : children) {
if (auto *o = getActiveObject(c_id))
o->updateAttachments();
}
}
}
void ClientEnvironment::removeActiveObject(u16 id)
{
// Get current attachment childs to detach them visually
std::unordered_set<int> attachment_childs;
if (auto *obj = getActiveObject(id))
attachment_childs = obj->getAttachmentChildIds();
m_ao_manager.removeObject(id);
// Perform a proper detach in Irrlicht
for (auto c_id : attachment_childs) {
if (ClientActiveObject *child = getActiveObject(c_id))
child->updateAttachments();
}
}
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(u16 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
*/
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,
&current_intersection, &current_normal)) {
objects.emplace_back((s16) obj->getId(), current_intersection, current_normal,
(current_intersection - shootline_on_map.start).getLengthSQ());
}
}
}

View File

@@ -0,0 +1,156 @@
/*
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"
#include "activeobjectmgr.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{
u16 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)
{
return m_ao_manager.getActiveObject(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(u16 damage, bool handle_hp=true);
/*
Client likes to call these
*/
// Get all nearby objects
void getActiveObjects(const v3f &origin, f32 max_d,
std::vector<DistanceSortedActiveObject> &dest)
{
return m_ao_manager.getActiveObjects(origin, max_d, 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
);
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;
client::ActiveObjectMgr m_ao_manager;
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;
};

148
src/client/clientevent.h Normal file
View File

@@ -0,0 +1,148 @@
/*
Minetest
Copyright (C) 2017 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
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 "irrlichttypes_bloated.h"
struct ParticleParameters;
struct ParticleSpawnerParameters;
struct SkyboxParams;
struct SunParams;
struct MoonParams;
struct StarParams;
enum ClientEventType : u8
{
CE_NONE,
CE_PLAYER_DAMAGE,
CE_PLAYER_FORCE_MOVE,
CE_DEATHSCREEN,
CE_SHOW_FORMSPEC,
CE_SHOW_LOCAL_FORMSPEC,
CE_SPAWN_PARTICLE,
CE_ADD_PARTICLESPAWNER,
CE_DELETE_PARTICLESPAWNER,
CE_HUDADD,
CE_HUDRM,
CE_HUDCHANGE,
CE_SET_SKY,
CE_SET_SUN,
CE_SET_MOON,
CE_SET_STARS,
CE_OVERRIDE_DAY_NIGHT_RATIO,
CE_CLOUD_PARAMS,
CLIENTEVENT_MAX,
};
struct ClientEventHudAdd
{
u32 server_id;
u8 type;
v2f pos, scale;
std::string name;
std::string text, text2;
u32 number, item, dir, style;
v2f align, offset;
v3f world_pos;
v2s32 size;
s16 z_index;
};
struct ClientEventHudChange
{
u32 id;
HudElementStat stat;
v2f v2fdata;
std::string sdata;
u32 data;
v3f v3fdata;
v2s32 v2s32data;
};
struct ClientEvent
{
ClientEventType type;
union
{
// struct{
//} none;
struct
{
u16 amount;
bool effect;
} player_damage;
struct
{
f32 pitch;
f32 yaw;
} player_force_move;
struct
{
bool set_camera_point_target;
f32 camera_point_target_x;
f32 camera_point_target_y;
f32 camera_point_target_z;
} deathscreen;
struct
{
std::string *formspec;
std::string *formname;
} show_formspec;
// struct{
//} textures_updated;
ParticleParameters *spawn_particle;
struct
{
ParticleSpawnerParameters *p;
u16 attached_id;
u64 id;
} add_particlespawner;
struct
{
u32 id;
} delete_particlespawner;
ClientEventHudAdd *hudadd;
struct
{
u32 id;
} hudrm;
ClientEventHudChange *hudchange;
SkyboxParams *set_sky;
struct
{
bool do_override;
float ratio_f;
} override_day_night_ratio;
struct
{
f32 density;
u32 color_bright;
u32 color_ambient;
f32 height;
f32 thickness;
f32 speed_x;
f32 speed_y;
} cloud_params;
SunParams *sun_params;
MoonParams *moon_params;
StarParams *star_params;
};
};

View File

@@ -0,0 +1,662 @@
/*
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 "gui/mainmenumanager.h"
#include "clouds.h"
#include "server.h"
#include "filesys.h"
#include "gui/guiMainMenu.h"
#include "game.h"
#include "player.h"
#include "chat.h"
#include "gettext.h"
#include "profiler.h"
#include "serverlist.h"
#include "gui/guiEngine.h"
#include "fontengine.h"
#include "clientlauncher.h"
#include "version.h"
#include "renderingengine.h"
#include "network/networkexceptions.h"
#if USE_SOUND
#include "sound_openal.h"
#endif
/* mainmenumanager.h
*/
gui::IGUIEnvironment *guienv = nullptr;
gui::IGUIStaticText *guiroot = nullptr;
MainMenuManager g_menumgr;
bool isMenuActive()
{
return g_menumgr.menuCount() != 0;
}
// Passed to menus to allow disconnecting and exiting
MainGameCallback *g_gamecallback = nullptr;
#if 0
// This can be helpful for the next code cleanup
static void dump_start_data(const GameStartData &data)
{
std::cout <<
"\ndedicated " << (int)data.is_dedicated_server <<
"\nport " << data.socket_port <<
"\nworld_path " << data.world_spec.path <<
"\nworld game " << data.world_spec.gameid <<
"\ngame path " << data.game_spec.path <<
"\nplayer name " << data.name <<
"\naddress " << data.address << std::endl;
}
#endif
ClientLauncher::~ClientLauncher()
{
delete input;
delete receiver;
delete g_fontengine;
delete g_gamecallback;
delete m_rendering_engine;
#if USE_SOUND
g_sound_manager_singleton.reset();
#endif
}
bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args)
{
/* This function is called when a client must be started.
* Covered cases:
* - Singleplayer (address but map provided)
* - Join server (no map but address provided)
* - Local server (for main menu only)
*/
init_args(start_data, cmd_args);
#if USE_SOUND
if (g_settings->getBool("enable_sound"))
g_sound_manager_singleton = createSoundManagerSingleton();
#endif
if (!init_engine()) {
errorstream << "Could not initialize game engine." << std::endl;
return false;
}
// Speed tests (done after irrlicht is loaded to get timer)
if (cmd_args.getFlag("speedtests")) {
dstream << "Running speed tests" << std::endl;
speed_tests();
return true;
}
if (m_rendering_engine->get_video_driver() == NULL) {
errorstream << "Could not initialize video driver." << std::endl;
return false;
}
m_rendering_engine->setupTopLevelWindow(PROJECT_NAME_C);
/*
This changes the minimum allowed number of vertices in a VBO.
Default is 500.
*/
//driver->setMinHardwareBufferVertexCount(50);
// Create game callback for menus
g_gamecallback = new MainGameCallback();
m_rendering_engine->setResizable(true);
init_input();
m_rendering_engine->get_scene_manager()->getParameters()->
setAttribute(scene::ALLOW_ZWRITE_ON_TRANSPARENT, true);
guienv = m_rendering_engine->get_gui_env();
skin = guienv->getSkin();
skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255, 255, 255, 255));
skin->setColor(gui::EGDC_3D_LIGHT, video::SColor(0, 0, 0, 0));
skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(255, 30, 30, 30));
skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(255, 0, 0, 0));
skin->setColor(gui::EGDC_HIGH_LIGHT, video::SColor(255, 70, 120, 50));
skin->setColor(gui::EGDC_HIGH_LIGHT_TEXT, video::SColor(255, 255, 255, 255));
#ifdef HAVE_TOUCHSCREENGUI
float density = RenderingEngine::getDisplayDensity();
skin->setSize(gui::EGDS_CHECK_BOX_WIDTH, (s32)(17.0f * density));
skin->setSize(gui::EGDS_SCROLLBAR_SIZE, (s32)(14.0f * density));
skin->setSize(gui::EGDS_WINDOW_BUTTON_WIDTH, (s32)(15.0f * density));
if (density > 1.5f) {
std::string sprite_path = porting::path_user + "/textures/base/pack/";
if (density > 3.5f)
sprite_path.append("checkbox_64.png");
else if (density > 2.0f)
sprite_path.append("checkbox_32.png");
else
sprite_path.append("checkbox_16.png");
// Texture dimensions should be a power of 2
gui::IGUISpriteBank *sprites = skin->getSpriteBank();
video::IVideoDriver *driver = m_rendering_engine->get_video_driver();
video::ITexture *sprite_texture = driver->getTexture(sprite_path.c_str());
if (sprite_texture) {
s32 sprite_id = sprites->addTextureAsSprite(sprite_texture);
if (sprite_id != -1)
skin->setIcon(gui::EGDI_CHECK_BOX_CHECKED, sprite_id);
}
}
#endif
g_fontengine = new FontEngine(guienv);
FATAL_ERROR_IF(g_fontengine == NULL, "Font engine creation failed.");
// Irrlicht 1.8 input colours
skin->setColor(gui::EGDC_EDITABLE, video::SColor(255, 128, 128, 128));
skin->setColor(gui::EGDC_FOCUSED_EDITABLE, video::SColor(255, 96, 134, 49));
// Create the menu clouds
if (!g_menucloudsmgr)
g_menucloudsmgr = m_rendering_engine->get_scene_manager()->createNewSceneManager();
if (!g_menuclouds)
g_menuclouds = new Clouds(g_menucloudsmgr, -1, rand());
g_menuclouds->setHeight(100.0f);
g_menuclouds->update(v3f(0, 0, 0), video::SColor(255, 240, 240, 255));
scene::ICameraSceneNode* camera;
camera = g_menucloudsmgr->addCameraSceneNode(NULL, v3f(0, 0, 0), v3f(0, 60, 100));
camera->setFarValue(10000);
/*
GUI stuff
*/
ChatBackend chat_backend;
// If an error occurs, this is set to something by menu().
// It is then displayed before the menu shows on the next call to menu()
std::string error_message;
bool reconnect_requested = false;
bool first_loop = true;
/*
Menu-game loop
*/
bool retval = true;
bool *kill = porting::signal_handler_killstatus();
while (m_rendering_engine->run() && !*kill &&
!g_gamecallback->shutdown_requested) {
// Set the window caption
const wchar_t *text = wgettext("Main Menu");
m_rendering_engine->get_raw_device()->
setWindowCaption((utf8_to_wide(PROJECT_NAME_C) +
L" " + utf8_to_wide(g_version_hash) +
L" [" + text + L"]").c_str());
delete[] text;
try { // This is used for catching disconnects
m_rendering_engine->get_gui_env()->clear();
/*
We need some kind of a root node to be able to add
custom gui elements directly on the screen.
Otherwise they won't be automatically drawn.
*/
guiroot = m_rendering_engine->get_gui_env()->addStaticText(L"",
core::rect<s32>(0, 0, 10000, 10000));
bool game_has_run = launch_game(error_message, reconnect_requested,
start_data, cmd_args);
// Reset the reconnect_requested flag
reconnect_requested = false;
// If skip_main_menu, we only want to startup once
if (skip_main_menu && !first_loop)
break;
first_loop = false;
if (!game_has_run) {
if (skip_main_menu)
break;
continue;
}
// Break out of menu-game loop to shut down cleanly
if (!m_rendering_engine->run() || *kill) {
if (!g_settings_path.empty())
g_settings->updateConfigFile(g_settings_path.c_str());
break;
}
m_rendering_engine->get_video_driver()->setTextureCreationFlag(
video::ETCF_CREATE_MIP_MAPS, g_settings->getBool("mip_map"));
#ifdef HAVE_TOUCHSCREENGUI
receiver->m_touchscreengui = new TouchScreenGUI(m_rendering_engine->get_raw_device(), receiver);
g_touchscreengui = receiver->m_touchscreengui;
#endif
the_game(
kill,
input,
m_rendering_engine,
start_data,
error_message,
chat_backend,
&reconnect_requested
);
} //try
catch (con::PeerNotFoundException &e) {
error_message = gettext("Connection error (timed out?)");
errorstream << error_message << std::endl;
}
#ifdef NDEBUG
catch (std::exception &e) {
std::string error_message = "Some exception: \"";
error_message += e.what();
error_message += "\"";
errorstream << error_message << std::endl;
}
#endif
m_rendering_engine->get_scene_manager()->clear();
#ifdef HAVE_TOUCHSCREENGUI
delete g_touchscreengui;
g_touchscreengui = NULL;
receiver->m_touchscreengui = NULL;
#endif
// If no main menu, show error and exit
if (skip_main_menu) {
if (!error_message.empty()) {
verbosestream << "error_message = "
<< error_message << std::endl;
retval = false;
}
break;
}
} // Menu-game loop
g_menuclouds->drop();
g_menucloudsmgr->drop();
return retval;
}
void ClientLauncher::init_args(GameStartData &start_data, const Settings &cmd_args)
{
skip_main_menu = cmd_args.getFlag("go");
start_data.address = g_settings->get("address");
if (cmd_args.exists("address")) {
// Join a remote server
start_data.address = cmd_args.get("address");
start_data.world_path.clear();
start_data.name = g_settings->get("name");
}
if (!start_data.world_path.empty()) {
// Start a singleplayer instance
start_data.address = "";
}
if (cmd_args.exists("name"))
start_data.name = cmd_args.get("name");
random_input = g_settings->getBool("random_input")
|| cmd_args.getFlag("random-input");
}
bool ClientLauncher::init_engine()
{
receiver = new MyEventReceiver();
m_rendering_engine = new RenderingEngine(receiver);
return m_rendering_engine->get_raw_device() != nullptr;
}
void ClientLauncher::init_input()
{
if (random_input)
input = new RandomInputHandler();
else
input = new RealInputHandler(receiver);
if (g_settings->getBool("enable_joysticks")) {
irr::core::array<irr::SJoystickInfo> infos;
std::vector<irr::SJoystickInfo> joystick_infos;
// Make sure this is called maximum once per
// irrlicht device, otherwise it will give you
// multiple events for the same joystick.
if (m_rendering_engine->get_raw_device()->activateJoysticks(infos)) {
infostream << "Joystick support enabled" << std::endl;
joystick_infos.reserve(infos.size());
for (u32 i = 0; i < infos.size(); i++) {
joystick_infos.push_back(infos[i]);
}
input->joystick.onJoystickConnect(joystick_infos);
} else {
errorstream << "Could not activate joystick support." << std::endl;
}
}
}
bool ClientLauncher::launch_game(std::string &error_message,
bool reconnect_requested, GameStartData &start_data,
const Settings &cmd_args)
{
// Prepare and check the start data to launch a game
std::string error_message_lua = error_message;
error_message.clear();
if (cmd_args.exists("password"))
start_data.password = cmd_args.get("password");
if (cmd_args.exists("password-file")) {
std::ifstream passfile(cmd_args.get("password-file"));
if (passfile.good()) {
getline(passfile, start_data.password);
} else {
error_message = gettext("Provided password file "
"failed to open: ")
+ cmd_args.get("password-file");
errorstream << error_message << std::endl;
return false;
}
}
// If a world was commanded, append and select it
// This is provieded by "get_world_from_cmdline()", main.cpp
if (!start_data.world_path.empty()) {
auto &spec = start_data.world_spec;
spec.path = start_data.world_path;
spec.gameid = getWorldGameId(spec.path, true);
spec.name = _("[--world parameter]");
if (spec.gameid.empty()) { // Create new
spec.gameid = g_settings->get("default_game");
spec.name += " [new]";
}
}
/* Show the GUI menu
*/
std::string server_name, server_description;
if (!skip_main_menu) {
// Initialize menu data
// TODO: Re-use existing structs (GameStartData)
MainMenuData menudata;
menudata.address = start_data.address;
menudata.name = start_data.name;
menudata.password = start_data.password;
menudata.port = itos(start_data.socket_port);
menudata.script_data.errormessage = error_message_lua;
menudata.script_data.reconnect_requested = reconnect_requested;
main_menu(&menudata);
// Skip further loading if there was an exit signal.
if (*porting::signal_handler_killstatus())
return false;
if (!menudata.script_data.errormessage.empty()) {
/* The calling function will pass this back into this function upon the
* next iteration (if any) causing it to be displayed by the GUI
*/
error_message = menudata.script_data.errormessage;
return false;
}
int newport = stoi(menudata.port);
if (newport != 0)
start_data.socket_port = newport;
// Update world information using main menu data
std::vector<WorldSpec> worldspecs = getAvailableWorlds();
int world_index = menudata.selected_world;
if (world_index >= 0 && world_index < (int)worldspecs.size()) {
g_settings->set("selected_world_path",
worldspecs[world_index].path);
start_data.world_spec = worldspecs[world_index];
}
start_data.name = menudata.name;
start_data.password = menudata.password;
start_data.address = std::move(menudata.address);
start_data.allow_login_or_register = menudata.allow_login_or_register;
server_name = menudata.servername;
server_description = menudata.serverdescription;
start_data.local_server = !menudata.simple_singleplayer_mode &&
start_data.address.empty();
} else {
start_data.local_server = !start_data.world_path.empty() &&
start_data.address.empty() && !start_data.name.empty();
}
if (!m_rendering_engine->run())
return false;
if (!start_data.isSinglePlayer() && start_data.name.empty()) {
error_message = gettext("Please choose a name!");
errorstream << error_message << std::endl;
return false;
}
// If using simple singleplayer mode, override
if (start_data.isSinglePlayer()) {
start_data.name = "singleplayer";
start_data.password = "";
start_data.socket_port = myrand_range(49152, 65535);
} else {
g_settings->set("name", start_data.name);
}
if (start_data.name.length() > PLAYERNAME_SIZE - 1) {
error_message = gettext("Player name too long.");
start_data.name.resize(PLAYERNAME_SIZE);
g_settings->set("name", start_data.name);
return false;
}
auto &worldspec = start_data.world_spec;
infostream << "Selected world: " << worldspec.name
<< " [" << worldspec.path << "]" << std::endl;
if (start_data.address.empty()) {
// For singleplayer and local server
if (worldspec.path.empty()) {
error_message = gettext("No world selected and no address "
"provided. Nothing to do.");
errorstream << error_message << std::endl;
return false;
}
if (!fs::PathExists(worldspec.path)) {
error_message = gettext("Provided world path doesn't exist: ")
+ worldspec.path;
errorstream << error_message << std::endl;
return false;
}
// Load gamespec for required game
start_data.game_spec = findWorldSubgame(worldspec.path);
if (!start_data.game_spec.isValid()) {
error_message = gettext("Could not find or load game: ")
+ worldspec.gameid;
errorstream << error_message << std::endl;
return false;
}
if (porting::signal_handler_killstatus())
return true;
if (!start_data.game_spec.isValid()) {
error_message = gettext("Invalid gamespec.");
error_message += " (world.gameid=" + worldspec.gameid + ")";
errorstream << error_message << std::endl;
return false;
}
}
start_data.world_path = start_data.world_spec.path;
return true;
}
void ClientLauncher::main_menu(MainMenuData *menudata)
{
bool *kill = porting::signal_handler_killstatus();
video::IVideoDriver *driver = m_rendering_engine->get_video_driver();
infostream << "Waiting for other menus" << std::endl;
while (m_rendering_engine->run() && !*kill) {
if (!isMenuActive())
break;
driver->beginScene(true, true, video::SColor(255, 128, 128, 128));
m_rendering_engine->get_gui_env()->drawAll();
driver->endScene();
// On some computers framerate doesn't seem to be automatically limited
sleep_ms(25);
}
infostream << "Waited for other menus" << std::endl;
// Cursor can be non-visible when coming from the game
#ifndef ANDROID
m_rendering_engine->get_raw_device()->getCursorControl()->setVisible(true);
#endif
/* show main menu */
GUIEngine mymenu(&input->joystick, guiroot, m_rendering_engine, &g_menumgr, menudata, *kill);
/* leave scene manager in a clean state */
m_rendering_engine->get_scene_manager()->clear();
}
void ClientLauncher::speed_tests()
{
// volatile to avoid some potential compiler optimisations
volatile static s16 temp16;
volatile static f32 tempf;
// Silence compiler warning
(void)temp16;
static v3f tempv3f1;
static v3f tempv3f2;
static std::string tempstring;
static std::string tempstring2;
tempv3f1 = v3f();
tempv3f2 = v3f();
tempstring.clear();
tempstring2.clear();
{
infostream << "The following test should take around 20ms." << std::endl;
TimeTaker timer("Testing std::string speed");
const u32 jj = 10000;
for (u32 j = 0; j < jj; j++) {
tempstring = "";
tempstring2 = "";
const u32 ii = 10;
for (u32 i = 0; i < ii; i++) {
tempstring2 += "asd";
}
for (u32 i = 0; i < ii+1; i++) {
tempstring += "asd";
if (tempstring == tempstring2)
break;
}
}
}
infostream << "All of the following tests should take around 100ms each."
<< std::endl;
{
TimeTaker timer("Testing floating-point conversion speed");
tempf = 0.001;
for (u32 i = 0; i < 4000000; i++) {
temp16 += tempf;
tempf += 0.001;
}
}
{
TimeTaker timer("Testing floating-point vector speed");
tempv3f1 = v3f(1, 2, 3);
tempv3f2 = v3f(4, 5, 6);
for (u32 i = 0; i < 10000000; i++) {
tempf += tempv3f1.dotProduct(tempv3f2);
tempv3f2 += v3f(7, 8, 9);
}
}
{
TimeTaker timer("Testing std::map speed");
std::map<v2s16, f32> map1;
tempf = -324;
const s16 ii = 300;
for (s16 y = 0; y < ii; y++) {
for (s16 x = 0; x < ii; x++) {
map1[v2s16(x, y)] = tempf;
tempf += 1;
}
}
for (s16 y = ii - 1; y >= 0; y--) {
for (s16 x = 0; x < ii; x++) {
tempf = map1[v2s16(x, y)];
}
}
}
{
infostream << "Around 5000/ms should do well here." << std::endl;
TimeTaker timer("Testing mutex speed");
std::mutex m;
u32 n = 0;
u32 i = 0;
do {
n += 10000;
for (; i < n; i++) {
m.lock();
m.unlock();
}
}
// Do at least 10ms
while(timer.getTimerTime() < 10);
u32 dtime = timer.stop();
u32 per_ms = n / dtime;
infostream << "Done. " << dtime << "ms, " << per_ms << "/ms" << std::endl;
}
}

View File

@@ -0,0 +1,55 @@
/*
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/inputhandler.h"
#include "gameparams.h"
class RenderingEngine;
class ClientLauncher
{
public:
ClientLauncher() = default;
~ClientLauncher();
bool run(GameStartData &start_data, const Settings &cmd_args);
private:
void init_args(GameStartData &start_data, const Settings &cmd_args);
bool init_engine();
void init_input();
bool launch_game(std::string &error_message, bool reconnect_requested,
GameStartData &start_data, const Settings &cmd_args);
void main_menu(MainMenuData *menudata);
void speed_tests();
bool skip_main_menu = false;
bool random_input = false;
RenderingEngine *m_rendering_engine = nullptr;
InputHandler *input = nullptr;
MyEventReceiver *receiver = nullptr;
gui::IGUISkin *skin = nullptr;
};

964
src/client/clientmap.cpp Normal file
View File

@@ -0,0 +1,964 @@
/*
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"
// struct MeshBufListList
void MeshBufListList::clear()
{
for (auto &list : lists)
list.clear();
}
void MeshBufListList::add(scene::IMeshBuffer *buf, v3s16 position, 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.emplace_back(position, buf);
return;
}
}
MeshBufList l;
l.m = m;
l.bufs.emplace_back(position, buf);
list.emplace_back(l);
}
// ClientMap
ClientMap::ClientMap(
Client *client,
RenderingEngine *rendering_engine,
MapDrawControl &control,
s32 id
):
Map(client),
scene::ISceneNode(rendering_engine->get_scene_manager()->getRootSceneNode(),
rendering_engine->get_scene_manager(), id),
m_client(client),
m_rendering_engine(rendering_engine),
m_control(control),
m_drawlist(MapBlockComparer(v3s16(0,0,0)))
{
/*
* @Liso: Sadly C++ doesn't have introspection, so the only way we have to know
* the class is whith a name ;) Name property cames from ISceneNode base class.
*/
Name = "ClientMap";
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");
m_cache_transparency_sorting_distance = g_settings->getU16("transparency_sorting_distance");
}
void ClientMap::updateCamera(v3f pos, v3f dir, f32 fov, v3s16 offset)
{
v3s16 previous_node = floatToInt(m_camera_position, BS) + m_camera_offset;
v3s16 previous_block = getContainerPos(previous_node, MAP_BLOCKSIZE);
m_camera_position = pos;
m_camera_direction = dir;
m_camera_fov = fov;
m_camera_offset = offset;
v3s16 current_node = floatToInt(m_camera_position, BS) + m_camera_offset;
v3s16 current_block = getContainerPos(current_node, MAP_BLOCKSIZE);
// reorder the blocks when camera crosses block boundary
if (previous_block != current_block)
m_needs_update_drawlist = true;
// reorder transparent meshes when camera crosses node boundary
if (previous_node != current_node)
m_needs_update_transparent_meshes = true;
}
MapSector * ClientMap::emergeSector(v2s16 p2d)
{
// Check that it doesn't exist already
MapSector *sector = getSectorNoGenerate(p2d);
// Create it if it does not exist yet
if (!sector) {
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();
if (!m_added_to_shadow_renderer) {
m_added_to_shadow_renderer = true;
if (auto shadows = m_rendering_engine->get_shadow_renderer())
shadows->addNodeToShadowList(this);
}
}
void ClientMap::getBlocksInViewRange(v3s16 cam_pos_nodes,
v3s16 *p_blocks_min, v3s16 *p_blocks_max, float range)
{
if (range <= 0.0f)
range = m_control.wanted_range;
v3s16 box_nodes_d = 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);
m_needs_update_drawlist = false;
for (auto &i : m_drawlist) {
MapBlock *block = i.second;
block->refDrop();
}
m_drawlist.clear();
const v3f camera_position = m_camera_position;
const v3f camera_direction = m_camera_direction;
// Use a higher fov to accomodate faster camera movements.
// Blocks are cropped better when they are drawn.
const f32 camera_fov = m_camera_fov * 1.1f;
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);
// Read the vision range, unless unlimited range is enabled.
float range = m_control.range_all ? 1e7 : m_control.wanted_range;
// Number of blocks currently loaded by the client
u32 blocks_loaded = 0;
// Number of blocks with mesh in rendering range
u32 blocks_in_range_with_mesh = 0;
// Number of blocks occlusion culled
u32 blocks_occlusion_culled = 0;
// No occlusion culling when free_move is on and camera is inside ground
bool occlusion_culling_enabled = true;
if (m_control.allow_noclip) {
MapNode n = getNode(cam_pos_nodes);
if (n.getContent() == CONTENT_IGNORE || m_nodedef->get(n).solidness == 2)
occlusion_culling_enabled = false;
}
v3s16 camera_block = getContainerPos(cam_pos_nodes, MAP_BLOCKSIZE);
m_drawlist = std::map<v3s16, MapBlock*, MapBlockComparer>(MapBlockComparer(camera_block));
// Uncomment to debug occluded blocks in the wireframe mode
// TODO: Include this as a flag for an extended debugging setting
//if (occlusion_culling_enabled && m_control.show_wireframe)
// occlusion_culling_enabled = porting::getTimeS() & 1;
for (const auto &sector_it : m_sectors) {
MapSector *sector = sector_it.second;
v2s16 sp = sector->getPos();
blocks_loaded += sector->size();
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 (MapBlock *block : sectorblocks) {
/*
Compare block position to camera position, skip
if not seen on display
*/
if (!block->mesh) {
// Ignore if mesh doesn't exist
continue;
}
v3s16 block_coord = block->getPos();
v3s16 block_position = block->getPosRelative() + MAP_BLOCKSIZE / 2;
// First, perform a simple distance check, with a padding of one extra block.
if (!m_control.range_all &&
block_position.getDistanceFrom(cam_pos_nodes) > range + MAP_BLOCKSIZE)
continue; // Out of range, skip.
// Keep the block alive as long as it is in range.
block->resetUsageTimer();
blocks_in_range_with_mesh++;
// Frustum culling
float d = 0.0;
if (!isBlockInSight(block_coord, camera_position,
camera_direction, camera_fov, range * BS, &d))
continue;
// Occlusion culling
if ((!m_control.range_all && d > m_control.wanted_range * BS) ||
(occlusion_culling_enabled && isBlockOccluded(block, cam_pos_nodes))) {
blocks_occlusion_culled++;
continue;
}
// Add to set
block->refGrab();
m_drawlist[block_coord] = block;
sector_blocks_drawn++;
} // foreach sectorblocks
if (sector_blocks_drawn != 0)
m_last_drawn_sectors.insert(sp);
}
g_profiler->avg("MapBlock meshes in range [#]", blocks_in_range_with_mesh);
g_profiler->avg("MapBlocks occlusion culled [#]", blocks_occlusion_culled);
g_profiler->avg("MapBlocks drawn [#]", m_drawlist.size());
g_profiler->avg("MapBlocks loaded [#]", blocks_loaded);
}
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 = "renderMap(SOLID): ";
else
prefix = "renderMap(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 animation parameters
*/
const float animation_time = m_client->getAnimationTime();
const int crack = m_client->getCrackLevel();
const u32 daynight_ratio = m_client->getEnv().getDayNightRatio();
const v3f camera_position = m_camera_position;
/*
Get all blocks and draw all visible ones
*/
u32 vertex_count = 0;
u32 drawcall_count = 0;
// For limiting number of mesh animations per frame
u32 mesh_animate_count = 0;
//u32 mesh_animate_count_far = 0;
/*
Update transparent meshes
*/
if (is_transparent_pass)
updateTransparentMeshBuffers();
/*
Draw the selected MapBlocks
*/
MeshBufListList grouped_buffers;
std::vector<DrawDescriptor> draw_order;
video::SMaterial previous_material;
for (auto &i : m_drawlist) {
v3s16 block_pos = i.first;
MapBlock *block = i.second;
// If the mesh of the block happened to get deleted, ignore it
if (!block->mesh)
continue;
v3f block_pos_r = intToFloat(block->getPosRelative() + MAP_BLOCKSIZE / 2, BS);
float d = camera_position.getDistanceFrom(block_pos_r);
d = MYMAX(0,d - BLOCK_MAX_RADIUS);
// Mesh animation
if (pass == scene::ESNRP_SOLID) {
MapBlockMesh *mapBlockMesh = block->mesh;
assert(mapBlockMesh);
// Pretty random but this should work somewhat nicely
bool faraway = d >= BS * 50;
if (mapBlockMesh->isAnimationForced() || !faraway ||
mesh_animate_count < (m_control.range_all ? 200 : 50)) {
bool animated = mapBlockMesh->animate(faraway, animation_time,
crack, daynight_ratio);
if (animated)
mesh_animate_count++;
} else {
mapBlockMesh->decreaseAnimationForceTimer();
}
}
/*
Get the meshbuffers of the block
*/
if (is_transparent_pass) {
// In transparent pass, the mesh will give us
// the partial buffers in the correct order
for (auto &buffer : block->mesh->getTransparentBuffers())
draw_order.emplace_back(block_pos, &buffer);
}
else {
// otherwise, group buffers across meshes
// using MeshBufListList
MapBlockMesh *mapBlockMesh = block->mesh;
assert(mapBlockMesh);
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) {
if (buf->getVertexCount() == 0)
errorstream << "Block [" << analyze_block(block)
<< "] contains an empty meshbuf" << std::endl;
grouped_buffers.add(buf, block_pos, layer);
}
}
}
}
}
// Capture draw order for all solid meshes
for (auto &lists : grouped_buffers.lists) {
for (MeshBufList &list : lists) {
// iterate in reverse to draw closest blocks first
for (auto it = list.bufs.rbegin(); it != list.bufs.rend(); ++it) {
draw_order.emplace_back(it->first, it->second, it != list.bufs.rbegin());
}
}
}
TimeTaker draw("Drawing mesh buffers");
core::matrix4 m; // Model matrix
v3f offset = intToFloat(m_camera_offset, BS);
u32 material_swaps = 0;
// Render all mesh buffers in order
drawcall_count += draw_order.size();
for (auto &descriptor : draw_order) {
scene::IMeshBuffer *buf = descriptor.getBuffer();
// Check and abort if the machine is swapping a lot
if (draw.getTimerTime() > 2000) {
infostream << "ClientMap::renderMap(): Rendering took >2s, " <<
"returning." << std::endl;
return;
}
if (!descriptor.m_reuse_material) {
auto &material = buf->getMaterial();
// Apply filter settings
material.setFlag(video::EMF_TRILINEAR_FILTER,
m_cache_trilinear_filter);
material.setFlag(video::EMF_BILINEAR_FILTER,
m_cache_bilinear_filter);
material.setFlag(video::EMF_ANISOTROPIC_FILTER,
m_cache_anistropic_filter);
material.setFlag(video::EMF_WIREFRAME,
m_control.show_wireframe);
// pass the shadow map texture to the buffer texture
ShadowRenderer *shadow = m_rendering_engine->get_shadow_renderer();
if (shadow && shadow->is_active()) {
auto &layer = material.TextureLayer[ShadowRenderer::TEXTURE_LAYER_SHADOW];
layer.Texture = shadow->get_texture();
layer.TextureWrapU = video::E_TEXTURE_CLAMP::ETC_CLAMP_TO_EDGE;
layer.TextureWrapV = video::E_TEXTURE_CLAMP::ETC_CLAMP_TO_EDGE;
// Do not enable filter on shadow texture to avoid visual artifacts
// with colored shadows.
// Filtering is done in shader code anyway
layer.BilinearFilter = false;
layer.AnisotropicFilter = false;
layer.TrilinearFilter = false;
}
driver->setMaterial(material);
++material_swaps;
material.TextureLayer[ShadowRenderer::TEXTURE_LAYER_SHADOW].Texture = nullptr;
}
v3f block_wpos = intToFloat(descriptor.m_pos * MAP_BLOCKSIZE, BS);
m.setTranslation(block_wpos - offset);
driver->setTransform(video::ETS_WORLD, m);
descriptor.draw(driver);
vertex_count += buf->getIndexCount();
}
g_profiler->avg(prefix + "draw meshes [ms]", draw.stop(true));
// Log only on solid pass because values are the same
if (pass == scene::ESNRP_SOLID) {
g_profiler->avg("renderMap(): animated meshes [#]", mesh_animate_count);
}
if (pass == scene::ESNRP_TRANSPARENT) {
g_profiler->avg("renderMap(): transparent buffers [#]", draw_order.size());
}
g_profiler->avg(prefix + "vertices drawn [#]", vertex_count);
g_profiler->avg(prefix + "drawcalls [#]", drawcall_count);
g_profiler->avg(prefix + "material swaps [#]", material_swaps);
}
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->getNode(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->getNode(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->getNode(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)
{
ScopeProfiler sp(g_profiler, "CM::getBackgroundBrightness", SPT_AVG);
static v3f z_directions[50] = {
v3f(-100, 0, 0)
};
static f32 z_offsets[50] = {
-1000,
};
if (z_directions[0].X < -99) {
for (u32 i = 0; i < ARRLEN(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;
values.reserve(ARRLEN(z_directions));
for (u32 i = 0; i < ARRLEN(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 = getNode(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 = getNode(floatToInt(m_camera_position, BS));
const ContentFeatures& features = m_nodedef->get(n);
video::SColor post_effect_color = features.post_effect_color;
// If the camera is in a solid node, make everything black.
// (first person mode only)
if (features.solidness == 2 && cam_mode == CAMERA_MODE_FIRST &&
!m_control.allow_noclip) {
post_effect_color = video::SColor(255, 0, 0, 0);
}
if (post_effect_color.getAlpha() != 0) {
// 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: ";
}
void ClientMap::renderMapShadows(video::IVideoDriver *driver,
const video::SMaterial &material, s32 pass, int frame, int total_frames)
{
bool is_transparent_pass = pass != scene::ESNRP_SOLID;
std::string prefix;
if (is_transparent_pass)
prefix = "renderMap(SHADOW TRANS): ";
else
prefix = "renderMap(SHADOW SOLID): ";
u32 drawcall_count = 0;
u32 vertex_count = 0;
MeshBufListList grouped_buffers;
std::vector<DrawDescriptor> draw_order;
int count = 0;
int low_bound = is_transparent_pass ? 0 : m_drawlist_shadow.size() / total_frames * frame;
int high_bound = is_transparent_pass ? m_drawlist_shadow.size() : m_drawlist_shadow.size() / total_frames * (frame + 1);
// transparent pass should be rendered in one go
if (is_transparent_pass && frame != total_frames - 1) {
return;
}
for (auto &i : m_drawlist_shadow) {
// only process specific part of the list & break early
++count;
if (count <= low_bound)
continue;
if (count > high_bound)
break;
v3s16 block_pos = i.first;
MapBlock *block = i.second;
// If the mesh of the block happened to get deleted, ignore it
if (!block->mesh)
continue;
/*
Get the meshbuffers of the block
*/
if (is_transparent_pass) {
// In transparent pass, the mesh will give us
// the partial buffers in the correct order
for (auto &buffer : block->mesh->getTransparentBuffers())
draw_order.emplace_back(block_pos, &buffer);
}
else {
// otherwise, group buffers across meshes
// using MeshBufListList
MapBlockMesh *mapBlockMesh = block->mesh;
assert(mapBlockMesh);
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 &mat = buf->getMaterial();
auto rnd = driver->getMaterialRenderer(mat.MaterialType);
bool transparent = rnd && rnd->isTransparent();
if (!transparent)
grouped_buffers.add(buf, block_pos, layer);
}
}
}
}
u32 buffer_count = 0;
for (auto &lists : grouped_buffers.lists)
for (MeshBufList &list : lists)
buffer_count += list.bufs.size();
draw_order.reserve(draw_order.size() + buffer_count);
// Capture draw order for all solid meshes
for (auto &lists : grouped_buffers.lists) {
for (MeshBufList &list : lists) {
// iterate in reverse to draw closest blocks first
for (auto it = list.bufs.rbegin(); it != list.bufs.rend(); ++it)
draw_order.emplace_back(it->first, it->second, it != list.bufs.rbegin());
}
}
TimeTaker draw("Drawing shadow mesh buffers");
core::matrix4 m; // Model matrix
v3f offset = intToFloat(m_camera_offset, BS);
u32 material_swaps = 0;
// Render all mesh buffers in order
drawcall_count += draw_order.size();
for (auto &descriptor : draw_order) {
scene::IMeshBuffer *buf = descriptor.getBuffer();
// Check and abort if the machine is swapping a lot
if (draw.getTimerTime() > 1000) {
infostream << "ClientMap::renderMapShadows(): Rendering "
"took >1s, returning." << std::endl;
break;
}
if (!descriptor.m_reuse_material) {
// override some material properties
video::SMaterial local_material = buf->getMaterial();
local_material.MaterialType = material.MaterialType;
local_material.BackfaceCulling = material.BackfaceCulling;
local_material.FrontfaceCulling = material.FrontfaceCulling;
local_material.BlendOperation = material.BlendOperation;
local_material.Lighting = false;
driver->setMaterial(local_material);
++material_swaps;
}
v3f block_wpos = intToFloat(descriptor.m_pos * MAP_BLOCKSIZE, BS);
m.setTranslation(block_wpos - offset);
driver->setTransform(video::ETS_WORLD, m);
descriptor.draw(driver);
vertex_count += buf->getIndexCount();
}
// restore the driver material state
video::SMaterial clean;
clean.BlendOperation = video::EBO_ADD;
driver->setMaterial(clean); // reset material to defaults
driver->draw3DLine(v3f(), v3f(), video::SColor(0));
g_profiler->avg(prefix + "draw meshes [ms]", draw.stop(true));
g_profiler->avg(prefix + "vertices drawn [#]", vertex_count);
g_profiler->avg(prefix + "drawcalls [#]", drawcall_count);
g_profiler->avg(prefix + "material swaps [#]", material_swaps);
}
/*
Custom update draw list for the pov of shadow light.
*/
void ClientMap::updateDrawListShadow(v3f shadow_light_pos, v3f shadow_light_dir, float radius, float length)
{
ScopeProfiler sp(g_profiler, "CM::updateDrawListShadow()", SPT_AVG);
v3s16 cam_pos_nodes = floatToInt(shadow_light_pos, BS);
v3s16 p_blocks_min;
v3s16 p_blocks_max;
getBlocksInViewRange(cam_pos_nodes, &p_blocks_min, &p_blocks_max, radius + length);
std::vector<v2s16> blocks_in_range;
for (auto &i : m_drawlist_shadow) {
MapBlock *block = i.second;
block->refDrop();
}
m_drawlist_shadow.clear();
// Number of blocks currently loaded by the client
u32 blocks_loaded = 0;
// Number of blocks with mesh in rendering range
u32 blocks_in_range_with_mesh = 0;
// Number of blocks occlusion culled
u32 blocks_occlusion_culled = 0;
for (auto &sector_it : m_sectors) {
MapSector *sector = sector_it.second;
if (!sector)
continue;
blocks_loaded += sector->size();
MapBlockVect sectorblocks;
sector->getBlocks(sectorblocks);
/*
Loop through blocks in sector
*/
for (MapBlock *block : sectorblocks) {
if (!block->mesh) {
// Ignore if mesh doesn't exist
continue;
}
v3f block_pos = intToFloat(block->getPos() * MAP_BLOCKSIZE, BS);
v3f projection = shadow_light_pos + shadow_light_dir * shadow_light_dir.dotProduct(block_pos - shadow_light_pos);
if (projection.getDistanceFrom(block_pos) > radius)
continue;
blocks_in_range_with_mesh++;
// This block is in range. Reset usage timer.
block->resetUsageTimer();
// Add to set
if (m_drawlist_shadow.find(block->getPos()) == m_drawlist_shadow.end()) {
block->refGrab();
m_drawlist_shadow[block->getPos()] = block;
}
}
}
g_profiler->avg("SHADOW MapBlock meshes in range [#]", blocks_in_range_with_mesh);
g_profiler->avg("SHADOW MapBlocks occlusion culled [#]", blocks_occlusion_culled);
g_profiler->avg("SHADOW MapBlocks drawn [#]", m_drawlist_shadow.size());
g_profiler->avg("SHADOW MapBlocks loaded [#]", blocks_loaded);
}
void ClientMap::updateTransparentMeshBuffers()
{
ScopeProfiler sp(g_profiler, "CM::updateTransparentMeshBuffers", SPT_AVG);
u32 sorted_blocks = 0;
u32 unsorted_blocks = 0;
f32 sorting_distance_sq = pow(m_cache_transparency_sorting_distance * BS, 2.0f);
// Update the order of transparent mesh buffers in each mesh
for (auto it = m_drawlist.begin(); it != m_drawlist.end(); it++) {
MapBlock* block = it->second;
if (!block->mesh)
continue;
if (m_needs_update_transparent_meshes ||
block->mesh->getTransparentBuffers().size() == 0) {
v3s16 block_pos = block->getPos();
v3f block_pos_f = intToFloat(block_pos * MAP_BLOCKSIZE + MAP_BLOCKSIZE / 2, BS);
f32 distance = m_camera_position.getDistanceFromSQ(block_pos_f);
if (distance <= sorting_distance_sq) {
block->mesh->updateTransparentBuffers(m_camera_position, block_pos);
++sorted_blocks;
}
else {
block->mesh->consolidateTransparentBuffers();
++unsorted_blocks;
}
}
}
g_profiler->avg("CM::Transparent Buffers - Sorted", sorted_blocks);
g_profiler->avg("CM::Transparent Buffers - Unsorted", unsorted_blocks);
m_needs_update_transparent_meshes = false;
}
scene::IMeshBuffer* ClientMap::DrawDescriptor::getBuffer()
{
return m_use_partial_buffer ? m_partial_buffer->getBuffer() : m_buffer;
}
void ClientMap::DrawDescriptor::draw(video::IVideoDriver* driver)
{
if (m_use_partial_buffer) {
m_partial_buffer->beforeDraw();
driver->drawMeshBuffer(m_partial_buffer->getBuffer());
m_partial_buffer->afterDraw();
} else {
driver->drawMeshBuffer(m_buffer);
}
}

209
src/client/clientmap.h Normal file
View File

@@ -0,0 +1,209 @@
/*
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
{
// Wanted drawing range
float wanted_range = 0.0f;
// Overrides limits by drawing everything
bool range_all = false;
// Allow rendering out of bounds
bool allow_noclip = false;
// show a wire frame for debugging
bool show_wireframe = false;
};
struct MeshBufList
{
video::SMaterial m;
std::vector<std::pair<v3s16,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();
void add(scene::IMeshBuffer *buf, v3s16 position, u8 layer);
};
class Client;
class ITextureSource;
class PartialMeshBuffer;
/*
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,
RenderingEngine *rendering_engine,
MapDrawControl &control,
s32 id
);
virtual ~ClientMap() = default;
bool maySaveBlocks() override
{
return false;
}
void drop() override
{
ISceneNode::drop(); // calls destructor
}
void updateCamera(v3f pos, v3f dir, f32 fov, v3s16 offset);
/*
Forcefully get a sector from somewhere
*/
MapSector * emergeSector(v2s16 p) override;
/*
ISceneNode methods
*/
virtual void OnRegisterSceneNode() override;
virtual void render() override
{
video::IVideoDriver* driver = SceneManager->getVideoDriver();
driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
renderMap(driver, SceneManager->getSceneNodeRenderPass());
}
virtual const aabb3f &getBoundingBox() const override
{
return m_box;
}
void getBlocksInViewRange(v3s16 cam_pos_nodes,
v3s16 *p_blocks_min, v3s16 *p_blocks_max, float range=-1.0f);
void updateDrawList();
void updateDrawListShadow(v3f shadow_light_pos, v3f shadow_light_dir, float radius, float length);
// Returns true if draw list needs updating before drawing the next frame.
bool needsUpdateDrawList() { return m_needs_update_drawlist; }
void renderMap(video::IVideoDriver* driver, s32 pass);
void renderMapShadows(video::IVideoDriver *driver,
const video::SMaterial &material, s32 pass, int frame, int total_frames);
int getBackgroundBrightness(float max_d, u32 daylight_factor,
int oldvalue, bool *sunlight_seen_result);
void renderPostFx(CameraMode cam_mode);
// For debug printing
void PrintInfo(std::ostream &out) override;
const MapDrawControl & getControl() const { return m_control; }
f32 getWantedRange() const { return m_control.wanted_range; }
f32 getCameraFov() const { return m_camera_fov; }
private:
// update the vertex order in transparent mesh buffers
void updateTransparentMeshBuffers();
// Orders blocks by distance to the camera
class MapBlockComparer
{
public:
MapBlockComparer(const v3s16 &camera_block) : m_camera_block(camera_block) {}
bool operator() (const v3s16 &left, const v3s16 &right) const
{
auto distance_left = left.getDistanceFromSQ(m_camera_block);
auto distance_right = right.getDistanceFromSQ(m_camera_block);
return distance_left > distance_right || (distance_left == distance_right && left > right);
}
private:
v3s16 m_camera_block;
};
// reference to a mesh buffer used when rendering the map.
struct DrawDescriptor {
v3s16 m_pos;
union {
scene::IMeshBuffer *m_buffer;
const PartialMeshBuffer *m_partial_buffer;
};
bool m_reuse_material:1;
bool m_use_partial_buffer:1;
DrawDescriptor(v3s16 pos, scene::IMeshBuffer *buffer, bool reuse_material) :
m_pos(pos), m_buffer(buffer), m_reuse_material(reuse_material), m_use_partial_buffer(false)
{}
DrawDescriptor(v3s16 pos, const PartialMeshBuffer *buffer) :
m_pos(pos), m_partial_buffer(buffer), m_reuse_material(false), m_use_partial_buffer(true)
{}
scene::IMeshBuffer* getBuffer();
void draw(video::IVideoDriver* driver);
};
Client *m_client;
RenderingEngine *m_rendering_engine;
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;
bool m_needs_update_transparent_meshes = true;
std::map<v3s16, MapBlock*, MapBlockComparer> m_drawlist;
std::map<v3s16, MapBlock*> m_drawlist_shadow;
bool m_needs_update_drawlist;
std::set<v2s16> m_last_drawn_sectors;
bool m_cache_trilinear_filter;
bool m_cache_bilinear_filter;
bool m_cache_anistropic_filter;
bool m_added_to_shadow_renderer{false};
u16 m_cache_transparency_sorting_distance;
};

792
src/client/clientmedia.cpp Normal file
View File

@@ -0,0 +1,792 @@
/*
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";
}
bool clientMediaUpdateCache(const std::string &raw_hash, const std::string &filedata)
{
FileCache media_cache(getMediaCacheDir());
std::string sha1_hex = hex_encode(raw_hash);
if (!media_cache.exists(sha1_hex))
return media_cache.update(sha1_hex, filedata);
return true;
}
/*
ClientMediaDownloader
*/
ClientMediaDownloader::ClientMediaDownloader():
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;
}
bool ClientMediaDownloader::loadMedia(Client *client, const std::string &data,
const std::string &name)
{
return client->loadMedia(data, name);
}
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;
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) {
const std::string &name = file_it.first;
FileStatus *filestatus = file_it.second;
const std::string &sha1 = filestatus->sha1;
if (tryLoadFromCache(name, sha1, client)) {
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)
createCacheDirs();
// 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();
// 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.method = HTTP_POST;
fetch_request.raw_data = required_hash_set;
fetch_request.extra_headers.emplace_back(
"Content-Type: application/octet-stream");
// Encapsulate possible IPv6 plain address in []
std::string addr = client->getAddressName();
if (addr.find(':', 0) != std::string::npos)
addr = '[' + addr + ']';
fetch_request.extra_headers.emplace_back(
std::string("Referer: minetest://") +
addr + ":" +
std::to_string(client->getServerAddress().getPort()));
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(auto 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;
}
}
}
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;
{
auto 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 (auto 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 +
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 =
g_settings->getS32("curl_file_download_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);
}
}
bool ClientMediaDownloader::conventionalTransferDone(
const std::string &name,
const std::string &data,
Client *client)
{
// Check that file was announced
auto 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 false;
}
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 true;
}
// 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);
return true;
}
/*
IClientMediaDownloader
*/
IClientMediaDownloader::IClientMediaDownloader():
m_media_cache(getMediaCacheDir()), m_write_to_cache(true)
{
}
void IClientMediaDownloader::createCacheDirs()
{
if (!m_write_to_cache)
return;
std::string path = getMediaCacheDir();
if (!fs::CreateAllDirs(path)) {
errorstream << "Client: Could not create media cache directory: "
<< path << std::endl;
}
}
bool IClientMediaDownloader::tryLoadFromCache(const std::string &name,
const std::string &sha1, Client *client)
{
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)
return checkAndLoad(name, sha1, tmp_os.str(), true, client);
return false;
}
bool IClientMediaDownloader::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 = loadMedia(client, 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_write_to_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 (const auto &it : m_files) {
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));
}
}
/*
SingleMediaDownloader
*/
SingleMediaDownloader::SingleMediaDownloader(bool write_to_cache):
m_httpfetch_caller(HTTPFETCH_DISCARD)
{
m_write_to_cache = write_to_cache;
}
SingleMediaDownloader::~SingleMediaDownloader()
{
if (m_httpfetch_caller != HTTPFETCH_DISCARD)
httpfetch_caller_free(m_httpfetch_caller);
}
bool SingleMediaDownloader::loadMedia(Client *client, const std::string &data,
const std::string &name)
{
return client->loadMedia(data, name, true);
}
void SingleMediaDownloader::addFile(const std::string &name, const std::string &sha1)
{
assert(m_stage == STAGE_INIT); // pre-condition
assert(!name.empty());
assert(sha1.size() == 20);
FATAL_ERROR_IF(!m_file_name.empty(), "Cannot add a second file");
m_file_name = name;
m_file_sha1 = sha1;
}
void SingleMediaDownloader::addRemoteServer(const std::string &baseurl)
{
assert(m_stage == STAGE_INIT); // pre-condition
if (g_settings->getBool("enable_remote_media_server"))
m_remotes.emplace_back(baseurl);
}
void SingleMediaDownloader::step(Client *client)
{
if (m_stage == STAGE_INIT) {
m_stage = STAGE_CACHE_CHECKED;
initialStep(client);
}
// Remote media: check for completion of fetches
if (m_httpfetch_caller != HTTPFETCH_DISCARD) {
HTTPFetchResult fetch_result;
while (httpfetch_async_get(m_httpfetch_caller, fetch_result)) {
remoteMediaReceived(fetch_result, client);
}
}
}
bool SingleMediaDownloader::conventionalTransferDone(const std::string &name,
const std::string &data, Client *client)
{
if (name != m_file_name)
return false;
// Mark file as received unconditionally and try to load it
m_stage = STAGE_DONE;
checkAndLoad(name, m_file_sha1, data, false, client);
return true;
}
void SingleMediaDownloader::initialStep(Client *client)
{
if (tryLoadFromCache(m_file_name, m_file_sha1, client))
m_stage = STAGE_DONE;
if (isDone())
return;
createCacheDirs();
// If the server reported no remote servers, immediately fall back to
// conventional transfer.
if (!USE_CURL || m_remotes.empty()) {
startConventionalTransfer(client);
} else {
// Otherwise start by requesting the file from the first remote media server
m_httpfetch_caller = httpfetch_caller_alloc();
m_current_remote = 0;
startRemoteMediaTransfer();
}
}
void SingleMediaDownloader::remoteMediaReceived(
const HTTPFetchResult &fetch_result, Client *client)
{
sanity_check(!isDone());
sanity_check(m_current_remote >= 0);
// If fetch succeeded, try to load it
if (fetch_result.succeeded) {
bool success = checkAndLoad(m_file_name, m_file_sha1,
fetch_result.data, false, client);
if (success) {
m_stage = STAGE_DONE;
return;
}
}
// Otherwise try the next remote server or fall back to conventional transfer
m_current_remote++;
if (m_current_remote >= (int)m_remotes.size()) {
infostream << "Client: Failed to remote-fetch \"" << m_file_name
<< "\". Requesting it the usual way." << std::endl;
m_current_remote = -1;
startConventionalTransfer(client);
} else {
startRemoteMediaTransfer();
}
}
void SingleMediaDownloader::startRemoteMediaTransfer()
{
std::string url = m_remotes.at(m_current_remote) + hex_encode(m_file_sha1);
verbosestream << "Client: Requesting remote media file "
<< "\"" << m_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 = g_settings->getS32("curl_file_download_timeout");
httpfetch_async(fetch_request);
m_httpfetch_next_id++;
}
void SingleMediaDownloader::startConventionalTransfer(Client *client)
{
std::vector<std::string> requests;
requests.emplace_back(m_file_name);
client->request_media(requests);
}

250
src/client/clientmedia.h Normal file
View File

@@ -0,0 +1,250 @@
/*
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 "util/basic_macros.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"
// Store file into media cache (unless it exists already)
// Validating the hash is responsibility of the caller
bool clientMediaUpdateCache(const std::string &raw_hash,
const std::string &filedata);
// more of a base class than an interface but this name was most convenient...
class IClientMediaDownloader
{
public:
DISABLE_CLASS_COPY(IClientMediaDownloader)
virtual bool isStarted() const = 0;
// If this returns true, the downloader is done and can be deleted
virtual bool isDone() const = 0;
// Add a file to the list of required file (but don't fetch it yet)
virtual void addFile(const std::string &name, const std::string &sha1) = 0;
// Add a remote server to the list; ignored if not built with cURL
virtual void addRemoteServer(const std::string &baseurl) = 0;
// 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.
virtual void step(Client *client) = 0;
// Must be called for each file received through TOCLIENT_MEDIA
// returns true if this file belongs to this downloader
virtual bool conventionalTransferDone(const std::string &name,
const std::string &data, Client *client) = 0;
protected:
IClientMediaDownloader();
virtual ~IClientMediaDownloader() = default;
// Forwards the call to the appropriate Client method
virtual bool loadMedia(Client *client, const std::string &data,
const std::string &name) = 0;
void createCacheDirs();
bool tryLoadFromCache(const std::string &name, const std::string &sha1,
Client *client);
bool checkAndLoad(const std::string &name, const std::string &sha1,
const std::string &data, bool is_from_cache, Client *client);
// Filesystem-based media cache
FileCache m_media_cache;
bool m_write_to_cache;
};
class ClientMediaDownloader : public IClientMediaDownloader
{
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 override {
return m_initial_step_done;
}
bool isDone() const override {
return m_initial_step_done &&
m_uncached_received_count == m_uncached_count;
}
void addFile(const std::string &name, const std::string &sha1) override;
void addRemoteServer(const std::string &baseurl) override;
void step(Client *client) override;
bool conventionalTransferDone(
const std::string &name,
const std::string &data,
Client *client) override;
protected:
bool loadMedia(Client *client, const std::string &data,
const std::string &name) override;
private:
struct FileStatus {
bool received;
std::string sha1;
s32 current_remote;
std::vector<s32> available_remotes;
};
struct RemoteServerStatus {
std::string baseurl;
s32 active_count;
};
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);
static void deSerializeHashSet(const std::string &data,
std::set<std::string> &result);
std::string serializeRequiredHashSet();
// Maps filename to file status
std::map<std::string, FileStatus*> m_files;
// Array of remote media servers
std::vector<RemoteServerStatus*> m_remotes;
// 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
u64 m_httpfetch_caller;
u64 m_httpfetch_next_id = 0;
s32 m_httpfetch_active = 0;
s32 m_httpfetch_active_limit = 0;
s32 m_outstanding_hash_sets = 0;
std::unordered_map<u64, 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 = "";
};
// A media downloader that only downloads a single file.
// It does/doesn't do several things the normal downloader does:
// - won't fetch hash sets from remote servers
// - will mark loaded media as coming from file push
// - writing to file cache is optional
class SingleMediaDownloader : public IClientMediaDownloader
{
public:
SingleMediaDownloader(bool write_to_cache);
~SingleMediaDownloader();
bool isStarted() const override {
return m_stage > STAGE_INIT;
}
bool isDone() const override {
return m_stage >= STAGE_DONE;
}
void addFile(const std::string &name, const std::string &sha1) override;
void addRemoteServer(const std::string &baseurl) override;
void step(Client *client) override;
bool conventionalTransferDone(const std::string &name,
const std::string &data, Client *client) override;
protected:
bool loadMedia(Client *client, const std::string &data,
const std::string &name) override;
private:
void initialStep(Client *client);
void remoteMediaReceived(const HTTPFetchResult &fetch_result, Client *client);
void startRemoteMediaTransfer();
void startConventionalTransfer(Client *client);
enum Stage {
STAGE_INIT,
STAGE_CACHE_CHECKED, // we have tried to load the file from cache
STAGE_DONE
};
// Information about the one file we want to fetch
std::string m_file_name;
std::string m_file_sha1;
s32 m_current_remote;
// Array of remote media servers
std::vector<std::string> m_remotes;
enum Stage m_stage = STAGE_INIT;
// Status of remote transfers
unsigned long m_httpfetch_caller;
unsigned long m_httpfetch_next_id = 0;
};

View 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;
}

116
src/client/clientobject.h Normal file
View File

@@ -0,0 +1,116 @@
/*
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>
#include <unordered_set>
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, scene::ISceneManager *smgr) = 0;
virtual void removeFromScene(bool permanent) {}
virtual void updateLight(u32 day_night_ratio) {}
virtual bool getCollisionBox(aabb3f *toset) const { return false; }
virtual bool getSelectionBox(aabb3f *toset) const { return false; }
virtual bool collideWithObjects() const { return false; }
virtual const v3f getPosition() const { return v3f(0.0f); }
virtual scene::ISceneNode *getSceneNode() const
{ return NULL; }
virtual scene::IAnimatedMeshSceneNode *getAnimatedMeshSceneNode() const
{ return NULL; }
virtual bool isLocalPlayer() const { return false; }
virtual ClientActiveObject *getParent() const { return nullptr; };
virtual const std::unordered_set<int> &getAttachmentChildIds() const
{ static std::unordered_set<int> rv; return rv; }
virtual void updateAttachments() {};
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 = nullptr,
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;
};
class DistanceSortedActiveObject
{
public:
ClientActiveObject *obj;
DistanceSortedActiveObject(ClientActiveObject *a_obj, f32 a_d)
{
obj = a_obj;
d = a_d;
}
bool operator < (const DistanceSortedActiveObject &other) const
{
return d < other.d;
}
private:
f32 d;
};

View File

@@ -0,0 +1,35 @@
/*
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_bloated.h"
class ClientEnvironment;
class ClientSimpleObject
{
protected:
public:
bool m_to_be_removed = false;
ClientSimpleObject() = default;
virtual ~ClientSimpleObject() = default;
virtual void step(float dtime) {}
};

385
src/client/clouds.cpp Normal file
View File

@@ -0,0 +1,385 @@
/*
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;
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, "Clouds::render()", 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
std::vector<bool> grid(m_cloud_radius_i * 2 * m_cloud_radius_i * 2);
std::vector<video::S3DVertex> vertices;
vertices.reserve(16 * m_cloud_radius_i * m_cloud_radius_i);
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 back to front for proper transparency
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)
};
const f32 rx = cloud_size / 2.0f;
// if clouds are flat, the top layer should be at the given height
const f32 ry = m_enable_3d ? m_params.thickness * BS : 0.0f;
const 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;
vertices.push_back(vertex);
}
}
}
int quad_count = vertices.size() / 4;
std::vector<u16> indices;
indices.reserve(quad_count * 6);
for (int k = 0; k < quad_count; k++) {
indices.push_back(4 * k + 0);
indices.push_back(4 * k + 1);
indices.push_back(4 * k + 2);
indices.push_back(4 * k + 2);
indices.push_back(4 * k + 3);
indices.push_back(4 * k + 0);
}
driver->drawVertexPrimitiveList(vertices.data(), vertices.size(), indices.data(), 2 * quad_count,
video::EVT_STANDARD, scene::EPT_TRIANGLES, video::EIT_16BIT);
// 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)
{
video::SColorf ambient(m_params.color_ambient);
video::SColorf bright(m_params.color_bright);
m_camera_pos = camera_p;
m_color.r = core::clamp(color_diffuse.r * bright.r, ambient.r, 1.0f);
m_color.g = core::clamp(color_diffuse.g * bright.g, ambient.g, 1.0f);
m_color.b = core::clamp(color_diffuse.b * bright.b, ambient.b, 1.0f);
m_color.a = bright.a;
// is the camera inside the cloud mesh?
m_camera_inside_cloud = false; // default
if (m_enable_3d) {
float camera_height = camera_p.Y - BS * m_camera_offset.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()
{
// Upper limit was chosen due to posible render bugs
m_cloud_radius_i = rangelim(g_settings->getU16("cloud_radius"), 1, 62);
m_enable_3d = g_settings->getBool("enable_3d_clouds");
}
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);
}

143
src/client/clouds.h Normal file
View File

@@ -0,0 +1,143 @@
/*
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 "skyparams.h"
// Menu clouds
class Clouds;
extern Clouds *g_menuclouds;
// Scene manager used for menu clouds
extern 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;
};

1988
src/client/content_cao.cpp Normal file

File diff suppressed because it is too large Load Diff

282
src/client/content_cao.h Normal file
View File

@@ -0,0 +1,282 @@
/*
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"
#include <cassert>
class Camera;
class Client;
struct Nametag;
struct MinimapMarker;
/*
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;
scene::IDummyTransformationSceneNode *m_matrixnode = nullptr;
Nametag *m_nametag = nullptr;
MinimapMarker *m_marker = nullptr;
v3f m_position = v3f(0.0f, 10.0f * BS, 0);
v3f m_velocity;
v3f m_acceleration;
v3f m_rotation;
u16 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;
int m_attachment_parent_id = 0;
std::unordered_set<int> m_attachment_child_ids;
std::string m_attachment_bone = "";
v3f m_attachment_position;
v3f m_attachment_rotation;
bool m_attached_to_local = false;
bool m_force_visible = 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;
video::SColor m_last_light = video::SColor(0xFFFFFFFF);
bool m_is_visible = false;
// Material
video::E_MATERIAL_TYPE m_material_type;
// Settings
bool m_enable_shaders = false;
bool visualExpiryRequired(const ObjectProperties &newprops) const;
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;
const v3f getPosition() const;
inline const v3f &getRotation() const { return m_rotation; }
bool isImmortal() const;
inline const ObjectProperties &getProperties() const { return m_prop; }
scene::ISceneNode *getSceneNode() const;
scene::IAnimatedMeshSceneNode *getAnimatedMeshSceneNode() const;
// m_matrixnode controls the position and rotation of the child node
// for all scene nodes, as a workaround for an Irrlicht problem with
// rotations. The child node's position can't be used because it's
// rotated, and must remain as 0.
// Note that m_matrixnode.setPosition() shouldn't be called. Use
// m_matrixnode->getRelativeTransformationMatrix().setTranslation()
// instead (aka getPosRotMatrix().setTranslation()).
inline core::matrix4 &getPosRotMatrix()
{
assert(m_matrixnode);
return m_matrixnode->getRelativeTransformationMatrix();
}
inline const core::matrix4 *getAbsolutePosRotMatrix() const
{
if (!m_matrixnode)
return nullptr;
return &m_matrixnode->getAbsoluteTransformation();
}
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);
void setAttachment(int parent_id, const std::string &bone, v3f position,
v3f rotation, bool force_visible);
void getAttachment(int *parent_id, std::string *bone, v3f *position,
v3f *rotation, bool *force_visible) const;
void clearChildAttachments();
void clearParentAttachment();
void addAttachmentChild(int child_id);
void removeAttachmentChild(int child_id);
ClientActiveObject *getParent() const;
const std::unordered_set<int> &getAttachmentChildIds() const
{ return m_attachment_child_ids; }
void updateAttachments();
void removeFromScene(bool permanent);
void addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr);
inline void expireVisuals()
{
m_visuals_expired = true;
}
void updateLight(u32 day_night_ratio);
void setNodeLight(const video::SColor &light);
/* Get light position(s).
* returns number of positions written into pos[], which must have space
* for at least 3 vectors. */
u16 getLightPosition(v3s16 *pos);
void updateNametag();
void updateMarker();
void updateNodePos();
void step(float dtime, ClientEnvironment *env);
void updateTexturePos();
// ffs this HAS TO BE a string copy! See #5739 if you think otherwise
// Reason: updateTextures(m_previous_texture_modifier);
void updateTextures(std::string mod);
void updateAnimation();
void updateAnimationSpeed();
void updateBonePosition();
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;
}
void updateMeshCulling();
};

View 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().getNode(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
View 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);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,180 @@
/*
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, u8 mask = 0);
void generateCuboidTextureCoords(aabb3f const &box, f32 *coords);
void drawAutoLightedCuboid(aabb3f box, const f32 *txc = NULL,
TileSpec *tiles = NULL, int tile_count = 0, u8 mask = 0);
u8 getNodeBoxMask(aabb3f box, u8 solid_neighbors, u8 sametype_neighbors) const;
// 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;
float 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(bool is_rooted = false);
// 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,
scene::IMeshManipulator *mm);
void generate();
void renderSingle(content_t node, u8 param2 = 0x00);
};

View File

@@ -0,0 +1,86 @@
/*
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 "mtevent.h"
#include <list>
#include <map>
class EventManager : public MtEventManager
{
static void receiverReceive(MtEvent *e, void *data)
{
MtEventReceiver *r = (MtEventReceiver *)data;
r->onEvent(e);
}
struct FuncSpec
{
event_receive_func f;
void *d;
FuncSpec(event_receive_func f, void *d) : f(f), d(d) {}
};
struct Dest
{
std::list<FuncSpec> funcs{};
};
std::map<MtEvent::Type, Dest> m_dest{};
public:
~EventManager() override = default;
void put(MtEvent *e) override
{
std::map<MtEvent::Type, Dest>::iterator i = m_dest.find(e->getType());
if (i != m_dest.end()) {
std::list<FuncSpec> &funcs = i->second.funcs;
for (FuncSpec &func : funcs) {
(*(func.f))(e, func.d);
}
}
delete e;
}
void reg(MtEvent::Type type, event_receive_func f, void *data) override
{
std::map<MtEvent::Type, Dest>::iterator i = m_dest.find(type);
if (i != m_dest.end()) {
i->second.funcs.emplace_back(f, data);
} else {
Dest dest;
dest.funcs.emplace_back(f, data);
m_dest[type] = dest;
}
}
void dereg(MtEvent::Type type, event_receive_func f, void *data) override
{
std::map<MtEvent::Type, Dest>::iterator i = m_dest.find(type);
if (i != m_dest.end()) {
std::list<FuncSpec> &funcs = i->second.funcs;
auto j = funcs.begin();
while (j != funcs.end()) {
bool remove = (j->f == f && (!data || j->d == data));
if (remove)
funcs.erase(j++);
else
++j;
}
}
}
};

97
src/client/filecache.cpp Normal file
View File

@@ -0,0 +1,97 @@
/*
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);
}
bool FileCache::exists(const std::string &name)
{
std::string path = m_dir + DIR_DELIM + name;
std::ifstream fis(path.c_str(), std::ios_base::binary);
return fis.good();
}

43
src/client/filecache.h Normal file
View File

@@ -0,0 +1,43 @@
/*
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);
bool exists(const std::string &name);
private:
std::string m_dir;
bool loadByPath(const std::string &path, std::ostream &os);
bool updateByPath(const std::string &path, const std::string &data);
};

270
src/client/fontengine.cpp Normal file
View File

@@ -0,0 +1,270 @@
/*
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"
#include "gettext.h"
#include "irrlicht_changes/CGUITTFont.h"
#include "util/numeric.h" // rangelim
/** maximum size distance for getting a "similar" font size */
#define MAX_FONT_SIZE_OFFSET 10
/** 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(gui::IGUIEnvironment* env) :
m_env(env)
{
for (u32 &i : m_default_size) {
i = FONT_SIZE_UNSPECIFIED;
}
assert(g_settings != NULL); // pre-condition
assert(m_env != NULL); // pre-condition
assert(m_env->getSkin() != NULL); // pre-condition
readSettings();
const char *settings[] = {
"font_size", "font_bold", "font_italic", "font_size_divisible_by",
"mono_font_size", "mono_font_size_divisible_by",
"font_shadow", "font_shadow_alpha",
"font_path", "font_path_bold", "font_path_italic", "font_path_bold_italic",
"mono_font_path", "mono_font_path_bold", "mono_font_path_italic",
"mono_font_path_bold_italic",
"fallback_font_path",
"screen_dpi", "gui_scaling",
};
for (auto name : settings)
g_settings->registerChangedCallback(name, font_setting_changed, NULL);
}
/******************************************************************************/
FontEngine::~FontEngine()
{
cleanCache();
}
/******************************************************************************/
void FontEngine::cleanCache()
{
RecursiveMutexAutoLock l(m_font_mutex);
for (auto &font_cache_it : m_font_cache) {
for (auto &font_it : font_cache_it) {
font_it.second->drop();
font_it.second = nullptr;
}
font_cache_it.clear();
}
}
/******************************************************************************/
irr::gui::IGUIFont *FontEngine::getFont(FontSpec spec)
{
return getFont(spec, false);
}
irr::gui::IGUIFont *FontEngine::getFont(FontSpec spec, bool may_fail)
{
if (spec.mode == FM_Unspecified) {
spec.mode = m_currentMode;
} else if (spec.mode == _FM_Fallback) {
// Fallback font doesn't support these
spec.bold = false;
spec.italic = false;
}
// Fallback to default size
if (spec.size == FONT_SIZE_UNSPECIFIED)
spec.size = m_default_size[spec.mode];
RecursiveMutexAutoLock l(m_font_mutex);
const auto &cache = m_font_cache[spec.getHash()];
auto it = cache.find(spec.size);
if (it != cache.end())
return it->second;
// Font does not yet exist
gui::IGUIFont *font = initFont(spec);
if (!font && !may_fail) {
errorstream << "Minetest cannot continue without a valid font. "
"Please correct the 'font_path' setting or install the font "
"file in the proper location." << std::endl;
abort();
}
m_font_cache[spec.getHash()][spec.size] = font;
return font;
}
/******************************************************************************/
unsigned int FontEngine::getTextHeight(const FontSpec &spec)
{
gui::IGUIFont *font = getFont(spec);
return font->getDimension(L"Some unimportant example String").Height;
}
/******************************************************************************/
unsigned int FontEngine::getTextWidth(const std::wstring &text, const FontSpec &spec)
{
gui::IGUIFont *font = getFont(spec);
return font->getDimension(text.c_str()).Width;
}
/** get line height for a specific font (including empty room between lines) */
unsigned int FontEngine::getLineHeight(const FontSpec &spec)
{
gui::IGUIFont *font = getFont(spec);
return font->getDimension(L"Some unimportant example String").Height
+ font->getKerningHeight();
}
/******************************************************************************/
unsigned int FontEngine::getDefaultFontSize()
{
return m_default_size[m_currentMode];
}
unsigned int FontEngine::getFontSize(FontMode mode)
{
if (mode == FM_Unspecified)
return m_default_size[FM_Standard];
return m_default_size[mode];
}
/******************************************************************************/
void FontEngine::readSettings()
{
m_default_size[FM_Standard] = rangelim(g_settings->getU16("font_size"), 5, 72);
m_default_size[_FM_Fallback] = m_default_size[FM_Standard];
m_default_size[FM_Mono] = rangelim(g_settings->getU16("mono_font_size"), 5, 72);
m_default_bold = g_settings->getBool("font_bold");
m_default_italic = g_settings->getBool("font_italic");
cleanCache();
updateFontCache();
updateSkin();
}
/******************************************************************************/
void FontEngine::updateSkin()
{
gui::IGUIFont *font = getFont();
assert(font);
m_env->getSkin()->setFont(font);
}
/******************************************************************************/
void FontEngine::updateFontCache()
{
/* the only font to be initialized is default one,
* all others are re-initialized on demand */
getFont(FONT_SIZE_UNSPECIFIED, FM_Unspecified);
}
/******************************************************************************/
gui::IGUIFont *FontEngine::initFont(const FontSpec &spec)
{
assert(spec.mode != FM_Unspecified);
assert(spec.size != FONT_SIZE_UNSPECIFIED);
std::string setting_prefix = "";
if (spec.mode == FM_Mono)
setting_prefix = "mono_";
std::string setting_suffix = "";
if (spec.bold)
setting_suffix.append("_bold");
if (spec.italic)
setting_suffix.append("_italic");
// Font size in pixels for FreeType
u32 size = rangelim(spec.size * RenderingEngine::getDisplayDensity() *
g_settings->getFloat("gui_scaling"), 1U, 500U);
// Constrain the font size to a certain multiple, if necessary
u16 divisible_by = g_settings->getU16(setting_prefix + "font_size_divisible_by");
if (divisible_by > 1) {
size = std::max<u32>(
std::round((double)size / divisible_by) * divisible_by, divisible_by);
}
sanity_check(size != 0);
u16 font_shadow = 0;
u16 font_shadow_alpha = 0;
g_settings->getU16NoEx(setting_prefix + "font_shadow", font_shadow);
g_settings->getU16NoEx(setting_prefix + "font_shadow_alpha",
font_shadow_alpha);
std::string path_setting;
if (spec.mode == _FM_Fallback)
path_setting = "fallback_font_path";
else
path_setting = setting_prefix + "font_path" + setting_suffix;
std::string fallback_settings[] = {
g_settings->get(path_setting),
Settings::getLayer(SL_DEFAULTS)->get(path_setting)
};
for (const std::string &font_path : fallback_settings) {
gui::CGUITTFont *font = gui::CGUITTFont::createTTFont(m_env,
font_path.c_str(), size, true, true, font_shadow,
font_shadow_alpha);
if (!font) {
errorstream << "FontEngine: Cannot load '" << font_path <<
"'. Trying to fall back to another path." << std::endl;
continue;
}
if (spec.mode != _FM_Fallback) {
FontSpec spec2(spec);
spec2.mode = _FM_Fallback;
font->setFallback(getFont(spec2, true));
}
return font;
}
return nullptr;
}

170
src/client/fontengine.h Normal file
View File

@@ -0,0 +1,170 @@
/*
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 "util/basic_macros.h"
#include "irrlichttypes.h"
#include <IGUIFont.h>
#include <IGUISkin.h>
#include <IGUIEnvironment.h>
#include "settings.h"
#include "threading/mutex_auto_lock.h"
#define FONT_SIZE_UNSPECIFIED 0xFFFFFFFF
enum FontMode : u8 {
FM_Standard = 0,
FM_Mono,
_FM_Fallback, // do not use directly
FM_MaxMode,
FM_Unspecified
};
struct FontSpec {
FontSpec(unsigned int font_size, FontMode mode, bool bold, bool italic) :
size(font_size),
mode(mode),
bold(bold),
italic(italic) {}
u16 getHash() const
{
return (mode << 2) | (static_cast<u8>(bold) << 1) | static_cast<u8>(italic);
}
unsigned int size;
FontMode mode;
bool bold;
bool italic;
};
class FontEngine
{
public:
FontEngine(gui::IGUIEnvironment* env);
~FontEngine();
// Get best possible font specified by FontSpec
irr::gui::IGUIFont *getFont(FontSpec spec);
irr::gui::IGUIFont *getFont(unsigned int font_size=FONT_SIZE_UNSPECIFIED,
FontMode mode=FM_Unspecified)
{
FontSpec spec(font_size, mode, m_default_bold, m_default_italic);
return getFont(spec);
}
/** get text height for a specific font */
unsigned int getTextHeight(const FontSpec &spec);
/** get text width if a text for a specific font */
unsigned int getTextHeight(
unsigned int font_size=FONT_SIZE_UNSPECIFIED,
FontMode mode=FM_Unspecified)
{
FontSpec spec(font_size, mode, m_default_bold, m_default_italic);
return getTextHeight(spec);
}
unsigned int getTextWidth(const std::wstring &text, const FontSpec &spec);
/** 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)
{
FontSpec spec(font_size, mode, m_default_bold, m_default_italic);
return getTextWidth(text, spec);
}
unsigned int getTextWidth(const std::string &text, const FontSpec &spec)
{
return getTextWidth(utf8_to_wide(text), spec);
}
unsigned int getTextWidth(const std::string& text,
unsigned int font_size=FONT_SIZE_UNSPECIFIED,
FontMode mode=FM_Unspecified)
{
FontSpec spec(font_size, mode, m_default_bold, m_default_italic);
return getTextWidth(utf8_to_wide(text), spec);
}
/** get line height for a specific font (including empty room between lines) */
unsigned int getLineHeight(const FontSpec &spec);
unsigned int getLineHeight(unsigned int font_size=FONT_SIZE_UNSPECIFIED,
FontMode mode=FM_Unspecified)
{
FontSpec spec(font_size, mode, m_default_bold, m_default_italic);
return getLineHeight(spec);
}
/** get default font size */
unsigned int getDefaultFontSize();
/** get font size for a specific mode */
unsigned int getFontSize(FontMode mode);
/** update internal parameters from settings */
void readSettings();
private:
irr::gui::IGUIFont *getFont(FontSpec spec, bool may_fail);
/** update content of font cache in case of a setting change made it invalid */
void updateFontCache();
/** initialize a new TTF font */
gui::IGUIFont *initFont(const FontSpec &spec);
/** update current minetest skin with font changes */
void updateSkin();
/** clean cache */
void cleanCache();
/** pointer to irrlicht gui environment */
gui::IGUIEnvironment* m_env = nullptr;
/** mutex used to protect font init and cache */
std::recursive_mutex m_font_mutex;
/** internal storage for caching fonts of different size */
std::map<unsigned int, irr::gui::IGUIFont*> m_font_cache[FM_MaxMode << 2];
/** default font size to use */
unsigned int m_default_size[FM_MaxMode];
/** default bold and italic */
bool m_default_bold = false;
bool m_default_italic = false;
/** default font engine mode (fixed) */
static const FontMode m_currentMode = FM_Standard;
DISABLE_CLASS_COPY(FontEngine);
};
/** interface to access main font engine*/
extern FontEngine* g_fontengine;

4376
src/client/game.cpp Normal file

File diff suppressed because it is too large Load Diff

53
src/client/game.h Normal file
View File

@@ -0,0 +1,53 @@
/*
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;
class RenderingEngine;
struct SubgameSpec;
struct GameStartData;
struct Jitter {
f32 max, min, avg, counter, max_sample, min_sample, max_fraction;
};
struct RunStats {
u64 drawtime; // (us)
Jitter dtime_jitter, busy_time_jitter;
};
struct CameraOrientation {
f32 camera_yaw; // "right/left"
f32 camera_pitch; // "up/down"
};
void the_game(bool *kill,
InputHandler *input,
RenderingEngine *rendering_engine,
const GameStartData &start_data,
std::string &error_message,
ChatBackend &chat_backend,
bool *reconnect_requested);

331
src/client/gameui.cpp Normal file
View File

@@ -0,0 +1,331 @@
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2018 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
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 "gameui.h"
#include <irrlicht_changes/static_text.h>
#include <gettext.h>
#include "gui/mainmenumanager.h"
#include "gui/guiChatConsole.h"
#include "util/pointedthing.h"
#include "client.h"
#include "clientmap.h"
#include "fontengine.h"
#include "nodedef.h"
#include "profiler.h"
#include "renderingengine.h"
#include "version.h"
inline static const char *yawToDirectionString(int yaw)
{
static const char *direction[4] =
{"North +Z", "West -X", "South -Z", "East +X"};
yaw = wrapDegrees_0_360(yaw);
yaw = (yaw + 45) % 360 / 90;
return direction[yaw];
}
GameUI::GameUI()
{
if (guienv && guienv->getSkin())
m_statustext_initial_color = guienv->getSkin()->getColor(gui::EGDC_BUTTON_TEXT);
else
m_statustext_initial_color = video::SColor(255, 0, 0, 0);
}
void GameUI::init()
{
// First line of debug text
m_guitext = gui::StaticText::add(guienv, utf8_to_wide(PROJECT_NAME_C).c_str(),
core::rect<s32>(0, 0, 0, 0), false, false, guiroot);
// Second line of debug text
m_guitext2 = gui::StaticText::add(guienv, L"", core::rect<s32>(0, 0, 0, 0), false,
false, guiroot);
// Chat text
m_guitext_chat = gui::StaticText::add(guienv, L"", core::rect<s32>(0, 0, 0, 0),
//false, false); // Disable word wrap as of now
false, true, guiroot);
u16 chat_font_size = g_settings->getU16("chat_font_size");
if (chat_font_size != 0) {
m_guitext_chat->setOverrideFont(g_fontengine->getFont(
rangelim(chat_font_size, 5, 72), FM_Unspecified));
}
// Infotext of nodes and objects.
// If in debug mode, object debug infos shown here, too.
// Located on the left on the screen, below chat.
u32 chat_font_height = m_guitext_chat->getActiveFont()->getDimension(L"Ay").Height;
m_guitext_info = gui::StaticText::add(guienv, L"",
// Size is limited; text will be truncated after 6 lines.
core::rect<s32>(0, 0, 400, g_fontengine->getTextHeight() * 6) +
v2s32(100, chat_font_height *
(g_settings->getU16("recent_chat_messages") + 3)),
false, true, guiroot);
// Status text (displays info when showing and hiding GUI stuff, etc.)
m_guitext_status = gui::StaticText::add(guienv, L"<Status>",
core::rect<s32>(0, 0, 0, 0), false, false, guiroot);
m_guitext_status->setVisible(false);
// Profiler text (size is updated when text is updated)
m_guitext_profiler = gui::StaticText::add(guienv, L"<Profiler>",
core::rect<s32>(0, 0, 0, 0), false, false, guiroot);
m_guitext_profiler->setOverrideFont(g_fontengine->getFont(
g_fontengine->getDefaultFontSize() * 0.9f, FM_Mono));
m_guitext_profiler->setVisible(false);
}
void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_control,
const CameraOrientation &cam, const PointedThing &pointed_old,
const GUIChatConsole *chat_console, float dtime)
{
v2u32 screensize = RenderingEngine::getWindowSize();
// Minimal debug text must only contain info that can't give a gameplay advantage
if (m_flags.show_minimal_debug) {
const u16 fps = 1.0 / stats.dtime_jitter.avg;
m_drawtime_avg *= 0.95f;
m_drawtime_avg += 0.05f * (stats.drawtime / 1000);
std::ostringstream os(std::ios_base::binary);
os << std::fixed
<< PROJECT_NAME_C " " << g_version_hash
<< " | FPS: " << fps
<< std::setprecision(0)
<< " | drawtime: " << m_drawtime_avg << "ms"
<< std::setprecision(1)
<< " | dtime jitter: "
<< (stats.dtime_jitter.max_fraction * 100.0) << "%"
<< std::setprecision(1)
<< " | view range: "
<< (draw_control->range_all ? "All" : itos(draw_control->wanted_range))
<< std::setprecision(2)
<< " | RTT: " << (client->getRTT() * 1000.0f) << "ms";
setStaticText(m_guitext, utf8_to_wide(os.str()).c_str());
m_guitext->setRelativePosition(core::rect<s32>(5, 5, screensize.X,
5 + g_fontengine->getTextHeight()));
}
// Finally set the guitext visible depending on the flag
m_guitext->setVisible(m_flags.show_minimal_debug);
// Basic debug text also shows info that might give a gameplay advantage
if (m_flags.show_basic_debug) {
LocalPlayer *player = client->getEnv().getLocalPlayer();
v3f player_position = player->getPosition();
std::ostringstream os(std::ios_base::binary);
os << std::setprecision(1) << std::fixed
<< "pos: (" << (player_position.X / BS)
<< ", " << (player_position.Y / BS)
<< ", " << (player_position.Z / BS)
<< ") | yaw: " << (wrapDegrees_0_360(cam.camera_yaw)) << "° "
<< yawToDirectionString(cam.camera_yaw)
<< " | pitch: " << (-wrapDegrees_180(cam.camera_pitch)) << "°"
<< " | seed: " << ((u64)client->getMapSeed());
if (pointed_old.type == POINTEDTHING_NODE) {
ClientMap &map = client->getEnv().getClientMap();
const NodeDefManager *nodedef = client->getNodeDefManager();
MapNode n = map.getNode(pointed_old.node_undersurface);
if (n.getContent() != CONTENT_IGNORE) {
if (nodedef->get(n).name == "unknown") {
os << ", pointed: <unknown node>";
} else {
os << ", pointed: " << nodedef->get(n).name;
}
os << ", param2: " << (u64) n.getParam2();
}
}
setStaticText(m_guitext2, utf8_to_wide(os.str()).c_str());
m_guitext2->setRelativePosition(core::rect<s32>(5,
5 + g_fontengine->getTextHeight(), screensize.X,
5 + g_fontengine->getTextHeight() * 2
));
}
m_guitext2->setVisible(m_flags.show_basic_debug);
setStaticText(m_guitext_info, m_infotext.c_str());
m_guitext_info->setVisible(m_flags.show_hud && g_menumgr.menuCount() == 0);
static const float statustext_time_max = 1.5f;
if (!m_statustext.empty()) {
m_statustext_time += dtime;
if (m_statustext_time >= statustext_time_max) {
clearStatusText();
m_statustext_time = 0.0f;
}
}
setStaticText(m_guitext_status, m_statustext.c_str());
m_guitext_status->setVisible(!m_statustext.empty());
if (!m_statustext.empty()) {
s32 status_width = m_guitext_status->getTextWidth();
s32 status_height = m_guitext_status->getTextHeight();
s32 status_y = screensize.Y - 150;
s32 status_x = (screensize.X - status_width) / 2;
m_guitext_status->setRelativePosition(core::rect<s32>(status_x ,
status_y - status_height, status_x + status_width, status_y));
// Fade out
video::SColor final_color = m_statustext_initial_color;
final_color.setAlpha(0);
video::SColor fade_color = m_statustext_initial_color.getInterpolated_quadratic(
m_statustext_initial_color, final_color, m_statustext_time / statustext_time_max);
m_guitext_status->setOverrideColor(fade_color);
m_guitext_status->enableOverrideColor(true);
}
// Hide chat when console is visible
m_guitext_chat->setVisible(isChatVisible() && !chat_console->isVisible());
}
void GameUI::initFlags()
{
m_flags = GameUI::Flags();
m_flags.show_minimal_debug = g_settings->getBool("show_debug");
}
void GameUI::showMinimap(bool show)
{
m_flags.show_minimap = show;
}
void GameUI::showTranslatedStatusText(const char *str)
{
const wchar_t *wmsg = wgettext(str);
showStatusText(wmsg);
delete[] wmsg;
}
void GameUI::setChatText(const EnrichedString &chat_text, u32 recent_chat_count)
{
setStaticText(m_guitext_chat, chat_text);
m_recent_chat_count = recent_chat_count;
}
void GameUI::updateChatSize()
{
// Update gui element size and position
s32 chat_y = 5;
if (m_flags.show_minimal_debug)
chat_y += g_fontengine->getLineHeight();
if (m_flags.show_basic_debug)
chat_y += g_fontengine->getLineHeight();
const v2u32 &window_size = RenderingEngine::getWindowSize();
core::rect<s32> chat_size(10, chat_y, window_size.X - 20, 0);
chat_size.LowerRightCorner.Y = std::min((s32)window_size.Y,
m_guitext_chat->getTextHeight() + chat_y);
if (chat_size == m_current_chat_size)
return;
m_current_chat_size = chat_size;
m_guitext_chat->setRelativePosition(chat_size);
}
void GameUI::updateProfiler()
{
if (m_profiler_current_page != 0) {
std::ostringstream os(std::ios_base::binary);
os << " Profiler page " << (int)m_profiler_current_page <<
", elapsed: " << g_profiler->getElapsedMs() << " ms)" << std::endl;
int lines = g_profiler->print(os, m_profiler_current_page, m_profiler_max_page);
++lines;
EnrichedString str(utf8_to_wide(os.str()));
str.setBackground(video::SColor(120, 0, 0, 0));
setStaticText(m_guitext_profiler, str);
core::dimension2d<u32> size = m_guitext_profiler->getOverrideFont()->
getDimension(str.c_str());
core::position2di upper_left(6, 50);
core::position2di lower_right = upper_left;
lower_right.X += size.Width + 10;
lower_right.Y += size.Height;
m_guitext_profiler->setRelativePosition(core::rect<s32>(upper_left, lower_right));
}
m_guitext_profiler->setVisible(m_profiler_current_page != 0);
}
void GameUI::toggleChat()
{
m_flags.show_chat = !m_flags.show_chat;
if (m_flags.show_chat)
showTranslatedStatusText("Chat shown");
else
showTranslatedStatusText("Chat hidden");
}
void GameUI::toggleHud()
{
m_flags.show_hud = !m_flags.show_hud;
if (m_flags.show_hud)
showTranslatedStatusText("HUD shown");
else
showTranslatedStatusText("HUD hidden");
}
void GameUI::toggleProfiler()
{
m_profiler_current_page = (m_profiler_current_page + 1) % (m_profiler_max_page + 1);
// FIXME: This updates the profiler with incomplete values
updateProfiler();
if (m_profiler_current_page != 0) {
std::wstring msg = fwgettext("Profiler shown (page %d of %d)",
m_profiler_current_page, m_profiler_max_page);
showStatusText(msg);
} else {
showTranslatedStatusText("Profiler hidden");
}
}
void GameUI::deleteFormspec()
{
if (m_formspec) {
m_formspec->drop();
m_formspec = nullptr;
}
m_formname.clear();
}

138
src/client/gameui.h Normal file
View File

@@ -0,0 +1,138 @@
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2018 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
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 <IGUIEnvironment.h>
#include "gui/guiFormSpecMenu.h"
#include "util/enriched_string.h"
#include "util/pointedthing.h"
#include "game.h"
using namespace irr;
class Client;
class GUIChatConsole;
struct MapDrawControl;
/*
* This object intend to contain the core UI elements
* It includes:
* - status texts
* - debug texts
* - chat texts
* - hud flags
*/
class GameUI
{
// Temporary between coding time to move things here
friend class Game;
// Permit unittests to access members directly
friend class TestGameUI;
public:
GameUI();
~GameUI() = default;
// Flags that can, or may, change during main game loop
struct Flags
{
bool show_chat = true;
bool show_hud = true;
bool show_minimap = false;
bool show_minimal_debug = false;
bool show_basic_debug = false;
bool show_profiler_graph = false;
};
void init();
void update(const RunStats &stats, Client *client, MapDrawControl *draw_control,
const CameraOrientation &cam, const PointedThing &pointed_old,
const GUIChatConsole *chat_console, float dtime);
void initFlags();
const Flags &getFlags() const { return m_flags; }
void showMinimap(bool show);
inline void setInfoText(const std::wstring &str) { m_infotext = str; }
inline void clearInfoText() { m_infotext.clear(); }
inline void showStatusText(const std::wstring &str)
{
m_statustext = str;
m_statustext_time = 0.0f;
}
void showTranslatedStatusText(const char *str);
inline void clearStatusText() { m_statustext.clear(); }
bool isChatVisible()
{
return m_flags.show_chat && m_recent_chat_count != 0 && m_profiler_current_page == 0;
}
void setChatText(const EnrichedString &chat_text, u32 recent_chat_count);
void updateChatSize();
void updateProfiler();
void toggleChat();
void toggleHud();
void toggleProfiler();
GUIFormSpecMenu *&updateFormspec(const std::string &formname)
{
m_formname = formname;
return m_formspec;
}
const std::string &getFormspecName() { return m_formname; }
GUIFormSpecMenu *&getFormspecGUI() { return m_formspec; }
void deleteFormspec();
private:
Flags m_flags;
float m_drawtime_avg = 0;
gui::IGUIStaticText *m_guitext = nullptr; // First line of debug text
gui::IGUIStaticText *m_guitext2 = nullptr; // Second line of debug text
gui::IGUIStaticText *m_guitext_info = nullptr; // At the middle of the screen
std::wstring m_infotext;
gui::IGUIStaticText *m_guitext_status = nullptr;
std::wstring m_statustext;
float m_statustext_time = 0.0f;
video::SColor m_statustext_initial_color;
gui::IGUIStaticText *m_guitext_chat = nullptr; // Chat text
u32 m_recent_chat_count = 0;
core::rect<s32> m_current_chat_size{0, 0, 0, 0};
gui::IGUIStaticText *m_guitext_profiler = nullptr; // Profiler text
u8 m_profiler_current_page = 0;
const u8 m_profiler_max_page = 3;
// Default: "". If other than "": Empty show_formspec packets will only
// close the formspec when the formname matches
std::string m_formname;
GUIFormSpecMenu *m_formspec = nullptr;
};

View File

@@ -0,0 +1,240 @@
/*
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(const io::path &key, video::IVideoDriver *driver, video::IImage *value)
{
if (!g_settings->getBool("gui_scaling_filter"))
return;
if (g_imgCache.find(key) != g_imgCache.end())
return; // Already cached.
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.
auto it_txr = g_txrCache.find(scalename);
video::ITexture *scaled = (it_txr != g_txrCache.end()) ? it_txr->second : nullptr;
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.
auto it_img = g_imgCache.find(origname);
video::IImage *srcimg = (it_img != g_imgCache.end()) ? it_img->second : nullptr;
if (!srcimg) {
if (!g_settings->getBool("gui_scaling_filter_txr2img"))
return src;
srcimg = driver->createImageFromData(src->getColorFormat(),
src->getSize(), src->lock(video::ETLM_READ_ONLY), 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);
#if ENABLE_GLES
// Some platforms are picky about textures being powers of 2, so expand
// the image dimensions to the next power of 2, if necessary.
if (!driver->queryFeature(video::EVDF_TEXTURE_NPOT)) {
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);
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);
}
void draw2DImage9Slice(video::IVideoDriver *driver, video::ITexture *texture,
const core::rect<s32> &destrect, const core::rect<s32> &srcrect,
const core::rect<s32> &middlerect, const core::rect<s32> *cliprect,
const video::SColor *const colors)
{
// `-x` is interpreted as `w - x`
core::rect<s32> middle = middlerect;
if (middlerect.LowerRightCorner.X < 0)
middle.LowerRightCorner.X += srcrect.getWidth();
if (middlerect.LowerRightCorner.Y < 0)
middle.LowerRightCorner.Y += srcrect.getHeight();
core::vector2di lower_right_offset = core::vector2di(srcrect.getWidth(),
srcrect.getHeight()) - middle.LowerRightCorner;
for (int y = 0; y < 3; ++y) {
for (int x = 0; x < 3; ++x) {
core::rect<s32> src = srcrect;
core::rect<s32> dest = destrect;
switch (x) {
case 0:
dest.LowerRightCorner.X = destrect.UpperLeftCorner.X + middle.UpperLeftCorner.X;
src.LowerRightCorner.X = srcrect.UpperLeftCorner.X + middle.UpperLeftCorner.X;
break;
case 1:
dest.UpperLeftCorner.X += middle.UpperLeftCorner.X;
dest.LowerRightCorner.X -= lower_right_offset.X;
src.UpperLeftCorner.X += middle.UpperLeftCorner.X;
src.LowerRightCorner.X -= lower_right_offset.X;
break;
case 2:
dest.UpperLeftCorner.X = destrect.LowerRightCorner.X - lower_right_offset.X;
src.UpperLeftCorner.X = srcrect.LowerRightCorner.X - lower_right_offset.X;
break;
}
switch (y) {
case 0:
dest.LowerRightCorner.Y = destrect.UpperLeftCorner.Y + middle.UpperLeftCorner.Y;
src.LowerRightCorner.Y = srcrect.UpperLeftCorner.Y + middle.UpperLeftCorner.Y;
break;
case 1:
dest.UpperLeftCorner.Y += middle.UpperLeftCorner.Y;
dest.LowerRightCorner.Y -= lower_right_offset.Y;
src.UpperLeftCorner.Y += middle.UpperLeftCorner.Y;
src.LowerRightCorner.Y -= lower_right_offset.Y;
break;
case 2:
dest.UpperLeftCorner.Y = destrect.LowerRightCorner.Y - lower_right_offset.Y;
src.UpperLeftCorner.Y = srcrect.LowerRightCorner.Y - lower_right_offset.Y;
break;
}
draw2DImageFilterScaled(driver, texture, dest, src, cliprect, colors, true);
}
}
}

View File

@@ -0,0 +1,58 @@
/*
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(const 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 = nullptr,
const video::SColor *const colors = nullptr, bool usealpha = false);
/*
* 9-slice / segment drawing
*/
void draw2DImage9Slice(video::IVideoDriver *driver, video::ITexture *texture,
const core::rect<s32> &destrect, const core::rect<s32> &srcrect,
const core::rect<s32> &middlerect, const core::rect<s32> *cliprect = nullptr,
const video::SColor *const colors = nullptr);

1260
src/client/hud.cpp Normal file

File diff suppressed because it is too large Load Diff

173
src/client/hud.h Normal file
View File

@@ -0,0 +1,173 @@
/*
Minetest
Copyright (C) 2010-2013 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
Copyright (C) 2017 red-001 <red-001@outlook.ie>
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 <vector>
#include <IGUIFont.h>
#include "irr_aabb3d.h"
#include "../hud.h"
class Client;
class ITextureSource;
class Inventory;
class InventoryList;
class LocalPlayer;
struct ItemStack;
class Hud
{
public:
enum BlockBoundsMode
{
BLOCK_BOUNDS_OFF,
BLOCK_BOUNDS_CURRENT,
BLOCK_BOUNDS_NEAR,
BLOCK_BOUNDS_MAX
} m_block_bounds_mode = BLOCK_BOUNDS_OFF;
video::SColor crosshair_argb;
video::SColor selectionbox_argb;
bool use_crosshair_image = false;
bool use_object_crosshair_image = false;
std::string hotbar_image = "";
bool use_hotbar_image = false;
std::string hotbar_selected_image = "";
bool use_hotbar_selected_image = false;
bool pointing_at_object = false;
Hud(Client *client, LocalPlayer *player,
Inventory *inventory);
~Hud();
enum BlockBoundsMode toggleBlockBounds();
void disableBlockBounds();
void drawBlockBounds();
void drawHotbar(u16 playeritem);
void resizeHotbar();
void drawCrosshair();
void drawSelectionMesh();
void updateSelectionMesh(const v3s16 &camera_offset);
std::vector<aabb3f> *getSelectionBoxes() { return &m_selection_boxes; }
void setSelectionPos(const v3f &pos, const v3s16 &camera_offset);
v3f getSelectionPos() const { return m_selection_pos; }
void setSelectionMeshColor(const video::SColor &color)
{
m_selection_mesh_color = color;
}
void setSelectedFaceNormal(const v3f &face_normal)
{
m_selected_face_normal = face_normal;
}
bool hasElementOfType(HudElementType type);
void drawLuaElements(const v3s16 &camera_offset);
private:
bool calculateScreenPos(const v3s16 &camera_offset, HudElement *e, v2s32 *pos);
void drawStatbar(v2s32 pos, u16 corner, u16 drawdir,
const std::string &texture, const std::string& bgtexture,
s32 count, s32 maxcount, v2s32 offset, v2s32 size = v2s32());
void drawItems(v2s32 upperleftpos, v2s32 screen_offset, s32 itemcount,
s32 inv_offset, InventoryList *mainlist, u16 selectitem,
u16 direction);
void drawItem(const ItemStack &item, const core::rect<s32> &rect, bool selected);
void drawCompassTranslate(HudElement *e, video::ITexture *texture,
const core::rect<s32> &rect, int way);
void drawCompassRotate(HudElement *e, video::ITexture *texture,
const core::rect<s32> &rect, int way);
Client *client = nullptr;
video::IVideoDriver *driver = nullptr;
LocalPlayer *player = nullptr;
Inventory *inventory = nullptr;
ITextureSource *tsrc = nullptr;
float m_hud_scaling; // cached minetest setting
float m_scale_factor;
v3s16 m_camera_offset;
v2u32 m_screensize;
v2s32 m_displaycenter;
s32 m_hotbar_imagesize; // Takes hud_scaling into account, updated by resizeHotbar()
s32 m_padding; // Takes hud_scaling into account, updated by resizeHotbar()
video::SColor hbar_colors[4];
std::vector<aabb3f> m_selection_boxes;
std::vector<aabb3f> m_halo_boxes;
v3f m_selection_pos;
v3f m_selection_pos_with_offset;
scene::IMesh *m_selection_mesh = nullptr;
video::SColor m_selection_mesh_color;
v3f m_selected_face_normal;
video::SMaterial m_selection_material;
scene::SMeshBuffer m_rotation_mesh_buffer;
enum
{
HIGHLIGHT_BOX,
HIGHLIGHT_HALO,
HIGHLIGHT_NONE
} m_mode;
};
enum ItemRotationKind
{
IT_ROT_SELECTED,
IT_ROT_HOVERED,
IT_ROT_DRAGGED,
IT_ROT_OTHER,
IT_ROT_NONE, // Must be last, also serves as number
};
void drawItemStack(video::IVideoDriver *driver,
gui::IGUIFont *font,
const ItemStack &item,
const core::rect<s32> &rect,
const core::rect<s32> *clip,
Client *client,
ItemRotationKind rotation_kind);
void drawItemStack(
video::IVideoDriver *driver,
gui::IGUIFont *font,
const ItemStack &item,
const core::rect<s32> &rect,
const core::rect<s32> *clip,
Client *client,
ItemRotationKind rotation_kind,
const v3s16 &angle,
const v3s16 &rotation_speed);

249
src/client/imagefilters.cpp Normal file
View File

@@ -0,0 +1,249 @@
/*
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>
#include <cassert>
#include <vector>
#include <algorithm>
// Simple 2D bitmap class with just the functionality needed here
class Bitmap {
u32 linesize, lines;
std::vector<u8> data;
static inline u32 bytepos(u32 index) { return index >> 3; }
static inline u8 bitpos(u32 index) { return index & 7; }
public:
Bitmap(u32 width, u32 height) : linesize(width), lines(height),
data(bytepos(width * height) + 1) {}
inline bool get(u32 x, u32 y) const {
u32 index = y * linesize + x;
return data[bytepos(index)] & (1 << bitpos(index));
}
inline void set(u32 x, u32 y) {
u32 index = y * linesize + x;
data[bytepos(index)] |= 1 << bitpos(index);
}
inline bool all() const {
for (u32 i = 0; i < data.size() - 1; i++) {
if (data[i] != 0xff)
return false;
}
// last byte not entirely filled
for (u8 i = 0; i < bitpos(linesize * lines); i++) {
bool value_of_bit = data.back() & (1 << i);
if (!value_of_bit)
return false;
}
return true;
}
inline void copy(Bitmap &to) const {
assert(to.linesize == linesize && to.lines == lines);
to.data = data;
}
};
/* 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 show 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 when the texture is used with ALPHA_CHANNEL_REF,
* 0 when alpha blending is used.
*/
void imageCleanTransparent(video::IImage *src, u32 threshold)
{
core::dimension2d<u32> dim = src->getDimension();
Bitmap bitmap(dim.Width, dim.Height);
// First pass: Mark all opaque pixels
// 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++) {
if (src->getPixel(ctrx, ctry).getAlpha() > threshold)
bitmap.set(ctrx, ctry);
}
// Exit early if all pixels opaque
if (bitmap.all())
return;
Bitmap newmap = bitmap;
// Cap iterations to keep runtime reasonable, for higher-res textures we can
// get away with filling less pixels.
int iter_max = 11 - std::max(dim.Width, dim.Height) / 16;
iter_max = std::max(iter_max, 2);
// Then repeatedly look for transparent pixels, filling them in until
// we're finished.
for (int iter = 0; iter < iter_max; iter++) {
for (u32 ctry = 0; ctry < dim.Height; ctry++)
for (u32 ctrx = 0; ctrx < dim.Width; ctrx++) {
// Skip pixels we have already processed
if (bitmap.get(ctrx, ctry))
continue;
video::SColor c = src->getPixel(ctrx, ctry);
// 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 pixels we haven't processed
if (!bitmap.get(sx, sy))
continue;
// Add RGB values weighted by alpha IF the pixel is opaque, otherwise
// use full weight since we want to propagate colors.
video::SColor d = src->getPixel(sx, sy);
u32 a = d.getAlpha() <= threshold ? 255 : d.getAlpha();
ss += a;
sr += a * d.getRed();
sg += a * d.getGreen();
sb += a * d.getBlue();
}
// 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);
newmap.set(ctrx, ctry);
}
}
if (newmap.all())
return;
// Apply changes to bitmap for next run. This is done so we don't introduce
// a bias in color propagation in the direction pixels are processed.
newmap.copy(bitmap);
}
}
/* 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 rectangle 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, sox + sw);
maxsx = minsx + sw / dim.Width;
maxsx = rangelim(maxsx, 0, sox + sw);
if (minsx > maxsx)
SWAP(double, minsx, maxsx);
minsy = soy + (dy * sh / dim.Height);
minsy = rangelim(minsy, 0, soy + sh);
maxsy = minsy + sh / dim.Height;
maxsy = rangelim(maxsy, 0, soy + 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
View 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 show 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 when the texture is used with ALPHA_CHANNEL_REF,
* 0 when alpha blending is used.
*/
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);

278
src/client/inputhandler.cpp Normal file
View File

@@ -0,0 +1,278 @@
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2017 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
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/numeric.h"
#include "inputhandler.h"
#include "gui/mainmenumanager.h"
#include "hud.h"
void KeyCache::populate_nonchanging()
{
key[KeyType::ESC] = EscapeKey;
}
void KeyCache::populate()
{
key[KeyType::FORWARD] = getKeySetting("keymap_forward");
key[KeyType::BACKWARD] = getKeySetting("keymap_backward");
key[KeyType::LEFT] = getKeySetting("keymap_left");
key[KeyType::RIGHT] = getKeySetting("keymap_right");
key[KeyType::JUMP] = getKeySetting("keymap_jump");
key[KeyType::AUX1] = getKeySetting("keymap_aux1");
key[KeyType::SNEAK] = getKeySetting("keymap_sneak");
key[KeyType::DIG] = getKeySetting("keymap_dig");
key[KeyType::PLACE] = getKeySetting("keymap_place");
key[KeyType::AUTOFORWARD] = getKeySetting("keymap_autoforward");
key[KeyType::DROP] = getKeySetting("keymap_drop");
key[KeyType::INVENTORY] = getKeySetting("keymap_inventory");
key[KeyType::CHAT] = getKeySetting("keymap_chat");
key[KeyType::CMD] = getKeySetting("keymap_cmd");
key[KeyType::CMD_LOCAL] = getKeySetting("keymap_cmd_local");
key[KeyType::CONSOLE] = getKeySetting("keymap_console");
key[KeyType::MINIMAP] = getKeySetting("keymap_minimap");
key[KeyType::FREEMOVE] = getKeySetting("keymap_freemove");
key[KeyType::PITCHMOVE] = getKeySetting("keymap_pitchmove");
key[KeyType::FASTMOVE] = getKeySetting("keymap_fastmove");
key[KeyType::NOCLIP] = getKeySetting("keymap_noclip");
key[KeyType::HOTBAR_PREV] = getKeySetting("keymap_hotbar_previous");
key[KeyType::HOTBAR_NEXT] = getKeySetting("keymap_hotbar_next");
key[KeyType::MUTE] = getKeySetting("keymap_mute");
key[KeyType::INC_VOLUME] = getKeySetting("keymap_increase_volume");
key[KeyType::DEC_VOLUME] = getKeySetting("keymap_decrease_volume");
key[KeyType::CINEMATIC] = getKeySetting("keymap_cinematic");
key[KeyType::SCREENSHOT] = getKeySetting("keymap_screenshot");
key[KeyType::TOGGLE_BLOCK_BOUNDS] = getKeySetting("keymap_toggle_block_bounds");
key[KeyType::TOGGLE_HUD] = getKeySetting("keymap_toggle_hud");
key[KeyType::TOGGLE_CHAT] = getKeySetting("keymap_toggle_chat");
key[KeyType::TOGGLE_FOG] = getKeySetting("keymap_toggle_fog");
key[KeyType::TOGGLE_UPDATE_CAMERA] = getKeySetting("keymap_toggle_update_camera");
key[KeyType::TOGGLE_DEBUG] = getKeySetting("keymap_toggle_debug");
key[KeyType::TOGGLE_PROFILER] = getKeySetting("keymap_toggle_profiler");
key[KeyType::CAMERA_MODE] = getKeySetting("keymap_camera_mode");
key[KeyType::INCREASE_VIEWING_RANGE] =
getKeySetting("keymap_increase_viewing_range_min");
key[KeyType::DECREASE_VIEWING_RANGE] =
getKeySetting("keymap_decrease_viewing_range_min");
key[KeyType::RANGESELECT] = getKeySetting("keymap_rangeselect");
key[KeyType::ZOOM] = getKeySetting("keymap_zoom");
key[KeyType::QUICKTUNE_NEXT] = getKeySetting("keymap_quicktune_next");
key[KeyType::QUICKTUNE_PREV] = getKeySetting("keymap_quicktune_prev");
key[KeyType::QUICKTUNE_INC] = getKeySetting("keymap_quicktune_inc");
key[KeyType::QUICKTUNE_DEC] = getKeySetting("keymap_quicktune_dec");
for (int i = 0; i < HUD_HOTBAR_ITEMCOUNT_MAX; i++) {
std::string slot_key_name = "keymap_slot" + std::to_string(i + 1);
key[KeyType::SLOT_1 + i] = getKeySetting(slot_key_name.c_str());
}
if (handler) {
// First clear all keys, then re-add the ones we listen for
handler->dontListenForKeys();
for (const KeyPress &k : key) {
handler->listenForKey(k);
}
handler->listenForKey(EscapeKey);
handler->listenForKey(CancelKey);
}
}
bool MyEventReceiver::OnEvent(const SEvent &event)
{
/*
React to nothing here if a menu is active
*/
if (isMenuActive()) {
#ifdef HAVE_TOUCHSCREENGUI
if (m_touchscreengui) {
m_touchscreengui->Toggle(false);
}
#endif
return g_menumgr.preprocessEvent(event);
}
// Remember whether each key is down or up
if (event.EventType == irr::EET_KEY_INPUT_EVENT) {
const KeyPress &keyCode = event.KeyInput;
if (keysListenedFor[keyCode]) {
if (event.KeyInput.PressedDown) {
if (!IsKeyDown(keyCode))
keyWasPressed.set(keyCode);
keyIsDown.set(keyCode);
keyWasDown.set(keyCode);
} else {
if (IsKeyDown(keyCode))
keyWasReleased.set(keyCode);
keyIsDown.unset(keyCode);
}
return true;
}
#ifdef HAVE_TOUCHSCREENGUI
} else if (m_touchscreengui && event.EventType == irr::EET_TOUCH_INPUT_EVENT) {
// In case of touchscreengui, we have to handle different events
m_touchscreengui->translateEvent(event);
return true;
#endif
} else if (event.EventType == irr::EET_JOYSTICK_INPUT_EVENT) {
// joystick may be nullptr if game is launched with '--random-input' parameter
return joystick && joystick->handleEvent(event.JoystickEvent);
} else if (event.EventType == irr::EET_MOUSE_INPUT_EVENT) {
// Handle mouse events
KeyPress key;
switch (event.MouseInput.Event) {
case EMIE_LMOUSE_PRESSED_DOWN:
key = "KEY_LBUTTON";
keyIsDown.set(key);
keyWasDown.set(key);
keyWasPressed.set(key);
break;
case EMIE_MMOUSE_PRESSED_DOWN:
key = "KEY_MBUTTON";
keyIsDown.set(key);
keyWasDown.set(key);
keyWasPressed.set(key);
break;
case EMIE_RMOUSE_PRESSED_DOWN:
key = "KEY_RBUTTON";
keyIsDown.set(key);
keyWasDown.set(key);
keyWasPressed.set(key);
break;
case EMIE_LMOUSE_LEFT_UP:
key = "KEY_LBUTTON";
keyIsDown.unset(key);
keyWasReleased.set(key);
break;
case EMIE_MMOUSE_LEFT_UP:
key = "KEY_MBUTTON";
keyIsDown.unset(key);
keyWasReleased.set(key);
break;
case EMIE_RMOUSE_LEFT_UP:
key = "KEY_RBUTTON";
keyIsDown.unset(key);
keyWasReleased.set(key);
break;
case EMIE_MOUSE_WHEEL:
mouse_wheel += event.MouseInput.Wheel;
break;
default: break;
}
} else if (event.EventType == irr::EET_LOG_TEXT_EVENT) {
static const LogLevel irr_loglev_conv[] = {
LL_VERBOSE, // ELL_DEBUG
LL_INFO, // ELL_INFORMATION
LL_WARNING, // ELL_WARNING
LL_ERROR, // ELL_ERROR
LL_NONE, // ELL_NONE
};
assert(event.LogEvent.Level < ARRLEN(irr_loglev_conv));
g_logger.log(irr_loglev_conv[event.LogEvent.Level],
std::string("Irrlicht: ") + event.LogEvent.Text);
return true;
}
/* always return false in order to continue processing events */
return false;
}
/*
* RandomInputHandler
*/
s32 RandomInputHandler::Rand(s32 min, s32 max)
{
return (myrand() % (max - min + 1)) + min;
}
struct RandomInputHandlerSimData {
std::string key;
float counter;
int time_max;
};
void RandomInputHandler::step(float dtime)
{
static RandomInputHandlerSimData rnd_data[] = {
{ "keymap_jump", 0.0f, 40 },
{ "keymap_aux1", 0.0f, 40 },
{ "keymap_forward", 0.0f, 40 },
{ "keymap_left", 0.0f, 40 },
{ "keymap_dig", 0.0f, 30 },
{ "keymap_place", 0.0f, 15 }
};
for (auto &i : rnd_data) {
i.counter -= dtime;
if (i.counter < 0.0) {
i.counter = 0.1 * Rand(1, i.time_max);
keydown.toggle(getKeySetting(i.key.c_str()));
}
}
{
static float counter1 = 0;
counter1 -= dtime;
if (counter1 < 0.0) {
counter1 = 0.1 * Rand(1, 20);
mousespeed = v2s32(Rand(-20, 20), Rand(-15, 20));
}
}
mousepos += mousespeed;
static bool useJoystick = false;
{
static float counterUseJoystick = 0;
counterUseJoystick -= dtime;
if (counterUseJoystick < 0.0) {
counterUseJoystick = 5.0; // switch between joystick and keyboard direction input
useJoystick = !useJoystick;
}
}
if (useJoystick) {
static float counterMovement = 0;
counterMovement -= dtime;
if (counterMovement < 0.0) {
counterMovement = 0.1 * Rand(1, 40);
movementSpeed = Rand(0,100)*0.01;
movementDirection = Rand(-100, 100)*0.01 * M_PI;
}
} else {
bool f = keydown[keycache.key[KeyType::FORWARD]],
l = keydown[keycache.key[KeyType::LEFT]];
if (f || l) {
movementSpeed = 1.0f;
if (f && !l)
movementDirection = 0.0;
else if (!f && l)
movementDirection = -M_PI_2;
else if (f && l)
movementDirection = -M_PI_4;
else
movementDirection = 0.0;
} else {
movementSpeed = 0.0;
movementDirection = 0.0;
}
}
}

435
src/client/inputhandler.h Normal file
View File

@@ -0,0 +1,435 @@
/*
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 "joystick_controller.h"
#include <list>
#include "keycode.h"
#include "renderingengine.h"
#ifdef HAVE_TOUCHSCREENGUI
#include "gui/touchscreengui.h"
#endif
class InputHandler;
/****************************************************************************
Fast key cache for main game loop
****************************************************************************/
/* This is faster than using getKeySetting with the tradeoff that functions
* using it must make sure that it's initialised before using it and there is
* no error handling (for example bounds checking). This is really intended for
* use only in the main running loop of the client (the_game()) where the faster
* (up to 10x faster) key lookup is an asset. Other parts of the codebase
* (e.g. formspecs) should continue using getKeySetting().
*/
struct KeyCache
{
KeyCache()
{
handler = NULL;
populate();
populate_nonchanging();
}
void populate();
// Keys that are not settings dependent
void populate_nonchanging();
KeyPress key[KeyType::INTERNAL_ENUM_COUNT];
InputHandler *handler;
};
class KeyList : private std::list<KeyPress>
{
typedef std::list<KeyPress> super;
typedef super::iterator iterator;
typedef super::const_iterator const_iterator;
virtual const_iterator find(const KeyPress &key) const
{
const_iterator f(begin());
const_iterator e(end());
while (f != e) {
if (*f == key)
return f;
++f;
}
return e;
}
virtual iterator find(const KeyPress &key)
{
iterator f(begin());
iterator e(end());
while (f != e) {
if (*f == key)
return f;
++f;
}
return e;
}
public:
void clear() { super::clear(); }
void set(const KeyPress &key)
{
if (find(key) == end())
push_back(key);
}
void unset(const KeyPress &key)
{
iterator p(find(key));
if (p != end())
erase(p);
}
void toggle(const KeyPress &key)
{
iterator p(this->find(key));
if (p != end())
erase(p);
else
push_back(key);
}
bool operator[](const KeyPress &key) const { return find(key) != end(); }
};
class MyEventReceiver : public IEventReceiver
{
public:
// This is the one method that we have to implement
virtual bool OnEvent(const SEvent &event);
bool IsKeyDown(const KeyPress &keyCode) const { return keyIsDown[keyCode]; }
// Checks whether a key was down and resets the state
bool WasKeyDown(const KeyPress &keyCode)
{
bool b = keyWasDown[keyCode];
if (b)
keyWasDown.unset(keyCode);
return b;
}
// Checks whether a key was just pressed. State will be cleared
// in the subsequent iteration of Game::processPlayerInteraction
bool WasKeyPressed(const KeyPress &keycode) const { return keyWasPressed[keycode]; }
// Checks whether a key was just released. State will be cleared
// in the subsequent iteration of Game::processPlayerInteraction
bool WasKeyReleased(const KeyPress &keycode) const { return keyWasReleased[keycode]; }
void listenForKey(const KeyPress &keyCode)
{
keysListenedFor.set(keyCode);
}
void dontListenForKeys()
{
keysListenedFor.clear();
}
s32 getMouseWheel()
{
s32 a = mouse_wheel;
mouse_wheel = 0;
return a;
}
void clearInput()
{
keyIsDown.clear();
keyWasDown.clear();
keyWasPressed.clear();
keyWasReleased.clear();
mouse_wheel = 0;
}
void clearWasKeyPressed()
{
keyWasPressed.clear();
}
void clearWasKeyReleased()
{
keyWasReleased.clear();
}
MyEventReceiver()
{
#ifdef HAVE_TOUCHSCREENGUI
m_touchscreengui = NULL;
#endif
}
JoystickController *joystick = nullptr;
#ifdef HAVE_TOUCHSCREENGUI
TouchScreenGUI *m_touchscreengui;
#endif
private:
s32 mouse_wheel = 0;
// The current state of keys
KeyList keyIsDown;
// Like keyIsDown but only reset when that key is read
KeyList keyWasDown;
// Whether a key has just been pressed
KeyList keyWasPressed;
// Whether a key has just been released
KeyList keyWasReleased;
// List of keys we listen for
// TODO perhaps the type of this is not really
// performant as KeyList is designed for few but
// often changing keys, and keysListenedFor is expected
// to change seldomly but contain lots of keys.
KeyList keysListenedFor;
};
class InputHandler
{
public:
InputHandler()
{
keycache.handler = this;
keycache.populate();
}
virtual ~InputHandler() = default;
virtual bool isRandom() const
{
return false;
}
virtual bool isKeyDown(GameKeyType k) = 0;
virtual bool wasKeyDown(GameKeyType k) = 0;
virtual bool wasKeyPressed(GameKeyType k) = 0;
virtual bool wasKeyReleased(GameKeyType k) = 0;
virtual bool cancelPressed() = 0;
virtual float getMovementSpeed() = 0;
virtual float getMovementDirection() = 0;
virtual void clearWasKeyPressed() {}
virtual void clearWasKeyReleased() {}
virtual void listenForKey(const KeyPress &keyCode) {}
virtual void dontListenForKeys() {}
virtual v2s32 getMousePos() = 0;
virtual void setMousePos(s32 x, s32 y) = 0;
virtual s32 getMouseWheel() = 0;
virtual void step(float dtime) {}
virtual void clear() {}
JoystickController joystick;
KeyCache keycache;
};
/*
Separated input handler
*/
class RealInputHandler : public InputHandler
{
public:
RealInputHandler(MyEventReceiver *receiver) : m_receiver(receiver)
{
m_receiver->joystick = &joystick;
}
virtual ~RealInputHandler()
{
m_receiver->joystick = nullptr;
}
virtual bool isKeyDown(GameKeyType k)
{
return m_receiver->IsKeyDown(keycache.key[k]) || joystick.isKeyDown(k);
}
virtual bool wasKeyDown(GameKeyType k)
{
return m_receiver->WasKeyDown(keycache.key[k]) || joystick.wasKeyDown(k);
}
virtual bool wasKeyPressed(GameKeyType k)
{
return m_receiver->WasKeyPressed(keycache.key[k]) || joystick.wasKeyPressed(k);
}
virtual bool wasKeyReleased(GameKeyType k)
{
return m_receiver->WasKeyReleased(keycache.key[k]) || joystick.wasKeyReleased(k);
}
virtual float getMovementSpeed()
{
bool f = m_receiver->IsKeyDown(keycache.key[KeyType::FORWARD]),
b = m_receiver->IsKeyDown(keycache.key[KeyType::BACKWARD]),
l = m_receiver->IsKeyDown(keycache.key[KeyType::LEFT]),
r = m_receiver->IsKeyDown(keycache.key[KeyType::RIGHT]);
if (f || b || l || r)
{
// if contradictory keys pressed, stay still
if (f && b && l && r)
return 0.0f;
else if (f && b && !l && !r)
return 0.0f;
else if (!f && !b && l && r)
return 0.0f;
return 1.0f; // If there is a keyboard event, assume maximum speed
}
return joystick.getMovementSpeed();
}
virtual float getMovementDirection()
{
float x = 0, z = 0;
/* Check keyboard for input */
if (m_receiver->IsKeyDown(keycache.key[KeyType::FORWARD]))
z += 1;
if (m_receiver->IsKeyDown(keycache.key[KeyType::BACKWARD]))
z -= 1;
if (m_receiver->IsKeyDown(keycache.key[KeyType::RIGHT]))
x += 1;
if (m_receiver->IsKeyDown(keycache.key[KeyType::LEFT]))
x -= 1;
if (x != 0 || z != 0) /* If there is a keyboard event, it takes priority */
return atan2(x, z);
else
return joystick.getMovementDirection();
}
virtual bool cancelPressed()
{
return wasKeyDown(KeyType::ESC) || m_receiver->WasKeyDown(CancelKey);
}
virtual void clearWasKeyPressed()
{
m_receiver->clearWasKeyPressed();
}
virtual void clearWasKeyReleased()
{
m_receiver->clearWasKeyReleased();
}
virtual void listenForKey(const KeyPress &keyCode)
{
m_receiver->listenForKey(keyCode);
}
virtual void dontListenForKeys()
{
m_receiver->dontListenForKeys();
}
virtual v2s32 getMousePos()
{
auto control = RenderingEngine::get_raw_device()->getCursorControl();
if (control) {
return control->getPosition();
}
return m_mousepos;
}
virtual void setMousePos(s32 x, s32 y)
{
auto control = RenderingEngine::get_raw_device()->getCursorControl();
if (control) {
control->setPosition(x, y);
} else {
m_mousepos = v2s32(x, y);
}
}
virtual s32 getMouseWheel()
{
return m_receiver->getMouseWheel();
}
void clear()
{
joystick.clear();
m_receiver->clearInput();
}
private:
MyEventReceiver *m_receiver = nullptr;
v2s32 m_mousepos;
};
class RandomInputHandler : public InputHandler
{
public:
RandomInputHandler() = default;
bool isRandom() const
{
return true;
}
virtual bool isKeyDown(GameKeyType k) { return keydown[keycache.key[k]]; }
virtual bool wasKeyDown(GameKeyType k) { return false; }
virtual bool wasKeyPressed(GameKeyType k) { return false; }
virtual bool wasKeyReleased(GameKeyType k) { return false; }
virtual bool cancelPressed() { return false; }
virtual float getMovementSpeed() { return movementSpeed; }
virtual float getMovementDirection() { return movementDirection; }
virtual v2s32 getMousePos() { return mousepos; }
virtual void setMousePos(s32 x, s32 y) { mousepos = v2s32(x, y); }
virtual s32 getMouseWheel() { return 0; }
virtual void step(float dtime);
s32 Rand(s32 min, s32 max);
private:
KeyList keydown;
v2s32 mousepos;
v2s32 mousespeed;
float movementSpeed;
float movementDirection;
};

View File

@@ -0,0 +1,330 @@
/*
Minetest
Copyright (C) 2016 est31, <MTest31@outlook.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 "joystick_controller.h"
#include "irrlichttypes_extrabloated.h"
#include "keys.h"
#include "settings.h"
#include "gettime.h"
#include "porting.h"
#include "util/string.h"
#include "util/numeric.h"
bool JoystickButtonCmb::isTriggered(const irr::SEvent::SJoystickEvent &ev) const
{
u32 buttons = ev.ButtonStates;
buttons &= filter_mask;
return buttons == compare_mask;
}
bool JoystickAxisCmb::isTriggered(const irr::SEvent::SJoystickEvent &ev) const
{
s16 ax_val = ev.Axis[axis_to_compare];
return (ax_val * direction < -thresh);
}
// spares many characters
#define JLO_B_PB(A, B, C) jlo.button_keys.emplace_back(A, B, C)
#define JLO_A_PB(A, B, C, D) jlo.axis_keys.emplace_back(A, B, C, D)
JoystickLayout create_default_layout()
{
JoystickLayout jlo;
jlo.axes_deadzone = g_settings->getU16("joystick_deadzone");
const JoystickAxisLayout axes[JA_COUNT] = {
{0, 1}, // JA_SIDEWARD_MOVE
{1, 1}, // JA_FORWARD_MOVE
{3, 1}, // JA_FRUSTUM_HORIZONTAL
{4, 1}, // JA_FRUSTUM_VERTICAL
};
memcpy(jlo.axes, axes, sizeof(jlo.axes));
u32 sb = 1 << 7; // START button mask
u32 fb = 1 << 3; // FOUR button mask
u32 bm = sb | fb; // Mask for Both Modifiers
// The back button means "ESC".
JLO_B_PB(KeyType::ESC, 1 << 6, 1 << 6);
// The start button counts as modifier as well as use key.
// JLO_B_PB(KeyType::USE, sb, sb));
// Accessible without start modifier button pressed
// regardless whether four is pressed or not
JLO_B_PB(KeyType::SNEAK, sb | 1 << 2, 1 << 2);
// Accessible without four modifier button pressed
// regardless whether start is pressed or not
JLO_B_PB(KeyType::DIG, fb | 1 << 4, 1 << 4);
JLO_B_PB(KeyType::PLACE, fb | 1 << 5, 1 << 5);
// Accessible without any modifier pressed
JLO_B_PB(KeyType::JUMP, bm | 1 << 0, 1 << 0);
JLO_B_PB(KeyType::AUX1, bm | 1 << 1, 1 << 1);
// Accessible with start button not pressed, but four pressed
// TODO find usage for button 0
JLO_B_PB(KeyType::DROP, bm | 1 << 1, fb | 1 << 1);
JLO_B_PB(KeyType::HOTBAR_PREV, bm | 1 << 4, fb | 1 << 4);
JLO_B_PB(KeyType::HOTBAR_NEXT, bm | 1 << 5, fb | 1 << 5);
// Accessible with start button and four pressed
// TODO find usage for buttons 0, 1 and 4, 5
// Now about the buttons simulated by the axes
// Movement buttons, important for vessels
JLO_A_PB(KeyType::FORWARD, 1, 1, jlo.axes_deadzone);
JLO_A_PB(KeyType::BACKWARD, 1, -1, jlo.axes_deadzone);
JLO_A_PB(KeyType::LEFT, 0, 1, jlo.axes_deadzone);
JLO_A_PB(KeyType::RIGHT, 0, -1, jlo.axes_deadzone);
// Scroll buttons
JLO_A_PB(KeyType::HOTBAR_PREV, 2, -1, jlo.axes_deadzone);
JLO_A_PB(KeyType::HOTBAR_NEXT, 5, -1, jlo.axes_deadzone);
return jlo;
}
JoystickLayout create_xbox_layout()
{
JoystickLayout jlo;
jlo.axes_deadzone = 7000;
const JoystickAxisLayout axes[JA_COUNT] = {
{0, 1}, // JA_SIDEWARD_MOVE
{1, 1}, // JA_FORWARD_MOVE
{2, 1}, // JA_FRUSTUM_HORIZONTAL
{3, 1}, // JA_FRUSTUM_VERTICAL
};
memcpy(jlo.axes, axes, sizeof(jlo.axes));
// The back button means "ESC".
JLO_B_PB(KeyType::ESC, 1 << 8, 1 << 8); // back
JLO_B_PB(KeyType::ESC, 1 << 9, 1 << 9); // start
// 4 Buttons
JLO_B_PB(KeyType::JUMP, 1 << 0, 1 << 0); // A/green
JLO_B_PB(KeyType::ESC, 1 << 1, 1 << 1); // B/red
JLO_B_PB(KeyType::AUX1, 1 << 2, 1 << 2); // X/blue
JLO_B_PB(KeyType::INVENTORY, 1 << 3, 1 << 3); // Y/yellow
// Analog Sticks
JLO_B_PB(KeyType::AUX1, 1 << 11, 1 << 11); // left
JLO_B_PB(KeyType::SNEAK, 1 << 12, 1 << 12); // right
// Triggers
JLO_B_PB(KeyType::DIG, 1 << 6, 1 << 6); // lt
JLO_B_PB(KeyType::PLACE, 1 << 7, 1 << 7); // rt
JLO_B_PB(KeyType::HOTBAR_PREV, 1 << 4, 1 << 4); // lb
JLO_B_PB(KeyType::HOTBAR_NEXT, 1 << 5, 1 << 5); // rb
// D-PAD
JLO_B_PB(KeyType::ZOOM, 1 << 15, 1 << 15); // up
JLO_B_PB(KeyType::DROP, 1 << 13, 1 << 13); // left
JLO_B_PB(KeyType::SCREENSHOT, 1 << 14, 1 << 14); // right
JLO_B_PB(KeyType::FREEMOVE, 1 << 16, 1 << 16); // down
// Movement buttons, important for vessels
JLO_A_PB(KeyType::FORWARD, 1, 1, jlo.axes_deadzone);
JLO_A_PB(KeyType::BACKWARD, 1, -1, jlo.axes_deadzone);
JLO_A_PB(KeyType::LEFT, 0, 1, jlo.axes_deadzone);
JLO_A_PB(KeyType::RIGHT, 0, -1, jlo.axes_deadzone);
return jlo;
}
JoystickLayout create_dragonrise_gamecube_layout()
{
JoystickLayout jlo;
jlo.axes_deadzone = 7000;
const JoystickAxisLayout axes[JA_COUNT] = {
// Control Stick
{0, 1}, // JA_SIDEWARD_MOVE
{1, 1}, // JA_FORWARD_MOVE
// C-Stick
{3, 1}, // JA_FRUSTUM_HORIZONTAL
{4, 1}, // JA_FRUSTUM_VERTICAL
};
memcpy(jlo.axes, axes, sizeof(jlo.axes));
// The center button
JLO_B_PB(KeyType::ESC, 1 << 9, 1 << 9); // Start/Pause Button
// Front right buttons
JLO_B_PB(KeyType::JUMP, 1 << 2, 1 << 2); // A Button
JLO_B_PB(KeyType::SNEAK, 1 << 3, 1 << 3); // B Button
JLO_B_PB(KeyType::DROP, 1 << 0, 1 << 0); // Y Button
JLO_B_PB(KeyType::AUX1, 1 << 1, 1 << 1); // X Button
// Triggers
JLO_B_PB(KeyType::DIG, 1 << 4, 1 << 4); // L Trigger
JLO_B_PB(KeyType::PLACE, 1 << 5, 1 << 5); // R Trigger
JLO_B_PB(KeyType::INVENTORY, 1 << 6, 1 << 6); // Z Button
// D-Pad
JLO_A_PB(KeyType::HOTBAR_PREV, 5, 1, jlo.axes_deadzone); // left
JLO_A_PB(KeyType::HOTBAR_NEXT, 5, -1, jlo.axes_deadzone); // right
// Axis are hard to actuate independantly, best to leave up and down unused.
//JLO_A_PB(0, 6, 1, jlo.axes_deadzone); // up
//JLO_A_PB(0, 6, -1, jlo.axes_deadzone); // down
// Movements tied to Control Stick, important for vessels
JLO_A_PB(KeyType::LEFT, 0, 1, jlo.axes_deadzone);
JLO_A_PB(KeyType::RIGHT, 0, -1, jlo.axes_deadzone);
JLO_A_PB(KeyType::FORWARD, 1, 1, jlo.axes_deadzone);
JLO_A_PB(KeyType::BACKWARD, 1, -1, jlo.axes_deadzone);
return jlo;
}
JoystickController::JoystickController()
{
doubling_dtime = std::max(g_settings->getFloat("repeat_joystick_button_time"), 0.001f);
for (float &i : m_past_pressed_time) {
i = 0;
}
m_layout.axes_deadzone = 0;
clear();
}
void JoystickController::onJoystickConnect(const std::vector<irr::SJoystickInfo> &joystick_infos)
{
s32 id = g_settings->getS32("joystick_id");
std::string layout = g_settings->get("joystick_type");
if (id < 0 || id >= (s32)joystick_infos.size()) {
// TODO: auto detection
id = 0;
}
if (id >= 0 && id < (s32)joystick_infos.size()) {
if (layout.empty() || layout == "auto")
setLayoutFromControllerName(joystick_infos[id].Name.c_str());
else
setLayoutFromControllerName(layout);
}
// Irrlicht restriction.
m_joystick_id = rangelim(id, 0, UINT8_MAX);
}
void JoystickController::setLayoutFromControllerName(const std::string &name)
{
if (lowercase(name).find("xbox") != std::string::npos) {
m_layout = create_xbox_layout();
} else if (lowercase(name).find("dragonrise_gamecube") != std::string::npos) {
m_layout = create_dragonrise_gamecube_layout();
} else {
m_layout = create_default_layout();
}
}
bool JoystickController::handleEvent(const irr::SEvent::SJoystickEvent &ev)
{
if (ev.Joystick != m_joystick_id)
return false;
m_internal_time = porting::getTimeMs() / 1000.f;
std::bitset<KeyType::INTERNAL_ENUM_COUNT> keys_pressed;
// First generate a list of keys pressed
for (const auto &button_key : m_layout.button_keys) {
if (button_key.isTriggered(ev)) {
keys_pressed.set(button_key.key);
}
}
for (const auto &axis_key : m_layout.axis_keys) {
if (axis_key.isTriggered(ev)) {
keys_pressed.set(axis_key.key);
}
}
// Then update the values
for (size_t i = 0; i < KeyType::INTERNAL_ENUM_COUNT; i++) {
if (keys_pressed[i]) {
if (!m_past_keys_pressed[i] &&
m_past_pressed_time[i] < m_internal_time - doubling_dtime) {
m_past_keys_pressed[i] = true;
m_past_pressed_time[i] = m_internal_time;
}
} else if (m_keys_down[i]) {
m_keys_released[i] = true;
}
if (keys_pressed[i] && !(m_keys_down[i]))
m_keys_pressed[i] = true;
m_keys_down[i] = keys_pressed[i];
}
for (size_t i = 0; i < JA_COUNT; i++) {
const JoystickAxisLayout &ax_la = m_layout.axes[i];
m_axes_vals[i] = ax_la.invert * ev.Axis[ax_la.axis_id];
}
return true;
}
void JoystickController::clear()
{
m_keys_pressed.reset();
m_keys_down.reset();
m_past_keys_pressed.reset();
m_keys_released.reset();
memset(m_axes_vals, 0, sizeof(m_axes_vals));
}
float JoystickController::getAxisWithoutDead(JoystickAxis axis)
{
s16 v = m_axes_vals[axis];
if (abs(v) < m_layout.axes_deadzone)
return 0.0f;
v += (v < 0 ? m_layout.axes_deadzone : -m_layout.axes_deadzone);
return (float)v / ((float)(INT16_MAX - m_layout.axes_deadzone));
}
float JoystickController::getMovementDirection()
{
return atan2(getAxisWithoutDead(JA_SIDEWARD_MOVE), -getAxisWithoutDead(JA_FORWARD_MOVE));
}
float JoystickController::getMovementSpeed()
{
float speed = sqrt(pow(getAxisWithoutDead(JA_FORWARD_MOVE), 2) + pow(getAxisWithoutDead(JA_SIDEWARD_MOVE), 2));
if (speed > 1.0f)
speed = 1.0f;
return speed;
}

View File

@@ -0,0 +1,172 @@
/*
Minetest
Copyright (C) 2016 est31, <MTest31@outlook.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 "keys.h"
#include <bitset>
#include <vector>
enum JoystickAxis {
JA_SIDEWARD_MOVE,
JA_FORWARD_MOVE,
JA_FRUSTUM_HORIZONTAL,
JA_FRUSTUM_VERTICAL,
// To know the count of enum values
JA_COUNT,
};
struct JoystickAxisLayout {
u16 axis_id;
// -1 if to invert, 1 if to keep it.
int invert;
};
struct JoystickCombination {
virtual bool isTriggered(const irr::SEvent::SJoystickEvent &ev) const=0;
GameKeyType key;
};
struct JoystickButtonCmb : public JoystickCombination {
JoystickButtonCmb() = default;
JoystickButtonCmb(GameKeyType key, u32 filter_mask, u32 compare_mask) :
filter_mask(filter_mask),
compare_mask(compare_mask)
{
this->key = key;
}
virtual ~JoystickButtonCmb() = default;
virtual bool isTriggered(const irr::SEvent::SJoystickEvent &ev) const;
u32 filter_mask;
u32 compare_mask;
};
struct JoystickAxisCmb : public JoystickCombination {
JoystickAxisCmb() = default;
JoystickAxisCmb(GameKeyType key, u16 axis_to_compare, int direction, s16 thresh) :
axis_to_compare(axis_to_compare),
direction(direction),
thresh(thresh)
{
this->key = key;
}
virtual ~JoystickAxisCmb() = default;
bool isTriggered(const irr::SEvent::SJoystickEvent &ev) const override;
u16 axis_to_compare;
// if -1, thresh must be smaller than the axis value in order to trigger
// if 1, thresh must be bigger than the axis value in order to trigger
int direction;
s16 thresh;
};
struct JoystickLayout {
std::vector<JoystickButtonCmb> button_keys;
std::vector<JoystickAxisCmb> axis_keys;
JoystickAxisLayout axes[JA_COUNT];
s16 axes_deadzone;
};
class JoystickController {
public:
JoystickController();
void onJoystickConnect(const std::vector<irr::SJoystickInfo> &joystick_infos);
bool handleEvent(const irr::SEvent::SJoystickEvent &ev);
void clear();
bool wasKeyDown(GameKeyType b)
{
bool r = m_past_keys_pressed[b];
m_past_keys_pressed[b] = false;
return r;
}
bool wasKeyReleased(GameKeyType b)
{
return m_keys_released[b];
}
void clearWasKeyReleased(GameKeyType b)
{
m_keys_released[b] = false;
}
bool wasKeyPressed(GameKeyType b)
{
return m_keys_pressed[b];
}
void clearWasKeyPressed(GameKeyType b)
{
m_keys_pressed[b] = false;
}
bool isKeyDown(GameKeyType b)
{
return m_keys_down[b];
}
s16 getAxis(JoystickAxis axis)
{
return m_axes_vals[axis];
}
float getAxisWithoutDead(JoystickAxis axis);
float getMovementDirection();
float getMovementSpeed();
f32 doubling_dtime;
private:
void setLayoutFromControllerName(const std::string &name);
JoystickLayout m_layout;
s16 m_axes_vals[JA_COUNT];
u8 m_joystick_id = 0;
std::bitset<KeyType::INTERNAL_ENUM_COUNT> m_keys_down;
std::bitset<KeyType::INTERNAL_ENUM_COUNT> m_keys_pressed;
f32 m_internal_time;
f32 m_past_pressed_time[KeyType::INTERNAL_ENUM_COUNT];
std::bitset<KeyType::INTERNAL_ENUM_COUNT> m_past_keys_pressed;
std::bitset<KeyType::INTERNAL_ENUM_COUNT> m_keys_released;
};

386
src/client/keycode.cpp Normal file
View 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 "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"))
//~ Key name, common on Windows keyboards
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"))
//~ Key name
DEFINEKEY1(KEY_SELECT, N_("Select"))
//~ "Print screen" key
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"))
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")
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." << std::endl;
}
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
View 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);

120
src/client/keys.h Normal file
View File

@@ -0,0 +1,120 @@
/*
Minetest
Copyright (C) 2016 est31, <MTest31@outlook.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 <list>
class KeyType
{
public:
enum T
{
// Player movement
FORWARD,
BACKWARD,
LEFT,
RIGHT,
JUMP,
AUX1,
SNEAK,
AUTOFORWARD,
DIG,
PLACE,
ESC,
// Other
DROP,
INVENTORY,
CHAT,
CMD,
CMD_LOCAL,
CONSOLE,
MINIMAP,
FREEMOVE,
PITCHMOVE,
FASTMOVE,
NOCLIP,
HOTBAR_PREV,
HOTBAR_NEXT,
MUTE,
INC_VOLUME,
DEC_VOLUME,
CINEMATIC,
SCREENSHOT,
TOGGLE_BLOCK_BOUNDS,
TOGGLE_HUD,
TOGGLE_CHAT,
TOGGLE_FOG,
TOGGLE_UPDATE_CAMERA,
TOGGLE_DEBUG,
TOGGLE_PROFILER,
CAMERA_MODE,
INCREASE_VIEWING_RANGE,
DECREASE_VIEWING_RANGE,
RANGESELECT,
ZOOM,
QUICKTUNE_NEXT,
QUICKTUNE_PREV,
QUICKTUNE_INC,
QUICKTUNE_DEC,
// hotbar
SLOT_1,
SLOT_2,
SLOT_3,
SLOT_4,
SLOT_5,
SLOT_6,
SLOT_7,
SLOT_8,
SLOT_9,
SLOT_10,
SLOT_11,
SLOT_12,
SLOT_13,
SLOT_14,
SLOT_15,
SLOT_16,
SLOT_17,
SLOT_18,
SLOT_19,
SLOT_20,
SLOT_21,
SLOT_22,
SLOT_23,
SLOT_24,
SLOT_25,
SLOT_26,
SLOT_27,
SLOT_28,
SLOT_29,
SLOT_30,
SLOT_31,
SLOT_32,
// Fake keycode for array size and internal checks
INTERNAL_ENUM_COUNT
};
};
typedef KeyType::T GameKeyType;

1138
src/client/localplayer.cpp Normal file

File diff suppressed because it is too large Load Diff

215
src/client/localplayer.h Normal file
View File

@@ -0,0 +1,215 @@
/*
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 "lighting.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;
// Initialize hp to 0, so that no hearts will be shown if server
// doesn't support health points
u16 hp = 0;
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;
// Slows down the player when moving through
u8 move_resistance = 0;
bool is_climbing = false;
bool swimming_vertical = false;
bool swimming_pitch = 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;
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;
u32 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 = 0.0f;
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; }
ClientActiveObject *getParent() const;
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; }
// Non-transformed eye offset getters
// For accurate positions, use the Camera functions
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; }
const aabb3f& getCollisionbox() const { return m_collisionbox; }
float getZoomFOV() const { return m_zoom_fov; }
void setZoomFOV(float zoom_fov) { m_zoom_fov = zoom_fov; }
bool getAutojump() const { return m_autojump; }
bool isDead() const;
inline void addVelocity(const v3f &vel)
{
added_velocity += vel;
}
inline Lighting& getLighting() { return m_lighting; }
private:
void accelerate(const v3f &target_speed, const f32 max_increase_H,
const f32 max_increase_V, const bool use_pitch);
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;
bool m_disable_jump = false;
u16 m_breath = PLAYER_MAX_BREATH_DEFAULT;
f32 m_yaw = 0.0f;
f32 m_pitch = 0.0f;
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;
v3f added_velocity = v3f(0.0f); // cleared on each move()
// TODO: Rename to adhere to convention: added_velocity --> m_added_velocity
GenericCAO *m_cao = nullptr;
Client *m_client;
Lighting m_lighting;
};

1581
src/client/mapblock_mesh.cpp Normal file

File diff suppressed because it is too large Load Diff

332
src/client/mapblock_mesh.h Normal file
View File

@@ -0,0 +1,332 @@
/*
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;
MeshMakeData(Client *client, bool use_shaders);
/*
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 the (node) position of a crack
*/
void setCrack(int crack_level, v3s16 crack_pos);
/*
Enable or disable smooth lighting
*/
void setSmoothLighting(bool smooth_lighting);
};
// represents a triangle as indexes into the vertex buffer in SMeshBuffer
class MeshTriangle
{
public:
scene::SMeshBuffer *buffer;
u16 p1, p2, p3;
v3f centroid;
float areaSQ;
void updateAttributes()
{
v3f v1 = buffer->getPosition(p1);
v3f v2 = buffer->getPosition(p2);
v3f v3 = buffer->getPosition(p3);
centroid = (v1 + v2 + v3) / 3;
areaSQ = (v2-v1).crossProduct(v3-v1).getLengthSQ() / 4;
}
v3f getNormal() const {
v3f v1 = buffer->getPosition(p1);
v3f v2 = buffer->getPosition(p2);
v3f v3 = buffer->getPosition(p3);
return (v2-v1).crossProduct(v3-v1);
}
};
/**
* Implements a binary space partitioning tree
* See also: https://en.wikipedia.org/wiki/Binary_space_partitioning
*/
class MapBlockBspTree
{
public:
MapBlockBspTree() {}
void buildTree(const std::vector<MeshTriangle> *triangles);
void traverse(v3f viewpoint, std::vector<s32> &output) const
{
traverse(root, viewpoint, output);
}
private:
// Tree node definition;
struct TreeNode
{
v3f normal;
v3f origin;
std::vector<s32> triangle_refs;
s32 front_ref;
s32 back_ref;
TreeNode() = default;
TreeNode(v3f normal, v3f origin, const std::vector<s32> &triangle_refs, s32 front_ref, s32 back_ref) :
normal(normal), origin(origin), triangle_refs(triangle_refs), front_ref(front_ref), back_ref(back_ref)
{}
};
s32 buildTree(v3f normal, v3f origin, float delta, const std::vector<s32> &list, u32 depth);
void traverse(s32 node, v3f viewpoint, std::vector<s32> &output) const;
const std::vector<MeshTriangle> *triangles = nullptr; // this reference is managed externally
std::vector<TreeNode> nodes; // list of nodes
s32 root = -1; // index of the root node
};
/*
* PartialMeshBuffer
*
* Attach alternate `Indices` to an existing mesh buffer, to make it possible to use different
* indices with the same vertex buffer.
*
* Irrlicht does not currently support this: `CMeshBuffer` ties together a single vertex buffer
* and a single index buffer. There's no way to share these between mesh buffers.
*
*/
class PartialMeshBuffer
{
public:
PartialMeshBuffer(scene::SMeshBuffer *buffer, std::vector<u16> &&vertex_indexes) :
m_buffer(buffer), m_vertex_indexes(std::move(vertex_indexes))
{}
scene::IMeshBuffer *getBuffer() const { return m_buffer; }
const std::vector<u16> &getVertexIndexes() const { return m_vertex_indexes; }
void beforeDraw() const;
void afterDraw() const;
private:
scene::SMeshBuffer *m_buffer;
mutable std::vector<u16> m_vertex_indexes;
};
/*
Holds a mesh for a mapblock.
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--;
}
/// update transparent buffers to render towards the camera
void updateTransparentBuffers(v3f camera_pos, v3s16 block_pos);
void consolidateTransparentBuffers();
/// get the list of transparent buffers
const std::vector<PartialMeshBuffer> &getTransparentBuffers() const
{
return this->m_transparent_buffers;
}
private:
struct AnimationInfo {
int frame; // last animation frame
int frame_offset;
TileLayer tile;
};
scene::IMesh *m_mesh[MAX_TILE_LAYERS];
MinimapMapblock *m_minimap_mapblock;
ITextureSource *m_tsrc;
IShaderSource *m_shdrsrc;
bool m_enable_shaders;
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 animation
// Maps mesh and mesh buffer indices to TileSpecs
// Keys are pairs of (mesh index, buffer index in the mesh)
std::map<std::pair<u8, u32>, AnimationInfo> m_animation_info;
// 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;
// list of all semitransparent triangles in the mapblock
std::vector<MeshTriangle> m_transparent_triangles;
// Binary Space Partitioning tree for the block
MapBlockBspTree m_bsp_tree;
// Ordered list of references to parts of transparent buffers to draw
std::vector<PartialMeshBuffer> m_transparent_buffers;
};
/*!
* 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);

499
src/client/mesh.cpp Normal file
View File

@@ -0,0 +1,499 @@
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "mesh.h"
#include "debug.h"
#include "log.h"
#include <cmath>
#include <iostream>
#include <IAnimatedMesh.h>
#include <SAnimatedMesh.h>
#include <IAnimatedMeshSceneNode.h>
inline static void applyShadeFactor(video::SColor& color, float factor)
{
color.setRed(core::clamp(core::round32(color.getRed()*factor), 0, 255));
color.setGreen(core::clamp(core::round32(color.getGreen()*factor), 0, 255));
color.setBlue(core::clamp(core::round32(color.getBlue()*factor), 0, 255));
}
void applyFacesShading(video::SColor &color, const v3f &normal)
{
/*
Some drawtypes have normals set to (0, 0, 0), this must result in
maximum brightness: shade factor 1.0.
Shade factors for aligned cube faces are:
+Y 1.000000 sqrt(1.0)
-Y 0.447213 sqrt(0.2)
+-X 0.670820 sqrt(0.45)
+-Z 0.836660 sqrt(0.7)
*/
float x2 = normal.X * normal.X;
float y2 = normal.Y * normal.Y;
float z2 = normal.Z * normal.Z;
if (normal.Y < 0)
applyShadeFactor(color, 0.670820f * x2 + 0.447213f * y2 + 0.836660f * z2);
else if ((x2 > 1e-3) || (z2 > 1e-3))
applyShadeFactor(color, 0.670820f * x2 + 1.000000f * y2 + 0.836660f * z2);
}
scene::IAnimatedMesh* createCubeMesh(v3f scale)
{
video::SColor c(255,255,255,255);
video::S3DVertex vertices[24] =
{
// Up
video::S3DVertex(-0.5,+0.5,-0.5, 0,1,0, c, 0,1),
video::S3DVertex(-0.5,+0.5,+0.5, 0,1,0, c, 0,0),
video::S3DVertex(+0.5,+0.5,+0.5, 0,1,0, c, 1,0),
video::S3DVertex(+0.5,+0.5,-0.5, 0,1,0, c, 1,1),
// Down
video::S3DVertex(-0.5,-0.5,-0.5, 0,-1,0, c, 0,0),
video::S3DVertex(+0.5,-0.5,-0.5, 0,-1,0, c, 1,0),
video::S3DVertex(+0.5,-0.5,+0.5, 0,-1,0, c, 1,1),
video::S3DVertex(-0.5,-0.5,+0.5, 0,-1,0, c, 0,1),
// Right
video::S3DVertex(+0.5,-0.5,-0.5, 1,0,0, c, 0,1),
video::S3DVertex(+0.5,+0.5,-0.5, 1,0,0, c, 0,0),
video::S3DVertex(+0.5,+0.5,+0.5, 1,0,0, c, 1,0),
video::S3DVertex(+0.5,-0.5,+0.5, 1,0,0, c, 1,1),
// Left
video::S3DVertex(-0.5,-0.5,-0.5, -1,0,0, c, 1,1),
video::S3DVertex(-0.5,-0.5,+0.5, -1,0,0, c, 0,1),
video::S3DVertex(-0.5,+0.5,+0.5, -1,0,0, c, 0,0),
video::S3DVertex(-0.5,+0.5,-0.5, -1,0,0, c, 1,0),
// Back
video::S3DVertex(-0.5,-0.5,+0.5, 0,0,1, c, 1,1),
video::S3DVertex(+0.5,-0.5,+0.5, 0,0,1, c, 0,1),
video::S3DVertex(+0.5,+0.5,+0.5, 0,0,1, c, 0,0),
video::S3DVertex(-0.5,+0.5,+0.5, 0,0,1, c, 1,0),
// Front
video::S3DVertex(-0.5,-0.5,-0.5, 0,0,-1, c, 0,1),
video::S3DVertex(-0.5,+0.5,-0.5, 0,0,-1, c, 0,0),
video::S3DVertex(+0.5,+0.5,-0.5, 0,0,-1, c, 1,0),
video::S3DVertex(+0.5,-0.5,-0.5, 0,0,-1, c, 1,1),
};
u16 indices[6] = {0,1,2,2,3,0};
scene::SMesh *mesh = new scene::SMesh();
for (u32 i=0; i<6; ++i)
{
scene::IMeshBuffer *buf = new scene::SMeshBuffer();
buf->append(vertices + 4 * i, 4, indices, 6);
// Set default material
buf->getMaterial().setFlag(video::EMF_LIGHTING, false);
buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false);
buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
// Add mesh buffer to mesh
mesh->addMeshBuffer(buf);
buf->drop();
}
scene::SAnimatedMesh *anim_mesh = new scene::SAnimatedMesh(mesh);
mesh->drop();
scaleMesh(anim_mesh, scale); // also recalculates bounding box
return anim_mesh;
}
void scaleMesh(scene::IMesh *mesh, v3f scale)
{
if (mesh == NULL)
return;
aabb3f bbox;
bbox.reset(0, 0, 0);
u32 mc = mesh->getMeshBufferCount();
for (u32 j = 0; j < mc; j++) {
scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
const u32 stride = getVertexPitchFromType(buf->getVertexType());
u32 vertex_count = buf->getVertexCount();
u8 *vertices = (u8 *)buf->getVertices();
for (u32 i = 0; i < vertex_count; i++)
((video::S3DVertex *)(vertices + i * stride))->Pos *= scale;
buf->recalculateBoundingBox();
// calculate total bounding box
if (j == 0)
bbox = buf->getBoundingBox();
else
bbox.addInternalBox(buf->getBoundingBox());
}
mesh->setBoundingBox(bbox);
}
void translateMesh(scene::IMesh *mesh, v3f vec)
{
if (mesh == NULL)
return;
aabb3f bbox;
bbox.reset(0, 0, 0);
u32 mc = mesh->getMeshBufferCount();
for (u32 j = 0; j < mc; j++) {
scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
const u32 stride = getVertexPitchFromType(buf->getVertexType());
u32 vertex_count = buf->getVertexCount();
u8 *vertices = (u8 *)buf->getVertices();
for (u32 i = 0; i < vertex_count; i++)
((video::S3DVertex *)(vertices + i * stride))->Pos += vec;
buf->recalculateBoundingBox();
// calculate total bounding box
if (j == 0)
bbox = buf->getBoundingBox();
else
bbox.addInternalBox(buf->getBoundingBox());
}
mesh->setBoundingBox(bbox);
}
void setMeshBufferColor(scene::IMeshBuffer *buf, const video::SColor &color)
{
const u32 stride = getVertexPitchFromType(buf->getVertexType());
u32 vertex_count = buf->getVertexCount();
u8 *vertices = (u8 *) buf->getVertices();
for (u32 i = 0; i < vertex_count; i++)
((video::S3DVertex *) (vertices + i * stride))->Color = color;
}
void setAnimatedMeshColor(scene::IAnimatedMeshSceneNode *node, const video::SColor &color)
{
for (u32 i = 0; i < node->getMaterialCount(); ++i) {
node->getMaterial(i).EmissiveColor = color;
}
}
void setMeshColor(scene::IMesh *mesh, const video::SColor &color)
{
if (mesh == NULL)
return;
u32 mc = mesh->getMeshBufferCount();
for (u32 j = 0; j < mc; j++)
setMeshBufferColor(mesh->getMeshBuffer(j), color);
}
void setMeshBufferTextureCoords(scene::IMeshBuffer *buf, const v2f *uv, u32 count)
{
const u32 stride = getVertexPitchFromType(buf->getVertexType());
assert(buf->getVertexCount() >= count);
u8 *vertices = (u8 *) buf->getVertices();
for (u32 i = 0; i < count; i++)
((video::S3DVertex*) (vertices + i * stride))->TCoords = uv[i];
}
template <typename F>
static void applyToMesh(scene::IMesh *mesh, const F &fn)
{
u16 mc = mesh->getMeshBufferCount();
for (u16 j = 0; j < mc; j++) {
scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
const u32 stride = getVertexPitchFromType(buf->getVertexType());
u32 vertex_count = buf->getVertexCount();
char *vertices = reinterpret_cast<char *>(buf->getVertices());
for (u32 i = 0; i < vertex_count; i++)
fn(reinterpret_cast<video::S3DVertex *>(vertices + i * stride));
}
}
void colorizeMeshBuffer(scene::IMeshBuffer *buf, const video::SColor *buffercolor)
{
const u32 stride = getVertexPitchFromType(buf->getVertexType());
u32 vertex_count = buf->getVertexCount();
u8 *vertices = (u8 *) buf->getVertices();
for (u32 i = 0; i < vertex_count; i++) {
video::S3DVertex *vertex = (video::S3DVertex *) (vertices + i * stride);
video::SColor *vc = &(vertex->Color);
// Reset color
*vc = *buffercolor;
// Apply shading
applyFacesShading(*vc, vertex->Normal);
}
}
void setMeshColorByNormalXYZ(scene::IMesh *mesh,
const video::SColor &colorX,
const video::SColor &colorY,
const video::SColor &colorZ)
{
if (!mesh)
return;
auto colorizator = [=] (video::S3DVertex *vertex) {
f32 x = fabs(vertex->Normal.X);
f32 y = fabs(vertex->Normal.Y);
f32 z = fabs(vertex->Normal.Z);
if (x >= y && x >= z)
vertex->Color = colorX;
else if (y >= z)
vertex->Color = colorY;
else
vertex->Color = colorZ;
};
applyToMesh(mesh, colorizator);
}
void setMeshColorByNormal(scene::IMesh *mesh, const v3f &normal,
const video::SColor &color)
{
if (!mesh)
return;
auto colorizator = [normal, color] (video::S3DVertex *vertex) {
if (vertex->Normal == normal)
vertex->Color = color;
};
applyToMesh(mesh, colorizator);
}
template <float v3f::*U, float v3f::*V>
static void rotateMesh(scene::IMesh *mesh, float degrees)
{
degrees *= M_PI / 180.0f;
float c = std::cos(degrees);
float s = std::sin(degrees);
auto rotator = [c, s] (video::S3DVertex *vertex) {
float u = vertex->Pos.*U;
float v = vertex->Pos.*V;
vertex->Pos.*U = c * u - s * v;
vertex->Pos.*V = s * u + c * v;
};
applyToMesh(mesh, rotator);
}
void rotateMeshXYby(scene::IMesh *mesh, f64 degrees)
{
rotateMesh<&v3f::X, &v3f::Y>(mesh, degrees);
}
void rotateMeshXZby(scene::IMesh *mesh, f64 degrees)
{
rotateMesh<&v3f::X, &v3f::Z>(mesh, degrees);
}
void rotateMeshYZby(scene::IMesh *mesh, f64 degrees)
{
rotateMesh<&v3f::Y, &v3f::Z>(mesh, degrees);
}
void rotateMeshBy6dFacedir(scene::IMesh *mesh, int facedir)
{
int axisdir = facedir >> 2;
facedir &= 0x03;
switch (facedir) {
case 1: rotateMeshXZby(mesh, -90); break;
case 2: rotateMeshXZby(mesh, 180); break;
case 3: rotateMeshXZby(mesh, 90); break;
}
switch (axisdir) {
case 1: rotateMeshYZby(mesh, 90); break; // z+
case 2: rotateMeshYZby(mesh, -90); break; // z-
case 3: rotateMeshXYby(mesh, -90); break; // x+
case 4: rotateMeshXYby(mesh, 90); break; // x-
case 5: rotateMeshXYby(mesh, -180); break;
}
}
void recalculateBoundingBox(scene::IMesh *src_mesh)
{
aabb3f bbox;
bbox.reset(0,0,0);
for (u16 j = 0; j < src_mesh->getMeshBufferCount(); j++) {
scene::IMeshBuffer *buf = src_mesh->getMeshBuffer(j);
buf->recalculateBoundingBox();
if (j == 0)
bbox = buf->getBoundingBox();
else
bbox.addInternalBox(buf->getBoundingBox());
}
src_mesh->setBoundingBox(bbox);
}
bool checkMeshNormals(scene::IMesh *mesh)
{
u32 buffer_count = mesh->getMeshBufferCount();
for (u32 i = 0; i < buffer_count; i++) {
scene::IMeshBuffer *buffer = mesh->getMeshBuffer(i);
// Here we intentionally check only first normal, assuming that if buffer
// has it valid, then most likely all other ones are fine too. We can
// check all of the normals to have length, but it seems like an overkill
// hurting the performance and covering only really weird broken models.
f32 length = buffer->getNormal(0).getLength();
if (!std::isfinite(length) || length < 1e-10f)
return false;
}
return true;
}
scene::IMeshBuffer* cloneMeshBuffer(scene::IMeshBuffer *mesh_buffer)
{
switch (mesh_buffer->getVertexType()) {
case video::EVT_STANDARD: {
video::S3DVertex *v = (video::S3DVertex *) mesh_buffer->getVertices();
u16 *indices = mesh_buffer->getIndices();
scene::SMeshBuffer *cloned_buffer = new scene::SMeshBuffer();
cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices,
mesh_buffer->getIndexCount());
return cloned_buffer;
}
case video::EVT_2TCOORDS: {
video::S3DVertex2TCoords *v =
(video::S3DVertex2TCoords *) mesh_buffer->getVertices();
u16 *indices = mesh_buffer->getIndices();
scene::SMeshBufferLightMap *cloned_buffer =
new scene::SMeshBufferLightMap();
cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices,
mesh_buffer->getIndexCount());
return cloned_buffer;
}
case video::EVT_TANGENTS: {
video::S3DVertexTangents *v =
(video::S3DVertexTangents *) mesh_buffer->getVertices();
u16 *indices = mesh_buffer->getIndices();
scene::SMeshBufferTangents *cloned_buffer =
new scene::SMeshBufferTangents();
cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices,
mesh_buffer->getIndexCount());
return cloned_buffer;
}
}
// This should not happen.
sanity_check(false);
return NULL;
}
scene::SMesh* cloneMesh(scene::IMesh *src_mesh)
{
scene::SMesh* dst_mesh = new scene::SMesh();
for (u16 j = 0; j < src_mesh->getMeshBufferCount(); j++) {
scene::IMeshBuffer *temp_buf = cloneMeshBuffer(
src_mesh->getMeshBuffer(j));
dst_mesh->addMeshBuffer(temp_buf);
temp_buf->drop();
}
return dst_mesh;
}
scene::IMesh* convertNodeboxesToMesh(const std::vector<aabb3f> &boxes,
const f32 *uv_coords, float expand)
{
scene::SMesh* dst_mesh = new scene::SMesh();
for (u16 j = 0; j < 6; j++)
{
scene::IMeshBuffer *buf = new scene::SMeshBuffer();
buf->getMaterial().setFlag(video::EMF_LIGHTING, false);
buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false);
dst_mesh->addMeshBuffer(buf);
buf->drop();
}
video::SColor c(255,255,255,255);
for (aabb3f box : boxes) {
box.repair();
box.MinEdge.X -= expand;
box.MinEdge.Y -= expand;
box.MinEdge.Z -= expand;
box.MaxEdge.X += expand;
box.MaxEdge.Y += expand;
box.MaxEdge.Z += expand;
// Compute texture UV coords
f32 tx1 = (box.MinEdge.X / BS) + 0.5;
f32 ty1 = (box.MinEdge.Y / BS) + 0.5;
f32 tz1 = (box.MinEdge.Z / BS) + 0.5;
f32 tx2 = (box.MaxEdge.X / BS) + 0.5;
f32 ty2 = (box.MaxEdge.Y / BS) + 0.5;
f32 tz2 = (box.MaxEdge.Z / BS) + 0.5;
f32 txc_default[24] = {
// up
tx1, 1 - tz2, tx2, 1 - tz1,
// down
tx1, tz1, tx2, tz2,
// right
tz1, 1 - ty2, tz2, 1 - ty1,
// left
1 - tz2, 1 - ty2, 1 - tz1, 1 - ty1,
// back
1 - tx2, 1 - ty2, 1 - tx1, 1 - ty1,
// front
tx1, 1 - ty2, tx2, 1 - ty1,
};
// use default texture UV mapping if not provided
const f32 *txc = uv_coords ? uv_coords : txc_default;
v3f min = box.MinEdge;
v3f max = box.MaxEdge;
video::S3DVertex vertices[24] =
{
// up
video::S3DVertex(min.X,max.Y,max.Z, 0,1,0, c, txc[0],txc[1]),
video::S3DVertex(max.X,max.Y,max.Z, 0,1,0, c, txc[2],txc[1]),
video::S3DVertex(max.X,max.Y,min.Z, 0,1,0, c, txc[2],txc[3]),
video::S3DVertex(min.X,max.Y,min.Z, 0,1,0, c, txc[0],txc[3]),
// down
video::S3DVertex(min.X,min.Y,min.Z, 0,-1,0, c, txc[4],txc[5]),
video::S3DVertex(max.X,min.Y,min.Z, 0,-1,0, c, txc[6],txc[5]),
video::S3DVertex(max.X,min.Y,max.Z, 0,-1,0, c, txc[6],txc[7]),
video::S3DVertex(min.X,min.Y,max.Z, 0,-1,0, c, txc[4],txc[7]),
// right
video::S3DVertex(max.X,max.Y,min.Z, 1,0,0, c, txc[ 8],txc[9]),
video::S3DVertex(max.X,max.Y,max.Z, 1,0,0, c, txc[10],txc[9]),
video::S3DVertex(max.X,min.Y,max.Z, 1,0,0, c, txc[10],txc[11]),
video::S3DVertex(max.X,min.Y,min.Z, 1,0,0, c, txc[ 8],txc[11]),
// left
video::S3DVertex(min.X,max.Y,max.Z, -1,0,0, c, txc[12],txc[13]),
video::S3DVertex(min.X,max.Y,min.Z, -1,0,0, c, txc[14],txc[13]),
video::S3DVertex(min.X,min.Y,min.Z, -1,0,0, c, txc[14],txc[15]),
video::S3DVertex(min.X,min.Y,max.Z, -1,0,0, c, txc[12],txc[15]),
// back
video::S3DVertex(max.X,max.Y,max.Z, 0,0,1, c, txc[16],txc[17]),
video::S3DVertex(min.X,max.Y,max.Z, 0,0,1, c, txc[18],txc[17]),
video::S3DVertex(min.X,min.Y,max.Z, 0,0,1, c, txc[18],txc[19]),
video::S3DVertex(max.X,min.Y,max.Z, 0,0,1, c, txc[16],txc[19]),
// front
video::S3DVertex(min.X,max.Y,min.Z, 0,0,-1, c, txc[20],txc[21]),
video::S3DVertex(max.X,max.Y,min.Z, 0,0,-1, c, txc[22],txc[21]),
video::S3DVertex(max.X,min.Y,min.Z, 0,0,-1, c, txc[22],txc[23]),
video::S3DVertex(min.X,min.Y,min.Z, 0,0,-1, c, txc[20],txc[23]),
};
u16 indices[] = {0,1,2,2,3,0};
for(u16 j = 0; j < 24; j += 4)
{
scene::IMeshBuffer *buf = dst_mesh->getMeshBuffer(j / 4);
buf->append(vertices + j, 4, indices, 6);
}
}
return dst_mesh;
}

135
src/client/mesh.h Normal file
View File

@@ -0,0 +1,135 @@
/*
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);
/*
Sets texture coords for vertices in the mesh buffer.
`uv[]` must have `count` elements
*/
void setMeshBufferTextureCoords(scene::IMeshBuffer *buf, const v2f *uv, u32 count);
/*
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);
/*
Check if mesh has valid normals and return true if it does.
We assume normal to be valid when it's 0 < length < Inf. and not NaN
*/
bool checkMeshNormals(scene::IMesh *mesh);

View File

@@ -0,0 +1,321 @@
/*
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"
#include "util/directiontables.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_next_cache_cleanup(0)
{
m_cache_enable_shaders = g_settings->getBool("enable_shaders");
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;
}
}
bool 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;
CachedMapBlockData *cached_block = cacheBlock(map, p, FORCE_UPDATE);
if (!cached_block->data)
return false; // nothing to update
cached_blocks.reserve(3*3*3);
cached_blocks.push_back(cached_block);
for (v3s16 dp : g_26dirs)
cached_blocks.push_back(cacheBlock(map, p + dp,
SKIP_UPDATE_IF_ALREADY_CACHED,
&cache_hit_counter));
g_profiler->avg("MeshUpdateQueue: MapBlocks from cache [%]",
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();
q->urgent |= urgent;
return true;
}
}
/*
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();
q->urgent = urgent;
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++;
}
return true;
}
// 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)
{
CachedMapBlockData *cached_block = nullptr;
auto it = m_cache.find(p);
if (it != m_cache.end()) {
cached_block = it->second;
if (mode == SKIP_UPDATE_IF_ALREADY_CACHED) {
if (cache_hit_counter)
(*cache_hit_counter)++;
return cached_block;
}
}
if (!cached_block) {
// Not yet in cache
cached_block = new CachedMapBlockData();
m_cache[p] = cached_block;
}
MapBlock *b = map->getBlockNoCreateNoEx(p);
if (b) {
if (!cached_block->data)
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 = nullptr;
}
return cached_block;
}
CachedMapBlockData* MeshUpdateQueue::getCachedBlock(const v3s16 &p)
{
auto 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);
q->data = data;
data->fillBlockDataBegin(q->p);
std::time_t t_now = std::time(0);
// Collect data for 3*3*3 blocks from cache
for (v3s16 dp : g_27dirs) {
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());
// Iterating the entire cache can get pretty expensive so don't do it too often
{
constexpr int cleanup_interval = 250;
const u64 now = porting::getTimeMs();
if (m_next_cache_cleanup > now)
return;
m_next_cache_cleanup = now + cleanup_interval;
}
// The cache size is kept roughly below cache_soft_max_size, not letting
// anything get older than cache_seconds_max or deleted before 2 seconds.
const int cache_seconds_max = 10;
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 (auto it = m_cache.begin(); it != m_cache.end(); ) {
CachedMapBlockData *cached_block = it->second;
if (cached_block->refcount_from_queue == 0 &&
cached_block->last_used_timestamp < t_now - cache_seconds) {
it = 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, bool update_neighbors)
{
static thread_local const bool many_neighbors =
g_settings->getBool("smooth_lighting")
&& !g_settings->getFlag("performance_tradeoffs");
if (!m_queue_in.addBlock(map, p, ack_block_to_server, urgent)) {
warningstream << "Update requested for non-existent block at ("
<< p.X << ", " << p.Y << ", " << p.Z << ")" << std::endl;
return;
}
if (update_neighbors) {
if (many_neighbors) {
for (v3s16 dp : g_26dirs)
m_queue_in.addBlock(map, p + dp, false, urgent);
} else {
for (v3s16 dp : g_6dirs)
m_queue_in.addBlock(map, p + dp, false, 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 (sum)");
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;
r.urgent = q->urgent;
m_queue_out.push_back(r);
delete q;
}
}

View File

@@ -0,0 +1,135 @@
/*
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 <unordered_map>
#include <unordered_set>
#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;
int crack_level = -1;
v3s16 crack_pos;
MeshMakeData *data = nullptr; // This is generated in MeshUpdateQueue::pop()
bool urgent = false;
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
bool 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::unordered_set<v3s16> m_urgents;
std::unordered_map<v3s16, CachedMapBlockData *> m_cache;
u64 m_next_cache_cleanup; // milliseconds
std::mutex m_mutex;
// TODO: Add callback to update these when g_settings changes
bool m_cache_enable_shaders;
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;
bool urgent = 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,
bool update_neighbors = false);
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();
};

View File

@@ -0,0 +1,104 @@
/*
Minetest
Copyright (C) 2018 numzero, Lobachevskiy Vitaliy <numzer0@yandex.ru>
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 "collector.h"
#include <stdexcept>
#include "log.h"
#include "client/mesh.h"
void MeshCollector::append(const TileSpec &tile, const video::S3DVertex *vertices,
u32 numVertices, const u16 *indices, u32 numIndices)
{
for (int layernum = 0; layernum < MAX_TILE_LAYERS; layernum++) {
const TileLayer *layer = &tile.layers[layernum];
if (layer->texture_id == 0)
continue;
append(*layer, vertices, numVertices, indices, numIndices, layernum,
tile.world_aligned);
}
}
void MeshCollector::append(const TileLayer &layer, const video::S3DVertex *vertices,
u32 numVertices, const u16 *indices, u32 numIndices, u8 layernum,
bool use_scale)
{
PreMeshBuffer &p = findBuffer(layer, layernum, numVertices);
f32 scale = 1.0f;
if (use_scale)
scale = 1.0f / layer.scale;
u32 vertex_count = p.vertices.size();
for (u32 i = 0; i < numVertices; i++)
p.vertices.emplace_back(vertices[i].Pos, vertices[i].Normal,
vertices[i].Color, scale * vertices[i].TCoords);
for (u32 i = 0; i < numIndices; i++)
p.indices.push_back(indices[i] + vertex_count);
}
void MeshCollector::append(const TileSpec &tile, const video::S3DVertex *vertices,
u32 numVertices, const u16 *indices, u32 numIndices, v3f pos,
video::SColor c, u8 light_source)
{
for (int layernum = 0; layernum < MAX_TILE_LAYERS; layernum++) {
const TileLayer *layer = &tile.layers[layernum];
if (layer->texture_id == 0)
continue;
append(*layer, vertices, numVertices, indices, numIndices, pos, c,
light_source, layernum, tile.world_aligned);
}
}
void MeshCollector::append(const TileLayer &layer, const video::S3DVertex *vertices,
u32 numVertices, const u16 *indices, u32 numIndices, v3f pos,
video::SColor c, u8 light_source, u8 layernum, bool use_scale)
{
PreMeshBuffer &p = findBuffer(layer, layernum, numVertices);
f32 scale = 1.0f;
if (use_scale)
scale = 1.0f / layer.scale;
u32 vertex_count = p.vertices.size();
for (u32 i = 0; i < numVertices; i++) {
video::SColor color = c;
if (!light_source)
applyFacesShading(color, vertices[i].Normal);
p.vertices.emplace_back(vertices[i].Pos + pos, vertices[i].Normal, color,
scale * vertices[i].TCoords);
}
for (u32 i = 0; i < numIndices; i++)
p.indices.push_back(indices[i] + vertex_count);
}
PreMeshBuffer &MeshCollector::findBuffer(
const TileLayer &layer, u8 layernum, u32 numVertices)
{
if (numVertices > U16_MAX)
throw std::invalid_argument(
"Mesh can't contain more than 65536 vertices");
std::vector<PreMeshBuffer> &buffers = prebuffers[layernum];
for (PreMeshBuffer &p : buffers)
if (p.layer == layer && p.vertices.size() + numVertices <= U16_MAX)
return p;
buffers.emplace_back(layer);
return buffers.back();
}

View File

@@ -0,0 +1,65 @@
/*
Minetest
Copyright (C) 2018 numzero, Lobachevskiy Vitaliy <numzer0@yandex.ru>
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 <array>
#include <vector>
#include "irrlichttypes.h"
#include <S3DVertex.h>
#include "client/tile.h"
struct PreMeshBuffer
{
TileLayer layer;
std::vector<u16> indices;
std::vector<video::S3DVertex> vertices;
PreMeshBuffer() = default;
explicit PreMeshBuffer(const TileLayer &layer) : layer(layer) {}
};
struct MeshCollector
{
std::array<std::vector<PreMeshBuffer>, MAX_TILE_LAYERS> prebuffers;
// clang-format off
void append(const TileSpec &material,
const video::S3DVertex *vertices, u32 numVertices,
const u16 *indices, u32 numIndices);
void append(const TileSpec &material,
const video::S3DVertex *vertices, u32 numVertices,
const u16 *indices, u32 numIndices,
v3f pos, video::SColor c, u8 light_source);
// clang-format on
private:
// clang-format off
void append(const TileLayer &material,
const video::S3DVertex *vertices, u32 numVertices,
const u16 *indices, u32 numIndices,
u8 layernum, bool use_scale = false);
void append(const TileLayer &material,
const video::S3DVertex *vertices, u32 numVertices,
const u16 *indices, u32 numIndices,
v3f pos, video::SColor c, u8 light_source,
u8 layernum, bool use_scale = false);
// clang-format on
PreMeshBuffer &findBuffer(const TileLayer &layer, u8 layernum, u32 numVertices);
};

762
src/client/minimap.cpp Normal file
View File

@@ -0,0 +1,762 @@
/*
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"
#include "gettext.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.type == MINIMAP_TYPE_RADAR ||
data->mode.type == MINIMAP_TYPE_SURFACE)) {
getMap(data->pos, data->mode.map_size, data->mode.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;
m_current_mode_index = 0;
// 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 modes
addMode(MINIMAP_TYPE_OFF);
addMode(MINIMAP_TYPE_SURFACE, 256);
addMode(MINIMAP_TYPE_SURFACE, 128);
addMode(MINIMAP_TYPE_SURFACE, 64);
addMode(MINIMAP_TYPE_RADAR, 512);
addMode(MINIMAP_TYPE_RADAR, 256);
addMode(MINIMAP_TYPE_RADAR, 128);
// Initialize minimap data
data = new MinimapData;
data->map_invalidated = true;
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");
setModeIndex(0);
// 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);
for (MinimapMarker *m : m_markers)
delete m;
m_markers.clear();
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::setModeIndex(size_t index)
{
MutexAutoLock lock(m_mutex);
if (index < m_modes.size()) {
data->mode = m_modes[index];
m_current_mode_index = index;
} else {
data->mode = {MINIMAP_TYPE_OFF, gettext("Minimap hidden"), 0, 0, "", 0};
m_current_mode_index = 0;
}
data->map_invalidated = true;
if (m_minimap_update_thread)
m_minimap_update_thread->deferUpdate();
}
void Minimap::addMode(MinimapModeDef mode)
{
// Check validity
if (mode.type == MINIMAP_TYPE_TEXTURE) {
if (mode.texture.empty())
return;
if (mode.scale < 1)
mode.scale = 1;
}
int zoom = -1;
// Build a default standard label
if (mode.label == "") {
switch (mode.type) {
case MINIMAP_TYPE_OFF:
mode.label = gettext("Minimap hidden");
break;
case MINIMAP_TYPE_SURFACE:
mode.label = gettext("Minimap in surface mode, Zoom x%d");
if (mode.map_size > 0)
zoom = 256 / mode.map_size;
break;
case MINIMAP_TYPE_RADAR:
mode.label = gettext("Minimap in radar mode, Zoom x%d");
if (mode.map_size > 0)
zoom = 512 / mode.map_size;
break;
case MINIMAP_TYPE_TEXTURE:
mode.label = gettext("Minimap in texture mode");
break;
default:
break;
}
}
// else: Custom labels need mod-provided client-side translation
if (zoom >= 0) {
char label_buf[1024];
porting::mt_snprintf(label_buf, sizeof(label_buf),
mode.label.c_str(), zoom);
mode.label = label_buf;
}
m_modes.push_back(mode);
}
void Minimap::addMode(MinimapType type, u16 size, std::string label,
std::string texture, u16 scale)
{
MinimapModeDef mode;
mode.type = type;
mode.label = label;
mode.map_size = size;
mode.texture = texture;
mode.scale = scale;
switch (type) {
case MINIMAP_TYPE_SURFACE:
mode.scan_height = m_surface_mode_scan_height;
break;
case MINIMAP_TYPE_RADAR:
mode.scan_height = 32;
break;
default:
mode.scan_height = 0;
}
addMode(mode);
}
void Minimap::nextMode()
{
if (m_modes.empty())
return;
m_current_mode_index++;
if (m_current_mode_index >= m_modes.size())
m_current_mode_index = 0;
setModeIndex(m_current_mode_index);
}
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->mode.map_size; x++)
for (s16 z = 0; z < data->mode.map_size; z++) {
MinimapPixel *mmpixel = &data->minimap_scan[x + z * data->mode.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->mode.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->mode.map_size; x++)
for (s16 z = 0; z < data->mode.map_size; z++) {
MinimapPixel *mmpixel = &data->minimap_scan[x + z * data->mode.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->mode.map_size - z - 1, tilecolor);
u32 h = mmpixel->height;
heightmap_image->setPixel(x,data->mode.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 && data->mode.type != MINIMAP_TYPE_TEXTURE)
return data->texture;
// create minimap and heightmap images in memory
core::dimension2d<u32> dim(data->mode.map_size, data->mode.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
switch(data->mode.type) {
case MINIMAP_TYPE_OFF:
break;
case MINIMAP_TYPE_SURFACE:
blitMinimapPixelsToImageSurface(map_image, heightmap_image);
break;
case MINIMAP_TYPE_RADAR:
blitMinimapPixelsToImageRadar(map_image);
break;
case MINIMAP_TYPE_TEXTURE:
// Want to use texture source, to : 1 find texture, 2 cache it
video::ITexture* texture = m_tsrc->getTexture(data->mode.texture);
video::IImage* image = driver->createImageFromData(
texture->getColorFormat(), texture->getSize(),
texture->lock(video::ETLM_READ_ONLY), true, false);
texture->unlock();
auto dim = image->getDimension();
map_image->fill(video::SColor(255, 0, 0, 0));
image->copyTo(map_image,
irr::core::vector2d<int> {
((data->mode.map_size - (static_cast<int>(dim.Width))) >> 1)
- data->pos.X / data->mode.scale,
((data->mode.map_size - (static_cast<int>(dim.Height))) >> 1)
+ data->pos.Z / data->mode.scale
});
image->drop();
}
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()
{
// Non hud managed minimap drawing (legacy minimap)
v2u32 screensize = RenderingEngine::getWindowSize();
const u32 size = 0.25 * screensize.Y;
drawMinimap(core::rect<s32>(
screensize.X - size - 10, 10,
screensize.X - 10, size + 10));
}
void Minimap::drawMinimap(core::rect<s32> rect) {
video::ITexture *minimap_texture = getMinimapTexture();
if (!minimap_texture)
return;
if (data->mode.type == MINIMAP_TYPE_OFF)
return;
updateActiveMarkers();
core::rect<s32> oldViewPort = driver->getViewPort();
core::matrix4 oldProjMat = driver->getTransform(video::ETS_PROJECTION);
core::matrix4 oldViewMat = driver->getTransform(video::ETS_VIEW);
driver->setViewPort(rect);
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->mode.type == MINIMAP_TYPE_SURFACE) {
u16 sid = m_shdrsrc->getShader("minimap_shader", TILE_MATERIAL_ALPHA);
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(rect.UpperLeftCorner.X, rect.UpperLeftCorner.Y);
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)rect.getWidth();;
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)rect.getWidth();
posf.Y = (posf.Y + 0.5) * (float)rect.getHeight();
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);
}
}
MinimapMarker* Minimap::addMarker(scene::ISceneNode *parent_node)
{
MinimapMarker *m = new MinimapMarker(parent_node);
m_markers.push_back(m);
return m;
}
void Minimap::removeMarker(MinimapMarker **m)
{
m_markers.remove(*m);
delete *m;
*m = nullptr;
}
void Minimap::updateActiveMarkers()
{
video::IImage *minimap_mask = data->minimap_shape_round ?
data->minimap_mask_round : data->minimap_mask_square;
m_active_markers.clear();
v3f cam_offset = intToFloat(client->getCamera()->getOffset(), BS);
v3s16 pos_offset = data->pos - v3s16(data->mode.map_size / 2,
data->mode.scan_height / 2,
data->mode.map_size / 2);
for (MinimapMarker *marker : m_markers) {
v3s16 pos = floatToInt(marker->parent_node->getAbsolutePosition() +
cam_offset, BS) - pos_offset;
if (pos.X < 0 || pos.X > data->mode.map_size ||
pos.Y < 0 || pos.Y > data->mode.scan_height ||
pos.Z < 0 || pos.Z > data->mode.map_size) {
continue;
}
pos.X = ((float)pos.X / data->mode.map_size) * MINIMAP_MAX_SX;
pos.Z = ((float)pos.Z / data->mode.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;
}
}

176
src/client/minimap.h Normal file
View File

@@ -0,0 +1,176 @@
/*
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 "../hud.h"
#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 MinimapShape {
MINIMAP_SHAPE_SQUARE,
MINIMAP_SHAPE_ROUND,
};
struct MinimapModeDef {
MinimapType type;
std::string label;
u16 scan_height;
u16 map_size;
std::string texture;
u16 scale;
};
struct MinimapMarker {
MinimapMarker(scene::ISceneNode *parent_node):
parent_node(parent_node)
{
}
scene::ISceneNode *parent_node;
};
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 {
MinimapModeDef mode;
v3s16 pos;
v3s16 old_pos;
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 toggleMinimapShape();
void setMinimapShape(MinimapShape shape);
MinimapShape getMinimapShape();
void clearModes() { m_modes.clear(); };
void addMode(MinimapModeDef mode);
void addMode(MinimapType type, u16 size = 0, std::string label = "",
std::string texture = "", u16 scale = 1);
void setModeIndex(size_t index);
size_t getModeIndex() const { return m_current_mode_index; };
size_t getMaxModeIndex() const { return m_modes.size() - 1; };
void nextMode();
MinimapModeDef getModeDef() const { return data->mode; }
video::ITexture *getMinimapTexture();
void blitMinimapPixelsToImageRadar(video::IImage *map_image);
void blitMinimapPixelsToImageSurface(video::IImage *map_image,
video::IImage *heightmap_image);
scene::SMeshBuffer *getMinimapMeshBuffer();
MinimapMarker* addMarker(scene::ISceneNode *parent_node);
void removeMarker(MinimapMarker **marker);
void updateActiveMarkers();
void drawMinimap();
void drawMinimap(core::rect<s32> rect);
video::IVideoDriver *driver;
Client* client;
MinimapData *data;
private:
ITextureSource *m_tsrc;
IShaderSource *m_shdrsrc;
const NodeDefManager *m_ndef;
MinimapUpdateThread *m_minimap_update_thread = nullptr;
scene::SMeshBuffer *m_meshbuffer;
bool m_enable_shaders;
std::vector<MinimapModeDef> m_modes;
size_t m_current_mode_index;
u16 m_surface_mode_scan_height;
f32 m_angle;
std::mutex m_mutex;
std::list<MinimapMarker*> m_markers;
std::list<v2f> m_active_markers;
};

927
src/client/particles.cpp Normal file
View File

@@ -0,0 +1,927 @@
/*
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/content_cao.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"
/*
Particle
*/
Particle::Particle(
IGameDef *gamedef,
LocalPlayer *player,
ClientEnvironment *env,
const ParticleParameters &p,
const ClientTexRef& texture,
v2f texpos,
v2f texsize,
video::SColor color
):
scene::ISceneNode(((Client *)gamedef)->getSceneManager()->getRootSceneNode(),
((Client *)gamedef)->getSceneManager()),
m_texture(texture)
{
// Misc
m_gamedef = gamedef;
m_env = env;
// translate blend modes to GL blend functions
video::E_BLEND_FACTOR bfsrc, bfdst;
video::E_BLEND_OPERATION blendop;
const auto blendmode = texture.tex != nullptr
? texture.tex -> blendmode
: ParticleParamTypes::BlendMode::alpha;
switch (blendmode) {
case ParticleParamTypes::BlendMode::add:
bfsrc = video::EBF_SRC_ALPHA;
bfdst = video::EBF_DST_ALPHA;
blendop = video::EBO_ADD;
break;
case ParticleParamTypes::BlendMode::sub:
bfsrc = video::EBF_SRC_ALPHA;
bfdst = video::EBF_DST_ALPHA;
blendop = video::EBO_REVSUBTRACT;
break;
case ParticleParamTypes::BlendMode::screen:
bfsrc = video::EBF_ONE;
bfdst = video::EBF_ONE_MINUS_SRC_COLOR;
blendop = video::EBO_ADD;
break;
default: // includes ParticleParamTypes::BlendMode::alpha
bfsrc = video::EBF_SRC_ALPHA;
bfdst = video::EBF_ONE_MINUS_SRC_ALPHA;
blendop = video::EBO_ADD;
break;
}
// Texture
m_material.setFlag(video::EMF_LIGHTING, false);
m_material.setFlag(video::EMF_BACK_FACE_CULLING, false);
m_material.setFlag(video::EMF_BILINEAR_FILTER, false);
m_material.setFlag(video::EMF_FOG_ENABLE, true);
// correctly render layered transparent particles -- see #10398
m_material.setFlag(video::EMF_ZWRITE_ENABLE, true);
// enable alpha blending and set blend mode
m_material.MaterialType = video::EMT_ONETEXTURE_BLEND;
m_material.MaterialTypeParam = video::pack_textureBlendFunc(
bfsrc, bfdst,
video::EMFN_MODULATE_1X,
video::EAS_TEXTURE | video::EAS_VERTEX_COLOR);
m_material.BlendOperation = blendop;
m_material.setTexture(0, m_texture.ref);
m_texpos = texpos;
m_texsize = texsize;
m_animation = p.animation;
// Color
m_base_color = color;
m_color = color;
// Particle related
m_pos = p.pos;
m_velocity = p.vel;
m_acceleration = p.acc;
m_drag = p.drag;
m_jitter = p.jitter;
m_bounce = p.bounce;
m_expiration = p.expirationtime;
m_player = player;
m_size = p.size;
m_collisiondetection = p.collisiondetection;
m_collision_removal = p.collision_removal;
m_object_collision = p.object_collision;
m_vertical = p.vertical;
m_glow = p.glow;
m_alpha = 0;
m_parent = nullptr;
// Irrlicht stuff
const float c = p.size / 2;
m_collisionbox = aabb3f(-c, -c, -c, c, c, c);
this->setAutomaticCulling(scene::EAC_OFF);
// Init lighting
updateLight();
// Init model
updateVertices();
}
Particle::~Particle()
{
/* if our textures aren't owned by a particlespawner, we need to clean
* them up ourselves when the particle dies */
if (m_parent == nullptr)
delete m_texture.tex;
}
void Particle::OnRegisterSceneNode()
{
if (IsVisible)
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;
// apply drag (not handled by collisionMoveSimple) and brownian motion
v3f av = vecAbsolute(m_velocity);
av -= av * (m_drag * dtime);
m_velocity = av*vecSign(m_velocity) + v3f(m_jitter.pickWithin())*dtime;
if (m_collisiondetection) {
aabb3f box = m_collisionbox;
v3f p_pos = m_pos * BS;
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);
f32 bounciness = m_bounce.pickWithin();
if (r.collides && (m_collision_removal || bounciness > 0)) {
if (m_collision_removal) {
// force expiration of the particle
m_expiration = -1.0f;
} else if (bounciness > 0) {
/* cheap way to get a decent bounce effect is to only invert the
* largest component of the velocity vector, so e.g. you don't
* have a rock immediately bounce back in your face when you try
* to skip it across the water (as would happen if we simply
* downscaled and negated the velocity vector). this means
* bounciness will work properly for cubic objects, but meshes
* with diagonal angles and entities will not yield the correct
* visual. this is probably unavoidable */
if (av.Y > av.X && av.Y > av.Z) {
m_velocity.Y = -(m_velocity.Y * bounciness);
} else if (av.X > av.Y && av.X > av.Z) {
m_velocity.X = -(m_velocity.X * bounciness);
} else if (av.Z > av.Y && av.Z > av.X) {
m_velocity.Z = -(m_velocity.Z * bounciness);
} else { // well now we're in a bit of a pickle
m_velocity = -(m_velocity * bounciness);
}
}
} else {
m_velocity = p_velocity / BS;
}
m_pos = p_pos / BS;
} else {
// apply acceleration
m_velocity += m_acceleration * dtime;
m_pos += m_velocity * dtime;
}
if (m_animation.type != TAT_NONE) {
m_animation_time += dtime;
int frame_length_i, frame_count;
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;
}
}
// animate particle alpha in accordance with settings
if (m_texture.tex != nullptr)
m_alpha = m_texture.tex -> alpha.blend(m_time / (m_expiration+0.1f));
else
m_alpha = 1.f;
// Update lighting
updateLight();
// Update model
updateVertices();
// Update position -- see #10398
v3s16 camera_offset = m_env->getCameraOffset();
setPosition(m_pos*BS - intToFloat(camera_offset, BS));
}
void Particle::updateLight()
{
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().getNode(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(m_alpha*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;
v2f scale;
if (m_texture.tex != nullptr)
scale = m_texture.tex -> scale.blend(m_time / (m_expiration+0.1));
else
scale = v2f(1.f, 1.f);
if (m_animation.type != TAT_NONE) {
const v2u32 texsize = m_material.getTexture(0)->getSize();
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;
}
auto half = m_size * .5f,
hx = half * scale.X,
hy = half * scale.Y;
m_vertices[0] = video::S3DVertex(-hx, -hy,
0, 0, 0, 0, m_color, tx0, ty1);
m_vertices[1] = video::S3DVertex(hx, -hy,
0, 0, 0, 0, m_color, tx1, ty1);
m_vertices[2] = video::S3DVertex(hx, hy,
0, 0, 0, 0, m_color, tx1, ty0);
m_vertices[3] = video::S3DVertex(-hx, hy,
0, 0, 0, 0, m_color, tx0, ty0);
// see #10398
// v3s16 camera_offset = m_env->getCameraOffset();
// particle position is now handled by step()
m_box.reset(v3f());
for (video::S3DVertex &vertex : m_vertices) {
if (m_vertical) {
v3f ppos = m_player->getPosition()/BS;
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);
}
}
/*
ParticleSpawner
*/
ParticleSpawner::ParticleSpawner(
IGameDef *gamedef,
LocalPlayer *player,
const ParticleSpawnerParameters &p,
u16 attached_id,
std::unique_ptr<ClientTexture[]>& texpool,
size_t texcount,
ParticleManager *p_manager
):
m_particlemanager(p_manager), p(p)
{
m_gamedef = gamedef;
m_player = player;
m_attached_id = attached_id;
m_texpool = std::move(texpool);
m_texcount = texcount;
m_time = 0;
m_active = 0;
m_dying = false;
m_spawntimes.reserve(p.amount + 1);
for (u16 i = 0; i <= p.amount; i++) {
float spawntime = myrand_float() * p.time;
m_spawntimes.push_back(spawntime);
}
size_t max_particles = 0; // maximum number of particles likely to be visible at any given time
if (p.time != 0) {
auto maxGenerations = p.time / std::min(p.exptime.start.min, p.exptime.end.min);
max_particles = p.amount / maxGenerations;
} else {
auto longestLife = std::max(p.exptime.start.max, p.exptime.end.max);
max_particles = p.amount * longestLife;
}
p_manager->reserveParticleSpace(max_particles * 1.2);
}
namespace {
GenericCAO *findObjectByID(ClientEnvironment *env, u16 id) {
if (id == 0)
return nullptr;
return env->getGenericCAO(id);
}
}
void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
const core::matrix4 *attached_absolute_pos_rot_matrix)
{
float fac = 0;
if (p.time != 0) { // ensure safety from divide-by-zeroes
fac = m_time / (p.time+0.1f);
}
auto r_pos = p.pos.blend(fac);
auto r_vel = p.vel.blend(fac);
auto r_acc = p.acc.blend(fac);
auto r_drag = p.drag.blend(fac);
auto r_radius = p.radius.blend(fac);
auto r_jitter = p.jitter.blend(fac);
auto r_bounce = p.bounce.blend(fac);
v3f attractor_origin = p.attractor_origin.blend(fac);
v3f attractor_direction = p.attractor_direction.blend(fac);
auto attractor_obj = findObjectByID(env, p.attractor_attachment);
auto attractor_direction_obj = findObjectByID(env, p.attractor_direction_attachment);
auto r_exp = p.exptime.blend(fac);
auto r_size = p.size.blend(fac);
auto r_attract = p.attract.blend(fac);
auto attract = r_attract.pickWithin();
v3f ppos = m_player->getPosition() / BS;
v3f pos = r_pos.pickWithin();
v3f sphere_radius = r_radius.pickWithin();
// Need to apply this first or the following check
// will be wrong for attached spawners
if (attached_absolute_pos_rot_matrix) {
pos *= BS;
attached_absolute_pos_rot_matrix->transformVect(pos);
pos /= BS;
v3s16 camera_offset = m_particlemanager->m_env->getCameraOffset();
pos.X += camera_offset.X;
pos.Y += camera_offset.Y;
pos.Z += camera_offset.Z;
}
if (pos.getDistanceFromSQ(ppos) > radius*radius)
return;
// Parameters for the single particle we're about to spawn
ParticleParameters pp;
pp.pos = pos;
pp.vel = r_vel.pickWithin();
pp.acc = r_acc.pickWithin();
pp.drag = r_drag.pickWithin();
pp.jitter = r_jitter;
pp.bounce = r_bounce;
if (attached_absolute_pos_rot_matrix) {
// Apply attachment rotation
attached_absolute_pos_rot_matrix->rotateVect(pp.vel);
attached_absolute_pos_rot_matrix->rotateVect(pp.acc);
}
if (attractor_obj)
attractor_origin += attractor_obj->getPosition() / BS;
if (attractor_direction_obj) {
auto *attractor_absolute_pos_rot_matrix = attractor_direction_obj->getAbsolutePosRotMatrix();
if (attractor_absolute_pos_rot_matrix)
attractor_absolute_pos_rot_matrix->rotateVect(attractor_direction);
}
pp.expirationtime = r_exp.pickWithin();
if (sphere_radius != v3f()) {
f32 l = sphere_radius.getLength();
v3f mag = sphere_radius;
mag.normalize();
v3f ofs = v3f(l,0,0);
ofs.rotateXZBy(myrand_range(0.f,360.f));
ofs.rotateYZBy(myrand_range(0.f,360.f));
ofs.rotateXYBy(myrand_range(0.f,360.f));
pp.pos += ofs * mag;
}
if (p.attractor_kind != ParticleParamTypes::AttractorKind::none && attract != 0) {
v3f dir;
f32 dist = 0; /* =0 necessary to silence warning */
switch (p.attractor_kind) {
case ParticleParamTypes::AttractorKind::none:
break;
case ParticleParamTypes::AttractorKind::point: {
dist = pp.pos.getDistanceFrom(attractor_origin);
dir = pp.pos - attractor_origin;
dir.normalize();
break;
}
case ParticleParamTypes::AttractorKind::line: {
// https://github.com/minetest/minetest/issues/11505#issuecomment-915612700
const auto& lorigin = attractor_origin;
v3f ldir = attractor_direction;
ldir.normalize();
auto origin_to_point = pp.pos - lorigin;
auto scalar_projection = origin_to_point.dotProduct(ldir);
auto point_on_line = lorigin + (ldir * scalar_projection);
dist = pp.pos.getDistanceFrom(point_on_line);
dir = (point_on_line - pp.pos);
dir.normalize();
dir *= -1; // flip it around so strength=1 attracts, not repulses
break;
}
case ParticleParamTypes::AttractorKind::plane: {
// https://github.com/minetest/minetest/issues/11505#issuecomment-915612700
const v3f& porigin = attractor_origin;
v3f normal = attractor_direction;
normal.normalize();
v3f point_to_origin = porigin - pp.pos;
f32 factor = normal.dotProduct(point_to_origin);
if (numericAbsolute(factor) == 0.0f) {
dir = normal;
} else {
factor = numericSign(factor);
dir = normal * factor;
}
dist = numericAbsolute(normal.dotProduct(pp.pos - porigin));
dir *= -1; // flip it around so strength=1 attracts, not repulses
break;
}
}
f32 speedTowards = numericAbsolute(attract) * dist;
v3f avel = dir * speedTowards;
if (attract > 0 && speedTowards > 0) {
avel *= -1;
if (p.attractor_kill) {
// make sure the particle dies after crossing the attractor threshold
f32 timeToCenter = dist / speedTowards;
if (timeToCenter < pp.expirationtime)
pp.expirationtime = timeToCenter;
}
}
pp.vel += avel;
}
p.copyCommon(pp);
ClientTexRef texture;
v2f texpos, texsize;
video::SColor color(0xFFFFFFFF);
if (p.node.getContent() != CONTENT_IGNORE) {
const ContentFeatures &f =
m_particlemanager->m_env->getGameDef()->ndef()->get(p.node);
if (!ParticleManager::getNodeParticleParams(p.node, f, pp, &texture.ref,
texpos, texsize, &color, p.node_tile))
return;
} else {
if (m_texcount == 0)
return;
texture = decltype(texture)(m_texpool[m_texcount == 1 ? 0 : myrand_range(0,m_texcount-1)]);
texpos = v2f(0.0f, 0.0f);
texsize = v2f(1.0f, 1.0f);
if (texture.tex->animated)
pp.animation = texture.tex->animation;
}
// synchronize animation length with particle life if desired
if (pp.animation.type != TAT_NONE) {
// FIXME: this should be moved into a TileAnimationParams class method
if (pp.animation.type == TAT_VERTICAL_FRAMES &&
pp.animation.vertical_frames.length < 0) {
auto& a = pp.animation.vertical_frames;
// we add a tiny extra value to prevent the first frame
// from flickering back on just before the particle dies
a.length = (pp.expirationtime / -a.length) + 0.1;
} else if (pp.animation.type == TAT_SHEET_2D &&
pp.animation.sheet_2d.frame_length < 0) {
auto& a = pp.animation.sheet_2d;
auto frames = a.frames_w * a.frames_h;
auto runtime = (pp.expirationtime / -a.frame_length) + 0.1;
pp.animation.sheet_2d.frame_length = frames / runtime;
}
}
// Allow keeping default random size
if (p.size.start.max > 0.0f || p.size.end.max > 0.0f)
pp.size = r_size.pickWithin();
++m_active;
auto pa = new Particle(
m_gamedef,
m_player,
env,
pp,
texture,
texpos,
texsize,
color
);
pa->m_parent = this;
m_particlemanager->addParticle(pa);
}
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;
const core::matrix4 *attached_absolute_pos_rot_matrix = nullptr;
if (m_attached_id) {
if (GenericCAO *attached = env->getGenericCAO(m_attached_id)) {
attached_absolute_pos_rot_matrix = attached->getAbsolutePosRotMatrix();
} else {
unloaded = true;
}
}
if (p.time != 0) {
// Spawner exists for a predefined timespan
for (auto i = m_spawntimes.begin(); i != m_spawntimes.end(); ) {
if ((*i) <= m_time && p.amount > 0) {
--p.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, attached_absolute_pos_rot_matrix);
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 <= p.amount; i++) {
if (myrand_float() < dtime)
spawnParticle(env, radius, attached_absolute_pos_rot_matrix);
}
}
}
/*
ParticleManager
*/
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 (auto i = m_particle_spawners.begin(); i != m_particle_spawners.end();) {
if (i->second->getExpired()) {
// the particlespawner owns the textures, so we need to make
// sure there are no active particles before we free it
if (i->second->m_active == 0) {
delete i->second;
m_particle_spawners.erase(i++);
} else {
++i;
}
} else {
i->second->step(dtime, m_env);
++i;
}
}
}
void ParticleManager::stepParticles(float dtime)
{
MutexAutoLock lock(m_particle_list_lock);
for (auto i = m_particles.begin(); i != m_particles.end();) {
if ((*i)->get_expired()) {
if ((*i)->m_parent) {
assert((*i)->m_parent->m_active != 0);
--(*i)->m_parent->m_active;
}
(*i)->remove();
delete *i;
i = m_particles.erase(i);
} else {
(*i)->step(dtime);
++i;
}
}
}
void ParticleManager::clearAll()
{
MutexAutoLock lock(m_spawner_list_lock);
MutexAutoLock lock2(m_particle_list_lock);
for (auto i = m_particle_spawners.begin(); i != m_particle_spawners.end();) {
delete i->second;
m_particle_spawners.erase(i++);
}
for(auto 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: {
deleteParticleSpawner(event->delete_particlespawner.id);
// no allocated memory in delete event
break;
}
case CE_ADD_PARTICLESPAWNER: {
deleteParticleSpawner(event->add_particlespawner.id);
const ParticleSpawnerParameters &p = *event->add_particlespawner.p;
// texture pool
std::unique_ptr<ClientTexture[]> texpool = nullptr;
size_t txpsz = 0;
if (!p.texpool.empty()) {
txpsz = p.texpool.size();
texpool = decltype(texpool)(new ClientTexture [txpsz]);
for (size_t i = 0; i < txpsz; ++i) {
texpool[i] = ClientTexture(p.texpool[i], client->tsrc());
}
} else {
// no texpool in use, use fallback texture
txpsz = 1;
texpool = decltype(texpool)(new ClientTexture[1] {
ClientTexture(p.texture, client->tsrc())
});
}
auto toadd = new ParticleSpawner(client, player,
p,
event->add_particlespawner.attached_id,
texpool,
txpsz,
this);
addParticleSpawner(event->add_particlespawner.id, toadd);
delete event->add_particlespawner.p;
break;
}
case CE_SPAWN_PARTICLE: {
ParticleParameters &p = *event->spawn_particle;
ClientTexRef texture;
v2f texpos, texsize;
video::SColor color(0xFFFFFFFF);
f32 oldsize = p.size;
if (p.node.getContent() != CONTENT_IGNORE) {
const ContentFeatures &f = m_env->getGameDef()->ndef()->get(p.node);
getNodeParticleParams(p.node, f, p, &texture.ref, texpos,
texsize, &color, p.node_tile);
} else {
/* with no particlespawner to own the texture, we need
* to save it on the heap. it will be freed when the
* particle is destroyed */
auto texstore = new ClientTexture(p.texture, client->tsrc());
texture = ClientTexRef(*texstore);
texpos = v2f(0.0f, 0.0f);
texsize = v2f(1.0f, 1.0f);
}
// Allow keeping default random size
if (oldsize > 0.0f)
p.size = oldsize;
if (texture.ref) {
Particle *toadd = new Particle(client, player, m_env,
p, texture, texpos, texsize, color);
addParticle(toadd);
}
delete event->spawn_particle;
break;
}
default: break;
}
}
bool ParticleManager::getNodeParticleParams(const MapNode &n,
const ContentFeatures &f, ParticleParameters &p, video::ITexture **texture,
v2f &texpos, v2f &texsize, video::SColor *color, u8 tilenum)
{
// No particles for "airlike" nodes
if (f.drawtype == NDT_AIRLIKE)
return false;
// Texture
u8 texid;
if (tilenum > 0 && tilenum <= 6)
texid = tilenum - 1;
else
texid = myrand_range(0,5);
const TileLayer &tile = f.tiles[texid].layers[0];
p.animation.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 = (myrand_range(0,8)) / 64.0f;
p.size = BS * size;
if (tile.scale)
size /= tile.scale;
texsize = v2f(size * 2.0f, size * 2.0f);
texpos.X = (myrand_range(0,64)) / 64.0f - texsize.X;
texpos.Y = (myrand_range(0,64)) / 64.0f - texsize.Y;
if (tile.has_color)
*color = tile.color;
else
n.getColor(f, color);
return true;
}
// 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)
{
ParticleParameters p;
video::ITexture *ref = nullptr;
v2f texpos, texsize;
video::SColor color;
if (!getNodeParticleParams(n, f, p, &ref, texpos, texsize, &color))
return;
p.expirationtime = myrand_range(0, 100) / 100.0f;
// Physics
p.vel = v3f(
myrand_range(-1.5f,1.5f),
myrand_range(0.f,3.f),
myrand_range(-1.5f,1.5f)
);
p.acc = v3f(
0.0f,
-player->movement_gravity * player->physics_override_gravity / BS,
0.0f
);
p.pos = v3f(
(f32)pos.X + myrand_range(0.f, .5f) - .25f,
(f32)pos.Y + myrand_range(0.f, .5f) - .25f,
(f32)pos.Z + myrand_range(0.f, .5f) - .25f
);
Particle *toadd = new Particle(
gamedef,
player,
m_env,
p,
ClientTexRef(ref),
texpos,
texsize,
color);
addParticle(toadd);
}
void ParticleManager::reserveParticleSpace(size_t max_estimate)
{
MutexAutoLock lock(m_particle_list_lock);
m_particles.reserve(m_particles.size() + max_estimate);
}
void ParticleManager::addParticle(Particle *toadd)
{
MutexAutoLock lock(m_particle_list_lock);
m_particles.push_back(toadd);
}
void ParticleManager::addParticleSpawner(u64 id, ParticleSpawner *toadd)
{
MutexAutoLock lock(m_spawner_list_lock);
m_particle_spawners[id] = toadd;
}
void ParticleManager::deleteParticleSpawner(u64 id)
{
MutexAutoLock lock(m_spawner_list_lock);
auto it = m_particle_spawners.find(id);
if (it != m_particle_spawners.end()) {
it->second->setDying();
}
}

242
src/client/particles.h Normal file
View File

@@ -0,0 +1,242 @@
/*
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 "../particles.h"
struct ClientEvent;
class ParticleManager;
class ClientEnvironment;
struct MapNode;
struct ContentFeatures;
struct ClientTexture
{
/* per-spawner structure used to store the ParticleTexture structs
* that spawned particles will refer to through ClientTexRef */
ParticleTexture tex;
video::ITexture *ref = nullptr;
ClientTexture() = default;
ClientTexture(const ServerParticleTexture& p, ITextureSource *t):
tex(p),
ref(t->getTextureForMesh(p.string)) {};
};
struct ClientTexRef
{
/* per-particle structure used to avoid massively duplicating the
* fairly large ParticleTexture struct */
ParticleTexture* tex = nullptr;
video::ITexture* ref = nullptr;
ClientTexRef() = default;
/* constructor used by particles spawned from a spawner */
ClientTexRef(ClientTexture& t):
tex(&t.tex), ref(t.ref) {};
/* constructor used for node particles */
ClientTexRef(decltype(ref) tp): ref(tp) {};
};
class ParticleSpawner;
class Particle : public scene::ISceneNode
{
public:
Particle(
IGameDef *gamedef,
LocalPlayer *player,
ClientEnvironment *env,
const ParticleParameters &p,
const ClientTexRef &texture,
v2f texpos,
v2f texsize,
video::SColor color
);
~Particle();
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; }
ParticleSpawner *m_parent;
private:
void updateLight();
void updateVertices();
void setVertexAlpha(float a);
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;
ClientTexRef m_texture;
video::SMaterial m_material;
v2f m_texpos;
v2f m_texsize;
v3f m_pos;
v3f m_velocity;
v3f m_acceleration;
v3f m_drag;
ParticleParamTypes::v3fRange m_jitter;
ParticleParamTypes::f32Range m_bounce;
LocalPlayer *m_player;
float m_size;
//! Color without lighting
video::SColor m_base_color;
//! Final rendered color
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;
float m_alpha = 0.0f;
};
class ParticleSpawner
{
public:
ParticleSpawner(IGameDef *gamedef,
LocalPlayer *player,
const ParticleSpawnerParameters &p,
u16 attached_id,
std::unique_ptr<ClientTexture[]> &texpool,
size_t texcount,
ParticleManager* p_manager);
void step(float dtime, ClientEnvironment *env);
size_t m_active;
bool getExpired() const
{ return m_dying || (p.amount <= 0 && p.time != 0); }
void setDying() { m_dying = true; }
private:
void spawnParticle(ClientEnvironment *env, float radius,
const core::matrix4 *attached_absolute_pos_rot_matrix);
ParticleManager *m_particlemanager;
float m_time;
bool m_dying;
IGameDef *m_gamedef;
LocalPlayer *m_player;
ParticleSpawnerParameters p;
std::unique_ptr<ClientTexture[]> m_texpool;
size_t m_texcount;
std::vector<float> m_spawntimes;
u16 m_attached_id;
};
/**
* 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);
void reserveParticleSpace(size_t max_estimate);
/**
* This function is only used by client particle spawners
*
* We don't need to check the particle spawner list because client ID will
* never overlap (u64)
* @return new id
*/
u64 generateSpawnerId()
{
return m_next_particle_spawner_id++;
}
protected:
static bool getNodeParticleParams(const MapNode &n, const ContentFeatures &f,
ParticleParameters &p, video::ITexture **texture, v2f &texpos,
v2f &texsize, video::SColor *color, u8 tilenum = 0);
void addParticle(Particle* toadd);
private:
void addParticleSpawner(u64 id, ParticleSpawner *toadd);
void deleteParticleSpawner(u64 id);
void stepParticles(float dtime);
void stepSpawners(float dtime);
void clearAll();
std::vector<Particle*> m_particles;
std::unordered_map<u64, ParticleSpawner*> m_particle_spawners;
// Start the particle spawner ids generated from here after u32_max. lower values are
// for server sent spawners.
u64 m_next_particle_spawner_id = U32_MAX + 1;
ClientEnvironment* m_env;
std::mutex m_particle_list_lock;
std::mutex m_spawner_list_lock;
};

View File

@@ -0,0 +1,52 @@
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2017 numzero, Lobachevskiy Vitaliy <numzer0@yandex.ru>
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 "anaglyph.h"
void RenderingCoreAnaglyph::drawAll()
{
renderBothImages();
drawPostFx();
drawHUD();
}
void RenderingCoreAnaglyph::setupMaterial(int color_mask)
{
video::SOverrideMaterial &mat = driver->getOverrideMaterial();
mat.reset();
mat.Material.ColorMask = color_mask;
mat.EnableFlags = video::EMF_COLOR_MASK;
mat.EnablePasses = scene::ESNRP_SKY_BOX | scene::ESNRP_SOLID |
scene::ESNRP_TRANSPARENT | scene::ESNRP_TRANSPARENT_EFFECT |
scene::ESNRP_SHADOW;
}
void RenderingCoreAnaglyph::useEye(bool right)
{
RenderingCoreStereo::useEye(right);
driver->clearBuffers(video::ECBF_DEPTH);
setupMaterial(right ? video::ECP_GREEN | video::ECP_BLUE : video::ECP_RED);
}
void RenderingCoreAnaglyph::resetEye()
{
setupMaterial(video::ECP_ALL);
RenderingCoreStereo::resetEye();
}

View File

@@ -0,0 +1,34 @@
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2017 numzero, Lobachevskiy Vitaliy <numzer0@yandex.ru>
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 "stereo.h"
class RenderingCoreAnaglyph : public RenderingCoreStereo
{
protected:
void setupMaterial(int color_mask);
void useEye(bool right) override;
void resetEye() override;
public:
using RenderingCoreStereo::RenderingCoreStereo;
void drawAll() override;
};

129
src/client/render/core.cpp Normal file
View File

@@ -0,0 +1,129 @@
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2017 numzero, Lobachevskiy Vitaliy <numzer0@yandex.ru>
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 "core.h"
#include "client/camera.h"
#include "client/client.h"
#include "client/clientmap.h"
#include "client/hud.h"
#include "client/minimap.h"
#include "client/shadows/dynamicshadowsrender.h"
RenderingCore::RenderingCore(IrrlichtDevice *_device, Client *_client, Hud *_hud)
: device(_device), driver(device->getVideoDriver()), smgr(device->getSceneManager()),
guienv(device->getGUIEnvironment()), client(_client), camera(client->getCamera()),
mapper(client->getMinimap()), hud(_hud),
shadow_renderer(nullptr)
{
screensize = driver->getScreenSize();
virtual_size = screensize;
// disable if unsupported
if (g_settings->getBool("enable_dynamic_shadows") && (
g_settings->get("video_driver") != "opengl" ||
!g_settings->getBool("enable_shaders"))) {
g_settings->setBool("enable_dynamic_shadows", false);
}
if (g_settings->getBool("enable_shaders") &&
g_settings->getBool("enable_dynamic_shadows")) {
shadow_renderer = new ShadowRenderer(device, client);
}
}
RenderingCore::~RenderingCore()
{
clearTextures();
delete shadow_renderer;
}
void RenderingCore::initialize()
{
// have to be called late as the VMT is not ready in the constructor:
initTextures();
if (shadow_renderer)
shadow_renderer->initialize();
}
void RenderingCore::updateScreenSize()
{
virtual_size = screensize;
clearTextures();
initTextures();
}
void RenderingCore::draw(video::SColor _skycolor, bool _show_hud, bool _show_minimap,
bool _draw_wield_tool, bool _draw_crosshair)
{
v2u32 ss = driver->getScreenSize();
if (screensize != ss) {
screensize = ss;
updateScreenSize();
}
skycolor = _skycolor;
show_hud = _show_hud;
show_minimap = _show_minimap;
draw_wield_tool = _draw_wield_tool;
draw_crosshair = _draw_crosshair;
if (shadow_renderer) {
// This is necessary to render shadows for animations correctly
smgr->getRootSceneNode()->OnAnimate(device->getTimer()->getTime());
shadow_renderer->update();
}
beforeDraw();
drawAll();
}
void RenderingCore::draw3D()
{
smgr->drawAll();
if (shadow_renderer)
shadow_renderer->drawDebug();
driver->setTransform(video::ETS_WORLD, core::IdentityMatrix);
if (!show_hud)
return;
hud->drawBlockBounds();
hud->drawSelectionMesh();
if (draw_wield_tool)
camera->drawWieldedTool();
}
void RenderingCore::drawHUD()
{
if (show_hud) {
if (draw_crosshair)
hud->drawCrosshair();
hud->drawHotbar(client->getEnv().getLocalPlayer()->getWieldIndex());
hud->drawLuaElements(camera->getOffset());
camera->drawNametags();
if (mapper && show_minimap)
mapper->drawMinimap();
}
guienv->drawAll();
}
void RenderingCore::drawPostFx()
{
client->getEnv().getClientMap().renderPostFx(camera->getCameraMode());
}

80
src/client/render/core.h Normal file
View File

@@ -0,0 +1,80 @@
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2017 numzero, Lobachevskiy Vitaliy <numzer0@yandex.ru>
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"
class ShadowRenderer;
class Camera;
class Client;
class Hud;
class Minimap;
class RenderingCore
{
protected:
v2u32 screensize;
v2u32 virtual_size;
video::SColor skycolor;
bool show_hud;
bool show_minimap;
bool draw_wield_tool;
bool draw_crosshair;
IrrlichtDevice *device;
video::IVideoDriver *driver;
scene::ISceneManager *smgr;
gui::IGUIEnvironment *guienv;
Client *client;
Camera *camera;
Minimap *mapper;
Hud *hud;
ShadowRenderer *shadow_renderer;
void updateScreenSize();
virtual void initTextures() {}
virtual void clearTextures() {}
virtual void beforeDraw() {}
virtual void drawAll() = 0;
void draw3D();
void drawHUD();
void drawPostFx();
public:
RenderingCore(IrrlichtDevice *_device, Client *_client, Hud *_hud);
RenderingCore(const RenderingCore &) = delete;
RenderingCore(RenderingCore &&) = delete;
virtual ~RenderingCore();
RenderingCore &operator=(const RenderingCore &) = delete;
RenderingCore &operator=(RenderingCore &&) = delete;
void initialize();
void draw(video::SColor _skycolor, bool _show_hud, bool _show_minimap,
bool _draw_wield_tool, bool _draw_crosshair);
inline v2u32 getVirtualSize() const { return virtual_size; }
ShadowRenderer *get_shadow_renderer() { return shadow_renderer; };
};

View File

@@ -0,0 +1,52 @@
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2017 numzero, Lobachevskiy Vitaliy <numzer0@yandex.ru>
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 "factory.h"
#include "log.h"
#include "plain.h"
#include "anaglyph.h"
#include "interlaced.h"
#include "pageflip.h"
#include "sidebyside.h"
RenderingCore *createRenderingCore(const std::string &stereo_mode, IrrlichtDevice *device,
Client *client, Hud *hud)
{
if (stereo_mode == "none")
return new RenderingCorePlain(device, client, hud);
if (stereo_mode == "anaglyph")
return new RenderingCoreAnaglyph(device, client, hud);
if (stereo_mode == "interlaced")
return new RenderingCoreInterlaced(device, client, hud);
#ifdef STEREO_PAGEFLIP_SUPPORTED
if (stereo_mode == "pageflip")
return new RenderingCorePageflip(device, client, hud);
#endif
if (stereo_mode == "sidebyside")
return new RenderingCoreSideBySide(device, client, hud);
if (stereo_mode == "topbottom")
return new RenderingCoreSideBySide(device, client, hud, true);
if (stereo_mode == "crossview")
return new RenderingCoreSideBySide(device, client, hud, false, true);
// fallback to plain renderer
errorstream << "Invalid rendering mode: " << stereo_mode << std::endl;
return new RenderingCorePlain(device, client, hud);
}

View File

@@ -0,0 +1,27 @@
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2017 numzero, Lobachevskiy Vitaliy <numzer0@yandex.ru>
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 "core.h"
RenderingCore *createRenderingCore(const std::string &stereo_mode, IrrlichtDevice *device,
Client *client, Hud *hud);

View File

@@ -0,0 +1,120 @@
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2017 numzero, Lobachevskiy Vitaliy <numzer0@yandex.ru>
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 "interlaced.h"
#include "client/client.h"
#include "client/shader.h"
#include "client/tile.h"
RenderingCoreInterlaced::RenderingCoreInterlaced(
IrrlichtDevice *_device, Client *_client, Hud *_hud)
: RenderingCoreStereo(_device, _client, _hud)
{
initMaterial();
}
void RenderingCoreInterlaced::initMaterial()
{
IShaderSource *s = client->getShaderSource();
mat.UseMipMaps = false;
mat.ZBuffer = false;
#if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR > 8
mat.ZWriteEnable = video::EZW_OFF;
#else
mat.ZWriteEnable = false;
#endif
u32 shader = s->getShader("3d_interlaced_merge", TILE_MATERIAL_BASIC);
mat.MaterialType = s->getShaderInfo(shader).material;
for (int k = 0; k < 3; ++k) {
mat.TextureLayer[k].AnisotropicFilter = false;
mat.TextureLayer[k].BilinearFilter = false;
mat.TextureLayer[k].TrilinearFilter = false;
mat.TextureLayer[k].TextureWrapU = video::ETC_CLAMP_TO_EDGE;
mat.TextureLayer[k].TextureWrapV = video::ETC_CLAMP_TO_EDGE;
}
}
void RenderingCoreInterlaced::initTextures()
{
v2u32 image_size{screensize.X, screensize.Y / 2};
left = driver->addRenderTargetTexture(
image_size, "3d_render_left", video::ECF_A8R8G8B8);
right = driver->addRenderTargetTexture(
image_size, "3d_render_right", video::ECF_A8R8G8B8);
mask = driver->addTexture(screensize, "3d_render_mask", video::ECF_A8R8G8B8);
initMask();
mat.TextureLayer[0].Texture = left;
mat.TextureLayer[1].Texture = right;
mat.TextureLayer[2].Texture = mask;
}
void RenderingCoreInterlaced::clearTextures()
{
driver->removeTexture(left);
driver->removeTexture(right);
driver->removeTexture(mask);
}
void RenderingCoreInterlaced::initMask()
{
u8 *data = reinterpret_cast<u8 *>(mask->lock());
for (u32 j = 0; j < screensize.Y; j++) {
u8 val = j % 2 ? 0xff : 0x00;
memset(data, val, 4 * screensize.X);
data += 4 * screensize.X;
}
mask->unlock();
}
void RenderingCoreInterlaced::drawAll()
{
renderBothImages();
merge();
drawHUD();
}
void RenderingCoreInterlaced::merge()
{
static const video::S3DVertex vertices[4] = {
video::S3DVertex(1.0, -1.0, 0.0, 0.0, 0.0, -1.0,
video::SColor(255, 0, 255, 255), 1.0, 0.0),
video::S3DVertex(-1.0, -1.0, 0.0, 0.0, 0.0, -1.0,
video::SColor(255, 255, 0, 255), 0.0, 0.0),
video::S3DVertex(-1.0, 1.0, 0.0, 0.0, 0.0, -1.0,
video::SColor(255, 255, 255, 0), 0.0, 1.0),
video::S3DVertex(1.0, 1.0, 0.0, 0.0, 0.0, -1.0,
video::SColor(255, 255, 255, 255), 1.0, 1.0),
};
static const u16 indices[6] = {0, 1, 2, 2, 3, 0};
driver->setMaterial(mat);
driver->drawVertexPrimitiveList(&vertices, 4, &indices, 2);
}
void RenderingCoreInterlaced::useEye(bool _right)
{
driver->setRenderTarget(_right ? right : left, true, true, skycolor);
RenderingCoreStereo::useEye(_right);
}
void RenderingCoreInterlaced::resetEye()
{
driver->setRenderTarget(nullptr, false, false, skycolor);
RenderingCoreStereo::resetEye();
}

View File

@@ -0,0 +1,43 @@
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2017 numzero, Lobachevskiy Vitaliy <numzer0@yandex.ru>
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 "stereo.h"
class RenderingCoreInterlaced : public RenderingCoreStereo
{
protected:
video::ITexture *left = nullptr;
video::ITexture *right = nullptr;
video::ITexture *mask = nullptr;
video::SMaterial mat;
void initMaterial();
void initTextures() override;
void clearTextures() override;
void initMask();
void useEye(bool right) override;
void resetEye() override;
void merge();
public:
RenderingCoreInterlaced(IrrlichtDevice *_device, Client *_client, Hud *_hud);
void drawAll() override;
};

View File

@@ -0,0 +1,59 @@
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2017 numzero, Lobachevskiy Vitaliy <numzer0@yandex.ru>
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 "pageflip.h"
#ifdef STEREO_PAGEFLIP_SUPPORTED
void RenderingCorePageflip::initTextures()
{
hud = driver->addRenderTargetTexture(
screensize, "3d_render_hud", video::ECF_A8R8G8B8);
}
void RenderingCorePageflip::clearTextures()
{
driver->removeTexture(hud);
}
void RenderingCorePageflip::drawAll()
{
driver->setRenderTarget(hud, true, true, video::SColor(0, 0, 0, 0));
drawHUD();
driver->setRenderTarget(nullptr, false, false, skycolor);
renderBothImages();
}
void RenderingCorePageflip::useEye(bool _right)
{
driver->setRenderTarget(_right ? video::ERT_STEREO_RIGHT_BUFFER
: video::ERT_STEREO_LEFT_BUFFER,
true, true, skycolor);
RenderingCoreStereo::useEye(_right);
}
void RenderingCorePageflip::resetEye()
{
driver->draw2DImage(hud, v2s32(0, 0));
driver->setRenderTarget(video::ERT_FRAME_BUFFER, false, false, skycolor);
RenderingCoreStereo::resetEye();
}
#endif // STEREO_PAGEFLIP_SUPPORTED

View File

@@ -0,0 +1,43 @@
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2017 numzero, Lobachevskiy Vitaliy <numzer0@yandex.ru>
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 "stereo.h"
// The support is absent in 1.9.0 (dropped in r5068)
#if (IRRLICHT_VERSION_MAJOR == 1) && (IRRLICHT_VERSION_MINOR <= 8)
#define STEREO_PAGEFLIP_SUPPORTED
class RenderingCorePageflip : public RenderingCoreStereo
{
protected:
video::ITexture *hud = nullptr;
void initTextures() override;
void clearTextures() override;
void useEye(bool right) override;
void resetEye() override;
public:
using RenderingCoreStereo::RenderingCoreStereo;
void drawAll() override;
};
#endif

View File

@@ -0,0 +1,76 @@
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2017 numzero, Lobachevskiy Vitaliy <numzer0@yandex.ru>
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 "plain.h"
#include "settings.h"
inline u32 scaledown(u32 coef, u32 size)
{
return (size + coef - 1) / coef;
}
RenderingCorePlain::RenderingCorePlain(
IrrlichtDevice *_device, Client *_client, Hud *_hud)
: RenderingCore(_device, _client, _hud)
{
scale = g_settings->getU16("undersampling");
}
void RenderingCorePlain::initTextures()
{
if (scale <= 1)
return;
v2u32 size{scaledown(scale, screensize.X), scaledown(scale, screensize.Y)};
lowres = driver->addRenderTargetTexture(
size, "render_lowres", video::ECF_A8R8G8B8);
}
void RenderingCorePlain::clearTextures()
{
if (scale <= 1)
return;
driver->removeTexture(lowres);
}
void RenderingCorePlain::beforeDraw()
{
if (scale <= 1)
return;
driver->setRenderTarget(lowres, true, true, skycolor);
}
void RenderingCorePlain::upscale()
{
if (scale <= 1)
return;
driver->setRenderTarget(0, true, true);
v2u32 size{scaledown(scale, screensize.X), scaledown(scale, screensize.Y)};
v2u32 dest_size{scale * size.X, scale * size.Y};
driver->draw2DImage(lowres, core::rect<s32>(0, 0, dest_size.X, dest_size.Y),
core::rect<s32>(0, 0, size.X, size.Y));
}
void RenderingCorePlain::drawAll()
{
draw3D();
drawPostFx();
upscale();
drawHUD();
}

38
src/client/render/plain.h Normal file
View File

@@ -0,0 +1,38 @@
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2017 numzero, Lobachevskiy Vitaliy <numzer0@yandex.ru>
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 "core.h"
class RenderingCorePlain : public RenderingCore
{
protected:
int scale = 0;
video::ITexture *lowres = nullptr;
void initTextures() override;
void clearTextures() override;
void beforeDraw() override;
void upscale();
public:
RenderingCorePlain(IrrlichtDevice *_device, Client *_client, Hud *_hud);
void drawAll() override;
};

View File

@@ -0,0 +1,74 @@
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2017 numzero, Lobachevskiy Vitaliy <numzer0@yandex.ru>
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 "sidebyside.h"
#include <ICameraSceneNode.h>
#include "client/hud.h"
RenderingCoreSideBySide::RenderingCoreSideBySide(
IrrlichtDevice *_device, Client *_client, Hud *_hud, bool _horizontal, bool _flipped)
: RenderingCoreStereo(_device, _client, _hud), horizontal(_horizontal), flipped(_flipped)
{
}
void RenderingCoreSideBySide::initTextures()
{
if (horizontal) {
image_size = {screensize.X, screensize.Y / 2};
rpos = v2s32(0, screensize.Y / 2);
} else {
image_size = {screensize.X / 2, screensize.Y};
rpos = v2s32(screensize.X / 2, 0);
}
virtual_size = image_size;
left = driver->addRenderTargetTexture(
image_size, "3d_render_left", video::ECF_A8R8G8B8);
right = driver->addRenderTargetTexture(
image_size, "3d_render_right", video::ECF_A8R8G8B8);
}
void RenderingCoreSideBySide::clearTextures()
{
driver->removeTexture(left);
driver->removeTexture(right);
}
void RenderingCoreSideBySide::drawAll()
{
driver->OnResize(image_size); // HACK to make GUI smaller
renderBothImages();
driver->OnResize(screensize);
driver->draw2DImage(left, {});
driver->draw2DImage(right, rpos);
}
void RenderingCoreSideBySide::useEye(bool _right)
{
driver->setRenderTarget(_right ? right : left, true, true, skycolor);
RenderingCoreStereo::useEye(_right ^ flipped);
}
void RenderingCoreSideBySide::resetEye()
{
hud->resizeHotbar();
drawHUD();
driver->setRenderTarget(nullptr, false, false, skycolor);
RenderingCoreStereo::resetEye();
}

View File

@@ -0,0 +1,43 @@
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2017 numzero, Lobachevskiy Vitaliy <numzer0@yandex.ru>
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 "stereo.h"
class RenderingCoreSideBySide : public RenderingCoreStereo
{
protected:
video::ITexture *left = nullptr;
video::ITexture *right = nullptr;
bool horizontal = false;
bool flipped = false;
core::dimension2du image_size;
v2s32 rpos;
void initTextures() override;
void clearTextures() override;
void useEye(bool right) override;
void resetEye() override;
public:
RenderingCoreSideBySide(IrrlichtDevice *_device, Client *_client, Hud *_hud,
bool _horizontal = false, bool _flipped = false);
void drawAll() override;
};

View File

@@ -0,0 +1,60 @@
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2017 numzero, Lobachevskiy Vitaliy <numzer0@yandex.ru>
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 "stereo.h"
#include "client/camera.h"
#include "constants.h"
#include "settings.h"
RenderingCoreStereo::RenderingCoreStereo(
IrrlichtDevice *_device, Client *_client, Hud *_hud)
: RenderingCore(_device, _client, _hud)
{
eye_offset = BS * g_settings->getFloat("3d_paralax_strength", -0.087f, 0.087f);
}
void RenderingCoreStereo::beforeDraw()
{
cam = camera->getCameraNode();
base_transform = cam->getRelativeTransformation();
}
void RenderingCoreStereo::useEye(bool right)
{
core::matrix4 move;
move.setTranslation(
core::vector3df(right ? eye_offset : -eye_offset, 0.0f, 0.0f));
cam->setPosition((base_transform * move).getTranslation());
}
void RenderingCoreStereo::resetEye()
{
cam->setPosition(base_transform.getTranslation());
}
void RenderingCoreStereo::renderBothImages()
{
useEye(false);
draw3D();
resetEye();
useEye(true);
draw3D();
resetEye();
}

View File

@@ -0,0 +1,38 @@
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2017 numzero, Lobachevskiy Vitaliy <numzer0@yandex.ru>
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 "core.h"
class RenderingCoreStereo : public RenderingCore
{
protected:
scene::ICameraSceneNode *cam;
core::matrix4 base_transform;
float eye_offset;
void beforeDraw() override;
virtual void useEye(bool right);
virtual void resetEye();
void renderBothImages();
public:
RenderingCoreStereo(IrrlichtDevice *_device, Client *_client, Hud *_hud);
};

View File

@@ -0,0 +1,652 @@
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2017 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
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 <IrrlichtDevice.h>
#include "fontengine.h"
#include "client.h"
#include "clouds.h"
#include "util/numeric.h"
#include "guiscalingfilter.h"
#include "localplayer.h"
#include "client/hud.h"
#include "camera.h"
#include "minimap.h"
#include "clientmap.h"
#include "renderingengine.h"
#include "render/core.h"
#include "render/factory.h"
#include "inputhandler.h"
#include "gettext.h"
#include "../gui/guiSkin.h"
#if !defined(_WIN32) && !defined(__APPLE__) && !defined(__ANDROID__) && \
!defined(SERVER) && !defined(__HAIKU__)
#define XORG_USED
#endif
#ifdef XORG_USED
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#endif
#ifdef _WIN32
#include <windows.h>
#include <winuser.h>
#endif
#if ENABLE_GLES
#include "filesys.h"
#endif
RenderingEngine *RenderingEngine::s_singleton = nullptr;
static gui::GUISkin *createSkin(gui::IGUIEnvironment *environment,
gui::EGUI_SKIN_TYPE type, video::IVideoDriver *driver)
{
gui::GUISkin *skin = new gui::GUISkin(type, driver);
gui::IGUIFont *builtinfont = environment->getBuiltInFont();
gui::IGUIFontBitmap *bitfont = nullptr;
if (builtinfont && builtinfont->getType() == gui::EGFT_BITMAP)
bitfont = (gui::IGUIFontBitmap*)builtinfont;
gui::IGUISpriteBank *bank = 0;
skin->setFont(builtinfont);
if (bitfont)
bank = bitfont->getSpriteBank();
skin->setSpriteBank(bank);
return skin;
}
RenderingEngine::RenderingEngine(IEventReceiver *receiver)
{
sanity_check(!s_singleton);
// Resolution selection
bool fullscreen = g_settings->getBool("fullscreen");
#ifdef __ANDROID__
u16 screen_w = 0, screen_h = 0;
#else
u16 screen_w = std::max<u16>(g_settings->getU16("screen_w"), 1);
u16 screen_h = std::max<u16>(g_settings->getU16("screen_h"), 1);
#endif
// bpp, fsaa, vsync
bool vsync = g_settings->getBool("vsync");
u16 fsaa = g_settings->getU16("fsaa");
// stereo buffer required for pageflip stereo
bool stereo_buffer = g_settings->get("3d_mode") == "pageflip";
// Determine driver
video::E_DRIVER_TYPE driverType = video::EDT_OPENGL;
const std::string &driverstring = g_settings->get("video_driver");
std::vector<video::E_DRIVER_TYPE> drivers =
RenderingEngine::getSupportedVideoDrivers();
u32 i;
for (i = 0; i != drivers.size(); i++) {
if (!strcasecmp(driverstring.c_str(),
RenderingEngine::getVideoDriverInfo(drivers[i]).name.c_str())) {
driverType = drivers[i];
break;
}
}
if (i == drivers.size()) {
errorstream << "Invalid video_driver specified; "
"defaulting to opengl"
<< std::endl;
}
SIrrlichtCreationParameters params = SIrrlichtCreationParameters();
if (tracestream)
params.LoggingLevel = irr::ELL_DEBUG;
params.DriverType = driverType;
params.WindowSize = core::dimension2d<u32>(screen_w, screen_h);
params.AntiAlias = fsaa;
params.Fullscreen = fullscreen;
params.Stencilbuffer = false;
params.Stereobuffer = stereo_buffer;
params.Vsync = vsync;
params.EventReceiver = receiver;
params.HighPrecisionFPU = true;
#ifdef __ANDROID__
params.PrivateData = porting::app_global;
#endif
#if ENABLE_GLES
// there is no standardized path for these on desktop
std::string rel_path = std::string("client") + DIR_DELIM
+ "shaders" + DIR_DELIM + "Irrlicht";
params.OGLES2ShaderPath = (porting::path_share + DIR_DELIM + rel_path + DIR_DELIM).c_str();
#endif
m_device = createDeviceEx(params);
driver = m_device->getVideoDriver();
s_singleton = this;
auto skin = createSkin(m_device->getGUIEnvironment(),
gui::EGST_WINDOWS_METALLIC, driver);
m_device->getGUIEnvironment()->setSkin(skin);
skin->drop();
}
RenderingEngine::~RenderingEngine()
{
core.reset();
m_device->closeDevice();
s_singleton = nullptr;
}
v2u32 RenderingEngine::_getWindowSize() const
{
if (core)
return core->getVirtualSize();
return m_device->getVideoDriver()->getScreenSize();
}
void RenderingEngine::setResizable(bool resize)
{
m_device->setResizable(resize);
}
void RenderingEngine::removeMesh(const scene::IMesh* mesh)
{
m_device->getSceneManager()->getMeshCache()->removeMesh(mesh);
}
void RenderingEngine::cleanupMeshCache()
{
auto mesh_cache = m_device->getSceneManager()->getMeshCache();
while (mesh_cache->getMeshCount() != 0) {
if (scene::IAnimatedMesh *mesh = mesh_cache->getMeshByIndex(0))
mesh_cache->removeMesh(mesh);
}
}
bool RenderingEngine::setupTopLevelWindow(const std::string &name)
{
// FIXME: It would make more sense for there to be a switch of some
// sort here that would call the correct toplevel setup methods for
// the environment Minetest is running in.
/* Setting Xorg properties for the top level window */
setupTopLevelXorgWindow(name);
/* Setting general properties for the top level window */
verbosestream << "Client: Configuring general top level"
<< " window properties"
<< std::endl;
bool result = setWindowIcon();
return result;
}
void RenderingEngine::setupTopLevelXorgWindow(const std::string &name)
{
#ifdef XORG_USED
const video::SExposedVideoData exposedData = driver->getExposedVideoData();
Display *x11_dpl = reinterpret_cast<Display *>(exposedData.OpenGLLinux.X11Display);
if (x11_dpl == NULL) {
warningstream << "Client: Could not find X11 Display in ExposedVideoData"
<< std::endl;
return;
}
verbosestream << "Client: Configuring X11-specific top level"
<< " window properties"
<< std::endl;
Window x11_win = reinterpret_cast<Window>(exposedData.OpenGLLinux.X11Window);
// Set application name and class hints. For now name and class are the same.
XClassHint *classhint = XAllocClassHint();
classhint->res_name = const_cast<char *>(name.c_str());
classhint->res_class = const_cast<char *>(name.c_str());
XSetClassHint(x11_dpl, x11_win, classhint);
XFree(classhint);
// FIXME: In the future WMNormalHints should be set ... e.g see the
// gtk/gdk code (gdk/x11/gdksurface-x11.c) for the setup_top_level
// method. But for now (as it would require some significant changes)
// leave the code as is.
// The following is borrowed from the above gdk source for setting top
// level windows. The source indicates and the Xlib docs suggest that
// this will set the WM_CLIENT_MACHINE and WM_LOCAL_NAME. This will not
// set the WM_CLIENT_MACHINE to a Fully Qualified Domain Name (FQDN) which is
// required by the Extended Window Manager Hints (EWMH) spec when setting
// the _NET_WM_PID (see further down) but running Minetest in an env
// where the window manager is on another machine from Minetest (therefore
// making the PID useless) is not expected to be a problem. Further
// more, using gtk/gdk as the model it would seem that not using a FQDN is
// not an issue for modern Xorg window managers.
verbosestream << "Client: Setting Xorg window manager Properties"
<< std::endl;
XSetWMProperties (x11_dpl, x11_win, NULL, NULL, NULL, 0, NULL, NULL, NULL);
// Set the _NET_WM_PID window property according to the EWMH spec. _NET_WM_PID
// (in conjunction with WM_CLIENT_MACHINE) can be used by window managers to
// force a shutdown of an application if it doesn't respond to the destroy
// window message.
verbosestream << "Client: Setting Xorg _NET_WM_PID extended window manager property"
<< std::endl;
Atom NET_WM_PID = XInternAtom(x11_dpl, "_NET_WM_PID", false);
pid_t pid = getpid();
XChangeProperty(x11_dpl, x11_win, NET_WM_PID,
XA_CARDINAL, 32, PropModeReplace,
reinterpret_cast<unsigned char *>(&pid),1);
// Set the WM_CLIENT_LEADER window property here. Minetest has only one
// window and that window will always be the leader.
verbosestream << "Client: Setting Xorg WM_CLIENT_LEADER property"
<< std::endl;
Atom WM_CLIENT_LEADER = XInternAtom(x11_dpl, "WM_CLIENT_LEADER", false);
XChangeProperty (x11_dpl, x11_win, WM_CLIENT_LEADER,
XA_WINDOW, 32, PropModeReplace,
reinterpret_cast<unsigned char *>(&x11_win), 1);
#endif
}
#ifdef _WIN32
static bool getWindowHandle(irr::video::IVideoDriver *driver, HWND &hWnd)
{
const video::SExposedVideoData exposedData = driver->getExposedVideoData();
switch (driver->getDriverType()) {
#if ENABLE_GLES
case video::EDT_OGLES1:
case video::EDT_OGLES2:
#endif
case video::EDT_OPENGL:
hWnd = reinterpret_cast<HWND>(exposedData.OpenGLWin32.HWnd);
break;
default:
return false;
}
return true;
}
#endif
bool RenderingEngine::setWindowIcon()
{
#if defined(XORG_USED)
#if RUN_IN_PLACE
return setXorgWindowIconFromPath(
porting::path_share + "/misc/" PROJECT_NAME "-xorg-icon-128.png");
#else
// We have semi-support for reading in-place data if we are
// compiled with RUN_IN_PLACE. Don't break with this and
// also try the path_share location.
return setXorgWindowIconFromPath(
ICON_DIR "/hicolor/128x128/apps/" PROJECT_NAME ".png") ||
setXorgWindowIconFromPath(porting::path_share + "/misc/" PROJECT_NAME
"-xorg-icon-128.png");
#endif
#elif defined(_WIN32)
HWND hWnd; // Window handle
if (!getWindowHandle(driver, hWnd))
return false;
// Load the ICON from resource file
const HICON hicon = LoadIcon(GetModuleHandle(NULL),
MAKEINTRESOURCE(130) // The ID of the ICON defined in
// winresource.rc
);
if (hicon) {
SendMessage(hWnd, WM_SETICON, ICON_BIG, reinterpret_cast<LPARAM>(hicon));
SendMessage(hWnd, WM_SETICON, ICON_SMALL,
reinterpret_cast<LPARAM>(hicon));
return true;
}
return false;
#else
return false;
#endif
}
bool RenderingEngine::setXorgWindowIconFromPath(const std::string &icon_file)
{
#ifdef XORG_USED
video::IImageLoader *image_loader = NULL;
u32 cnt = driver->getImageLoaderCount();
for (u32 i = 0; i < cnt; i++) {
if (driver->getImageLoader(i)->isALoadableFileExtension(
icon_file.c_str())) {
image_loader = driver->getImageLoader(i);
break;
}
}
if (!image_loader) {
warningstream << "Could not find image loader for file '" << icon_file
<< "'" << std::endl;
return false;
}
io::IReadFile *icon_f =
m_device->getFileSystem()->createAndOpenFile(icon_file.c_str());
if (!icon_f) {
warningstream << "Could not load icon file '" << icon_file << "'"
<< std::endl;
return false;
}
video::IImage *img = image_loader->loadImage(icon_f);
if (!img) {
warningstream << "Could not load icon file '" << icon_file << "'"
<< std::endl;
icon_f->drop();
return false;
}
u32 height = img->getDimension().Height;
u32 width = img->getDimension().Width;
size_t icon_buffer_len = 2 + height * width;
long *icon_buffer = new long[icon_buffer_len];
icon_buffer[0] = width;
icon_buffer[1] = height;
for (u32 x = 0; x < width; x++) {
for (u32 y = 0; y < height; y++) {
video::SColor col = img->getPixel(x, y);
long pixel_val = 0;
pixel_val |= (u8)col.getAlpha() << 24;
pixel_val |= (u8)col.getRed() << 16;
pixel_val |= (u8)col.getGreen() << 8;
pixel_val |= (u8)col.getBlue();
icon_buffer[2 + x + y * width] = pixel_val;
}
}
img->drop();
icon_f->drop();
const video::SExposedVideoData &video_data = driver->getExposedVideoData();
Display *x11_dpl = (Display *)video_data.OpenGLLinux.X11Display;
if (x11_dpl == NULL) {
warningstream << "Could not find x11 display for setting its icon."
<< std::endl;
delete[] icon_buffer;
return false;
}
Window x11_win = (Window)video_data.OpenGLLinux.X11Window;
Atom net_wm_icon = XInternAtom(x11_dpl, "_NET_WM_ICON", False);
Atom cardinal = XInternAtom(x11_dpl, "CARDINAL", False);
XChangeProperty(x11_dpl, x11_win, net_wm_icon, cardinal, 32, PropModeReplace,
(const unsigned char *)icon_buffer, icon_buffer_len);
delete[] icon_buffer;
#endif
return true;
}
/*
Draws a screen with a single text on it.
Text will be removed when the screen is drawn the next time.
Additionally, a progressbar can be drawn when percent is set between 0 and 100.
*/
void RenderingEngine::draw_load_screen(const std::wstring &text,
gui::IGUIEnvironment *guienv, ITextureSource *tsrc, float dtime,
int percent, bool clouds)
{
v2u32 screensize = getWindowSize();
v2s32 textsize(g_fontengine->getTextWidth(text), g_fontengine->getLineHeight());
v2s32 center(screensize.X / 2, screensize.Y / 2);
core::rect<s32> textrect(center - textsize / 2, center + textsize / 2);
gui::IGUIStaticText *guitext =
guienv->addStaticText(text.c_str(), textrect, false, false);
guitext->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
bool cloud_menu_background = clouds && g_settings->getBool("menu_clouds");
if (cloud_menu_background) {
g_menuclouds->step(dtime * 3);
g_menuclouds->render();
get_video_driver()->beginScene(
true, true, video::SColor(255, 140, 186, 250));
g_menucloudsmgr->drawAll();
} else
get_video_driver()->beginScene(true, true, video::SColor(255, 0, 0, 0));
// draw progress bar
if ((percent >= 0) && (percent <= 100)) {
video::ITexture *progress_img = tsrc->getTexture("progress_bar.png");
video::ITexture *progress_img_bg =
tsrc->getTexture("progress_bar_bg.png");
if (progress_img && progress_img_bg) {
#ifndef __ANDROID__
const core::dimension2d<u32> &img_size =
progress_img_bg->getSize();
u32 imgW = rangelim(img_size.Width, 200, 600);
u32 imgH = rangelim(img_size.Height, 24, 72);
#else
const core::dimension2d<u32> img_size(256, 48);
float imgRatio = (float)img_size.Height / img_size.Width;
u32 imgW = screensize.X / 2.2f;
u32 imgH = floor(imgW * imgRatio);
#endif
v2s32 img_pos((screensize.X - imgW) / 2,
(screensize.Y - imgH) / 2);
draw2DImageFilterScaled(get_video_driver(), progress_img_bg,
core::rect<s32>(img_pos.X, img_pos.Y,
img_pos.X + imgW,
img_pos.Y + imgH),
core::rect<s32>(0, 0, img_size.Width,
img_size.Height),
0, 0, true);
draw2DImageFilterScaled(get_video_driver(), progress_img,
core::rect<s32>(img_pos.X, img_pos.Y,
img_pos.X + (percent * imgW) / 100,
img_pos.Y + imgH),
core::rect<s32>(0, 0,
(percent * img_size.Width) / 100,
img_size.Height),
0, 0, true);
}
}
guienv->drawAll();
get_video_driver()->endScene();
guitext->remove();
}
/*
Draws the menu scene including (optional) cloud background.
*/
void RenderingEngine::draw_menu_scene(gui::IGUIEnvironment *guienv,
float dtime, bool clouds)
{
bool cloud_menu_background = clouds && g_settings->getBool("menu_clouds");
if (cloud_menu_background) {
g_menuclouds->step(dtime * 3);
g_menuclouds->render();
get_video_driver()->beginScene(
true, true, video::SColor(255, 140, 186, 250));
g_menucloudsmgr->drawAll();
} else
get_video_driver()->beginScene(true, true, video::SColor(255, 0, 0, 0));
guienv->drawAll();
get_video_driver()->endScene();
}
std::vector<irr::video::E_DRIVER_TYPE> RenderingEngine::getSupportedVideoDrivers()
{
// Only check these drivers.
// We do not support software and D3D in any capacity.
static const irr::video::E_DRIVER_TYPE glDrivers[4] = {
irr::video::EDT_NULL,
irr::video::EDT_OPENGL,
irr::video::EDT_OGLES1,
irr::video::EDT_OGLES2,
};
std::vector<irr::video::E_DRIVER_TYPE> drivers;
for (int i = 0; i < 4; i++) {
if (irr::IrrlichtDevice::isDriverSupported(glDrivers[i]))
drivers.push_back(glDrivers[i]);
}
return drivers;
}
void RenderingEngine::initialize(Client *client, Hud *hud)
{
const std::string &draw_mode = g_settings->get("3d_mode");
core.reset(createRenderingCore(draw_mode, m_device, client, hud));
core->initialize();
}
void RenderingEngine::finalize()
{
core.reset();
}
void RenderingEngine::draw_scene(video::SColor skycolor, bool show_hud,
bool show_minimap, bool draw_wield_tool, bool draw_crosshair)
{
core->draw(skycolor, show_hud, show_minimap, draw_wield_tool, draw_crosshair);
}
const VideoDriverInfo &RenderingEngine::getVideoDriverInfo(irr::video::E_DRIVER_TYPE type)
{
static const std::unordered_map<int, VideoDriverInfo> driver_info_map = {
{(int)video::EDT_NULL, {"null", "NULL Driver"}},
{(int)video::EDT_OPENGL, {"opengl", "OpenGL"}},
{(int)video::EDT_OGLES1, {"ogles1", "OpenGL ES1"}},
{(int)video::EDT_OGLES2, {"ogles2", "OpenGL ES2"}},
};
return driver_info_map.at((int)type);
}
#ifndef __ANDROID__
#if defined(XORG_USED)
static float calcDisplayDensity()
{
const char *current_display = getenv("DISPLAY");
if (current_display != NULL) {
Display *x11display = XOpenDisplay(current_display);
if (x11display != NULL) {
/* try x direct */
int dh = DisplayHeight(x11display, 0);
int dw = DisplayWidth(x11display, 0);
int dh_mm = DisplayHeightMM(x11display, 0);
int dw_mm = DisplayWidthMM(x11display, 0);
XCloseDisplay(x11display);
if (dh_mm != 0 && dw_mm != 0) {
float dpi_height = floor(dh / (dh_mm * 0.039370) + 0.5);
float dpi_width = floor(dw / (dw_mm * 0.039370) + 0.5);
return std::max(dpi_height, dpi_width) / 96.0;
}
}
}
/* return manually specified dpi */
return g_settings->getFloat("screen_dpi") / 96.0;
}
float RenderingEngine::getDisplayDensity()
{
static float cached_display_density = calcDisplayDensity();
return std::max(cached_display_density * g_settings->getFloat("display_density_factor"), 0.5f);
}
#elif defined(_WIN32)
static float calcDisplayDensity(irr::video::IVideoDriver *driver)
{
HWND hWnd;
if (getWindowHandle(driver, hWnd)) {
HDC hdc = GetDC(hWnd);
float dpi = GetDeviceCaps(hdc, LOGPIXELSX);
ReleaseDC(hWnd, hdc);
return dpi / 96.0f;
}
/* return manually specified dpi */
return g_settings->getFloat("screen_dpi") / 96.0f;
}
float RenderingEngine::getDisplayDensity()
{
static bool cached = false;
static float display_density;
if (!cached) {
display_density = calcDisplayDensity(get_video_driver());
cached = true;
}
return std::max(display_density * g_settings->getFloat("display_density_factor"), 0.5f);
}
#else
float RenderingEngine::getDisplayDensity()
{
return std::max(g_settings->getFloat("screen_dpi") / 96.0f *
g_settings->getFloat("display_density_factor"), 0.5f);
}
#endif
#else // __ANDROID__
float RenderingEngine::getDisplayDensity()
{
return porting::getDisplayDensity();
}
#endif // __ANDROID__

View File

@@ -0,0 +1,138 @@
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2017 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
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 <vector>
#include <memory>
#include <string>
#include "irrlichttypes_extrabloated.h"
#include "debug.h"
#include "client/render/core.h"
// include the shadow mapper classes too
#include "client/shadows/dynamicshadowsrender.h"
struct VideoDriverInfo {
std::string name;
std::string friendly_name;
};
class ITextureSource;
class Camera;
class Client;
class LocalPlayer;
class Hud;
class Minimap;
class RenderingCore;
class RenderingEngine
{
public:
RenderingEngine(IEventReceiver *eventReceiver);
~RenderingEngine();
void setResizable(bool resize);
video::IVideoDriver *getVideoDriver() { return driver; }
static const VideoDriverInfo &getVideoDriverInfo(irr::video::E_DRIVER_TYPE type);
static float getDisplayDensity();
bool setupTopLevelWindow(const std::string &name);
void setupTopLevelXorgWindow(const std::string &name);
bool setWindowIcon();
bool setXorgWindowIconFromPath(const std::string &icon_file);
static bool print_video_modes();
void cleanupMeshCache();
void removeMesh(const scene::IMesh* mesh);
static v2u32 getWindowSize()
{
sanity_check(s_singleton);
return s_singleton->_getWindowSize();
}
io::IFileSystem *get_filesystem()
{
return m_device->getFileSystem();
}
static video::IVideoDriver *get_video_driver()
{
sanity_check(s_singleton && s_singleton->m_device);
return s_singleton->m_device->getVideoDriver();
}
scene::ISceneManager *get_scene_manager()
{
return m_device->getSceneManager();
}
static irr::IrrlichtDevice *get_raw_device()
{
sanity_check(s_singleton && s_singleton->m_device);
return s_singleton->m_device;
}
u32 get_timer_time()
{
return m_device->getTimer()->getTime();
}
gui::IGUIEnvironment *get_gui_env()
{
return m_device->getGUIEnvironment();
}
void draw_load_screen(const std::wstring &text,
gui::IGUIEnvironment *guienv, ITextureSource *tsrc,
float dtime = 0, int percent = 0, bool clouds = true);
void draw_menu_scene(gui::IGUIEnvironment *guienv, float dtime, bool clouds);
void draw_scene(video::SColor skycolor, bool show_hud,
bool show_minimap, bool draw_wield_tool, bool draw_crosshair);
void initialize(Client *client, Hud *hud);
void finalize();
bool run()
{
return m_device->run();
}
// FIXME: this is still global when it shouldn't be
static ShadowRenderer *get_shadow_renderer()
{
if (s_singleton && s_singleton->core)
return s_singleton->core->get_shadow_renderer();
return nullptr;
}
static std::vector<irr::video::E_DRIVER_TYPE> getSupportedVideoDrivers();
private:
v2u32 _getWindowSize() const;
std::unique_ptr<RenderingCore> core;
irr::IrrlichtDevice *m_device = nullptr;
irr::video::IVideoDriver *driver;
static RenderingEngine *s_singleton;
};

829
src/client/shader.cpp Normal file
View File

@@ -0,0 +1,829 @@
/*
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 "irr_ptr.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"
#include "config.h"
#include <mt_opengl.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<std::unique_ptr<IShaderConstantSetter>> m_setters;
public:
template <typename Factories>
ShaderCallback(const Factories &factories)
{
for (auto &&factory : factories)
m_setters.push_back(std::unique_ptr<IShaderConstantSetter>(factory->create()));
}
virtual void OnSetConstants(video::IMaterialRendererServices *services, s32 userData) override
{
video::IVideoDriver *driver = services->getVideoDriver();
sanity_check(driver != NULL);
for (auto &&setter : m_setters)
setter->onSetConstants(services);
}
virtual void OnSetMaterial(const video::SMaterial& material) override
{
for (auto &&setter : m_setters)
setter->onSetMaterial(material);
}
};
/*
MainShaderConstantSetter: Set basic constants required for almost everything
*/
class MainShaderConstantSetter : public IShaderConstantSetter
{
CachedVertexShaderSetting<f32, 16> m_world_view_proj;
CachedVertexShaderSetting<f32, 16> m_world;
// Shadow-related
CachedPixelShaderSetting<f32, 16> m_shadow_view_proj;
CachedPixelShaderSetting<f32, 3> m_light_direction;
CachedPixelShaderSetting<f32> m_texture_res;
CachedPixelShaderSetting<f32> m_shadow_strength;
CachedPixelShaderSetting<f32> m_time_of_day;
CachedPixelShaderSetting<f32> m_shadowfar;
CachedPixelShaderSetting<f32, 4> m_camera_pos;
CachedPixelShaderSetting<s32> m_shadow_texture;
CachedVertexShaderSetting<f32> m_perspective_bias0_vertex;
CachedPixelShaderSetting<f32> m_perspective_bias0_pixel;
CachedVertexShaderSetting<f32> m_perspective_bias1_vertex;
CachedPixelShaderSetting<f32> m_perspective_bias1_pixel;
CachedVertexShaderSetting<f32> m_perspective_zbias_vertex;
CachedPixelShaderSetting<f32> m_perspective_zbias_pixel;
#if ENABLE_GLES
// Modelview matrix
CachedVertexShaderSetting<float, 16> m_world_view;
// Texture matrix
CachedVertexShaderSetting<float, 16> m_texture;
// Normal matrix
CachedVertexShaderSetting<float, 9> m_normal;
#endif
public:
MainShaderConstantSetter() :
m_world_view_proj("mWorldViewProj")
, m_world("mWorld")
, m_shadow_view_proj("m_ShadowViewProj")
, m_light_direction("v_LightDirection")
, m_texture_res("f_textureresolution")
, m_shadow_strength("f_shadow_strength")
, m_time_of_day("f_timeofday")
, m_shadowfar("f_shadowfar")
, m_camera_pos("CameraPos")
, m_shadow_texture("ShadowMapSampler")
, m_perspective_bias0_vertex("xyPerspectiveBias0")
, m_perspective_bias0_pixel("xyPerspectiveBias0")
, m_perspective_bias1_vertex("xyPerspectiveBias1")
, m_perspective_bias1_pixel("xyPerspectiveBias1")
, m_perspective_zbias_vertex("zPerspectiveBias")
, m_perspective_zbias_pixel("zPerspectiveBias")
#if ENABLE_GLES
, m_world_view("mWorldView")
, m_texture("mTexture")
, m_normal("mNormal")
#endif
{}
~MainShaderConstantSetter() = default;
virtual void onSetConstants(video::IMaterialRendererServices *services) override
{
video::IVideoDriver *driver = services->getVideoDriver();
sanity_check(driver);
// Set world matrix
core::matrix4 world = driver->getTransform(video::ETS_WORLD);
m_world.set(*reinterpret_cast<float(*)[16]>(world.pointer()), services);
// Set clip matrix
core::matrix4 worldView;
worldView = driver->getTransform(video::ETS_VIEW);
worldView *= world;
core::matrix4 worldViewProj;
worldViewProj = driver->getTransform(video::ETS_PROJECTION);
worldViewProj *= worldView;
m_world_view_proj.set(*reinterpret_cast<float(*)[16]>(worldViewProj.pointer()), services);
#if ENABLE_GLES
core::matrix4 texture = driver->getTransform(video::ETS_TEXTURE_0);
m_world_view.set(*reinterpret_cast<float(*)[16]>(worldView.pointer()), services);
m_texture.set(*reinterpret_cast<float(*)[16]>(texture.pointer()), services);
core::matrix4 normal;
worldView.getTransposed(normal);
sanity_check(normal.makeInverse());
float m[9] = {
normal[0], normal[1], normal[2],
normal[4], normal[5], normal[6],
normal[8], normal[9], normal[10],
};
m_normal.set(m, services);
#endif
// Set uniforms for Shadow shader
if (ShadowRenderer *shadow = RenderingEngine::get_shadow_renderer()) {
const auto &light = shadow->getDirectionalLight();
core::matrix4 shadowViewProj = light.getProjectionMatrix();
shadowViewProj *= light.getViewMatrix();
m_shadow_view_proj.set(shadowViewProj.pointer(), services);
f32 v_LightDirection[3];
light.getDirection().getAs3Values(v_LightDirection);
m_light_direction.set(v_LightDirection, services);
f32 TextureResolution = light.getMapResolution();
m_texture_res.set(&TextureResolution, services);
f32 ShadowStrength = shadow->getShadowStrength();
m_shadow_strength.set(&ShadowStrength, services);
f32 timeOfDay = shadow->getTimeOfDay();
m_time_of_day.set(&timeOfDay, services);
f32 shadowFar = shadow->getMaxShadowFar();
m_shadowfar.set(&shadowFar, services);
f32 cam_pos[4];
shadowViewProj.transformVect(cam_pos, light.getPlayerPos());
m_camera_pos.set(cam_pos, services);
// I dont like using this hardcoded value. maybe something like
// MAX_TEXTURE - 1 or somthing like that??
s32 TextureLayerID = 3;
m_shadow_texture.set(&TextureLayerID, services);
f32 bias0 = shadow->getPerspectiveBiasXY();
m_perspective_bias0_vertex.set(&bias0, services);
m_perspective_bias0_pixel.set(&bias0, services);
f32 bias1 = 1.0f - bias0 + 1e-5f;
m_perspective_bias1_vertex.set(&bias1, services);
m_perspective_bias1_pixel.set(&bias1, services);
f32 zbias = shadow->getPerspectiveBiasZ();
m_perspective_zbias_vertex.set(&zbias, services);
m_perspective_zbias_pixel.set(&zbias, services);
}
}
};
class MainShaderConstantSetterFactory : public IShaderConstantSetterFactory
{
public:
virtual IShaderConstantSetter* create()
{ return new MainShaderConstantSetter(); }
};
/*
ShaderSource
*/
class ShaderSource : public IWritableShaderSource
{
public:
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,
MaterialType material_type, NodeDrawType drawtype) override;
/*
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,
MaterialType material_type, NodeDrawType drawtype) override;
ShaderInfo getShaderInfo(u32 id) override;
// Processes queued shader requests from other threads.
// Shall be called from the main thread.
void processQueue() override;
// 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) override;
// Rebuild shaders from the current set of source shaders
// Shall be called from the main thread.
void rebuildShaders() override;
void addShaderConstantSetterFactory(IShaderConstantSetterFactory *setter) override
{
m_setter_factories.push_back(std::unique_ptr<IShaderConstantSetterFactory>(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<std::unique_ptr<IShaderConstantSetterFactory>> m_setter_factories;
// Generate shader given the shader name.
ShaderInfo generateShader(const std::string &name,
MaterialType material_type, NodeDrawType drawtype);
};
IWritableShaderSource *createShaderSource()
{
return new ShaderSource();
}
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());
}
u32 ShaderSource::getShader(const std::string &name,
MaterialType material_type, NodeDrawType 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,
MaterialType material_type, NodeDrawType 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 = generateShader(name, material_type, drawtype);
/*
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 = generateShader(info->name, info->material_type, info->drawtype);
}
}
}
ShaderInfo ShaderSource::generateShader(const std::string &name,
MaterialType material_type, NodeDrawType drawtype)
{
ShaderInfo shaderinfo;
shaderinfo.name = name;
shaderinfo.material_type = material_type;
shaderinfo.drawtype = drawtype;
switch (material_type) {
case TILE_MATERIAL_OPAQUE:
case TILE_MATERIAL_LIQUID_OPAQUE:
case TILE_MATERIAL_WAVING_LIQUID_OPAQUE:
shaderinfo.base_material = video::EMT_SOLID;
break;
case TILE_MATERIAL_ALPHA:
case TILE_MATERIAL_PLAIN_ALPHA:
case TILE_MATERIAL_LIQUID_TRANSPARENT:
case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT:
shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
break;
case TILE_MATERIAL_BASIC:
case TILE_MATERIAL_PLAIN:
case TILE_MATERIAL_WAVING_LEAVES:
case TILE_MATERIAL_WAVING_PLANTS:
case TILE_MATERIAL_WAVING_LIQUID_BASIC:
shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
break;
}
shaderinfo.material = shaderinfo.base_material;
bool enable_shaders = g_settings->getBool("enable_shaders");
if (!enable_shaders)
return shaderinfo;
video::IVideoDriver *driver = RenderingEngine::get_video_driver();
if (!driver->queryFeature(video::EVDF_ARB_GLSL)) {
errorstream << "Shaders are enabled but GLSL is not supported by the driver\n";
return shaderinfo;
}
video::IGPUProgrammingServices *gpu = driver->getGPUProgrammingServices();
// Create shaders header
bool use_gles = false;
#if ENABLE_GLES
use_gles = driver->getDriverType() == video::EDT_OGLES2;
#endif
std::stringstream shaders_header;
shaders_header
<< std::noboolalpha
<< std::showpoint // for GLSL ES
;
std::string vertex_header, fragment_header, geometry_header;
if (use_gles) {
shaders_header << R"(
#version 100
)";
vertex_header = R"(
precision mediump float;
uniform highp mat4 mWorldView;
uniform highp mat4 mWorldViewProj;
uniform mediump mat4 mTexture;
uniform mediump mat3 mNormal;
attribute highp vec4 inVertexPosition;
attribute lowp vec4 inVertexColor;
attribute mediump vec4 inTexCoord0;
attribute mediump vec3 inVertexNormal;
attribute mediump vec4 inVertexTangent;
attribute mediump vec4 inVertexBinormal;
)";
fragment_header = R"(
precision mediump float;
)";
} else {
shaders_header << R"(
#version 120
#define lowp
#define mediump
#define highp
)";
vertex_header = R"(
#define mWorldView gl_ModelViewMatrix
#define mWorldViewProj gl_ModelViewProjectionMatrix
#define mTexture (gl_TextureMatrix[0])
#define mNormal gl_NormalMatrix
#define inVertexPosition gl_Vertex
#define inVertexColor gl_Color
#define inTexCoord0 gl_MultiTexCoord0
#define inVertexNormal gl_Normal
#define inVertexTangent gl_MultiTexCoord1
#define inVertexBinormal gl_MultiTexCoord2
)";
}
// Since this is the first time we're using the GL bindings be extra careful.
// This should be removed before 5.6.0 or similar.
if (!GL.GetString) {
errorstream << "OpenGL procedures were not loaded correctly, "
"please open a bug report with details about your platform/OS." << std::endl;
abort();
}
bool use_discard = use_gles;
// For renderers that should use discard instead of GL_ALPHA_TEST
const char *renderer = reinterpret_cast<const char*>(GL.GetString(GL.RENDERER));
if (strstr(renderer, "GC7000"))
use_discard = true;
if (use_discard) {
if (shaderinfo.base_material == video::EMT_TRANSPARENT_ALPHA_CHANNEL)
shaders_header << "#define USE_DISCARD 1\n";
else if (shaderinfo.base_material == video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF)
shaders_header << "#define USE_DISCARD_REF 1\n";
}
#define PROVIDE(constant) shaders_header << "#define " #constant " " << (int)constant << "\n"
PROVIDE(NDT_NORMAL);
PROVIDE(NDT_AIRLIKE);
PROVIDE(NDT_LIQUID);
PROVIDE(NDT_FLOWINGLIQUID);
PROVIDE(NDT_GLASSLIKE);
PROVIDE(NDT_ALLFACES);
PROVIDE(NDT_ALLFACES_OPTIONAL);
PROVIDE(NDT_TORCHLIKE);
PROVIDE(NDT_SIGNLIKE);
PROVIDE(NDT_PLANTLIKE);
PROVIDE(NDT_FENCELIKE);
PROVIDE(NDT_RAILLIKE);
PROVIDE(NDT_NODEBOX);
PROVIDE(NDT_GLASSLIKE_FRAMED);
PROVIDE(NDT_FIRELIKE);
PROVIDE(NDT_GLASSLIKE_FRAMED_OPTIONAL);
PROVIDE(NDT_PLANTLIKE_ROOTED);
PROVIDE(TILE_MATERIAL_BASIC);
PROVIDE(TILE_MATERIAL_ALPHA);
PROVIDE(TILE_MATERIAL_LIQUID_TRANSPARENT);
PROVIDE(TILE_MATERIAL_LIQUID_OPAQUE);
PROVIDE(TILE_MATERIAL_WAVING_LEAVES);
PROVIDE(TILE_MATERIAL_WAVING_PLANTS);
PROVIDE(TILE_MATERIAL_OPAQUE);
PROVIDE(TILE_MATERIAL_WAVING_LIQUID_BASIC);
PROVIDE(TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT);
PROVIDE(TILE_MATERIAL_WAVING_LIQUID_OPAQUE);
PROVIDE(TILE_MATERIAL_PLAIN);
PROVIDE(TILE_MATERIAL_PLAIN_ALPHA);
#undef PROVIDE
shaders_header << "#define MATERIAL_TYPE " << (int)material_type << "\n";
shaders_header << "#define DRAW_TYPE " << (int)drawtype << "\n";
bool enable_waving_water = g_settings->getBool("enable_waving_water");
shaders_header << "#define ENABLE_WAVING_WATER " << enable_waving_water << "\n";
if (enable_waving_water) {
shaders_header << "#define WATER_WAVE_HEIGHT " << g_settings->getFloat("water_wave_height") << "\n";
shaders_header << "#define WATER_WAVE_LENGTH " << g_settings->getFloat("water_wave_length") << "\n";
shaders_header << "#define WATER_WAVE_SPEED " << g_settings->getFloat("water_wave_speed") << "\n";
}
shaders_header << "#define ENABLE_WAVING_LEAVES " << g_settings->getBool("enable_waving_leaves") << "\n";
shaders_header << "#define ENABLE_WAVING_PLANTS " << g_settings->getBool("enable_waving_plants") << "\n";
shaders_header << "#define ENABLE_TONE_MAPPING " << g_settings->getBool("tone_mapping") << "\n";
shaders_header << "#define FOG_START " << core::clamp(g_settings->getFloat("fog_start"), 0.0f, 0.99f) << "\n";
if (g_settings->getBool("enable_dynamic_shadows")) {
shaders_header << "#define ENABLE_DYNAMIC_SHADOWS 1\n";
if (g_settings->getBool("shadow_map_color"))
shaders_header << "#define COLORED_SHADOWS 1\n";
if (g_settings->getBool("shadow_poisson_filter"))
shaders_header << "#define POISSON_FILTER 1\n";
s32 shadow_filter = g_settings->getS32("shadow_filters");
shaders_header << "#define SHADOW_FILTER " << shadow_filter << "\n";
float shadow_soft_radius = g_settings->getFloat("shadow_soft_radius");
if (shadow_soft_radius < 1.0f)
shadow_soft_radius = 1.0f;
shaders_header << "#define SOFTSHADOWRADIUS " << shadow_soft_radius << "\n";
}
shaders_header << "#line 0\n"; // reset the line counter for meaningful diagnostics
std::string common_header = shaders_header.str();
std::string vertex_shader = m_sourcecache.getOrLoad(name, "opengl_vertex.glsl");
std::string fragment_shader = m_sourcecache.getOrLoad(name, "opengl_fragment.glsl");
std::string geometry_shader = m_sourcecache.getOrLoad(name, "opengl_geometry.glsl");
vertex_shader = common_header + vertex_header + vertex_shader;
fragment_shader = common_header + fragment_header + fragment_shader;
const char *geometry_shader_ptr = nullptr; // optional
if (!geometry_shader.empty()) {
geometry_shader = common_header + geometry_header + geometry_shader;
geometry_shader_ptr = geometry_shader.c_str();
}
irr_ptr<ShaderCallback> cb{new ShaderCallback(m_setter_factories)};
infostream<<"Compiling high level shaders for "<<name<<std::endl;
s32 shadermat = gpu->addHighLevelShaderMaterial(
vertex_shader.c_str(), nullptr, video::EVST_VS_1_1,
fragment_shader.c_str(), nullptr, video::EPST_PS_1_1,
geometry_shader_ptr, nullptr, video::EGST_GS_4_0, scene::EPT_TRIANGLES, scene::EPT_TRIANGLES, 0,
cb.get(), shaderinfo.base_material, 1);
if (shadermat == -1) {
errorstream<<"generate_shader(): "
"failed to generate \""<<name<<"\", "
"addHighLevelShaderMaterial failed."
<<std::endl;
dumpShaderProgram(warningstream, "Vertex", vertex_shader);
dumpShaderProgram(warningstream, "Fragment", fragment_shader);
dumpShaderProgram(warningstream, "Geometry", geometry_shader);
return shaderinfo;
}
// Apply the newly created material type
shaderinfo.material = (video::E_MATERIAL_TYPE) shadermat;
return shaderinfo;
}
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
View 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 "irrlichttypes_bloated.h"
#include <IMaterialRendererServices.h>
#include <string>
#include "tile.h"
#include "nodedef.h"
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;
NodeDrawType drawtype = NDT_NORMAL;
MaterialType material_type = TILE_MATERIAL_BASIC;
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) = 0;
virtual void onSetMaterial(const video::SMaterial& material)
{ }
};
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(services->getPixelShaderConstantID(m_name), value, count);
else
services->setVertexShaderConstant(services->getVertexShaderConstantID(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,
MaterialType material_type, NodeDrawType drawtype = NDT_NORMAL){return 0;}
virtual ShaderInfo getShaderInfo(u32 id){return ShaderInfo();}
virtual u32 getShader(const std::string &name,
MaterialType material_type, NodeDrawType drawtype = NDT_NORMAL){return 0;}
};
class IWritableShaderSource : public IShaderSource {
public:
IWritableShaderSource() = default;
virtual ~IWritableShaderSource() = default;
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;
/// @note Takes ownership of @p setter.
virtual void addShaderConstantSetterFactory(IShaderConstantSetterFactory *setter) = 0;
};
IWritableShaderSource *createShaderSource();
void dumpShaderProgram(std::ostream &output_stream,
const std::string &program_type, const std::string &program);

View File

@@ -0,0 +1,191 @@
/*
Minetest
Copyright (C) 2021 Liso <anlismon@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 <cmath>
#include "client/shadows/dynamicshadows.h"
#include "client/client.h"
#include "client/clientenvironment.h"
#include "client/clientmap.h"
#include "client/camera.h"
using m4f = core::matrix4;
static v3f quantizeDirection(v3f direction, float step)
{
float yaw = std::atan2(direction.Z, direction.X);
float pitch = std::asin(direction.Y); // assume look is normalized
yaw = std::floor(yaw / step) * step;
pitch = std::floor(pitch / step) * step;
return v3f(std::cos(yaw)*std::cos(pitch), std::sin(pitch), std::sin(yaw)*std::cos(pitch));
}
void DirectionalLight::createSplitMatrices(const Camera *cam)
{
const float DISTANCE_STEP = BS * 2.0; // 2 meters
v3f newCenter;
v3f look = cam->getDirection();
look = quantizeDirection(look, M_PI / 12.0); // 15 degrees
// camera view tangents
float tanFovY = tanf(cam->getFovY() * 0.5f);
float tanFovX = tanf(cam->getFovX() * 0.5f);
// adjusted frustum boundaries
float sfNear = future_frustum.zNear;
float sfFar = adjustDist(future_frustum.zFar, cam->getFovY());
// adjusted camera positions
v3f cam_pos_world = cam->getPosition();
cam_pos_world = v3f(
floor(cam_pos_world.X / DISTANCE_STEP) * DISTANCE_STEP,
floor(cam_pos_world.Y / DISTANCE_STEP) * DISTANCE_STEP,
floor(cam_pos_world.Z / DISTANCE_STEP) * DISTANCE_STEP);
v3f cam_pos_scene = v3f(cam_pos_world.X - cam->getOffset().X * BS,
cam_pos_world.Y - cam->getOffset().Y * BS,
cam_pos_world.Z - cam->getOffset().Z * BS);
cam_pos_scene += look * sfNear;
cam_pos_world += look * sfNear;
// center point of light frustum
v3f center_scene = cam_pos_scene + look * 0.35 * (sfFar - sfNear);
v3f center_world = cam_pos_world + look * 0.35 * (sfFar - sfNear);
// Create a vector to the frustum far corner
const v3f &viewUp = cam->getCameraNode()->getUpVector();
v3f viewRight = look.crossProduct(viewUp);
v3f farCorner = (look + viewRight * tanFovX + viewUp * tanFovY).normalize();
// Compute the frustumBoundingSphere radius
v3f boundVec = (cam_pos_scene + farCorner * sfFar) - center_scene;
float radius = boundVec.getLength();
float length = radius * 3.0f;
v3f eye_displacement = quantizeDirection(direction, M_PI / 2880 /*15 seconds*/) * length;
// we must compute the viewmat with the position - the camera offset
// but the future_frustum position must be the actual world position
v3f eye = center_scene - eye_displacement;
future_frustum.player = cam_pos_scene;
future_frustum.position = center_world - eye_displacement;
future_frustum.length = length;
future_frustum.radius = radius;
future_frustum.ViewMat.buildCameraLookAtMatrixLH(eye, center_scene, v3f(0.0f, 1.0f, 0.0f));
future_frustum.ProjOrthMat.buildProjectionMatrixOrthoLH(radius, radius,
0.0f, length, false);
future_frustum.camera_offset = cam->getOffset();
}
DirectionalLight::DirectionalLight(const u32 shadowMapResolution,
const v3f &position, video::SColorf lightColor,
f32 farValue) :
diffuseColor(lightColor),
farPlane(farValue), mapRes(shadowMapResolution), pos(position)
{}
void DirectionalLight::update_frustum(const Camera *cam, Client *client, bool force)
{
if (dirty && !force)
return;
float zNear = cam->getCameraNode()->getNearValue();
float zFar = getMaxFarValue();
if (!client->getEnv().getClientMap().getControl().range_all)
zFar = MYMIN(zFar, client->getEnv().getClientMap().getControl().wanted_range * BS);
///////////////////////////////////
// update splits near and fars
future_frustum.zNear = zNear;
future_frustum.zFar = zFar;
// update shadow frustum
createSplitMatrices(cam);
// get the draw list for shadows
client->getEnv().getClientMap().updateDrawListShadow(
getPosition(), getDirection(), future_frustum.radius, future_frustum.length);
should_update_map_shadow = true;
dirty = true;
// when camera offset changes, adjust the current frustum view matrix to avoid flicker
v3s16 cam_offset = cam->getOffset();
if (cam_offset != shadow_frustum.camera_offset) {
v3f rotated_offset;
shadow_frustum.ViewMat.rotateVect(rotated_offset, intToFloat(cam_offset - shadow_frustum.camera_offset, BS));
shadow_frustum.ViewMat.setTranslation(shadow_frustum.ViewMat.getTranslation() + rotated_offset);
shadow_frustum.player += intToFloat(shadow_frustum.camera_offset - cam->getOffset(), BS);
shadow_frustum.camera_offset = cam_offset;
}
}
void DirectionalLight::commitFrustum()
{
if (!dirty)
return;
shadow_frustum = future_frustum;
dirty = false;
}
void DirectionalLight::setDirection(v3f dir)
{
direction = -dir;
direction.normalize();
}
v3f DirectionalLight::getPosition() const
{
return shadow_frustum.position;
}
v3f DirectionalLight::getPlayerPos() const
{
return shadow_frustum.player;
}
v3f DirectionalLight::getFuturePlayerPos() const
{
return future_frustum.player;
}
const m4f &DirectionalLight::getViewMatrix() const
{
return shadow_frustum.ViewMat;
}
const m4f &DirectionalLight::getProjectionMatrix() const
{
return shadow_frustum.ProjOrthMat;
}
const m4f &DirectionalLight::getFutureViewMatrix() const
{
return future_frustum.ViewMat;
}
const m4f &DirectionalLight::getFutureProjectionMatrix() const
{
return future_frustum.ProjOrthMat;
}
m4f DirectionalLight::getViewProjMatrix()
{
return shadow_frustum.ProjOrthMat * shadow_frustum.ViewMat;
}

View File

@@ -0,0 +1,120 @@
/*
Minetest
Copyright (C) 2021 Liso <anlismon@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_bloated.h"
#include <matrix4.h>
#include "util/basic_macros.h"
#include "constants.h"
class Camera;
class Client;
struct shadowFrustum
{
f32 zNear{0.0f};
f32 zFar{0.0f};
f32 length{0.0f};
f32 radius{0.0f};
core::matrix4 ProjOrthMat;
core::matrix4 ViewMat;
v3f position;
v3f player;
v3s16 camera_offset;
};
class DirectionalLight
{
public:
DirectionalLight(const u32 shadowMapResolution,
const v3f &position,
video::SColorf lightColor = video::SColor(0xffffffff),
f32 farValue = 100.0f);
~DirectionalLight() = default;
//DISABLE_CLASS_COPY(DirectionalLight)
void update_frustum(const Camera *cam, Client *client, bool force = false);
// when set direction is updated to negative normalized(direction)
void setDirection(v3f dir);
v3f getDirection() const{
return direction;
};
v3f getPosition() const;
v3f getPlayerPos() const;
v3f getFuturePlayerPos() const;
/// Gets the light's matrices.
const core::matrix4 &getViewMatrix() const;
const core::matrix4 &getProjectionMatrix() const;
const core::matrix4 &getFutureViewMatrix() const;
const core::matrix4 &getFutureProjectionMatrix() const;
core::matrix4 getViewProjMatrix();
/// Gets the light's maximum far value, i.e. the shadow boundary
f32 getMaxFarValue() const
{
return farPlane * BS;
}
/// Gets the current far value of the light
f32 getFarValue() const
{
return shadow_frustum.zFar;
}
/// Gets the light's color.
const video::SColorf &getLightColor() const
{
return diffuseColor;
}
/// Sets the light's color.
void setLightColor(const video::SColorf &lightColor)
{
diffuseColor = lightColor;
}
/// Gets the shadow map resolution for this light.
u32 getMapResolution() const
{
return mapRes;
}
bool should_update_map_shadow{true};
void commitFrustum();
private:
void createSplitMatrices(const Camera *cam);
video::SColorf diffuseColor;
f32 farPlane;
u32 mapRes;
v3f pos;
v3f direction{0};
shadowFrustum shadow_frustum;
shadowFrustum future_frustum;
bool dirty{false};
};

View File

@@ -0,0 +1,709 @@
/*
Minetest
Copyright (C) 2021 Liso <anlismon@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 <cstring>
#include <cmath>
#include "client/shadows/dynamicshadowsrender.h"
#include "client/shadows/shadowsScreenQuad.h"
#include "client/shadows/shadowsshadercallbacks.h"
#include "settings.h"
#include "filesys.h"
#include "util/string.h"
#include "client/shader.h"
#include "client/client.h"
#include "client/clientmap.h"
#include "profiler.h"
ShadowRenderer::ShadowRenderer(IrrlichtDevice *device, Client *client) :
m_smgr(device->getSceneManager()), m_driver(device->getVideoDriver()),
m_client(client), m_current_frame(0),
m_perspective_bias_xy(0.8), m_perspective_bias_z(0.5)
{
(void) m_client;
m_shadows_supported = true; // assume shadows supported. We will check actual support in initialize
m_shadows_enabled = true;
m_shadow_strength_gamma = g_settings->getFloat("shadow_strength_gamma");
if (std::isnan(m_shadow_strength_gamma))
m_shadow_strength_gamma = 1.0f;
m_shadow_strength_gamma = core::clamp(m_shadow_strength_gamma, 0.1f, 10.0f);
m_shadow_map_max_distance = g_settings->getFloat("shadow_map_max_distance");
m_shadow_map_texture_size = g_settings->getFloat("shadow_map_texture_size");
m_shadow_map_texture_32bit = g_settings->getBool("shadow_map_texture_32bit");
m_shadow_map_colored = g_settings->getBool("shadow_map_color");
m_shadow_samples = g_settings->getS32("shadow_filters");
m_map_shadow_update_frames = g_settings->getS16("shadow_update_frames");
}
ShadowRenderer::~ShadowRenderer()
{
// call to disable releases dynamically allocated resources
disable();
if (m_shadow_depth_cb)
delete m_shadow_depth_cb;
if (m_shadow_depth_entity_cb)
delete m_shadow_depth_entity_cb;
if (m_shadow_depth_trans_cb)
delete m_shadow_depth_trans_cb;
if (m_shadow_mix_cb)
delete m_shadow_mix_cb;
m_shadow_node_array.clear();
m_light_list.clear();
}
void ShadowRenderer::disable()
{
m_shadows_enabled = false;
if (shadowMapTextureFinal) {
m_driver->setRenderTarget(shadowMapTextureFinal, true, true,
video::SColor(255, 255, 255, 255));
m_driver->setRenderTarget(0, false, false);
}
if (shadowMapTextureDynamicObjects) {
m_driver->removeTexture(shadowMapTextureDynamicObjects);
shadowMapTextureDynamicObjects = nullptr;
}
if (shadowMapTextureFinal) {
m_driver->removeTexture(shadowMapTextureFinal);
shadowMapTextureFinal = nullptr;
}
if (shadowMapTextureColors) {
m_driver->removeTexture(shadowMapTextureColors);
shadowMapTextureColors = nullptr;
}
if (shadowMapClientMap) {
m_driver->removeTexture(shadowMapClientMap);
shadowMapClientMap = nullptr;
}
if (shadowMapClientMapFuture) {
m_driver->removeTexture(shadowMapClientMapFuture);
shadowMapClientMapFuture = nullptr;
}
for (auto node : m_shadow_node_array)
if (node.shadowMode & E_SHADOW_MODE::ESM_RECEIVE)
node.node->setMaterialTexture(TEXTURE_LAYER_SHADOW, nullptr);
}
void ShadowRenderer::initialize()
{
auto *gpu = m_driver->getGPUProgrammingServices();
// we need glsl
if (m_shadows_supported && gpu && m_driver->queryFeature(video::EVDF_ARB_GLSL)) {
createShaders();
} else {
m_shadows_supported = false;
warningstream << "Shadows: GLSL Shader not supported on this system."
<< std::endl;
return;
}
m_texture_format = m_shadow_map_texture_32bit
? video::ECOLOR_FORMAT::ECF_R32F
: video::ECOLOR_FORMAT::ECF_R16F;
m_texture_format_color = m_shadow_map_texture_32bit
? video::ECOLOR_FORMAT::ECF_G32R32F
: video::ECOLOR_FORMAT::ECF_G16R16F;
m_shadows_enabled &= m_shadows_supported;
}
size_t ShadowRenderer::addDirectionalLight()
{
m_light_list.emplace_back(m_shadow_map_texture_size,
v3f(0.f, 0.f, 0.f),
video::SColor(255, 255, 255, 255), m_shadow_map_max_distance);
return m_light_list.size() - 1;
}
DirectionalLight &ShadowRenderer::getDirectionalLight(u32 index)
{
return m_light_list[index];
}
size_t ShadowRenderer::getDirectionalLightCount() const
{
return m_light_list.size();
}
f32 ShadowRenderer::getMaxShadowFar() const
{
if (!m_light_list.empty()) {
float zMax = m_light_list[0].getFarValue();
return zMax;
}
return 0.0f;
}
void ShadowRenderer::setShadowIntensity(float shadow_intensity)
{
m_shadow_strength = pow(shadow_intensity, 1.0f / m_shadow_strength_gamma);
if (m_shadow_strength > 1E-2)
enable();
else
disable();
}
void ShadowRenderer::addNodeToShadowList(
scene::ISceneNode *node, E_SHADOW_MODE shadowMode)
{
if (!node)
return;
m_shadow_node_array.emplace_back(node, shadowMode);
if (shadowMode == ESM_RECEIVE || shadowMode == ESM_BOTH)
node->setMaterialTexture(TEXTURE_LAYER_SHADOW, shadowMapTextureFinal);
}
void ShadowRenderer::removeNodeFromShadowList(scene::ISceneNode *node)
{
if (!node)
return;
node->setMaterialTexture(TEXTURE_LAYER_SHADOW, nullptr);
for (auto it = m_shadow_node_array.begin(); it != m_shadow_node_array.end();) {
if (it->node == node) {
it = m_shadow_node_array.erase(it);
break;
} else {
++it;
}
}
}
void ShadowRenderer::updateSMTextures()
{
if (!m_shadows_enabled || m_smgr->getActiveCamera() == nullptr) {
return;
}
if (!shadowMapTextureDynamicObjects) {
shadowMapTextureDynamicObjects = getSMTexture(
std::string("shadow_dynamic_") + itos(m_shadow_map_texture_size),
m_texture_format, true);
assert(shadowMapTextureDynamicObjects != nullptr);
}
if (!shadowMapClientMap) {
shadowMapClientMap = getSMTexture(
std::string("shadow_clientmap_") + itos(m_shadow_map_texture_size),
m_shadow_map_colored ? m_texture_format_color : m_texture_format,
true);
assert(shadowMapClientMap != nullptr);
}
if (!shadowMapClientMapFuture && m_map_shadow_update_frames > 1) {
shadowMapClientMapFuture = getSMTexture(
std::string("shadow_clientmap_bb_") + itos(m_shadow_map_texture_size),
m_shadow_map_colored ? m_texture_format_color : m_texture_format,
true);
assert(shadowMapClientMapFuture != nullptr);
}
if (m_shadow_map_colored && !shadowMapTextureColors) {
shadowMapTextureColors = getSMTexture(
std::string("shadow_colored_") + itos(m_shadow_map_texture_size),
m_shadow_map_colored ? m_texture_format_color : m_texture_format,
true);
assert(shadowMapTextureColors != nullptr);
}
// The merge all shadowmaps texture
if (!shadowMapTextureFinal) {
video::ECOLOR_FORMAT frt;
if (m_shadow_map_texture_32bit) {
if (m_shadow_map_colored)
frt = video::ECOLOR_FORMAT::ECF_A32B32G32R32F;
else
frt = video::ECOLOR_FORMAT::ECF_R32F;
} else {
if (m_shadow_map_colored)
frt = video::ECOLOR_FORMAT::ECF_A16B16G16R16F;
else
frt = video::ECOLOR_FORMAT::ECF_R16F;
}
shadowMapTextureFinal = getSMTexture(
std::string("shadowmap_final_") + itos(m_shadow_map_texture_size),
frt, true);
assert(shadowMapTextureFinal != nullptr);
for (auto &node : m_shadow_node_array)
if (node.shadowMode == ESM_RECEIVE || node.shadowMode == ESM_BOTH)
node.node->setMaterialTexture(TEXTURE_LAYER_SHADOW, shadowMapTextureFinal);
}
if (!m_shadow_node_array.empty() && !m_light_list.empty()) {
bool reset_sm_texture = false;
// detect if SM should be regenerated
for (DirectionalLight &light : m_light_list) {
if (light.should_update_map_shadow || m_force_update_shadow_map) {
light.should_update_map_shadow = false;
m_current_frame = 0;
reset_sm_texture = true;
}
}
video::ITexture* shadowMapTargetTexture = shadowMapClientMapFuture;
if (shadowMapTargetTexture == nullptr)
shadowMapTargetTexture = shadowMapClientMap;
// Update SM incrementally:
for (DirectionalLight &light : m_light_list) {
// Static shader values.
for (auto cb : {m_shadow_depth_cb, m_shadow_depth_entity_cb, m_shadow_depth_trans_cb})
if (cb) {
cb->MapRes = (f32)m_shadow_map_texture_size;
cb->MaxFar = (f32)m_shadow_map_max_distance * BS;
cb->PerspectiveBiasXY = getPerspectiveBiasXY();
cb->PerspectiveBiasZ = getPerspectiveBiasZ();
cb->CameraPos = light.getFuturePlayerPos();
}
// set the Render Target
// right now we can only render in usual RTT, not
// Depth texture is available in irrlicth maybe we
// should put some gl* fn here
if (m_current_frame < m_map_shadow_update_frames || m_force_update_shadow_map) {
m_driver->setRenderTarget(shadowMapTargetTexture, reset_sm_texture, true,
video::SColor(255, 255, 255, 255));
renderShadowMap(shadowMapTargetTexture, light);
// Render transparent part in one pass.
// This is also handled in ClientMap.
if (m_current_frame == m_map_shadow_update_frames - 1 || m_force_update_shadow_map) {
if (m_shadow_map_colored) {
m_driver->setRenderTarget(0, false, false);
m_driver->setRenderTarget(shadowMapTextureColors,
true, false, video::SColor(255, 255, 255, 255));
}
renderShadowMap(shadowMapTextureColors, light,
scene::ESNRP_TRANSPARENT);
}
m_driver->setRenderTarget(0, false, false);
}
reset_sm_texture = false;
} // end for lights
// move to the next section
if (m_current_frame <= m_map_shadow_update_frames)
++m_current_frame;
// pass finished, swap textures and commit light changes
if (m_current_frame == m_map_shadow_update_frames || m_force_update_shadow_map) {
if (shadowMapClientMapFuture != nullptr)
std::swap(shadowMapClientMapFuture, shadowMapClientMap);
// Let all lights know that maps are updated
for (DirectionalLight &light : m_light_list)
light.commitFrustum();
}
m_force_update_shadow_map = false;
}
}
void ShadowRenderer::update(video::ITexture *outputTarget)
{
if (!m_shadows_enabled || m_smgr->getActiveCamera() == nullptr) {
return;
}
updateSMTextures();
if (shadowMapTextureFinal == nullptr) {
return;
}
if (!m_shadow_node_array.empty() && !m_light_list.empty()) {
for (DirectionalLight &light : m_light_list) {
// Static shader values for entities are set in updateSMTextures
// SM texture for entities is not updated incrementally and
// must by updated using current player position.
m_shadow_depth_entity_cb->CameraPos = light.getPlayerPos();
// render shadows for the n0n-map objects.
m_driver->setRenderTarget(shadowMapTextureDynamicObjects, true,
true, video::SColor(255, 255, 255, 255));
renderShadowObjects(shadowMapTextureDynamicObjects, light);
// clear the Render Target
m_driver->setRenderTarget(0, false, false);
// in order to avoid too many map shadow renders,
// we should make a second pass to mix clientmap shadows and
// entities shadows :(
m_screen_quad->getMaterial().setTexture(0, shadowMapClientMap);
// dynamic objs shadow texture.
if (m_shadow_map_colored)
m_screen_quad->getMaterial().setTexture(1, shadowMapTextureColors);
m_screen_quad->getMaterial().setTexture(2, shadowMapTextureDynamicObjects);
m_driver->setRenderTarget(shadowMapTextureFinal, false, false,
video::SColor(255, 255, 255, 255));
m_screen_quad->render(m_driver);
m_driver->setRenderTarget(0, false, false);
} // end for lights
}
}
void ShadowRenderer::drawDebug()
{
/* this code just shows shadows textures in screen and in ONLY for debugging*/
#if 0
// this is debug, ignore for now.
if (shadowMapTextureFinal)
m_driver->draw2DImage(shadowMapTextureFinal,
core::rect<s32>(0, 50, 128, 128 + 50),
core::rect<s32>({0, 0}, shadowMapTextureFinal->getSize()));
if (shadowMapClientMap)
m_driver->draw2DImage(shadowMapClientMap,
core::rect<s32>(0, 50 + 128, 128, 128 + 50 + 128),
core::rect<s32>({0, 0}, shadowMapTextureFinal->getSize()));
if (shadowMapTextureDynamicObjects)
m_driver->draw2DImage(shadowMapTextureDynamicObjects,
core::rect<s32>(0, 128 + 50 + 128, 128,
128 + 50 + 128 + 128),
core::rect<s32>({0, 0}, shadowMapTextureDynamicObjects->getSize()));
if (m_shadow_map_colored && shadowMapTextureColors) {
m_driver->draw2DImage(shadowMapTextureColors,
core::rect<s32>(128,128 + 50 + 128 + 128,
128 + 128, 128 + 50 + 128 + 128 + 128),
core::rect<s32>({0, 0}, shadowMapTextureColors->getSize()));
}
#endif
}
video::ITexture *ShadowRenderer::getSMTexture(const std::string &shadow_map_name,
video::ECOLOR_FORMAT texture_format, bool force_creation)
{
if (force_creation) {
return m_driver->addRenderTargetTexture(
core::dimension2du(m_shadow_map_texture_size,
m_shadow_map_texture_size),
shadow_map_name.c_str(), texture_format);
}
return m_driver->getTexture(shadow_map_name.c_str());
}
void ShadowRenderer::renderShadowMap(video::ITexture *target,
DirectionalLight &light, scene::E_SCENE_NODE_RENDER_PASS pass)
{
m_driver->setTransform(video::ETS_VIEW, light.getFutureViewMatrix());
m_driver->setTransform(video::ETS_PROJECTION, light.getFutureProjectionMatrix());
// Operate on the client map
for (const auto &shadow_node : m_shadow_node_array) {
if (strcmp(shadow_node.node->getName(), "ClientMap") != 0)
continue;
ClientMap *map_node = static_cast<ClientMap *>(shadow_node.node);
video::SMaterial material;
if (map_node->getMaterialCount() > 0) {
// we only want the first material, which is the one with the albedo info
material = map_node->getMaterial(0);
}
material.BackfaceCulling = false;
material.FrontfaceCulling = true;
if (m_shadow_map_colored && pass != scene::ESNRP_SOLID) {
material.MaterialType = (video::E_MATERIAL_TYPE) depth_shader_trans;
}
else {
material.MaterialType = (video::E_MATERIAL_TYPE) depth_shader;
material.BlendOperation = video::EBO_MIN;
}
m_driver->setTransform(video::ETS_WORLD,
map_node->getAbsoluteTransformation());
int frame = m_force_update_shadow_map ? 0 : m_current_frame;
int total_frames = m_force_update_shadow_map ? 1 : m_map_shadow_update_frames;
map_node->renderMapShadows(m_driver, material, pass, frame, total_frames);
break;
}
}
void ShadowRenderer::renderShadowObjects(
video::ITexture *target, DirectionalLight &light)
{
m_driver->setTransform(video::ETS_VIEW, light.getViewMatrix());
m_driver->setTransform(video::ETS_PROJECTION, light.getProjectionMatrix());
for (const auto &shadow_node : m_shadow_node_array) {
// we only take care of the shadow casters
if (shadow_node.shadowMode == ESM_RECEIVE ||
strcmp(shadow_node.node->getName(), "ClientMap") == 0)
continue;
// render other objects
u32 n_node_materials = shadow_node.node->getMaterialCount();
std::vector<s32> BufferMaterialList;
std::vector<std::pair<bool, bool>> BufferMaterialCullingList;
std::vector<video::E_BLEND_OPERATION> BufferBlendOperationList;
BufferMaterialList.reserve(n_node_materials);
BufferMaterialCullingList.reserve(n_node_materials);
BufferBlendOperationList.reserve(n_node_materials);
// backup materialtype for each material
// (aka shader)
// and replace it by our "depth" shader
for (u32 m = 0; m < n_node_materials; m++) {
auto &current_mat = shadow_node.node->getMaterial(m);
BufferMaterialList.push_back(current_mat.MaterialType);
current_mat.MaterialType =
(video::E_MATERIAL_TYPE)depth_shader_entities;
BufferMaterialCullingList.emplace_back(
(bool)current_mat.BackfaceCulling, (bool)current_mat.FrontfaceCulling);
BufferBlendOperationList.push_back(current_mat.BlendOperation);
current_mat.BackfaceCulling = true;
current_mat.FrontfaceCulling = false;
}
m_driver->setTransform(video::ETS_WORLD,
shadow_node.node->getAbsoluteTransformation());
shadow_node.node->render();
// restore the material.
for (u32 m = 0; m < n_node_materials; m++) {
auto &current_mat = shadow_node.node->getMaterial(m);
current_mat.MaterialType = (video::E_MATERIAL_TYPE) BufferMaterialList[m];
current_mat.BackfaceCulling = BufferMaterialCullingList[m].first;
current_mat.FrontfaceCulling = BufferMaterialCullingList[m].second;
current_mat.BlendOperation = BufferBlendOperationList[m];
}
} // end for caster shadow nodes
}
void ShadowRenderer::mixShadowsQuad()
{
}
/*
* @Liso's disclaimer ;) This function loads the Shadow Mapping Shaders.
* I used a custom loader because I couldn't figure out how to use the base
* Shaders system with custom IShaderConstantSetCallBack without messing up the
* code too much. If anyone knows how to integrate this with the standard MT
* shaders, please feel free to change it.
*/
void ShadowRenderer::createShaders()
{
video::IGPUProgrammingServices *gpu = m_driver->getGPUProgrammingServices();
if (depth_shader == -1) {
std::string depth_shader_vs = getShaderPath("shadow_shaders", "pass1_vertex.glsl");
if (depth_shader_vs.empty()) {
m_shadows_supported = false;
errorstream << "Error shadow mapping vs shader not found." << std::endl;
return;
}
std::string depth_shader_fs = getShaderPath("shadow_shaders", "pass1_fragment.glsl");
if (depth_shader_fs.empty()) {
m_shadows_supported = false;
errorstream << "Error shadow mapping fs shader not found." << std::endl;
return;
}
m_shadow_depth_cb = new ShadowDepthShaderCB();
depth_shader = gpu->addHighLevelShaderMaterial(
readShaderFile(depth_shader_vs).c_str(), "vertexMain",
video::EVST_VS_1_1,
readShaderFile(depth_shader_fs).c_str(), "pixelMain",
video::EPST_PS_1_2, m_shadow_depth_cb, video::EMT_ONETEXTURE_BLEND);
if (depth_shader == -1) {
// upsi, something went wrong loading shader.
delete m_shadow_depth_cb;
m_shadow_depth_cb = nullptr;
m_shadows_enabled = false;
m_shadows_supported = false;
errorstream << "Error compiling shadow mapping shader." << std::endl;
return;
}
// HACK, TODO: investigate this better
// Grab the material renderer once more so minetest doesn't crash
// on exit
m_driver->getMaterialRenderer(depth_shader)->grab();
}
// This creates a clone of depth_shader with base material set to EMT_SOLID,
// because entities won't render shadows with base material EMP_ONETEXTURE_BLEND
if (depth_shader_entities == -1) {
std::string depth_shader_vs = getShaderPath("shadow_shaders", "pass1_vertex.glsl");
if (depth_shader_vs.empty()) {
m_shadows_supported = false;
errorstream << "Error shadow mapping vs shader not found." << std::endl;
return;
}
std::string depth_shader_fs = getShaderPath("shadow_shaders", "pass1_fragment.glsl");
if (depth_shader_fs.empty()) {
m_shadows_supported = false;
errorstream << "Error shadow mapping fs shader not found." << std::endl;
return;
}
m_shadow_depth_entity_cb = new ShadowDepthShaderCB();
depth_shader_entities = gpu->addHighLevelShaderMaterial(
readShaderFile(depth_shader_vs).c_str(), "vertexMain",
video::EVST_VS_1_1,
readShaderFile(depth_shader_fs).c_str(), "pixelMain",
video::EPST_PS_1_2, m_shadow_depth_entity_cb);
if (depth_shader_entities == -1) {
// upsi, something went wrong loading shader.
delete m_shadow_depth_entity_cb;
m_shadow_depth_entity_cb = nullptr;
m_shadows_enabled = false;
m_shadows_supported = false;
errorstream << "Error compiling shadow mapping shader (dynamic)." << std::endl;
return;
}
// HACK, TODO: investigate this better
// Grab the material renderer once more so minetest doesn't crash
// on exit
m_driver->getMaterialRenderer(depth_shader_entities)->grab();
}
if (mixcsm_shader == -1) {
std::string depth_shader_vs = getShaderPath("shadow_shaders", "pass2_vertex.glsl");
if (depth_shader_vs.empty()) {
m_shadows_supported = false;
errorstream << "Error cascade shadow mapping fs shader not found." << std::endl;
return;
}
std::string depth_shader_fs = getShaderPath("shadow_shaders", "pass2_fragment.glsl");
if (depth_shader_fs.empty()) {
m_shadows_supported = false;
errorstream << "Error cascade shadow mapping fs shader not found." << std::endl;
return;
}
m_shadow_mix_cb = new shadowScreenQuadCB();
m_screen_quad = new shadowScreenQuad();
mixcsm_shader = gpu->addHighLevelShaderMaterial(
readShaderFile(depth_shader_vs).c_str(), "vertexMain",
video::EVST_VS_1_1,
readShaderFile(depth_shader_fs).c_str(), "pixelMain",
video::EPST_PS_1_2, m_shadow_mix_cb);
m_screen_quad->getMaterial().MaterialType =
(video::E_MATERIAL_TYPE)mixcsm_shader;
if (mixcsm_shader == -1) {
// upsi, something went wrong loading shader.
delete m_shadow_mix_cb;
delete m_screen_quad;
m_shadows_supported = false;
errorstream << "Error compiling cascade shadow mapping shader." << std::endl;
return;
}
// HACK, TODO: investigate this better
// Grab the material renderer once more so minetest doesn't crash
// on exit
m_driver->getMaterialRenderer(mixcsm_shader)->grab();
}
if (m_shadow_map_colored && depth_shader_trans == -1) {
std::string depth_shader_vs = getShaderPath("shadow_shaders", "pass1_trans_vertex.glsl");
if (depth_shader_vs.empty()) {
m_shadows_supported = false;
errorstream << "Error shadow mapping vs shader not found." << std::endl;
return;
}
std::string depth_shader_fs = getShaderPath("shadow_shaders", "pass1_trans_fragment.glsl");
if (depth_shader_fs.empty()) {
m_shadows_supported = false;
errorstream << "Error shadow mapping fs shader not found." << std::endl;
return;
}
m_shadow_depth_trans_cb = new ShadowDepthShaderCB();
depth_shader_trans = gpu->addHighLevelShaderMaterial(
readShaderFile(depth_shader_vs).c_str(), "vertexMain",
video::EVST_VS_1_1,
readShaderFile(depth_shader_fs).c_str(), "pixelMain",
video::EPST_PS_1_2, m_shadow_depth_trans_cb);
if (depth_shader_trans == -1) {
// upsi, something went wrong loading shader.
delete m_shadow_depth_trans_cb;
m_shadow_depth_trans_cb = nullptr;
m_shadow_map_colored = false;
m_shadows_supported = false;
errorstream << "Error compiling colored shadow mapping shader." << std::endl;
return;
}
// HACK, TODO: investigate this better
// Grab the material renderer once more so minetest doesn't crash
// on exit
m_driver->getMaterialRenderer(depth_shader_trans)->grab();
}
}
std::string ShadowRenderer::readShaderFile(const std::string &path)
{
std::string prefix;
if (m_shadow_map_colored)
prefix.append("#define COLORED_SHADOWS 1\n");
prefix.append("#line 0\n");
std::string content;
fs::ReadFile(path, content);
return prefix + content;
}

Some files were not shown because too many files have changed in this diff Show More