mirror of
https://github.com/MCLx86/xtreemtest.git
synced 2026-03-09 11:53:28 +01:00
Initial commit
This commit is contained in:
910
src/CMakeLists.txt
Normal file
910
src/CMakeLists.txt
Normal 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
132
src/activeobject.h
Normal 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
66
src/activeobjectmgr.h
Normal 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
142
src/ban.cpp
Normal 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
49
src/ban.h
Normal 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;
|
||||
};
|
||||
7
src/benchmark/CMakeLists.txt
Normal file
7
src/benchmark/CMakeLists.txt
Normal 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)
|
||||
32
src/benchmark/benchmark.cpp
Normal file
32
src/benchmark/benchmark.cpp
Normal 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
26
src/benchmark/benchmark.h
Normal 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
|
||||
71
src/benchmark/benchmark_serialize.cpp
Normal file
71
src/benchmark/benchmark_serialize.cpp
Normal 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()
|
||||
}
|
||||
22
src/benchmark/benchmark_setup.h
Normal file
22
src/benchmark/benchmark_setup.h
Normal 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
845
src/chat.cpp
Normal 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
309
src/chat.h
Normal 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
79
src/chat_interface.h
Normal 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
49
src/chatmessage.h
Normal 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
66
src/client/CMakeLists.txt
Normal 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
|
||||
)
|
||||
109
src/client/activeobjectmgr.cpp
Normal file
109
src/client/activeobjectmgr.cpp
Normal 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
|
||||
41
src/client/activeobjectmgr.h
Normal file
41
src/client/activeobjectmgr.h
Normal 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
708
src/client/camera.cpp
Normal 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
270
src/client/camera.h
Normal 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
2065
src/client/client.cpp
Normal file
File diff suppressed because it is too large
Load Diff
609
src/client/client.h
Normal file
609
src/client/client.h
Normal 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;
|
||||
};
|
||||
515
src/client/clientenvironment.cpp
Normal file
515
src/client/clientenvironment.cpp
Normal 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,
|
||||
¤t_intersection, ¤t_normal)) {
|
||||
objects.emplace_back((s16) obj->getId(), current_intersection, current_normal,
|
||||
(current_intersection - shootline_on_map.start).getLengthSQ());
|
||||
}
|
||||
}
|
||||
}
|
||||
156
src/client/clientenvironment.h
Normal file
156
src/client/clientenvironment.h
Normal 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
148
src/client/clientevent.h
Normal 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;
|
||||
};
|
||||
};
|
||||
662
src/client/clientlauncher.cpp
Normal file
662
src/client/clientlauncher.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
55
src/client/clientlauncher.h
Normal file
55
src/client/clientlauncher.h
Normal 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
964
src/client/clientmap.cpp
Normal 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 §or_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 §or_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
209
src/client/clientmap.h
Normal 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
792
src/client/clientmedia.cpp
Normal 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
250
src/client/clientmedia.h
Normal 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;
|
||||
|
||||
};
|
||||
66
src/client/clientobject.cpp
Normal file
66
src/client/clientobject.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
Minetest
|
||||
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "clientobject.h"
|
||||
#include "debug.h"
|
||||
#include "porting.h"
|
||||
|
||||
/*
|
||||
ClientActiveObject
|
||||
*/
|
||||
|
||||
ClientActiveObject::ClientActiveObject(u16 id, Client *client,
|
||||
ClientEnvironment *env):
|
||||
ActiveObject(id),
|
||||
m_client(client),
|
||||
m_env(env)
|
||||
{
|
||||
}
|
||||
|
||||
ClientActiveObject::~ClientActiveObject()
|
||||
{
|
||||
removeFromScene(true);
|
||||
}
|
||||
|
||||
ClientActiveObject* ClientActiveObject::create(ActiveObjectType type,
|
||||
Client *client, ClientEnvironment *env)
|
||||
{
|
||||
// Find factory function
|
||||
auto n = m_types.find(type);
|
||||
if (n == m_types.end()) {
|
||||
// If factory is not found, just return.
|
||||
warningstream << "ClientActiveObject: No factory for type="
|
||||
<< (int)type << std::endl;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Factory f = n->second;
|
||||
ClientActiveObject *object = (*f)(client, env);
|
||||
return object;
|
||||
}
|
||||
|
||||
void ClientActiveObject::registerType(u16 type, Factory f)
|
||||
{
|
||||
auto n = m_types.find(type);
|
||||
if (n != m_types.end())
|
||||
return;
|
||||
m_types[type] = f;
|
||||
}
|
||||
|
||||
|
||||
116
src/client/clientobject.h
Normal file
116
src/client/clientobject.h
Normal 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;
|
||||
};
|
||||
35
src/client/clientsimpleobject.h
Normal file
35
src/client/clientsimpleobject.h
Normal 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
385
src/client/clouds.cpp
Normal 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
143
src/client/clouds.h
Normal 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
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
282
src/client/content_cao.h
Normal 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();
|
||||
};
|
||||
77
src/client/content_cso.cpp
Normal file
77
src/client/content_cso.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
Minetest
|
||||
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "content_cso.h"
|
||||
#include <IBillboardSceneNode.h>
|
||||
#include "client/tile.h"
|
||||
#include "clientenvironment.h"
|
||||
#include "client.h"
|
||||
#include "map.h"
|
||||
|
||||
class SmokePuffCSO: public ClientSimpleObject
|
||||
{
|
||||
float m_age = 0.0f;
|
||||
scene::IBillboardSceneNode *m_spritenode = nullptr;
|
||||
public:
|
||||
SmokePuffCSO(scene::ISceneManager *smgr,
|
||||
ClientEnvironment *env, const v3f &pos, const v2f &size)
|
||||
{
|
||||
infostream<<"SmokePuffCSO: constructing"<<std::endl;
|
||||
m_spritenode = smgr->addBillboardSceneNode(
|
||||
NULL, v2f(1,1), pos, -1);
|
||||
m_spritenode->setMaterialTexture(0,
|
||||
env->getGameDef()->tsrc()->getTextureForMesh("smoke_puff.png"));
|
||||
m_spritenode->setMaterialFlag(video::EMF_LIGHTING, false);
|
||||
m_spritenode->setMaterialFlag(video::EMF_BILINEAR_FILTER, false);
|
||||
//m_spritenode->setMaterialType(video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF);
|
||||
m_spritenode->setMaterialType(video::EMT_TRANSPARENT_ALPHA_CHANNEL);
|
||||
m_spritenode->setMaterialFlag(video::EMF_FOG_ENABLE, true);
|
||||
m_spritenode->setColor(video::SColor(255,0,0,0));
|
||||
m_spritenode->setVisible(true);
|
||||
m_spritenode->setSize(size);
|
||||
/* Update brightness */
|
||||
u8 light;
|
||||
bool pos_ok;
|
||||
MapNode n = env->getMap().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
26
src/client/content_cso.h
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
Minetest
|
||||
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "irrlichttypes_extrabloated.h"
|
||||
#include "clientsimpleobject.h"
|
||||
|
||||
ClientSimpleObject* createSmokePuff(scene::ISceneManager *smgr,
|
||||
ClientEnvironment *env, v3f pos, v2f size);
|
||||
1635
src/client/content_mapblock.cpp
Normal file
1635
src/client/content_mapblock.cpp
Normal file
File diff suppressed because it is too large
Load Diff
180
src/client/content_mapblock.h
Normal file
180
src/client/content_mapblock.h
Normal 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);
|
||||
};
|
||||
86
src/client/event_manager.h
Normal file
86
src/client/event_manager.h
Normal 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
97
src/client/filecache.cpp
Normal 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
43
src/client/filecache.h
Normal 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
270
src/client/fontengine.cpp
Normal 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
170
src/client/fontengine.h
Normal 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
4376
src/client/game.cpp
Normal file
File diff suppressed because it is too large
Load Diff
53
src/client/game.h
Normal file
53
src/client/game.h
Normal 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
331
src/client/gameui.cpp
Normal 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
138
src/client/gameui.h
Normal 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;
|
||||
};
|
||||
240
src/client/guiscalingfilter.cpp
Normal file
240
src/client/guiscalingfilter.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
58
src/client/guiscalingfilter.h
Normal file
58
src/client/guiscalingfilter.h
Normal 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
1260
src/client/hud.cpp
Normal file
File diff suppressed because it is too large
Load Diff
173
src/client/hud.h
Normal file
173
src/client/hud.h
Normal 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
249
src/client/imagefilters.cpp
Normal 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
43
src/client/imagefilters.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
Copyright (C) 2015 Aaron Suen <warr1024@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "irrlichttypes_extrabloated.h"
|
||||
|
||||
/* Fill in RGB values for transparent pixels, to correct for odd colors
|
||||
* appearing at borders when blending. This is because many PNG optimizers
|
||||
* like to discard RGB values of transparent pixels, but when blending then
|
||||
* with non-transparent neighbors, their RGB values will 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
278
src/client/inputhandler.cpp
Normal 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
435
src/client/inputhandler.h
Normal 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;
|
||||
};
|
||||
330
src/client/joystick_controller.cpp
Normal file
330
src/client/joystick_controller.cpp
Normal 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;
|
||||
}
|
||||
172
src/client/joystick_controller.h
Normal file
172
src/client/joystick_controller.h
Normal 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
386
src/client/keycode.cpp
Normal file
@@ -0,0 +1,386 @@
|
||||
/*
|
||||
Minetest
|
||||
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "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
67
src/client/keycode.h
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
Minetest
|
||||
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "irrlichttypes.h"
|
||||
#include "Keycodes.h"
|
||||
#include <IEventReceiver.h>
|
||||
#include <string>
|
||||
|
||||
/* A key press, consisting of either an Irrlicht keycode
|
||||
or an actual char */
|
||||
|
||||
class KeyPress
|
||||
{
|
||||
public:
|
||||
KeyPress() = default;
|
||||
|
||||
KeyPress(const char *name);
|
||||
|
||||
KeyPress(const irr::SEvent::SKeyInput &in, bool prefer_character = false);
|
||||
|
||||
bool operator==(const KeyPress &o) const
|
||||
{
|
||||
return (Char > 0 && Char == o.Char) || (valid_kcode(Key) && Key == o.Key);
|
||||
}
|
||||
|
||||
const char *sym() const;
|
||||
const char *name() const;
|
||||
|
||||
protected:
|
||||
static bool valid_kcode(irr::EKEY_CODE k)
|
||||
{
|
||||
return k > 0 && k < irr::KEY_KEY_CODES_COUNT;
|
||||
}
|
||||
|
||||
irr::EKEY_CODE Key = irr::KEY_KEY_CODES_COUNT;
|
||||
wchar_t Char = L'\0';
|
||||
std::string m_name = "";
|
||||
};
|
||||
|
||||
extern const KeyPress EscapeKey;
|
||||
extern const KeyPress CancelKey;
|
||||
|
||||
// Key configuration getter
|
||||
KeyPress getKeySetting(const char *settingname);
|
||||
|
||||
// Clear fast lookup cache
|
||||
void clearKeyCache();
|
||||
|
||||
irr::EKEY_CODE keyname_to_keycode(const char *name);
|
||||
120
src/client/keys.h
Normal file
120
src/client/keys.h
Normal 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
1138
src/client/localplayer.cpp
Normal file
File diff suppressed because it is too large
Load Diff
215
src/client/localplayer.h
Normal file
215
src/client/localplayer.h
Normal 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
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
332
src/client/mapblock_mesh.h
Normal 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
499
src/client/mesh.cpp
Normal 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
135
src/client/mesh.h
Normal 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);
|
||||
321
src/client/mesh_generator_thread.cpp
Normal file
321
src/client/mesh_generator_thread.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
135
src/client/mesh_generator_thread.h
Normal file
135
src/client/mesh_generator_thread.h
Normal 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();
|
||||
};
|
||||
104
src/client/meshgen/collector.cpp
Normal file
104
src/client/meshgen/collector.cpp
Normal 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();
|
||||
}
|
||||
65
src/client/meshgen/collector.h
Normal file
65
src/client/meshgen/collector.h
Normal 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
762
src/client/minimap.cpp
Normal 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
176
src/client/minimap.h
Normal 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
927
src/client/particles.cpp
Normal 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
242
src/client/particles.h
Normal 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;
|
||||
};
|
||||
52
src/client/render/anaglyph.cpp
Normal file
52
src/client/render/anaglyph.cpp
Normal 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();
|
||||
}
|
||||
34
src/client/render/anaglyph.h
Normal file
34
src/client/render/anaglyph.h
Normal 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
129
src/client/render/core.cpp
Normal 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
80
src/client/render/core.h
Normal 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; };
|
||||
};
|
||||
52
src/client/render/factory.cpp
Normal file
52
src/client/render/factory.cpp
Normal 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);
|
||||
}
|
||||
27
src/client/render/factory.h
Normal file
27
src/client/render/factory.h
Normal 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);
|
||||
120
src/client/render/interlaced.cpp
Normal file
120
src/client/render/interlaced.cpp
Normal 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();
|
||||
}
|
||||
43
src/client/render/interlaced.h
Normal file
43
src/client/render/interlaced.h
Normal 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;
|
||||
};
|
||||
59
src/client/render/pageflip.cpp
Normal file
59
src/client/render/pageflip.cpp
Normal 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
|
||||
43
src/client/render/pageflip.h
Normal file
43
src/client/render/pageflip.h
Normal 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
|
||||
76
src/client/render/plain.cpp
Normal file
76
src/client/render/plain.cpp
Normal 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
38
src/client/render/plain.h
Normal 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;
|
||||
};
|
||||
74
src/client/render/sidebyside.cpp
Normal file
74
src/client/render/sidebyside.cpp
Normal 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();
|
||||
}
|
||||
43
src/client/render/sidebyside.h
Normal file
43
src/client/render/sidebyside.h
Normal 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;
|
||||
};
|
||||
60
src/client/render/stereo.cpp
Normal file
60
src/client/render/stereo.cpp
Normal 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();
|
||||
}
|
||||
38
src/client/render/stereo.h
Normal file
38
src/client/render/stereo.h
Normal 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);
|
||||
};
|
||||
652
src/client/renderingengine.cpp
Normal file
652
src/client/renderingengine.cpp
Normal 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__
|
||||
138
src/client/renderingengine.h
Normal file
138
src/client/renderingengine.h
Normal 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
829
src/client/shader.cpp
Normal 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
156
src/client/shader.h
Normal file
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
Minetest
|
||||
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
|
||||
Copyright (C) 2013 Kahrl <kahrl@gmx.net>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "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);
|
||||
191
src/client/shadows/dynamicshadows.cpp
Normal file
191
src/client/shadows/dynamicshadows.cpp
Normal 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;
|
||||
}
|
||||
120
src/client/shadows/dynamicshadows.h
Normal file
120
src/client/shadows/dynamicshadows.h
Normal 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};
|
||||
};
|
||||
709
src/client/shadows/dynamicshadowsrender.cpp
Normal file
709
src/client/shadows/dynamicshadowsrender.cpp
Normal 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 ¤t_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 ¤t_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
Reference in New Issue
Block a user