diff --git a/.gitignore b/.gitignore index b757c53f1..9f2cba3bf 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ CMakeLists.txt.user .cproject .project .settings/ +.directory diff --git a/.travis.yml b/.travis.yml index 5c69f49f0..caf2e3389 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ before_install: - sudo apt-get install -qq libqt4-dev libxaw7-dev libxrandr-dev libfreeimage-dev libpng-dev - sudo apt-get install -qq libopenal-dev libmpg123-dev libsndfile1-dev - sudo apt-get install -qq libavcodec-dev libavformat-dev libavdevice-dev libavutil-dev libswscale-dev libpostproc-dev - - sudo apt-get install -qq libbullet-dev libogre-static-dev libmygui-static-dev libsdl2-static-dev + - sudo apt-get install -qq libbullet-dev libogre-static-dev libmygui-static-dev libsdl2-static-dev libunshield-dev - sudo mkdir /usr/src/gtest/build - cd /usr/src/gtest/build - sudo cmake .. -DBUILD_SHARED_LIBS=1 diff --git a/CMakeLists.txt b/CMakeLists.txt index 575fecd0c..625239aa0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,7 +19,7 @@ include (OpenMWMacros) # Version set (OPENMW_VERSION_MAJOR 0) -set (OPENMW_VERSION_MINOR 25) +set (OPENMW_VERSION_MINOR 26) set (OPENMW_VERSION_RELEASE 0) set (OPENMW_VERSION "${OPENMW_VERSION_MAJOR}.${OPENMW_VERSION_MINOR}.${OPENMW_VERSION_RELEASE}") @@ -161,6 +161,20 @@ if (NOT FFMPEG_FOUND) message(WARNING "--------------------") endif (NOT FFMPEG_FOUND) +# TinyXML +option(USE_SYSTEM_TINYXML "Use system TinyXML library instead of internal." OFF) +if(USE_SYSTEM_TINYXML) + find_library(TINYXML_LIBRARIES tinyxml) + find_path(TINYXML_INCLUDE_DIR tinyxml.h) + message(STATUS "Found TinyXML: ${TINYXML_LIBRARIES} ${TINYXML_INCLUDE_DIR}") + add_definitions (-DTIXML_USE_STL) + if(TINYXML_LIBRARIES AND TINYXML_INCLUDE_DIR) + include_directories(${TINYXML_INCLUDE_DIR}) + message(STATUS "Using system TinyXML library.") + else() + message(FATAL_ERROR "Detection of system TinyXML incomplete.") + endif() +endif() # Platform specific if (WIN32) @@ -216,7 +230,6 @@ ENDIF(WIN32) ENDIF(OGRE_STATIC) include_directories("." ${OGRE_INCLUDE_DIR} ${OGRE_INCLUDE_DIR}/Ogre ${OGRE_INCLUDE_DIR}/OGRE ${OGRE_PLUGIN_INCLUDE_DIRS} - ${OGRE_Terrain_INCLUDE_DIR} ${SDL2_INCLUDE_DIR} ${Boost_INCLUDE_DIR} ${PLATFORM_INCLUDE_DIR} @@ -334,12 +347,18 @@ IF(NOT WIN32 AND NOT APPLE) IF (DPKG_PROGRAM) ## Debian specific SET(CMAKE_INSTALL_PREFIX "/usr") + SET(DATAROOTDIR "share" CACHE PATH "Sets the root of data directories to a non-default location") + SET(DATADIR "share/games/openmw" CACHE PATH "Sets the openmw data directories to a non-default location") + SET(ICONDIR "share/pixmaps" CACHE PATH "Set icon dir") SET(SYSCONFDIR "../etc/openmw" CACHE PATH "Set config dir") ELSE () ## Non debian specific - SET(SYSCONFDIR "/etc/openmw" CACHE PATH "Set config dir") SET(BINDIR "${CMAKE_INSTALL_PREFIX}/bin" CACHE PATH "Where to install binaries") - SET(LICDIR "${CMAKE_INSTALL_PREFIX}/share/licenses/openmw" CACHE PATH "Sets the openmw license directory to a non-default location.") + SET(DATAROOTDIR "${CMAKE_INSTALL_PREFIX}/share" CACHE PATH "Sets the root of data directories to a non-default location") + SET(DATADIR "${DATAROOTDIR}/games/openmw" CACHE PATH "Sets the openmw data directories to a non-default location") + SET(ICONDIR "${DATAROOTDIR}/pixmaps" CACHE PATH "Set icon dir") + SET(LICDIR "${DATAROOTDIR}/licenses/openmw" CACHE PATH "Sets the openmw license directory to a non-default location.") + SET(SYSCONFDIR "/etc/openmw" CACHE PATH "Set config dir") # Install binaries INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw" DESTINATION "${BINDIR}" ) @@ -367,11 +386,11 @@ IF(NOT WIN32 AND NOT APPLE) ENDIF (DPKG_PROGRAM) # Install icon and desktop file - INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.desktop" DESTINATION "share/applications/" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "openmw") - INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/launcher/images/openmw.png" DESTINATION "share/pixmaps/" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "openmw") + INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.desktop" DESTINATION "${DATAROOTDIR}/applications" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "openmw") + INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/launcher/images/openmw.png" DESTINATION "${ICONDIR}" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "openmw") IF(BUILD_OPENCS) - INSTALL(FILES "${OpenMW_BINARY_DIR}/opencs.desktop" DESTINATION "share/applications/" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "opencs") - INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/opencs/opencs.png" DESTINATION "share/pixmaps/" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "opencs") + INSTALL(FILES "${OpenMW_BINARY_DIR}/opencs.desktop" DESTINATION "${DATAROOTDIR}/applications" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "opencs") + INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/opencs/opencs.png" DESTINATION "${ICONDIR}" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "opencs") ENDIF(BUILD_OPENCS) # Install global configuration files @@ -383,8 +402,8 @@ IF(NOT WIN32 AND NOT APPLE) ENDIF(BUILD_OPENCS) # Install resources - INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION "share/games/openmw/" FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ COMPONENT "Resources") - INSTALL(DIRECTORY DESTINATION "share/games/openmw/data/" COMPONENT "Resources") + INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION "${DATADIR}" FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ COMPONENT "Resources") + INSTALL(DIRECTORY DESTINATION "${DATADIR}/data" COMPONENT "Resources") IF (DPKG_PROGRAM) ## Debian Specific @@ -515,6 +534,13 @@ if (BUILD_ESMTOOL) endif() if (BUILD_LAUNCHER) + if(NOT WIN32) + find_package(LIBUNSHIELD REQUIRED) + if(NOT LIBUNSHIELD_FOUND) + message(SEND_ERROR "Failed to find libunshield") + endif(NOT LIBUNSHIELD_FOUND) + endif(NOT WIN32) + add_subdirectory( apps/launcher ) endif() diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index bff26b63c..92cabffff 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -4,6 +4,7 @@ set(LAUNCHER main.cpp maindialog.cpp playpage.cpp + textslotmsgbox.cpp settings/gamesettings.cpp settings/graphicssettings.cpp @@ -14,12 +15,16 @@ set(LAUNCHER ${CMAKE_SOURCE_DIR}/files/launcher/launcher.rc ) +if(NOT WIN32) + LIST(APPEND LAUNCHER unshieldthread.cpp) +endif(NOT WIN32) set(LAUNCHER_HEADER datafilespage.hpp graphicspage.hpp maindialog.hpp playpage.hpp + textslotmsgbox.hpp settings/gamesettings.hpp settings/graphicssettings.hpp @@ -30,6 +35,10 @@ set(LAUNCHER_HEADER utils/textinputdialog.hpp ) +if(NOT WIN32) + LIST(APPEND LAUNCHER_HEADER unshieldthread.hpp) +endif(NOT WIN32) + # Headers that must be pre-processed set(LAUNCHER_HEADER_MOC @@ -37,11 +46,17 @@ set(LAUNCHER_HEADER_MOC graphicspage.hpp maindialog.hpp playpage.hpp + textslotmsgbox.hpp utils/checkablemessagebox.hpp utils/textinputdialog.hpp ) +if(NOT WIN32) + LIST(APPEND LAUNCHER_HEADER_MOC unshieldthread.hpp) +endif(NOT WIN32) + + set(LAUNCHER_UI ${CMAKE_SOURCE_DIR}/files/ui/datafilespage.ui ${CMAKE_SOURCE_DIR}/files/ui/graphicspage.ui @@ -64,8 +79,12 @@ QT4_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/launcher/launcher.qrc) QT4_WRAP_CPP(MOC_SRCS ${LAUNCHER_HEADER_MOC}) QT4_WRAP_UI(UI_HDRS ${LAUNCHER_UI}) + include(${QT_USE_FILE}) include_directories(${CMAKE_CURRENT_BINARY_DIR}) +if(NOT WIN32) + include_directories(${LIBUNSHIELD_INCLUDE}) +endif(NOT WIN32) # Main executable IF(OGRE_STATIC) @@ -94,6 +113,13 @@ target_link_libraries(omwlauncher ${QT_LIBRARIES} components ) +if(NOT WIN32) + target_link_libraries(omwlauncher + ${LIBUNSHIELD_LIBRARY} + ) +endif(NOT WIN32) + + if(DPKG_PROGRAM) INSTALL(TARGETS omwlauncher RUNTIME DESTINATION games COMPONENT omwlauncher) diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 4d5975e58..e71fc429f 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -3,6 +3,11 @@ #include #include #include + +#ifdef __APPLE__ +// We need to do this because of Qt: https://bugreports.qt-project.org/browse/QTBUG-22154 +#define MAC_OS_X_VERSION_MIN_REQUIRED __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ +#endif #include #include diff --git a/apps/launcher/main.cpp b/apps/launcher/main.cpp index dfe2d7413..f67f5edcf 100644 --- a/apps/launcher/main.cpp +++ b/apps/launcher/main.cpp @@ -3,6 +3,10 @@ #include #include +#ifdef __APPLE__ +// We need to do this because of Qt: https://bugreports.qt-project.org/browse/QTBUG-22154 +#define MAC_OS_X_VERSION_MIN_REQUIRED __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ +#endif #include #include "maindialog.hpp" diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index b75d09c51..032f70916 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -11,6 +11,12 @@ #include +#ifndef WIN32 + #include "unshieldthread.hpp" +#endif + +#include "textslotmsgbox.hpp" + #include "utils/checkablemessagebox.hpp" #include "playpage.hpp" @@ -128,11 +134,16 @@ bool MainDialog::showFirstRunDialog() QDir dir(path); dir.setPath(dir.canonicalPath()); // Resolve symlinks - if (!dir.cdUp()) - continue; // Cannot move from Data Files - if (dir.exists(QString("Morrowind.ini"))) iniPaths.append(dir.absoluteFilePath(QString("Morrowind.ini"))); + else + { + if (!dir.cdUp()) + continue; // Cannot move from Data Files + + if (dir.exists(QString("Morrowind.ini"))) + iniPaths.append(dir.absoluteFilePath(QString("Morrowind.ini"))); + } } // Ask the user where the Morrowind.ini is @@ -344,6 +355,78 @@ bool MainDialog::setupLauncherSettings() return true; } +#ifndef WIN32 +bool expansions(UnshieldThread& cd) +{ + if(cd.BloodmoonDone()) + { + cd.Done(); + return false; + } + + QMessageBox expansionsBox; + expansionsBox.setText(QObject::tr("
Would you like to install expansions now ? (make sure you have the disc)
\ + If you want to install both Bloodmoon and Tribunal, you have to install Tribunal first.
")); + + QAbstractButton* tribunalButton = NULL; + if(!cd.TribunalDone()) + tribunalButton = expansionsBox.addButton(QObject::tr("&Tribunal"), QMessageBox::ActionRole); + + QAbstractButton* bloodmoonButton = expansionsBox.addButton(QObject::tr("&Bloodmoon"), QMessageBox::ActionRole); + QAbstractButton* noneButton = expansionsBox.addButton(QObject::tr("&None"), QMessageBox::ActionRole); + + expansionsBox.exec(); + + if(expansionsBox.clickedButton() == noneButton) + { + cd.Done(); + return false; + } + else if(expansionsBox.clickedButton() == tribunalButton) + { + + TextSlotMsgBox cdbox; + cdbox.setStandardButtons(QMessageBox::Cancel); + + QObject::connect(&cd,SIGNAL(signalGUI(const QString&)), &cdbox, SLOT(setTextSlot(const QString&))); + QObject::connect(&cd,SIGNAL(close()), &cdbox, SLOT(reject())); + + cd.SetTribunalPath( + QFileDialog::getOpenFileName( + NULL, + QObject::tr("Select data1.hdr from Tribunal Installation CD (Tribunal/data1.hdr on GOTY CDs)"), + QDir::currentPath(), + QString(QObject::tr("Installshield hdr file (*.hdr)"))).toUtf8().constData()); + + cd.start(); + cdbox.exec(); + } + else if(expansionsBox.clickedButton() == bloodmoonButton) + { + + TextSlotMsgBox cdbox; + cdbox.setStandardButtons(QMessageBox::Cancel); + + QObject::connect(&cd,SIGNAL(signalGUI(const QString&)), &cdbox, SLOT(setTextSlot(const QString&))); + QObject::connect(&cd,SIGNAL(close()), &cdbox, SLOT(reject())); + + cd.SetBloodmoonPath( + QFileDialog::getOpenFileName( + NULL, + QObject::tr("Select data1.hdr from Bloodmoon Installation CD (Bloodmoon/data1.hdr on GOTY CDs)"), + QDir::currentPath(), + QString(QObject::tr("Installshield hdr file (*.hdr)"))).toUtf8().constData()); + + cd.start(); + cdbox.exec(); + } + + + + return true; +} +#endif // WIN32 + bool MainDialog::setupGameSettings() { QString userPath = QString::fromStdString(mCfgMgr.getUserPath().string()); @@ -401,9 +484,15 @@ bool MainDialog::setupGameSettings() Press \"Browse...\" to specify the location manually.
")); QAbstractButton *dirSelectButton = - msgBox.addButton(QObject::tr("B&rowse..."), QMessageBox::ActionRole); + msgBox.addButton(QObject::tr("Browse to &Install..."), QMessageBox::ActionRole); + + #ifndef WIN32 + QAbstractButton *cdSelectButton = + msgBox.addButton(QObject::tr("Browse to &CD..."), QMessageBox::ActionRole); + #endif + - msgBox.exec(); + msgBox.exec(); QString selectedFile; if (msgBox.clickedButton() == dirSelectButton) { @@ -413,6 +502,40 @@ bool MainDialog::setupGameSettings() QDir::currentPath(), QString(tr("Morrowind master file (*.esm)"))); } + #ifndef WIN32 + else if(msgBox.clickedButton() == cdSelectButton) { + UnshieldThread cd; + + { + TextSlotMsgBox cdbox; + cdbox.setStandardButtons(QMessageBox::Cancel); + + QObject::connect(&cd,SIGNAL(signalGUI(const QString&)), &cdbox, SLOT(setTextSlot(const QString&))); + QObject::connect(&cd,SIGNAL(close()), &cdbox, SLOT(reject())); + + cd.SetMorrowindPath( + QFileDialog::getOpenFileName( + NULL, + QObject::tr("Select data1.hdr from Morrowind Installation CD"), + QDir::currentPath(), + QString(tr("Installshield hdr file (*.hdr)"))).toUtf8().constData()); + + cd.SetOutputPath( + QFileDialog::getExistingDirectory( + NULL, + QObject::tr("Select where to extract files to"), + QDir::currentPath(), + QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks).toUtf8().constData()); + + cd.start(); + cdbox.exec(); + } + + while(expansions(cd)); + + selectedFile = QString::fromStdString(cd.GetMWEsmPath()); + } + #endif // WIN32 if (selectedFile.isEmpty()) return false; // Cancel was clicked; diff --git a/apps/launcher/textslotmsgbox.cpp b/apps/launcher/textslotmsgbox.cpp new file mode 100644 index 000000000..0607d1cc6 --- /dev/null +++ b/apps/launcher/textslotmsgbox.cpp @@ -0,0 +1,6 @@ +#include "textslotmsgbox.hpp" + +void TextSlotMsgBox::setTextSlot(const QString& string) +{ + setText(string); +} diff --git a/apps/launcher/textslotmsgbox.hpp b/apps/launcher/textslotmsgbox.hpp new file mode 100644 index 000000000..a29e2c354 --- /dev/null +++ b/apps/launcher/textslotmsgbox.hpp @@ -0,0 +1,13 @@ +#ifndef TEXT_SLOT_MSG_BOX +#define TEXT_SLOT_MSG_BOX + +#include + +class TextSlotMsgBox : public QMessageBox +{ +Q_OBJECT + public slots: + void setTextSlot(const QString& string); +}; + +#endif diff --git a/apps/launcher/unshieldthread.cpp b/apps/launcher/unshieldthread.cpp new file mode 100644 index 000000000..6ddad7a21 --- /dev/null +++ b/apps/launcher/unshieldthread.cpp @@ -0,0 +1,493 @@ +#include "unshieldthread.hpp" + +#include +#include + +namespace bfs = boost::filesystem; + +namespace +{ + static bool make_sure_directory_exists(bfs::path directory) + { + + if(!bfs::exists(directory)) + { + bfs::create_directories(directory); + } + + return bfs::exists(directory); + } + + void fill_path(bfs::path& path, const std::string& name) + { + size_t start = 0; + + size_t i; + for(i = 0; i < name.length(); i++) + { + switch(name[i]) + { + case '\\': + path /= name.substr(start, i-start); + start = i+1; + break; + } + } + + path /= name.substr(start, i-start); + } + + std::string get_setting(const std::string& category, const std::string& setting, const std::string& inx) + { + size_t start = inx.find(category); + start = inx.find(setting, start) + setting.length() + 3; + + size_t end = inx.find("!", start); + + return inx.substr(start, end-start); + } + + std::string read_to_string(const bfs::path& path) + { + std::ifstream strstream(path.c_str(), std::ios::in | std::ios::binary); + std::string str; + + strstream.seekg(0, std::ios::end); + str.resize(strstream.tellg()); + strstream.seekg(0, std::ios::beg); + strstream.read(&str[0], str.size()); + strstream.close(); + + return str; + } + + void add_setting(const std::string& category, const std::string& setting, const std::string& val, std::string& ini) + { + size_t loc; + loc = ini.find("[" + category + "]"); + + // If category is not found, create it + if(loc == std::string::npos) + { + loc = ini.size() + 2; + ini += ("\r\n[" + category + "]\r\n"); + } + + loc += category.length() +2 +2; + ini.insert(loc, setting + "=" + val + "\r\n"); + } + + void bloodmoon_fix_ini(std::string& ini, const bfs::path inxPath) + { + std::string inx = read_to_string(inxPath); + + // Remove this one setting (the only one actually changed by bloodmoon, as opposed to just adding new ones) + size_t start = ini.find("[Weather Blight]"); + start = ini.find("Ambient Loop Sound ID", start); + size_t end = ini.find("\r\n", start) +2; + ini.erase(start, end-start); + + std::string category; + std::string setting; + + category = "General"; + { + setting = "Werewolf FOV"; add_setting(category, setting, get_setting(category, setting, inx), ini); + } + category = "Moons"; + { + setting = "Script Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + } + category = "Weather"; + { + setting = "Snow Ripples"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Snow Ripple Radius"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Snow Ripples Per Flake"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Snow Ripple Scale"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Snow Ripple Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Snow Gravity Scale"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Snow High Kill"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Snow Low Kill"; add_setting(category, setting, get_setting(category, setting, inx), ini); + } + category = "Weather Blight"; + { + setting = "Ambient Loop Sound ID"; add_setting(category, setting, get_setting(category, setting, inx), ini); + } + category = "Weather Snow"; + { + setting = "Sky Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sky Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sky Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sky Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Fog Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Fog Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Fog Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Fog Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Ambient Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Ambient Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Ambient Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Ambient Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sun Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sun Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sun Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sun Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sun Disc Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Transition Delta"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Land Fog Day Depth"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Land Fog Night Depth"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Clouds Maximum Percent"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Wind Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Cloud Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Glare View"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Cloud Texture"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Ambient Loop Sound ID"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Snow Threshold"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Snow Diameter"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Snow Height Min"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Snow Height Max"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Snow Entrance Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Max Snowflakes"; add_setting(category, setting, get_setting(category, setting, inx), ini); + } + category = "Weather Blizzard"; + { + setting = "Sky Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sky Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sky Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sky Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Fog Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Fog Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Fog Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Fog Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Ambient Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Ambient Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Ambient Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Ambient Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sun Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sun Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sun Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sun Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sun Disc Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Transition Delta"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Land Fog Day Depth"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Land Fog Night Depth"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Clouds Maximum Percent"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Wind Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Cloud Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Glare View"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Cloud Texture"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Ambient Loop Sound ID"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Storm Threshold"; add_setting(category, setting, get_setting(category, setting, inx), ini); + } + } + + + void fix_ini(const bfs::path& output_dir, bfs::path cdPath, bool tribunal, bool bloodmoon) + { + bfs::path ini_path = output_dir; + ini_path /= "Morrowind.ini"; + + std::string ini = read_to_string(ini_path.string()); + + if(tribunal) + { + add_setting("Game Files", "GameFile1", "Tribunal.esm", ini); + add_setting("Archives", "Archive 0", "Tribunal.bsa", ini); + } + if(bloodmoon) + { + bloodmoon_fix_ini(ini, cdPath / "setup.inx"); + add_setting("Game Files", "GameFile2", "Bloodmoon.esm", ini); + add_setting("Archives", "Archive 1", "Bloodmoon.bsa", ini); + } + + std::ofstream inistream(ini_path.c_str()); + inistream << ini; + inistream.close(); + } + + void installToPath(const bfs::path& from, const bfs::path& to, bool copy = false) + { + make_sure_directory_exists(to); + + for ( bfs::directory_iterator end, dir(from); dir != end; ++dir ) + { + if(bfs::is_directory(dir->path())) + installToPath(dir->path(), to / dir->path().filename(), copy); + else + { + if(copy) + { + bfs::path dest = to / dir->path().filename(); + if(bfs::exists(dest)) + bfs::remove_all(dest); + bfs::copy_file(dir->path(), dest); + } + else + bfs::rename(dir->path(), to / dir->path().filename()); + } + } + } + + bfs::path findFile(const bfs::path& in, std::string filename, bool recursive = true) + { + if(recursive) + { + for ( bfs::recursive_directory_iterator end, dir(in); dir != end; ++dir ) + { + if(Misc::StringUtils::lowerCase(dir->path().filename().string()) == filename) + return dir->path(); + } + } + else + { + for ( bfs::directory_iterator end, dir(in); dir != end; ++dir ) + { + if(Misc::StringUtils::lowerCase(dir->path().filename().string()) == filename) + return dir->path(); + } + } + + return ""; + } + + bool contains(const bfs::path& in, std::string filename) + { + for(bfs::directory_iterator end, dir(in); dir != end; ++dir) + { + if(Misc::StringUtils::lowerCase(dir->path().filename().string()) == filename) + return true; + } + + return false; + } + + time_t getTime(const char* time) + { + struct tm tms; + memset(&tms, 0, sizeof(struct tm)); + strptime(time, "%d %B %Y", &tms); + return mktime(&tms); + } +} + +bool UnshieldThread::SetMorrowindPath(const std::string& path) +{ + mMorrowindPath = path; + return true; +} + +bool UnshieldThread::SetTribunalPath(const std::string& path) +{ + mTribunalPath = path; + return true; +} + +bool UnshieldThread::SetBloodmoonPath(const std::string& path) +{ + mBloodmoonPath = path; + return true; +} + +void UnshieldThread::SetOutputPath(const std::string& path) +{ + mOutputPath = path; +} + +bool UnshieldThread::extract_file(Unshield* unshield, bfs::path output_dir, const char* prefix, int index) +{ + bool success; + bfs::path dirname; + bfs::path filename; + int directory = unshield_file_directory(unshield, index); + + dirname = output_dir; + + if (prefix && prefix[0]) + dirname /= prefix; + + if (directory >= 0) + { + const char* tmp = unshield_directory_name(unshield, directory); + if (tmp && tmp[0]) + fill_path(dirname, tmp); + } + + make_sure_directory_exists(dirname); + + filename = dirname; + filename /= unshield_file_name(unshield, index); + + emit signalGUI(QString("Extracting: ") + QString(filename.c_str())); + + success = unshield_file_save(unshield, index, filename.c_str()); + + if (!success) + bfs::remove(filename); + + return success; +} + +void UnshieldThread::extract_cab(const bfs::path& cab, const bfs::path& output_dir, bool extract_ini) +{ + Unshield * unshield; + unshield = unshield_open(cab.c_str()); + + int i; + for (i = 0; i < unshield_file_group_count(unshield); i++) + { + UnshieldFileGroup* file_group = unshield_file_group_get(unshield, i); + + for (size_t j = file_group->first_file; j <= file_group->last_file; j++) + { + if (unshield_file_is_valid(unshield, j)) + extract_file(unshield, output_dir, file_group->name, j); + } + } + unshield_close(unshield); +} + + +bool UnshieldThread::extract() +{ + bfs::path outputDataFilesDir = mOutputPath; + outputDataFilesDir /= "Data Files"; + bfs::path extractPath = mOutputPath; + extractPath /= "extract-temp"; + + if(!mMorrowindDone && mMorrowindPath.string().length() > 0) + { + mMorrowindDone = true; + + bfs::path mwExtractPath = extractPath / "morrowind"; + extract_cab(mMorrowindPath, mwExtractPath, true); + + bfs::path dFilesDir = findFile(mwExtractPath, "morrowind.esm").parent_path(); + + installToPath(dFilesDir, outputDataFilesDir); + + // Videos are often kept uncompressed on the cd + bfs::path videosPath = findFile(mMorrowindPath.parent_path(), "video", false); + if(videosPath.string() != "") + { + emit signalGUI(QString("Installing Videos...")); + installToPath(videosPath, outputDataFilesDir / "Video", true); + } + + bfs::path cdDFiles = findFile(mMorrowindPath.parent_path(), "data files", false); + if(cdDFiles.string() != "") + { + emit signalGUI(QString("Installing Uncompressed Data files from CD...")); + installToPath(cdDFiles, outputDataFilesDir, true); + } + + + bfs::rename(findFile(mwExtractPath, "morrowind.ini"), outputDataFilesDir / "Morrowind.ini"); + + mTribunalDone = contains(outputDataFilesDir, "tribunal.esm"); + mBloodmoonDone = contains(outputDataFilesDir, "bloodmoon.esm"); + + } + + else if(!mTribunalDone && mTribunalPath.string().length() > 0) + { + mTribunalDone = true; + + bfs::path tbExtractPath = extractPath / "tribunal"; + extract_cab(mTribunalPath, tbExtractPath, true); + + bfs::path dFilesDir = findFile(tbExtractPath, "tribunal.esm").parent_path(); + + installToPath(dFilesDir, outputDataFilesDir); + + // Mt GOTY CD has Sounds in a seperate folder from the rest of the data files + bfs::path soundsPath = findFile(tbExtractPath, "sounds", false); + if(soundsPath.string() != "") + installToPath(soundsPath, outputDataFilesDir / "Sounds"); + + bfs::path cdDFiles = findFile(mTribunalPath.parent_path(), "data files", false); + if(cdDFiles.string() != "") + { + emit signalGUI(QString("Installing Uncompressed Data files from CD...")); + installToPath(cdDFiles, outputDataFilesDir, true); + } + + mBloodmoonDone = contains(outputDataFilesDir, "bloodmoon.esm"); + + fix_ini(outputDataFilesDir, bfs::path(mTribunalPath).parent_path(), mTribunalDone, mBloodmoonDone); + } + + else if(!mBloodmoonDone && mBloodmoonPath.string().length() > 0) + { + mBloodmoonDone = true; + + bfs::path bmExtractPath = extractPath / "bloodmoon"; + extract_cab(mBloodmoonPath, bmExtractPath, true); + + bfs::path dFilesDir = findFile(bmExtractPath, "bloodmoon.esm").parent_path(); + + installToPath(dFilesDir, outputDataFilesDir); + + // My GOTY CD contains a folder within cab files called Tribunal patch, + // which contains Tribunal.esm + bfs::path tbPatchPath = findFile(bmExtractPath, "tribunal.esm"); + if(tbPatchPath.string() != "") + bfs::rename(tbPatchPath, outputDataFilesDir / "Tribunal.esm"); + + bfs::path cdDFiles = findFile(mBloodmoonPath.parent_path(), "data files", false); + if(cdDFiles.string() != "") + { + emit signalGUI(QString("Installing Uncompressed Data files from CD...")); + installToPath(cdDFiles, outputDataFilesDir, true); + } + + fix_ini(outputDataFilesDir, bfs::path(mBloodmoonPath).parent_path(), false, mBloodmoonDone); + } + + + return true; +} + +void UnshieldThread::Done() +{ + // Get rid of unnecessary files + bfs::remove_all(mOutputPath / "extract-temp"); + + // Set modified time to release dates, to preserve load order + if(mMorrowindDone) + bfs::last_write_time(findFile(mOutputPath, "morrowind.esm"), getTime("1 May 2002")); + + if(mTribunalDone) + bfs::last_write_time(findFile(mOutputPath, "tribunal.esm"), getTime("6 November 2002")); + + if(mBloodmoonDone) + bfs::last_write_time(findFile(mOutputPath, "bloodmoon.esm"), getTime("3 June 2003")); +} + +std::string UnshieldThread::GetMWEsmPath() +{ + return findFile(mOutputPath / "Data Files", "morrowind.esm").string(); +} + +bool UnshieldThread::TribunalDone() +{ + return mTribunalDone; +} + +bool UnshieldThread::BloodmoonDone() +{ + return mBloodmoonDone; +} + +void UnshieldThread::run() +{ + extract(); + emit close(); +} + +UnshieldThread::UnshieldThread() +{ + unshield_set_log_level(0); + mMorrowindDone = false; + mTribunalDone = false; + mBloodmoonDone = false; +} diff --git a/apps/launcher/unshieldthread.hpp b/apps/launcher/unshieldthread.hpp new file mode 100644 index 000000000..655cb5b53 --- /dev/null +++ b/apps/launcher/unshieldthread.hpp @@ -0,0 +1,56 @@ +#ifndef UNSHIELD_THREAD_H +#define UNSHIELD_THREAD_H + +#include + +#include + +#include + +class UnshieldThread : public QThread +{ + Q_OBJECT + + public: + bool SetMorrowindPath(const std::string& path); + bool SetTribunalPath(const std::string& path); + bool SetBloodmoonPath(const std::string& path); + + void SetOutputPath(const std::string& path); + + bool extract(); + + bool TribunalDone(); + bool BloodmoonDone(); + + void Done(); + + std::string GetMWEsmPath(); + + UnshieldThread(); + + private: + + void extract_cab(const boost::filesystem::path& cab, const boost::filesystem::path& output_dir, bool extract_ini = false); + bool extract_file(Unshield* unshield, boost::filesystem::path output_dir, const char* prefix, int index); + + boost::filesystem::path mMorrowindPath; + boost::filesystem::path mTribunalPath; + boost::filesystem::path mBloodmoonPath; + + bool mMorrowindDone; + bool mTribunalDone; + bool mBloodmoonDone; + + boost::filesystem::path mOutputPath; + + + protected: + virtual void run(); + + signals: + void signalGUI(QString); + void close(); +}; + +#endif diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index f7b7daee4..aa6f6ba76 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -43,7 +43,8 @@ opencs_units_noqt (model/tools opencs_units (view/doc - viewmanager view operations operation subview startup filedialog + viewmanager view operations operation subview startup filedialog newgame filewidget + adjusterwidget ) @@ -107,10 +108,18 @@ opencs_units_noqt (model/settings settingsitem ) +opencs_units_noqt (model/filter + node unarynode narynode leafnode booleannode parser andnode ornode notnode textnode valuenode + ) + opencs_hdrs_noqt (model/filter filter ) +opencs_units (view/filter + filtercreator filterbox recordfilterbox editwidget + ) + set (OPENCS_US ) @@ -127,7 +136,7 @@ if(WIN32) set(QT_USE_QTMAIN TRUE) endif(WIN32) -find_package(Qt4 COMPONENTS QtCore QtGui QtXml QtXmlPatterns REQUIRED) +find_package(Qt4 COMPONENTS QtCore QtGui QtNetwork QtXml QtXmlPatterns REQUIRED) include(${QT_USE_FILE}) qt4_wrap_ui(OPENCS_UI_HDR ${OPENCS_UI}) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 082fa376c..9a6832ec0 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -1,23 +1,38 @@ #include "editor.hpp" -#include +#include +#include +#include +#include #include "model/doc/document.hpp" #include "model/world/data.hpp" + CS::Editor::Editor() : mViewManager (mDocumentManager) { - connect (&mViewManager, SIGNAL (newDocumentRequest ()), this, SLOT (createDocument ())); + mIpcServerName = "org.openmw.OpenCS"; + + setupDataFiles(); + + mNewGame.setLocalData (mLocal); + + connect (&mViewManager, SIGNAL (newGameRequest ()), this, SLOT (createGame ())); + connect (&mViewManager, SIGNAL (newAddonRequest ()), this, SLOT (createAddon ())); connect (&mViewManager, SIGNAL (loadDocumentRequest ()), this, SLOT (loadDocument ())); + connect (&mViewManager, SIGNAL (editSettingsRequest()), this, SLOT (showSettings ())); - connect (&mStartup, SIGNAL (createDocument()), this, SLOT (createDocument ())); + connect (&mStartup, SIGNAL (createGame()), this, SLOT (createGame ())); + connect (&mStartup, SIGNAL (createAddon()), this, SLOT (createAddon ())); connect (&mStartup, SIGNAL (loadDocument()), this, SLOT (loadDocument ())); + connect (&mStartup, SIGNAL (editConfig()), this, SLOT (showSettings ())); connect (&mFileDialog, SIGNAL(openFiles()), this, SLOT(openFiles())); connect (&mFileDialog, SIGNAL(createNewFile()), this, SLOT(createNewFile())); - setupDataFiles(); + connect (&mNewGame, SIGNAL (createRequest (const boost::filesystem::path&)), + this, SLOT (createNewGame (const boost::filesystem::path&))); } void CS::Editor::setupDataFiles() @@ -35,26 +50,39 @@ void CS::Editor::setupDataFiles() mCfgMgr.readConfiguration(variables, desc); - Files::PathContainer mDataDirs, mDataLocal; + Files::PathContainer dataDirs, dataLocal; if (!variables["data"].empty()) { - mDataDirs = Files::PathContainer(variables["data"].as()); + dataDirs = Files::PathContainer(variables["data"].as()); } std::string local = variables["data-local"].as(); if (!local.empty()) { - mDataLocal.push_back(Files::PathContainer::value_type(local)); + dataLocal.push_back(Files::PathContainer::value_type(local)); } - mCfgMgr.processPaths(mDataDirs); - mCfgMgr.processPaths(mDataLocal); + mCfgMgr.processPaths (dataDirs); + mCfgMgr.processPaths (dataLocal, true); + + if (!dataLocal.empty()) + mLocal = dataLocal[0]; + else + { + QMessageBox messageBox; + messageBox.setWindowTitle (tr ("No local data path available")); + messageBox.setIcon (QMessageBox::Critical); + messageBox.setStandardButtons (QMessageBox::Ok); + messageBox.setText(tr("
OpenCS is unable to access the local data directory. This may indicate a faulty configuration or a broken install.")); + messageBox.exec(); + + QApplication::exit (1); + return; + } // Set the charset for reading the esm/esp files QString encoding = QString::fromStdString(variables["encoding"].as()); mFileDialog.setEncoding(encoding); - Files::PathContainer dataDirs; - dataDirs.insert(dataDirs.end(), mDataDirs.begin(), mDataDirs.end()); - dataDirs.insert(dataDirs.end(), mDataLocal.begin(), mDataLocal.end()); + dataDirs.insert (dataDirs.end(), dataLocal.begin(), dataLocal.end()); for (Files::PathContainer::const_iterator iter = dataDirs.begin(); iter != dataDirs.end(); ++iter) { @@ -65,10 +93,20 @@ void CS::Editor::setupDataFiles() //load the settings into the userSettings instance. const QString settingFileName = "opencs.cfg"; CSMSettings::UserSettings::instance().loadSettings(settingFileName); - } -void CS::Editor::createDocument() +void CS::Editor::createGame() +{ + mStartup.hide(); + + if (mNewGame.isHidden()) + mNewGame.show(); + + mNewGame.raise(); + mNewGame.activateWindow(); +} + +void CS::Editor::createAddon() { mStartup.hide(); @@ -91,7 +129,9 @@ void CS::Editor::openFiles() files.push_back(path.toStdString()); } - CSMDoc::Document *document = mDocumentManager.addDocument(files, false); + /// \todo Get the save path from the file dialogue + + CSMDoc::Document *document = mDocumentManager.addDocument (files, *files.rbegin(), false); mViewManager.addView (document); mFileDialog.hide(); @@ -108,14 +148,70 @@ void CS::Editor::createNewFile() files.push_back(mFileDialog.fileName().toStdString()); - CSMDoc::Document *document = mDocumentManager.addDocument (files, true); + /// \todo Get the save path from the file dialogue. + + CSMDoc::Document *document = mDocumentManager.addDocument (files, *files.rbegin(), true); mViewManager.addView (document); mFileDialog.hide(); } +void CS::Editor::createNewGame (const boost::filesystem::path& file) +{ + std::vector files; + + files.push_back (file); + + CSMDoc::Document *document = mDocumentManager.addDocument (files, file, true); + + mViewManager.addView (document); + + mNewGame.hide(); +} + +void CS::Editor::showStartup() +{ + if(mStartup.isHidden()) + mStartup.show(); + mStartup.raise(); + mStartup.activateWindow(); +} + +void CS::Editor::showSettings() +{ + if (mSettings.isHidden()) + mSettings.show(); + + mSettings.raise(); + mSettings.activateWindow(); +} + +bool CS::Editor::makeIPCServer() +{ + mServer = new QLocalServer(this); + + if(mServer->listen(mIpcServerName)) + { + connect(mServer, SIGNAL(newConnection()), this, SLOT(showStartup())); + return true; + } + + mServer->close(); + return false; +} + +void CS::Editor::connectToIPCServer() +{ + mClientSocket = new QLocalSocket(this); + mClientSocket->connectToServer(mIpcServerName); + mClientSocket->close(); +} + int CS::Editor::run() { + if (mLocal.empty()) + return 1; + mStartup.show(); QApplication::setQuitOnLastWindowClosed (true); diff --git a/apps/opencs/editor.hpp b/apps/opencs/editor.hpp index c88efcfb9..248ebf2c5 100644 --- a/apps/opencs/editor.hpp +++ b/apps/opencs/editor.hpp @@ -2,15 +2,23 @@ #define CS_EDITOR_H #include +#include +#include +#include + #ifndef Q_MOC_RUN #include #endif + +#include "model/settings/usersettings.hpp" #include "model/doc/documentmanager.hpp" #include "view/doc/viewmanager.hpp" #include "view/doc/startup.hpp" #include "view/doc/filedialog.hpp" -#include "model/settings/usersettings.hpp" +#include "view/doc/newgame.hpp" + +#include "view/settings/usersettingsdialog.hpp" namespace CS { @@ -22,9 +30,13 @@ namespace CS CSMDoc::DocumentManager mDocumentManager; CSVDoc::ViewManager mViewManager; CSVDoc::StartupDialogue mStartup; + CSVDoc::NewGameDialogue mNewGame; + CSVSettings::UserSettingsDialog mSettings; CSVDoc::FileDialog mFileDialog; Files::ConfigurationManager mCfgMgr; + boost::filesystem::path mLocal; + void setupDataFiles(); // not implemented @@ -35,16 +47,31 @@ namespace CS Editor(); + bool makeIPCServer(); + void connectToIPCServer(); + int run(); ///< \return error status private slots: - void createDocument(); + void createGame(); + void createAddon(); void loadDocument(); void openFiles(); void createNewFile(); + void createNewGame (const boost::filesystem::path& file); + + void showStartup(); + + void showSettings(); + + private: + + QString mIpcServerName; + QLocalServer *mServer; + QLocalSocket *mClientSocket; }; } diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp index aa315804b..ef7123c20 100644 --- a/apps/opencs/main.cpp +++ b/apps/opencs/main.cpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include class Application : public QApplication @@ -39,5 +39,11 @@ int main(int argc, char *argv[]) CS::Editor editor; + if(!editor.makeIPCServer()) + { + editor.connectToIPCServer(); + return 0; + } + return editor.run(); } diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 30e9c21d1..d7138f671 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -2139,18 +2139,13 @@ void CSMDoc::Document::createBase() } } -CSMDoc::Document::Document (const std::vector& files, bool new_) -: mTools (mData) +CSMDoc::Document::Document (const std::vector& files, + const boost::filesystem::path& savePath, bool new_) +: mSavePath (savePath), mTools (mData) { if (files.empty()) throw std::runtime_error ("Empty content file sequence"); - /// \todo adjust last file name: - /// \li make sure it is located in the data-local directory (adjust path if necessary) - /// \li make sure the extension matches the new scheme (change it if necesarry) - - mName = files.back().filename().string(); - if (new_ && files.size()==1) createBase(); else @@ -2201,9 +2196,9 @@ int CSMDoc::Document::getState() const return state; } -const std::string& CSMDoc::Document::getName() const +const boost::filesystem::path& CSMDoc::Document::getSavePath() const { - return mName; + return mSavePath; } void CSMDoc::Document::save() diff --git a/apps/opencs/model/doc/document.hpp b/apps/opencs/model/doc/document.hpp index 1c6d9db2a..3532721ea 100644 --- a/apps/opencs/model/doc/document.hpp +++ b/apps/opencs/model/doc/document.hpp @@ -31,7 +31,7 @@ namespace CSMDoc private: - std::string mName; ///< \todo replace name with ESX list + boost::filesystem::path mSavePath; CSMWorld::Data mData; CSMTools::Tools mTools; @@ -64,15 +64,16 @@ namespace CSMDoc public: - Document (const std::vector& files, bool new_); + Document (const std::vector& files, + const boost::filesystem::path& savePath, bool new_); + ~Document(); QUndoStack& getUndoStack(); int getState() const; - const std::string& getName() const; - ///< \todo replace with ESX list + const boost::filesystem::path& getSavePath() const; void save(); diff --git a/apps/opencs/model/doc/documentmanager.cpp b/apps/opencs/model/doc/documentmanager.cpp index 740c0b582..b079109ea 100644 --- a/apps/opencs/model/doc/documentmanager.cpp +++ b/apps/opencs/model/doc/documentmanager.cpp @@ -14,10 +14,10 @@ CSMDoc::DocumentManager::~DocumentManager() delete *iter; } -CSMDoc::Document *CSMDoc::DocumentManager::addDocument (const std::vector& files, +CSMDoc::Document *CSMDoc::DocumentManager::addDocument (const std::vector& files, const boost::filesystem::path& savePath, bool new_) { - Document *document = new Document (files, new_); + Document *document = new Document (files, savePath, new_); mDocuments.push_back (document); diff --git a/apps/opencs/model/doc/documentmanager.hpp b/apps/opencs/model/doc/documentmanager.hpp index a307b76a5..dfded8d5c 100644 --- a/apps/opencs/model/doc/documentmanager.hpp +++ b/apps/opencs/model/doc/documentmanager.hpp @@ -23,7 +23,8 @@ namespace CSMDoc ~DocumentManager(); - Document *addDocument (const std::vector& files, bool new_); + Document *addDocument (const std::vector& files, + const boost::filesystem::path& savePath, bool new_); ///< The ownership of the returned document is not transferred to the caller. /// /// \param new_ Do not load the last content file in \a files and instead create in an diff --git a/apps/opencs/model/filter/andnode.cpp b/apps/opencs/model/filter/andnode.cpp new file mode 100644 index 000000000..dfaa56e78 --- /dev/null +++ b/apps/opencs/model/filter/andnode.cpp @@ -0,0 +1,20 @@ + +#include "andnode.hpp" + +#include + +CSMFilter::AndNode::AndNode (const std::vector >& nodes) +: NAryNode (nodes, "and") +{} + +bool CSMFilter::AndNode::test (const CSMWorld::IdTable& table, int row, + const std::map& columns) const +{ + int size = getSize(); + + for (int i=0; i >& nodes); + + virtual bool test (const CSMWorld::IdTable& table, int row, + const std::map& columns) const; + ///< \return Can the specified table row pass through to filter? + /// \param columns column ID to column index mapping + }; +} + +#endif diff --git a/apps/opencs/model/filter/booleannode.cpp b/apps/opencs/model/filter/booleannode.cpp new file mode 100644 index 000000000..267e06a64 --- /dev/null +++ b/apps/opencs/model/filter/booleannode.cpp @@ -0,0 +1,15 @@ + +#include "booleannode.hpp" + +CSMFilter::BooleanNode::BooleanNode (bool true_) : mTrue (true_) {} + +bool CSMFilter::BooleanNode::test (const CSMWorld::IdTable& table, int row, + const std::map& columns) const +{ + return mTrue; +} + +std::string CSMFilter::BooleanNode::toString (bool numericColumns) const +{ + return mTrue ? "true" : "false"; +} \ No newline at end of file diff --git a/apps/opencs/model/filter/booleannode.hpp b/apps/opencs/model/filter/booleannode.hpp new file mode 100644 index 000000000..d19219e35 --- /dev/null +++ b/apps/opencs/model/filter/booleannode.hpp @@ -0,0 +1,29 @@ +#ifndef CSM_FILTER_BOOLEANNODE_H +#define CSM_FILTER_BOOLEANNODE_H + +#include "leafnode.hpp" + +namespace CSMFilter +{ + class BooleanNode : public LeafNode + { + bool mTrue; + + public: + + BooleanNode (bool true_); + + virtual bool test (const CSMWorld::IdTable& table, int row, + const std::map& columns) const; + ///< \return Can the specified table row pass through to filter? + /// \param columns column ID to column index mapping + + virtual std::string toString (bool numericColumns) const; + ///< Return a string that represents this node. + /// + /// \param numericColumns Use numeric IDs instead of string to represent columns. + + }; +} + +#endif \ No newline at end of file diff --git a/apps/opencs/model/filter/filter.hpp b/apps/opencs/model/filter/filter.hpp index bddc9cc2d..62170ca80 100644 --- a/apps/opencs/model/filter/filter.hpp +++ b/apps/opencs/model/filter/filter.hpp @@ -11,15 +11,14 @@ namespace CSMFilter /// \brief Wrapper for Filter record struct Filter : public ESM::Filter { - enum scope + enum Scope { - Global = 0, - Local = 1, - Session = 2, - Content = 3 + Scope_Project = 0, // per project + Scope_Session = 1, // exists only for one editing session; not saved + Scope_Content = 2 // embedded in the edited content file }; - scope mScope; + Scope mScope; }; } diff --git a/apps/opencs/model/filter/leafnode.cpp b/apps/opencs/model/filter/leafnode.cpp new file mode 100644 index 000000000..055a1747c --- /dev/null +++ b/apps/opencs/model/filter/leafnode.cpp @@ -0,0 +1,8 @@ + +#include "leafnode.hpp" + +std::vector CSMFilter::LeafNode::getReferencedColumns() const +{ + return std::vector(); +} + diff --git a/apps/opencs/model/filter/leafnode.hpp b/apps/opencs/model/filter/leafnode.hpp new file mode 100644 index 000000000..2f3d91070 --- /dev/null +++ b/apps/opencs/model/filter/leafnode.hpp @@ -0,0 +1,20 @@ +#ifndef CSM_FILTER_LEAFNODE_H +#define CSM_FILTER_LEAFNODE_H + +#include + +#include "node.hpp" + +namespace CSMFilter +{ + class LeafNode : public Node + { + public: + + virtual std::vector getReferencedColumns() const; + ///< Return a list of the IDs of the columns referenced by this node. The column mapping + /// passed into test as columns must contain all columns listed here. + }; +} + +#endif diff --git a/apps/opencs/model/filter/narynode.cpp b/apps/opencs/model/filter/narynode.cpp new file mode 100644 index 000000000..98f706c87 --- /dev/null +++ b/apps/opencs/model/filter/narynode.cpp @@ -0,0 +1,60 @@ + +#include "narynode.hpp" + +#include + +CSMFilter::NAryNode::NAryNode (const std::vector >& nodes, + const std::string& name) +: mNodes (nodes), mName (name) +{} + +int CSMFilter::NAryNode::getSize() const +{ + return mNodes.size(); +} + +const CSMFilter::Node& CSMFilter::NAryNode::operator[] (int index) const +{ + return *mNodes.at (index); +} + +std::vector CSMFilter::NAryNode::getReferencedColumns() const +{ + std::vector columns; + + for (std::vector >::const_iterator iter (mNodes.begin()); + iter!=mNodes.end(); ++iter) + { + std::vector columns2 = (*iter)->getReferencedColumns(); + + columns.insert (columns.end(), columns2.begin(), columns2.end()); + } + + return columns; +} + +std::string CSMFilter::NAryNode::toString (bool numericColumns) const +{ + std::ostringstream stream; + + stream << mName << " ("; + + bool first = true; + int size = getSize(); + + for (int i=0; i +#include + +#include + +#include "node.hpp" + +namespace CSMFilter +{ + class NAryNode : public Node + { + std::vector > mNodes; + std::string mName; + + public: + + NAryNode (const std::vector >& nodes, const std::string& name); + + int getSize() const; + + const Node& operator[] (int index) const; + + virtual std::vector getReferencedColumns() const; + ///< Return a list of the IDs of the columns referenced by this node. The column mapping + /// passed into test as columns must contain all columns listed here. + + virtual std::string toString (bool numericColumns) const; + ///< Return a string that represents this node. + /// + /// \param numericColumns Use numeric IDs instead of string to represent columns. + }; +} + +#endif diff --git a/apps/opencs/model/filter/node.cpp b/apps/opencs/model/filter/node.cpp new file mode 100644 index 000000000..276861cdc --- /dev/null +++ b/apps/opencs/model/filter/node.cpp @@ -0,0 +1,6 @@ + +#include "node.hpp" + +CSMFilter::Node::Node() {} + +CSMFilter::Node::~Node() {} \ No newline at end of file diff --git a/apps/opencs/model/filter/node.hpp b/apps/opencs/model/filter/node.hpp new file mode 100644 index 000000000..ef18353a4 --- /dev/null +++ b/apps/opencs/model/filter/node.hpp @@ -0,0 +1,53 @@ +#ifndef CSM_FILTER_NODE_H +#define CSM_FILTER_NODE_H + +#include +#include +#include + +#include + +#include + +namespace CSMWorld +{ + class IdTable; +} + +namespace CSMFilter +{ + /// \brief Root class for the filter node hierarchy + /// + /// \note When the function documentation for this class mentions "this node", this should be + /// interpreted as "the node and all its children". + class Node + { + // not implemented + Node (const Node&); + Node& operator= (const Node&); + + public: + + Node(); + + virtual ~Node(); + + virtual bool test (const CSMWorld::IdTable& table, int row, + const std::map& columns) const = 0; + ///< \return Can the specified table row pass through to filter? + /// \param columns column ID to column index mapping + + virtual std::vector getReferencedColumns() const = 0; + ///< Return a list of the IDs of the columns referenced by this node. The column mapping + /// passed into test as columns must contain all columns listed here. + + virtual std::string toString (bool numericColumns) const = 0; + ///< Return a string that represents this node. + /// + /// \param numericColumns Use numeric IDs instead of string to represent columns. + }; +} + +Q_DECLARE_METATYPE (boost::shared_ptr) + +#endif diff --git a/apps/opencs/model/filter/notnode.cpp b/apps/opencs/model/filter/notnode.cpp new file mode 100644 index 000000000..1b22ea7a6 --- /dev/null +++ b/apps/opencs/model/filter/notnode.cpp @@ -0,0 +1,10 @@ + +#include "notnode.hpp" + +CSMFilter::NotNode::NotNode (boost::shared_ptr child) : UnaryNode (child, "not") {} + +bool CSMFilter::NotNode::test (const CSMWorld::IdTable& table, int row, + const std::map& columns) const +{ + return !getChild().test (table, row, columns); +} \ No newline at end of file diff --git a/apps/opencs/model/filter/notnode.hpp b/apps/opencs/model/filter/notnode.hpp new file mode 100644 index 000000000..b9e80b8c6 --- /dev/null +++ b/apps/opencs/model/filter/notnode.hpp @@ -0,0 +1,21 @@ +#ifndef CSM_FILTER_NOTNODE_H +#define CSM_FILTER_NOTNODE_H + +#include "unarynode.hpp" + +namespace CSMFilter +{ + class NotNode : public UnaryNode + { + public: + + NotNode (boost::shared_ptr child); + + virtual bool test (const CSMWorld::IdTable& table, int row, + const std::map& columns) const; + ///< \return Can the specified table row pass through to filter? + /// \param columns column ID to column index mapping + }; +} + +#endif diff --git a/apps/opencs/model/filter/ornode.cpp b/apps/opencs/model/filter/ornode.cpp new file mode 100644 index 000000000..4fc34e1d5 --- /dev/null +++ b/apps/opencs/model/filter/ornode.cpp @@ -0,0 +1,20 @@ + +#include "ornode.hpp" + +#include + +CSMFilter::OrNode::OrNode (const std::vector >& nodes) +: NAryNode (nodes, "or") +{} + +bool CSMFilter::OrNode::test (const CSMWorld::IdTable& table, int row, + const std::map& columns) const +{ + int size = getSize(); + + for (int i=0; i >& nodes); + + virtual bool test (const CSMWorld::IdTable& table, int row, + const std::map& columns) const; + ///< \return Can the specified table row pass through to filter? + /// \param columns column ID to column index mapping + }; +} + +#endif diff --git a/apps/opencs/model/filter/parser.cpp b/apps/opencs/model/filter/parser.cpp new file mode 100644 index 000000000..8f4fcb70c --- /dev/null +++ b/apps/opencs/model/filter/parser.cpp @@ -0,0 +1,617 @@ + +#include "parser.hpp" + +#include +#include +#include + +#include + +#include "../world/columns.hpp" +#include "../world/data.hpp" +#include "../world/idcollection.hpp" + +#include "booleannode.hpp" +#include "ornode.hpp" +#include "andnode.hpp" +#include "notnode.hpp" +#include "textnode.hpp" +#include "valuenode.hpp" + +namespace CSMFilter +{ + struct Token + { + enum Type + { + Type_EOS, + Type_None, + Type_String, + Type_Number, + Type_Open, + Type_Close, + Type_OpenSquare, + Type_CloseSquare, + Type_Comma, + Type_OneShot, + Type_Keyword_True, ///< \attention Keyword enums must be arranged continuously. + Type_Keyword_False, + Type_Keyword_And, + Type_Keyword_Or, + Type_Keyword_Not, + Type_Keyword_Text, + Type_Keyword_Value + }; + + Type mType; + std::string mString; + double mNumber; + + Token (Type type = Type_None); + + Token (Type type, const std::string& string); + ///< Non-string type that can also be interpreted as a string. + + Token (const std::string& string); + + Token (double number); + + operator bool() const; + + bool isString() const; + }; + + Token::Token (Type type) : mType (type) {} + + Token::Token (Type type, const std::string& string) : mType (type), mString (string) {} + + Token::Token (const std::string& string) : mType (Type_String), mString (string) {} + + Token::Token (double number) : mType (Type_Number), mNumber (number) {} + + bool Token::isString() const + { + return mType==Type_String || mType>=Type_Keyword_True; + } + + Token::operator bool() const + { + return mType!=Type_None; + } + + bool operator== (const Token& left, const Token& right) + { + if (left.mType!=right.mType) + return false; + + switch (left.mType) + { + case Token::Type_String: return left.mString==right.mString; + case Token::Type_Number: return left.mNumber==right.mNumber; + + default: return true; + } + } +} + +CSMFilter::Token CSMFilter::Parser::getStringToken() +{ + std::string string; + + int size = static_cast (mInput.size()); + + for (; mIndex1) + { + ++mIndex; + break; + } + }; + + if (!string.empty()) + { + if (string[0]=='"' && (string[string.size()-1]!='"' || string.size()<2) ) + { + error(); + return Token (Token::Type_None); + } + + if (string[0]!='"' && string[string.size()-1]=='"') + { + error(); + return Token (Token::Type_None); + } + + if (string[0]=='"') + return string.substr (1, string.size()-2); + } + + return checkKeywords (string); +} + +CSMFilter::Token CSMFilter::Parser::getNumberToken() +{ + std::string string; + + int size = static_cast (mInput.size()); + + bool hasDecimalPoint = false; + bool hasDigit = false; + + for (; mIndex> value; + + return value; +} + +CSMFilter::Token CSMFilter::Parser::checkKeywords (const Token& token) +{ + static const char *sKeywords[] = + { + "true", "false", + "and", "or", "not", + "string", "value", + 0 + }; + + std::string string = Misc::StringUtils::lowerCase (token.mString); + + for (int i=0; sKeywords[i]; ++i) + if (sKeywords[i]==string || (string.size()==1 && sKeywords[i][0]==string[0])) + return Token (static_cast (i+Token::Type_Keyword_True), token.mString); + + return token; +} + +CSMFilter::Token CSMFilter::Parser::getNextToken() +{ + int size = static_cast (mInput.size()); + + char c = 0; + + for (; mIndex=size) + return Token (Token::Type_EOS); + + switch (c) + { + case '(': ++mIndex; return Token (Token::Type_Open); + case ')': ++mIndex; return Token (Token::Type_Close); + case '[': ++mIndex; return Token (Token::Type_OpenSquare); + case ']': ++mIndex; return Token (Token::Type_CloseSquare); + case ',': ++mIndex; return Token (Token::Type_Comma); + case '!': ++mIndex; return Token (Token::Type_OneShot); + } + + if (c=='"' || c=='_' || std::isalpha (c) || c==':') + return getStringToken(); + + if (c=='-' || c=='.' || std::isdigit (c)) + return getNumberToken(); + + error(); + return Token (Token::Type_None); +} + +boost::shared_ptr CSMFilter::Parser::parseImp (bool allowEmpty, bool ignoreOneShot) +{ + if (Token token = getNextToken()) + { + if (token==Token (Token::Type_OneShot)) + token = getNextToken(); + + if (token) + switch (token.mType) + { + case Token::Type_Keyword_True: + + return boost::shared_ptr (new BooleanNode (true)); + + case Token::Type_Keyword_False: + + return boost::shared_ptr (new BooleanNode (false)); + + case Token::Type_Keyword_And: + case Token::Type_Keyword_Or: + + return parseNAry (token); + + case Token::Type_Keyword_Not: + { + boost::shared_ptr node = parseImp(); + + if (mError) + return boost::shared_ptr(); + + return boost::shared_ptr (new NotNode (node)); + } + + case Token::Type_Keyword_Text: + + return parseText(); + + case Token::Type_Keyword_Value: + + return parseValue(); + + case Token::Type_EOS: + + if (!allowEmpty) + error(); + + return boost::shared_ptr(); + + default: + + error(); + } + } + + return boost::shared_ptr(); +} + +boost::shared_ptr CSMFilter::Parser::parseNAry (const Token& keyword) +{ + std::vector > nodes; + + Token token = getNextToken(); + + if (token.mType!=Token::Type_Open) + { + error(); + return boost::shared_ptr(); + } + + for (;;) + { + boost::shared_ptr node = parseImp(); + + if (mError) + return boost::shared_ptr(); + + nodes.push_back (node); + + Token token = getNextToken(); + + if (!token || (token.mType!=Token::Type_Close && token.mType!=Token::Type_Comma)) + { + error(); + return boost::shared_ptr(); + } + + if (token.mType==Token::Type_Close) + break; + } + + if (nodes.empty()) + { + error(); + return boost::shared_ptr(); + } + + switch (keyword.mType) + { + case Token::Type_Keyword_And: return boost::shared_ptr (new AndNode (nodes)); + case Token::Type_Keyword_Or: return boost::shared_ptr (new OrNode (nodes)); + default: error(); return boost::shared_ptr(); + } +} + +boost::shared_ptr CSMFilter::Parser::parseText() +{ + Token token = getNextToken(); + + if (token.mType!=Token::Type_Open) + { + error(); + return boost::shared_ptr(); + } + + token = getNextToken(); + + if (!token) + return boost::shared_ptr(); + + // parse column ID + int columnId = -1; + + if (token.mType==Token::Type_Number) + { + if (static_cast (token.mNumber)==token.mNumber) + columnId = static_cast (token.mNumber); + } + else if (token.isString()) + { + columnId = CSMWorld::Columns::getId (token.mString); + } + + if (columnId<0) + { + error(); + return boost::shared_ptr(); + } + + token = getNextToken(); + + if (token.mType!=Token::Type_Comma) + { + error(); + return boost::shared_ptr(); + } + + // parse text pattern + token = getNextToken(); + + if (!token.isString()) + { + error(); + return boost::shared_ptr(); + } + + std::string text = token.mString; + + token = getNextToken(); + + if (token.mType!=Token::Type_Close) + { + error(); + return boost::shared_ptr(); + } + + return boost::shared_ptr (new TextNode (columnId, text)); +} + +boost::shared_ptr CSMFilter::Parser::parseValue() +{ + Token token = getNextToken(); + + if (token.mType!=Token::Type_Open) + { + error(); + return boost::shared_ptr(); + } + + token = getNextToken(); + + if (!token) + return boost::shared_ptr(); + + // parse column ID + int columnId = -1; + + if (token.mType==Token::Type_Number) + { + if (static_cast (token.mNumber)==token.mNumber) + columnId = static_cast (token.mNumber); + } + else if (token.isString()) + { + columnId = CSMWorld::Columns::getId (token.mString); + } + + if (columnId<0) + { + error(); + return boost::shared_ptr(); + } + + token = getNextToken(); + + if (token.mType!=Token::Type_Comma) + { + error(); + return boost::shared_ptr(); + } + + // parse value + double lower = 0; + double upper = 0; + ValueNode::Type lowerType = ValueNode::Type_Open; + ValueNode::Type upperType = ValueNode::Type_Open; + + token = getNextToken(); + + if (token.mType==Token::Type_Number) + { + // single value + lower = upper = token.mNumber; + lowerType = upperType = ValueNode::Type_Closed; + } + else + { + // interval + if (token.mType==Token::Type_OpenSquare) + lowerType = ValueNode::Type_Closed; + else if (token.mType!=Token::Type_CloseSquare && token.mType!=Token::Type_Open) + { + error(); + return boost::shared_ptr(); + } + + token = getNextToken(); + + if (token.mType==Token::Type_Number) + { + lower = token.mNumber; + + token = getNextToken(); + + if (token.mType!=Token::Type_Comma) + { + error(); + return boost::shared_ptr(); + } + } + else if (token.mType==Token::Type_Comma) + { + lowerType = ValueNode::Type_Infinite; + } + else + { + error(); + return boost::shared_ptr(); + } + + token = getNextToken(); + + if (token.mType==Token::Type_Number) + { + upper = token.mNumber; + + token = getNextToken(); + } + else + upperType = ValueNode::Type_Infinite; + + if (token.mType==Token::Type_CloseSquare) + { + if (upperType!=ValueNode::Type_Infinite) + upperType = ValueNode::Type_Closed; + } + else if (token.mType!=Token::Type_OpenSquare && token.mType!=Token::Type_Close) + { + error(); + return boost::shared_ptr(); + } + } + + token = getNextToken(); + + if (token.mType!=Token::Type_Close) + { + error(); + return boost::shared_ptr(); + } + + return boost::shared_ptr (new ValueNode (columnId, lowerType, upperType, lower, upper)); +} + +void CSMFilter::Parser::error() +{ + mError = true; +} + +CSMFilter::Parser::Parser (const CSMWorld::Data& data) +: mIndex (0), mError (false), mData (data) {} + +bool CSMFilter::Parser::parse (const std::string& filter, bool allowPredefined) +{ + // reset + mFilter.reset(); + mError = false; + mInput = filter; + mIndex = 0; + + Token token; + + if (allowPredefined) + token = getNextToken(); + + if (!allowPredefined || token==Token (Token::Type_OneShot)) + { + boost::shared_ptr node = parseImp (true, token!=Token (Token::Type_OneShot)); + + if (mError) + return false; + + if (getNextToken()!=Token (Token::Type_EOS)) + { + error(); + return false; + } + + if (node) + mFilter = node; + else + { + // Empty filter string equals to filter "true". + mFilter.reset (new BooleanNode (true)); + } + + return true; + } + // We do not use isString() here, because there could be a pre-defined filter with an ID that is + // equal a filter keyword. + else if (token.mType==Token::Type_String && allowPredefined) + { + if (getNextToken()!=Token (Token::Type_EOS)) + { + error(); + return false; + } + + int index = mData.getFilters().searchId (token.mString); + + if (index==-1) + { + error(); + return false; + } + + const CSMWorld::Record& record = mData.getFilters().getRecord (index); + + if (record.isDeleted()) + { + error(); + return false; + } + + return parse (record.get().mFilter, false); + } + else + { + error(); + return false; + } +} + +boost::shared_ptr CSMFilter::Parser::getFilter() const +{ + if (mError) + throw std::logic_error ("No filter available"); + + return mFilter; +} \ No newline at end of file diff --git a/apps/opencs/model/filter/parser.hpp b/apps/opencs/model/filter/parser.hpp new file mode 100644 index 000000000..5700102cf --- /dev/null +++ b/apps/opencs/model/filter/parser.hpp @@ -0,0 +1,59 @@ +#ifndef CSM_FILTER_PARSER_H +#define CSM_FILTER_PARSER_H + +#include + +#include "node.hpp" + +namespace CSMWorld +{ + class Data; +} + +namespace CSMFilter +{ + struct Token; + + class Parser + { + boost::shared_ptr mFilter; + std::string mInput; + int mIndex; + bool mError; + const CSMWorld::Data& mData; + + Token getStringToken(); + + Token getNumberToken(); + + Token getNextToken(); + + Token checkKeywords (const Token& token); + ///< Turn string token into keyword token, if possible. + + boost::shared_ptr parseImp (bool allowEmpty = false, bool ignoreOneShot = false); + ///< Will return a null-pointer, if there is nothing more to parse. + + boost::shared_ptr parseNAry (const Token& keyword); + + boost::shared_ptr parseText(); + + boost::shared_ptr parseValue(); + + void error(); + + public: + + Parser (const CSMWorld::Data& data); + + bool parse (const std::string& filter, bool allowPredefined = true); + ///< Discards any previous calls to parse + /// + /// \return Success? + + boost::shared_ptr getFilter() const; + ///< Throws an exception if the last call to parse did not return true. + }; +} + +#endif diff --git a/apps/opencs/model/filter/textnode.cpp b/apps/opencs/model/filter/textnode.cpp new file mode 100644 index 000000000..f3d98ce53 --- /dev/null +++ b/apps/opencs/model/filter/textnode.cpp @@ -0,0 +1,83 @@ + +#include "textnode.hpp" + +#include +#include + +#include + +#include "../world/columns.hpp" +#include "../world/idtable.hpp" + +CSMFilter::TextNode::TextNode (int columnId, const std::string& text) +: mColumnId (columnId), mText (text) +{} + +bool CSMFilter::TextNode::test (const CSMWorld::IdTable& table, int row, + const std::map& columns) const +{ + const std::map::const_iterator iter = columns.find (mColumnId); + + if (iter==columns.end()) + throw std::logic_error ("invalid column in text node test"); + + if (iter->second==-1) + return true; + + QModelIndex index = table.index (row, iter->second); + + QVariant data = table.data (index); + + QString string; + + if (data.type()==QVariant::String) + { + string = data.toString(); + } + else if (data.type()==QVariant::Int || data.type()==QVariant::UInt || + CSMWorld::Columns::hasEnums (static_cast (mColumnId))) + { + int value = data.toInt(); + + std::vector enums = + CSMWorld::Columns::getEnums (static_cast (mColumnId)); + + if (value>=0 && value (enums.size())) + string = QString::fromUtf8 (enums[value].c_str()); + } + else if (data.type()==QVariant::Bool) + { + string = data.toBool() ? "true" : " false"; + } + else + return false; + + /// \todo make pattern syntax configurable + QRegExp regExp (QString::fromUtf8 (mText.c_str()), Qt::CaseInsensitive); + + return regExp.exactMatch (string); +} + +std::vector CSMFilter::TextNode::getReferencedColumns() const +{ + return std::vector (1, mColumnId); +} + +std::string CSMFilter::TextNode::toString (bool numericColumns) const +{ + std::ostringstream stream; + + stream << "text ("; + + if (numericColumns) + stream << mColumnId; + else + stream + << "\"" + << CSMWorld::Columns::getName (static_cast (mColumnId)) + << "\""; + + stream << ", \"" << mText << "\")"; + + return stream.str(); +} \ No newline at end of file diff --git a/apps/opencs/model/filter/textnode.hpp b/apps/opencs/model/filter/textnode.hpp new file mode 100644 index 000000000..663fa7382 --- /dev/null +++ b/apps/opencs/model/filter/textnode.hpp @@ -0,0 +1,33 @@ +#ifndef CSM_FILTER_TEXTNODE_H +#define CSM_FILTER_TEXTNODE_H + +#include "leafnode.hpp" + +namespace CSMFilter +{ + class TextNode : public LeafNode + { + int mColumnId; + std::string mText; + + public: + + TextNode (int columnId, const std::string& text); + + virtual bool test (const CSMWorld::IdTable& table, int row, + const std::map& columns) const; + ///< \return Can the specified table row pass through to filter? + /// \param columns column ID to column index mapping + + virtual std::vector getReferencedColumns() const; + ///< Return a list of the IDs of the columns referenced by this node. The column mapping + /// passed into test as columns must contain all columns listed here. + + virtual std::string toString (bool numericColumns) const; + ///< Return a string that represents this node. + /// + /// \param numericColumns Use numeric IDs instead of string to represent columns. + }; +} + +#endif diff --git a/apps/opencs/model/filter/unarynode.cpp b/apps/opencs/model/filter/unarynode.cpp new file mode 100644 index 000000000..43a24b76a --- /dev/null +++ b/apps/opencs/model/filter/unarynode.cpp @@ -0,0 +1,26 @@ + +#include "unarynode.hpp" + +CSMFilter::UnaryNode::UnaryNode (boost::shared_ptr child, const std::string& name) +: mChild (child), mName (name) +{} + +const CSMFilter::Node& CSMFilter::UnaryNode::getChild() const +{ + return *mChild; +} + +CSMFilter::Node& CSMFilter::UnaryNode::getChild() +{ + return *mChild; +} + +std::vector CSMFilter::UnaryNode::getReferencedColumns() const +{ + return mChild->getReferencedColumns(); +} + +std::string CSMFilter::UnaryNode::toString (bool numericColumns) const +{ + return mName + " " + mChild->toString (numericColumns); +} \ No newline at end of file diff --git a/apps/opencs/model/filter/unarynode.hpp b/apps/opencs/model/filter/unarynode.hpp new file mode 100644 index 000000000..6bbc96092 --- /dev/null +++ b/apps/opencs/model/filter/unarynode.hpp @@ -0,0 +1,34 @@ +#ifndef CSM_FILTER_UNARYNODE_H +#define CSM_FILTER_UNARYNODE_H + +#include + +#include "node.hpp" + +namespace CSMFilter +{ + class UnaryNode : public Node + { + boost::shared_ptr mChild; + std::string mName; + + public: + + UnaryNode (boost::shared_ptr child, const std::string& name); + + const Node& getChild() const; + + Node& getChild(); + + virtual std::vector getReferencedColumns() const; + ///< Return a list of the IDs of the columns referenced by this node. The column mapping + /// passed into test as columns must contain all columns listed here. + + virtual std::string toString (bool numericColumns) const; + ///< Return a string that represents this node. + /// + /// \param numericColumns Use numeric IDs instead of string to represent columns. + }; +} + +#endif diff --git a/apps/opencs/model/filter/valuenode.cpp b/apps/opencs/model/filter/valuenode.cpp new file mode 100644 index 000000000..7eeb6beab --- /dev/null +++ b/apps/opencs/model/filter/valuenode.cpp @@ -0,0 +1,97 @@ + +#include "valuenode.hpp" + +#include +#include + +#include "../world/columns.hpp" +#include "../world/idtable.hpp" + +CSMFilter::ValueNode::ValueNode (int columnId, Type lowerType, Type upperType, + double lower, double upper) +: mColumnId (columnId), mLowerType (lowerType), mUpperType (upperType), mLower (lower), mUpper (upper){} + +bool CSMFilter::ValueNode::test (const CSMWorld::IdTable& table, int row, + const std::map& columns) const +{ + const std::map::const_iterator iter = columns.find (mColumnId); + + if (iter==columns.end()) + throw std::logic_error ("invalid column in test value test"); + + if (iter->second==-1) + return true; + + QModelIndex index = table.index (row, iter->second); + + QVariant data = table.data (index); + + if (data.type()!=QVariant::Double && data.type()!=QVariant::Bool && data.type()!=QVariant::Int && + data.type()!=QVariant::UInt) + return false; + + double value = data.toDouble(); + + switch (mLowerType) + { + case Type_Closed: if (valuemUpper) return false; break; + case Type_Open: if (value>=mUpper) return false; break; + case Type_Infinite: break; + } + + return true; +} + +std::vector CSMFilter::ValueNode::getReferencedColumns() const +{ + return std::vector (1, mColumnId); +} + +std::string CSMFilter::ValueNode::toString (bool numericColumns) const +{ + std::ostringstream stream; + + stream << "value ("; + + if (numericColumns) + stream << mColumnId; + else + stream + << "\"" + << CSMWorld::Columns::getName (static_cast (mColumnId)) + << "\""; + + stream << ", \""; + + if (mLower==mUpper && mLowerType!=Type_Infinite && mUpperType!=Type_Infinite) + stream << mLower; + else + { + switch (mLowerType) + { + case Type_Closed: stream << "[" << mLower; break; + case Type_Open: stream << "(" << mLower; break; + case Type_Infinite: stream << "("; break; + } + + stream << ", "; + + switch (mUpperType) + { + case Type_Closed: stream << mUpper << "]"; break; + case Type_Open: stream << mUpper << ")"; break; + case Type_Infinite: stream << ")"; break; + } + } + + stream << ")"; + + return stream.str(); +} \ No newline at end of file diff --git a/apps/opencs/model/filter/valuenode.hpp b/apps/opencs/model/filter/valuenode.hpp new file mode 100644 index 000000000..b1050709d --- /dev/null +++ b/apps/opencs/model/filter/valuenode.hpp @@ -0,0 +1,46 @@ +#ifndef CSM_FILTER_VALUENODE_H +#define CSM_FILTER_VALUENODE_H + +#include "leafnode.hpp" + +namespace CSMFilter +{ + class ValueNode : public LeafNode + { + public: + + enum Type + { + Type_Closed, Type_Open, Type_Infinite + }; + + private: + + int mColumnId; + std::string mText; + double mLower; + double mUpper; + Type mLowerType; + Type mUpperType; + + public: + + ValueNode (int columnId, Type lowerType, Type upperType, double lower, double upper); + + virtual bool test (const CSMWorld::IdTable& table, int row, + const std::map& columns) const; + ///< \return Can the specified table row pass through to filter? + /// \param columns column ID to column index mapping + + virtual std::vector getReferencedColumns() const; + ///< Return a list of the IDs of the columns referenced by this node. The column mapping + /// passed into test as columns must contain all columns listed here. + + virtual std::string toString (bool numericColumns) const; + ///< Return a string that represents this node. + /// + /// \param numericColumns Use numeric IDs instead of string to represent columns. + }; +} + +#endif diff --git a/apps/opencs/model/world/cell.cpp b/apps/opencs/model/world/cell.cpp index fc367d1cc..cd58fca1e 100644 --- a/apps/opencs/model/world/cell.cpp +++ b/apps/opencs/model/world/cell.cpp @@ -7,7 +7,7 @@ void CSMWorld::Cell::load (ESM::ESMReader &esm) { mName = mId; - ESM::Cell::load (esm, true); /// \todo set this to false, once the bug in ESM::Cell::load is fixed + ESM::Cell::load (esm, false); if (!(mData.mFlags & Interior)) { diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp index 526c07815..6cf31d0a4 100644 --- a/apps/opencs/model/world/collection.hpp +++ b/apps/opencs/model/world/collection.hpp @@ -236,14 +236,15 @@ namespace CSMWorld if (iter->second>=index+count) { iter->second -= count; + ++iter; } else { mIndex.erase (iter++); } } - - ++iter; + else + ++iter; } } diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index 8a1595a30..1a2bf9df1 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -257,7 +257,7 @@ namespace CSMWorld int mIndex; UseValueColumn (int index) - : Column (Columns::ColumnId_UseValue1 + index - 1, ColumnBase::Display_Float), + : Column (Columns::ColumnId_UseValue1 + index, ColumnBase::Display_Float), mIndex (index) {} @@ -339,7 +339,7 @@ namespace CSMWorld int mIndex; AttributesColumn (int index) - : Column (Columns::ColumnId_Attribute1 + index - 1, ColumnBase::Display_Attribute), + : Column (Columns::ColumnId_Attribute1 + index, ColumnBase::Display_Attribute), mIndex (index) {} @@ -372,7 +372,7 @@ namespace CSMWorld SkillsColumn (int index, bool typePrefix = false, bool major = false) : Column ((typePrefix ? ( major ? Columns::ColumnId_MajorSkill1 : Columns::ColumnId_MinorSkill1) : - Columns::ColumnId_Skill1) + index - 1, ColumnBase::Display_String), + Columns::ColumnId_Skill1) + index, ColumnBase::Display_String), mIndex (index), mMajor (major) {} @@ -1191,6 +1191,31 @@ namespace CSMWorld return true; } }; + + template + struct FilterColumn : public Column + { + FilterColumn() : Column (Columns::ColumnId_Filter, ColumnBase::Display_String) {} + + virtual QVariant get (const Record& record) const + { + return QString::fromUtf8 (record.get().mFilter.c_str()); + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + + record2.mFilter = data.toString().toUtf8().constData(); + + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + }; } #endif diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index 2198d1b0c..5616a4a48 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -3,6 +3,8 @@ #include +#include "universalid.hpp" + namespace CSMWorld { namespace Columns @@ -144,6 +146,7 @@ namespace CSMWorld { ColumnId_MaxThrust, "Max Thrust" }, { ColumnId_Magical, "Magical" }, { ColumnId_Silver, "Silver" }, + { ColumnId_Filter, "Filter" }, { ColumnId_UseValue1, "Use value 1" }, { ColumnId_UseValue2, "Use value 2" }, @@ -166,10 +169,11 @@ namespace CSMWorld { ColumnId_MinorSkill5, "Minor Skill 5" }, { ColumnId_Skill1, "Skill 1" }, - { ColumnId_Skill1, "Skill 2" }, - { ColumnId_Skill1, "Skill 3" }, - { ColumnId_Skill1, "Skill 4" }, - { ColumnId_Skill1, "Skill 5" }, + { ColumnId_Skill2, "Skill 2" }, + { ColumnId_Skill3, "Skill 3" }, + { ColumnId_Skill4, "Skill 4" }, + { ColumnId_Skill5, "Skill 5" }, + { ColumnId_Skill6, "Skill 6" }, { -1, 0 } // end marker }; @@ -194,4 +198,104 @@ int CSMWorld::Columns::getId (const std::string& name) return sNames[i].mId; return -1; +} + +namespace +{ + static const char *sSpecialisations[] = + { + "Combat", "Magic", "Stealth", 0 + }; + + static const char *sAttributes[] = + { + "Strength", "Intelligence", "Willpower", "Agility", "Speed", "Endurance", "Personality", + "Luck", 0 + }; + + static const char *sSpellTypes[] = + { + "Spell", "Ability", "Blight", "Disease", "Curse", "Power", 0 + }; + + static const char *sApparatusTypes[] = + { + "Mortar & Pestle", "Albemic", "Calcinator", "Retort", 0 + }; + + static const char *sArmorTypes[] = + { + "Helmet", "Cuirass", "Left Pauldron", "Right Pauldron", "Greaves", "Boots", "Left Gauntlet", + "Right Gauntlet", "Shield", "Left Bracer", "Right Bracer", 0 + }; + + static const char *sClothingTypes[] = + { + "Pants", "Shoes", "Shirt", "Belt", "Robe", "Right Glove", "Left Glove", "Skirt", "Ring", + "Amulet", 0 + }; + + static const char *sCreatureTypes[] = + { + "Creature", "Deadra", "Undead", "Humanoid", 0 + }; + + static const char *sWeaponTypes[] = + { + "Short Blade 1H", "Long Blade 1H", "Long Blade 2H", "Blunt 1H", "Blunt 2H Close", + "Blunt 2H Wide", "Spear 2H", "Axe 1H", "Axe 2H", "Bow", "Crossbow", "Thrown", "Arrow", + "Bolt", 0 + }; + + static const char *sModificationEnums[] = + { + "Base", "Modified", "Added", "Deleted", "Deleted", 0 + }; + + static const char *sVarTypeEnums[] = + { + "unknown", "none", "short", "integer", "long", "float", "string", 0 + }; + + const char **getEnumNames (CSMWorld::Columns::ColumnId column) + { + switch (column) + { + case CSMWorld::Columns::ColumnId_Specialisation: return sSpecialisations; + case CSMWorld::Columns::ColumnId_Attribute: return sAttributes; + case CSMWorld::Columns::ColumnId_SpellType: return sSpellTypes; + case CSMWorld::Columns::ColumnId_ApparatusType: return sApparatusTypes; + case CSMWorld::Columns::ColumnId_ArmorType: return sArmorTypes; + case CSMWorld::Columns::ColumnId_ClothingType: return sClothingTypes; + case CSMWorld::Columns::ColumnId_CreatureType: return sCreatureTypes; + case CSMWorld::Columns::ColumnId_WeaponType: return sWeaponTypes; + case CSMWorld::Columns::ColumnId_Modification: return sModificationEnums; + case CSMWorld::Columns::ColumnId_ValueType: return sVarTypeEnums; + + default: return 0; + } + } +} + +bool CSMWorld::Columns::hasEnums (ColumnId column) +{ + return getEnumNames (column)!=0 || column==ColumnId_RecordType; +} + +std::vector CSMWorld::Columns::getEnums (ColumnId column) +{ + std::vector enums; + + if (const char **table = getEnumNames (column)) + for (int i=0; table[i]; ++i) + enums.push_back (table[i]); + else if (column==ColumnId_RecordType) + { + enums.push_back (""); // none + + for (int i=UniversalId::Type_None+1; i (i)).getTypeName()); + } + + return enums; } \ No newline at end of file diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index adde80dc9..69b20583a 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -2,6 +2,7 @@ #define CSM_WOLRD_COLUMNS_H #include +#include namespace CSMWorld { @@ -138,6 +139,7 @@ namespace CSMWorld ColumnId_MaxThrust = 106, ColumnId_Magical = 107, ColumnId_Silver = 108, + ColumnId_Filter = 109, // Allocated to a separate value range, so we don't get a collision should we ever need // to extend the number of use values. @@ -171,13 +173,19 @@ namespace CSMWorld ColumnId_Skill2 = 0x50001, ColumnId_Skill3 = 0x50002, ColumnId_Skill4 = 0x50003, - ColumnId_Skill5 = 0x50004 + ColumnId_Skill5 = 0x50004, + ColumnId_Skill6 = 0x50005 }; std::string getName (ColumnId column); int getId (const std::string& name); ///< Will return -1 for an invalid name. + + bool hasEnums (ColumnId column); + + std::vector getEnums (ColumnId column); + ///< Returns an empty vector, if \æ column isn't an enum type column. } } diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index 43ecaca63..f6f421c6a 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -69,7 +69,9 @@ CSMWorld::RevertCommand::~RevertCommand() void CSMWorld::RevertCommand::redo() { - QModelIndex index = mModel.getModelIndex (mId, 1); + int column = mModel.findColumnIndex (Columns::ColumnId_Modification); + + QModelIndex index = mModel.getModelIndex (mId, column); RecordBase::State state = static_cast (mModel.data (index).toInt()); if (state==RecordBase::State_ModifiedOnly) @@ -102,7 +104,9 @@ CSMWorld::DeleteCommand::~DeleteCommand() void CSMWorld::DeleteCommand::redo() { - QModelIndex index = mModel.getModelIndex (mId, 1); + int column = mModel.findColumnIndex (Columns::ColumnId_Modification); + + QModelIndex index = mModel.getModelIndex (mId, column); RecordBase::State state = static_cast (mModel.data (index).toInt()); if (state==RecordBase::State_ModifiedOnly) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 9c5e13562..fbdbb4413 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -150,6 +150,8 @@ CSMWorld::Data::Data() : mRefs (mCells) mFilters.addColumn (new StringIdColumn); mFilters.addColumn (new RecordStateColumn); + mFilters.addColumn (new FilterColumn); + mFilters.addColumn (new DescriptionColumn); addModel (new IdTable (&mGlobals), UniversalId::Type_Globals, UniversalId::Type_Global); addModel (new IdTable (&mGmsts), UniversalId::Type_Gmsts, UniversalId::Type_Gmst); @@ -315,6 +317,16 @@ CSMWorld::RefCollection& CSMWorld::Data::getReferences() return mRefs; } +const CSMWorld::IdCollection& CSMWorld::Data::getFilters() const +{ + return mFilters; +} + +CSMWorld::IdCollection& CSMWorld::Data::getFilters() +{ + return mFilters; +} + QAbstractItemModel *CSMWorld::Data::getTableModel (const UniversalId& id) { std::map::iterator iter = mModelIndex.find (id.getType()); diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index aebdd6ecd..2f8a2117e 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -119,6 +119,10 @@ namespace CSMWorld RefCollection& getReferences(); + const IdCollection& getFilters() const; + + IdCollection& getFilters(); + QAbstractItemModel *getTableModel (const UniversalId& id); ///< If no table model is available for \a id, an exception is thrown. /// diff --git a/apps/opencs/model/world/idtableproxymodel.cpp b/apps/opencs/model/world/idtableproxymodel.cpp index e99e1575c..2b757adfe 100644 --- a/apps/opencs/model/world/idtableproxymodel.cpp +++ b/apps/opencs/model/world/idtableproxymodel.cpp @@ -1,8 +1,36 @@ #include "idtableproxymodel.hpp" +#include + #include "idtable.hpp" +void CSMWorld::IdTableProxyModel::updateColumnMap() +{ + mColumnMap.clear(); + + if (mFilter) + { + std::vector columns = mFilter->getReferencedColumns(); + + const IdTable& table = dynamic_cast (*sourceModel()); + + for (std::vector::const_iterator iter (columns.begin()); iter!=columns.end(); ++iter) + mColumnMap.insert (std::make_pair (*iter, + table.searchColumnIndex (static_cast (*iter)))); + } +} + +bool CSMWorld::IdTableProxyModel::filterAcceptsRow (int sourceRow, const QModelIndex& sourceParent) + const +{ + if (!mFilter) + return true; + + return mFilter->test ( + dynamic_cast (*sourceModel()), sourceRow, mColumnMap); +} + CSMWorld::IdTableProxyModel::IdTableProxyModel (QObject *parent) : QSortFilterProxyModel (parent) {} @@ -10,4 +38,11 @@ CSMWorld::IdTableProxyModel::IdTableProxyModel (QObject *parent) QModelIndex CSMWorld::IdTableProxyModel::getModelIndex (const std::string& id, int column) const { return mapFromSource (dynamic_cast (*sourceModel()).getModelIndex (id, column)); +} + +void CSMWorld::IdTableProxyModel::setFilter (const boost::shared_ptr& filter) +{ + mFilter = filter; + updateColumnMap(); + invalidateFilter(); } \ No newline at end of file diff --git a/apps/opencs/model/world/idtableproxymodel.hpp b/apps/opencs/model/world/idtableproxymodel.hpp index 200b99fe2..b63dccd5e 100644 --- a/apps/opencs/model/world/idtableproxymodel.hpp +++ b/apps/opencs/model/world/idtableproxymodel.hpp @@ -1,9 +1,15 @@ #ifndef CSM_WOLRD_IDTABLEPROXYMODEL_H #define CSM_WOLRD_IDTABLEPROXYMODEL_H +#include + +#include + +#include + #include -#include +#include "../filter/node.hpp" namespace CSMWorld { @@ -11,11 +17,22 @@ namespace CSMWorld { Q_OBJECT + boost::shared_ptr mFilter; + std::map mColumnMap; // column ID, column index in this model (or -1) + + private: + + void updateColumnMap(); + + bool filterAcceptsRow (int sourceRow, const QModelIndex& sourceParent) const; + public: IdTableProxyModel (QObject *parent = 0); virtual QModelIndex getModelIndex (const std::string& id, int column) const; + + void setFilter (const boost::shared_ptr& filter); }; } diff --git a/apps/opencs/model/world/ref.cpp b/apps/opencs/model/world/ref.cpp index e5e948928..af363bafb 100644 --- a/apps/opencs/model/world/ref.cpp +++ b/apps/opencs/model/world/ref.cpp @@ -8,8 +8,6 @@ void CSMWorld::CellRef::load (ESM::ESMReader &esm, Cell& cell, const std::string mId = id; mCellId = cell.mId; - cell.getNextRef (esm, *this); - if (!mDeleted) cell.addRef (mId); } \ No newline at end of file diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index 085817753..696aeefaa 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -6,10 +6,6 @@ #include "ref.hpp" #include "cell.hpp" -CSMWorld::RefCollection::RefCollection (Collection& cells) -: mCells (cells), mNextId (0) -{} - void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool base) { Record cell = mCells.getRecord (cellIndex); diff --git a/apps/opencs/model/world/refcollection.hpp b/apps/opencs/model/world/refcollection.hpp index 895315a17..b5f8c8064 100644 --- a/apps/opencs/model/world/refcollection.hpp +++ b/apps/opencs/model/world/refcollection.hpp @@ -16,8 +16,10 @@ namespace CSMWorld int mNextId; public: - - RefCollection (Collection& cells); + // MSVC needs the constructor for a class inheriting a template to be defined in header + RefCollection (Collection& cells) + : mCells (cells), mNextId (0) + {} void load (ESM::ESMReader& reader, int cellIndex, bool base); ///< Load a sequence of references. diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp index 42ebd1f80..d360fde8f 100644 --- a/apps/opencs/model/world/universalid.cpp +++ b/apps/opencs/model/world/universalid.cpp @@ -80,7 +80,7 @@ namespace { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Static, "Static", ":./static.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Weapon, "Weapon", ":./weapon.png" }, { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_Reference, "Reference", 0 }, - + { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_Filter, "Filter", 0 }, { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0, 0 } // end marker }; @@ -90,8 +90,6 @@ namespace { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0, 0 } // end marker }; - - static const unsigned int IDARG_SIZE = sizeof (sIdArg) / sizeof (TypeData); } CSMWorld::UniversalId::UniversalId (const std::string& universalId) @@ -151,6 +149,22 @@ CSMWorld::UniversalId::UniversalId (Type type) : mArgumentType (ArgumentType_Non return; } + for (int i=0; sIdArg[i].mName; ++i) + if (type==sIdArg[i].mType) + { + mArgumentType = ArgumentType_Id; + mClass = sIdArg[i].mClass; + return; + } + + for (int i=0; sIndexArg[i].mName; ++i) + if (type==sIndexArg[i].mType) + { + mArgumentType = ArgumentType_Index; + mClass = sIndexArg[i].mClass; + return; + } + throw std::logic_error ("invalid argument-less UniversalId type"); } @@ -293,25 +307,6 @@ std::vector CSMWorld::UniversalId::listReferenceabl return list; } -std::pair CSMWorld::UniversalId::getIdArgPair (unsigned int index) -{ - std::pair retPair; - - if ( index < IDARG_SIZE ) - { - retPair.first = sIdArg[index].mType; - retPair.second = sIdArg[index].mName; - } - - return retPair; -} - -unsigned int CSMWorld::UniversalId::getIdArgSize() -{ - return IDARG_SIZE; -} - - bool CSMWorld::operator== (const CSMWorld::UniversalId& left, const CSMWorld::UniversalId& right) { return left.isEqual (right); diff --git a/apps/opencs/model/world/universalid.hpp b/apps/opencs/model/world/universalid.hpp index 8042c3dfd..aa0cdacc0 100644 --- a/apps/opencs/model/world/universalid.hpp +++ b/apps/opencs/model/world/universalid.hpp @@ -34,7 +34,7 @@ namespace CSMWorld enum Type { - Type_None, + Type_None = 0, Type_Globals, Type_Global, Type_VerificationResults, @@ -89,6 +89,8 @@ namespace CSMWorld Type_Filters }; + enum { NumberOfTypes = Type_Filters+1 }; + private: Class mClass; @@ -102,7 +104,6 @@ namespace CSMWorld UniversalId (const std::string& universalId); UniversalId (Type type = Type_None); - ///< Using a type for a non-argument-less UniversalId will throw an exception. UniversalId (Type type, const std::string& id); ///< Using a type for a non-ID-argument UniversalId will throw an exception. @@ -134,9 +135,6 @@ namespace CSMWorld ///< Will return an empty string, if no icon is available. static std::vector listReferenceableTypes(); - - static std::pair getIdArgPair (unsigned int index); - static unsigned int getIdArgSize (); }; bool operator== (const UniversalId& left, const UniversalId& right); diff --git a/apps/opencs/view/doc/adjusterwidget.cpp b/apps/opencs/view/doc/adjusterwidget.cpp new file mode 100644 index 000000000..910819700 --- /dev/null +++ b/apps/opencs/view/doc/adjusterwidget.cpp @@ -0,0 +1,91 @@ + +#include "adjusterwidget.hpp" + +#include +#include + +#include + +#include +#include +#include + +CSVDoc::AdjusterWidget::AdjusterWidget (QWidget *parent) +: QWidget (parent), mValid (false) +{ + QHBoxLayout *layout = new QHBoxLayout (this); + + mIcon = new QLabel (this); + + layout->addWidget (mIcon, 0); + + mMessage = new QLabel (this); + mMessage->setWordWrap (true); + mMessage->setSizePolicy (QSizePolicy (QSizePolicy::Minimum, QSizePolicy::Minimum)); + + layout->addWidget (mMessage, 1); + + setName ("", false); + + setLayout (layout); +} + +void CSVDoc::AdjusterWidget::setLocalData (const boost::filesystem::path& localData) +{ + mLocalData = localData; +} + +boost::filesystem::path CSVDoc::AdjusterWidget::getPath() const +{ + if (!mValid) + throw std::logic_error ("invalid content file path"); + + return mResultPath; +} + +void CSVDoc::AdjusterWidget::setName (const QString& name, bool addon) +{ + QString message; + + if (name.isEmpty()) + { + mValid = false; + message = "No name."; + } + else + { + boost::filesystem::path path (name.toUtf8().data()); + + path.replace_extension (addon ? ".omwaddon" : ".omwgame"); + + if (path.parent_path().string()==mLocalData.string()) + { + // path already points to the local data directory + message = QString::fromUtf8 (("Will be saved as: " + path.native()).c_str()); + mResultPath = path; + mValid = true; + } + else + { + // path points somewhere else or is a leaf name. + path = mLocalData / path.filename(); + + message = QString::fromUtf8 (("Will be saved as: " + path.native()).c_str()); + mResultPath = path; + mValid = true; + + if (boost::filesystem::exists (path)) + { + /// \todo add an user setting to make this an error. + message += "

But a file with the same name already exists. If you continue, it will be overwritten."; + } + } + } + + mMessage->setText (message); + mIcon->setPixmap (style()->standardIcon ( + mValid ? QStyle::SP_MessageBoxInformation : QStyle::SP_MessageBoxWarning). + pixmap (QSize (16, 16))); + + emit stateChanged (mValid); +} \ No newline at end of file diff --git a/apps/opencs/view/doc/adjusterwidget.hpp b/apps/opencs/view/doc/adjusterwidget.hpp new file mode 100644 index 000000000..f578dc4ae --- /dev/null +++ b/apps/opencs/view/doc/adjusterwidget.hpp @@ -0,0 +1,41 @@ +#ifndef CSV_DOC_ADJUSTERWIDGET_H +#define CSV_DOC_ADJUSTERWIDGET_H + +#include + +#include + +class QLabel; + +namespace CSVDoc +{ + class AdjusterWidget : public QWidget + { + Q_OBJECT + + boost::filesystem::path mLocalData; + QLabel *mMessage; + QLabel *mIcon; + bool mValid; + boost::filesystem::path mResultPath; + + public: + + AdjusterWidget (QWidget *parent = 0); + + void setLocalData (const boost::filesystem::path& localData); + + boost::filesystem::path getPath() const; + ///< This function must not be called if there is no valid path. + + public slots: + + void setName (const QString& name, bool addon); + + signals: + + void stateChanged (bool valid); + }; +} + +#endif \ No newline at end of file diff --git a/apps/opencs/view/doc/filewidget.cpp b/apps/opencs/view/doc/filewidget.cpp new file mode 100644 index 000000000..c8f33e92d --- /dev/null +++ b/apps/opencs/view/doc/filewidget.cpp @@ -0,0 +1,53 @@ + +#include "filewidget.hpp" + +#include +#include +#include +#include +#include + +QString CSVDoc::FileWidget::getExtension() const +{ + return mAddon ? ".omwaddon" : ".omwgame"; +} + +CSVDoc::FileWidget::FileWidget (QWidget *parent) : QWidget (parent), mAddon (false) +{ + QHBoxLayout *layout = new QHBoxLayout (this); + + mInput = new QLineEdit (this); + mInput->setValidator (new QRegExpValidator(QRegExp("^[a-zA-Z0-9\\s]*$"))); + + layout->addWidget (mInput, 1); + + mType = new QLabel (this); + + layout ->addWidget (mType); + + connect (mInput, SIGNAL (textChanged (const QString&)), this, SLOT (textChanged (const QString&))); + + setLayout (layout); +} + +void CSVDoc::FileWidget::setType (bool addon) +{ + mAddon = addon; + + mType->setText (getExtension()); +} + +QString CSVDoc::FileWidget::getName() const +{ + QString text = mInput->text(); + + if (text.isEmpty()) + return ""; + + return text + getExtension(); +} + +void CSVDoc::FileWidget::textChanged (const QString& text) +{ + emit nameChanged (getName(), mAddon); +} \ No newline at end of file diff --git a/apps/opencs/view/doc/filewidget.hpp b/apps/opencs/view/doc/filewidget.hpp new file mode 100644 index 000000000..c51c29632 --- /dev/null +++ b/apps/opencs/view/doc/filewidget.hpp @@ -0,0 +1,40 @@ +#ifndef CSV_DOC_FILEWIDGET_H +#define CSV_DOC_FILEWIDGET_H + +#include + +class QLabel; +class QString; +class QLineEdit; + +namespace CSVDoc +{ + class FileWidget : public QWidget + { + Q_OBJECT + + bool mAddon; + QLineEdit *mInput; + QLabel *mType; + + QString getExtension() const; + + public: + + FileWidget (QWidget *parent = 0); + + void setType (bool addon); + + QString getName() const; + + private slots: + + void textChanged (const QString& text); + + signals: + + void nameChanged (const QString& file, bool addon); + }; +} + +#endif diff --git a/apps/opencs/view/doc/newgame.cpp b/apps/opencs/view/doc/newgame.cpp new file mode 100644 index 000000000..98681c499 --- /dev/null +++ b/apps/opencs/view/doc/newgame.cpp @@ -0,0 +1,68 @@ + +#include "newgame.hpp" + +#include +#include +#include +#include +#include + +#include "filewidget.hpp" +#include "adjusterwidget.hpp" + +CSVDoc::NewGameDialogue::NewGameDialogue() +{ + setWindowTitle ("Create New Game"); + + QVBoxLayout *layout = new QVBoxLayout (this); + + mFileWidget = new FileWidget (this); + mFileWidget->setType (false); + + layout->addWidget (mFileWidget, 1); + + mAdjusterWidget = new AdjusterWidget (this); + + layout->addWidget (mAdjusterWidget, 1); + + QDialogButtonBox *buttons = new QDialogButtonBox (this); + + mCreate = new QPushButton ("Create", this); + mCreate->setDefault (true); + mCreate->setEnabled (false); + + buttons->addButton (mCreate, QDialogButtonBox::AcceptRole); + + QPushButton *cancel = new QPushButton ("Cancel", this); + + buttons->addButton (cancel, QDialogButtonBox::RejectRole); + + layout->addWidget (buttons); + + setLayout (layout); + + connect (mAdjusterWidget, SIGNAL (stateChanged (bool)), this, SLOT (stateChanged (bool))); + connect (mCreate, SIGNAL (clicked()), this, SLOT (create())); + connect (cancel, SIGNAL (clicked()), this, SLOT (reject())); + connect (mFileWidget, SIGNAL (nameChanged (const QString&, bool)), + mAdjusterWidget, SLOT (setName (const QString&, bool))); + + QRect scr = QApplication::desktop()->screenGeometry(); + QRect rect = geometry(); + move (scr.center().x() - rect.center().x(), scr.center().y() - rect.center().y()); +} + +void CSVDoc::NewGameDialogue::setLocalData (const boost::filesystem::path& localData) +{ + mAdjusterWidget->setLocalData (localData); +} + +void CSVDoc::NewGameDialogue::stateChanged (bool valid) +{ + mCreate->setEnabled (valid); +} + +void CSVDoc::NewGameDialogue::create() +{ + emit createRequest (mAdjusterWidget->getPath()); +} diff --git a/apps/opencs/view/doc/newgame.hpp b/apps/opencs/view/doc/newgame.hpp new file mode 100644 index 000000000..aa97682ff --- /dev/null +++ b/apps/opencs/view/doc/newgame.hpp @@ -0,0 +1,44 @@ +#ifndef CSV_DOC_NEWGAME_H +#define CSV_DOC_NEWGAME_H + +#include + +#include +#include + +Q_DECLARE_METATYPE (boost::filesystem::path) + +class QPushButton; + +namespace CSVDoc +{ + class FileWidget; + class AdjusterWidget; + + class NewGameDialogue : public QDialog + { + Q_OBJECT + + QPushButton *mCreate; + FileWidget *mFileWidget; + AdjusterWidget *mAdjusterWidget; + + public: + + NewGameDialogue(); + + void setLocalData (const boost::filesystem::path& localData); + + signals: + + void createRequest (const boost::filesystem::path& file); + + private slots: + + void stateChanged (bool valid); + + void create(); + }; +} + +#endif diff --git a/apps/opencs/view/doc/startup.cpp b/apps/opencs/view/doc/startup.cpp index 6c1e74058..4cc64f2df 100644 --- a/apps/opencs/view/doc/startup.cpp +++ b/apps/opencs/view/doc/startup.cpp @@ -3,21 +3,106 @@ #include #include -#include +#include #include #include +#include +#include +#include +#include + +QPushButton *CSVDoc::StartupDialogue::addButton (const QString& label, const QIcon& icon) +{ + int column = mColumn--; + + QPushButton *button = new QPushButton (this); + + button->setIcon (QIcon (icon)); + + button->setSizePolicy (QSizePolicy (QSizePolicy::Preferred, QSizePolicy::Preferred)); + + mLayout->addWidget (button, 0, column); + + mLayout->addWidget (new QLabel (label, this), 1, column, Qt::AlignCenter); + + int width = mLayout->itemAtPosition (1, column)->widget()->sizeHint().width(); -CSVDoc::StartupDialogue::StartupDialogue() + if (width>mWidth) + mWidth = width; + + return button; +} + + +QWidget *CSVDoc::StartupDialogue::createButtons() { - QHBoxLayout *layout = new QHBoxLayout (this); + QWidget *widget = new QWidget (this); - QPushButton *createDocument = new QPushButton ("new", this); - connect (createDocument, SIGNAL (clicked()), this, SIGNAL (createDocument())); - layout->addWidget (createDocument); + mLayout = new QGridLayout (widget); - QPushButton *loadDocument = new QPushButton ("load", this); + /// \todo add icons + QPushButton *loadDocument = addButton ("Edit A Content File", QIcon (":startup/edit-content")); connect (loadDocument, SIGNAL (clicked()), this, SIGNAL (loadDocument())); - layout->addWidget (loadDocument); + + QPushButton *createAddon = addButton ("Create A New Addon", QIcon (":startup/create-addon")); + connect (createAddon, SIGNAL (clicked()), this, SIGNAL (createAddon())); + + QPushButton *createGame = addButton ("Create A New Game", QIcon (":startup/create-game")); + connect (createGame, SIGNAL (clicked()), this, SIGNAL (createGame())); + + for (int i=0; i<3; ++i) + mLayout->setColumnMinimumWidth (i, mWidth); + + mLayout->setRowMinimumHeight (0, mWidth); + + mLayout->setSizeConstraint (QLayout::SetMinimumSize); + mLayout->setHorizontalSpacing (32); + + mLayout->setContentsMargins (16, 16, 16, 8); + + loadDocument->setIconSize (QSize (mWidth, mWidth)); + createGame->setIconSize (QSize (mWidth, mWidth)); + createAddon->setIconSize (QSize (mWidth, mWidth)); + + widget->setLayout (mLayout); + + return widget; +} + +QWidget *CSVDoc::StartupDialogue::createTools() +{ + QWidget *widget = new QWidget (this); + + QHBoxLayout *layout = new QHBoxLayout (widget); + layout->setDirection (QBoxLayout::RightToLeft); + layout->setContentsMargins (4, 4, 4, 4); + + QPushButton *config = new QPushButton (widget); + + config->setSizePolicy (QSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed)); + config->setIcon (QIcon (":startup/configure")); + + layout->addWidget (config); + + layout->addWidget (new QWidget, 1); // dummy widget; stops buttons from taking all the space + + widget->setLayout (layout); + + connect (config, SIGNAL (clicked()), this, SIGNAL (editConfig())); + + return widget; +} + +CSVDoc::StartupDialogue::StartupDialogue() : mWidth (0), mColumn (2) +{ + setWindowTitle ("Open CS"); + + QVBoxLayout *layout = new QVBoxLayout (this); + + layout->setContentsMargins (0, 0, 0, 0); + + layout->addWidget (createButtons()); + layout->addWidget (createTools()); setLayout (layout); diff --git a/apps/opencs/view/doc/startup.hpp b/apps/opencs/view/doc/startup.hpp index f24d2a64b..f059a44e5 100644 --- a/apps/opencs/view/doc/startup.hpp +++ b/apps/opencs/view/doc/startup.hpp @@ -3,21 +3,43 @@ #include +class QGridLayout; +class QString; +class QPushButton; +class QWidget; +class QIcon; + namespace CSVDoc { class StartupDialogue : public QWidget { Q_OBJECT + private: + + int mWidth; + int mColumn; + QGridLayout *mLayout; + + QPushButton *addButton (const QString& label, const QIcon& icon); + + QWidget *createButtons(); + + QWidget *createTools(); + public: StartupDialogue(); signals: - void createDocument(); + void createGame(); + + void createAddon(); void loadDocument(); + + void editConfig(); }; } diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index 6801ea20d..7183753e1 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -27,9 +27,13 @@ void CSVDoc::View::setupFileMenu() { QMenu *file = menuBar()->addMenu (tr ("&File")); - QAction *new_ = new QAction (tr ("New"), this); - connect (new_, SIGNAL (triggered()), this, SIGNAL (newDocumentRequest())); - file->addAction (new_); + QAction *newGame = new QAction (tr ("New Game"), this); + connect (newGame, SIGNAL (triggered()), this, SIGNAL (newGameRequest())); + file->addAction (newGame); + + QAction *newAddon = new QAction (tr ("New Addon"), this); + connect (newAddon, SIGNAL (triggered()), this, SIGNAL (newAddonRequest())); + file->addAction (newAddon); QAction *open = new QAction (tr ("&Open"), this); connect (open, SIGNAL (triggered()), this, SIGNAL (loadDocumentRequest())); @@ -67,7 +71,7 @@ void CSVDoc::View::setupEditMenu() edit->addAction (mRedo); QAction *userSettings = new QAction (tr ("&Preferences"), this); - connect (userSettings, SIGNAL (triggered()), this, SLOT (showUserSettings())); + connect (userSettings, SIGNAL (triggered()), this, SIGNAL (editSettingsRequest())); edit->addAction (userSettings); } @@ -180,7 +184,7 @@ void CSVDoc::View::updateTitle() { std::ostringstream stream; - stream << mDocument->getName(); + stream << mDocument->getSavePath().filename().string(); if (mDocument->getState() & CSMDoc::State_Modified) stream << " *"; @@ -415,13 +419,6 @@ void CSVDoc::View::exit() emit exitApplicationRequest (this); } -void CSVDoc::View::showUserSettings() -{ - CSVSettings::UserSettingsDialog *settingsDialog = new CSVSettings::UserSettingsDialog(this); - - settingsDialog->show(); -} - void CSVDoc::View::resizeViewWidth (int width) { if (width >= 0) diff --git a/apps/opencs/view/doc/view.hpp b/apps/opencs/view/doc/view.hpp index 56c0b3edd..29a1d52f7 100644 --- a/apps/opencs/view/doc/view.hpp +++ b/apps/opencs/view/doc/view.hpp @@ -106,12 +106,16 @@ namespace CSVDoc signals: - void newDocumentRequest(); + void newGameRequest(); + + void newAddonRequest(); void loadDocumentRequest(); void exitApplicationRequest (CSVDoc::View *view); + void editSettingsRequest(); + public slots: void addSubView (const CSMWorld::UniversalId& id); @@ -160,8 +164,6 @@ namespace CSVDoc void addFiltersSubView(); - void showUserSettings(); - void toggleShowStatusBar (bool show); }; } diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp index 6d06e4248..a5fe6d356 100644 --- a/apps/opencs/view/doc/viewmanager.cpp +++ b/apps/opencs/view/doc/viewmanager.cpp @@ -8,6 +8,7 @@ #include "../../model/doc/documentmanager.hpp" #include "../../model/doc/document.hpp" +#include "../../model/world/columns.hpp" #include "../world/util.hpp" #include "../world/enumdelegate.hpp" @@ -43,51 +44,6 @@ void CSVDoc::ViewManager::updateIndices() CSVDoc::ViewManager::ViewManager (CSMDoc::DocumentManager& documentManager) : mDocumentManager (documentManager), mExitOnSaveStateChange(false), mUserWarned(false) { - static const char *sSpecialisations[] = - { - "Combat", "Magic", "Stealth", 0 - }; - - static const char *sAttributes[] = - { - "Strength", "Intelligence", "Willpower", "Agility", "Speed", "Endurance", "Personality", - "Luck", 0 - }; - - static const char *sSpellTypes[] = - { - "Spell", "Ability", "Blight", "Disease", "Curse", "Power", 0 - }; - - static const char *sApparatusTypes[] = - { - "Mortar & Pestle", "Albemic", "Calcinator", "Retort", 0 - }; - - static const char *sArmorTypes[] = - { - "Helmet", "Cuirass", "Left Pauldron", "Right Pauldron", "Greaves", "Boots", "Left Gauntlet", - "Right Gauntlet", "Shield", "Left Bracer", "Right Bracer", 0 - }; - - static const char *sClothingTypes[] = - { - "Pants", "Shoes", "Shirt", "Belt", "Robe", "Right Glove", "Left Glove", "Skirt", "Ring", - "Amulet", 0 - }; - - static const char *sCreatureTypes[] = - { - "Creature", "Deadra", "Undead", "Humanoid", 0 - }; - - static const char *sWeaponTypes[] = - { - "Short Blade 1H", "Long Blade 1H", "Long Blade 2H", "Blunt 1H", "Blunt 2H Close", - "Blunt 2H Wide", "Spear 2H", "Axe 1H", "Axe 2H", "Bow", "Crossbow", "Thrown", "Arrow", - "Bolt", 0 - }; - mDelegateFactories = new CSVWorld::CommandDelegateFactoryCollection; mDelegateFactories->add (CSMWorld::ColumnBase::Display_GmstVarType, @@ -96,38 +52,37 @@ CSVDoc::ViewManager::ViewManager (CSMDoc::DocumentManager& documentManager) mDelegateFactories->add (CSMWorld::ColumnBase::Display_GlobalVarType, new CSVWorld::VarTypeDelegateFactory (ESM::VT_Short, ESM::VT_Long, ESM::VT_Float)); - mDelegateFactories->add (CSMWorld::ColumnBase::Display_Specialisation, - new CSVWorld::EnumDelegateFactory (sSpecialisations)); - - mDelegateFactories->add (CSMWorld::ColumnBase::Display_Attribute, - new CSVWorld::EnumDelegateFactory (sAttributes, true)); - - mDelegateFactories->add (CSMWorld::ColumnBase::Display_SpellType, - new CSVWorld::EnumDelegateFactory (sSpellTypes)); - - mDelegateFactories->add (CSMWorld::ColumnBase::Display_ApparatusType, - new CSVWorld::EnumDelegateFactory (sApparatusTypes)); - - mDelegateFactories->add (CSMWorld::ColumnBase::Display_ArmorType, - new CSVWorld::EnumDelegateFactory (sArmorTypes)); - - mDelegateFactories->add (CSMWorld::ColumnBase::Display_ClothingType, - new CSVWorld::EnumDelegateFactory (sClothingTypes)); + mDelegateFactories->add (CSMWorld::ColumnBase::Display_RecordState, + new CSVWorld::RecordStatusDelegateFactory()); - mDelegateFactories->add (CSMWorld::ColumnBase::Display_CreatureType, - new CSVWorld::EnumDelegateFactory (sCreatureTypes)); + mDelegateFactories->add (CSMWorld::ColumnBase::Display_RefRecordType, + new CSVWorld::RefIdTypeDelegateFactory()); - mDelegateFactories->add (CSMWorld::ColumnBase::Display_WeaponType, - new CSVWorld::EnumDelegateFactory (sWeaponTypes)); + struct Mapping + { + CSMWorld::ColumnBase::Display mDisplay; + CSMWorld::Columns::ColumnId mColumnId; + bool mAllowNone; + }; - mDelegateFactories->add (CSMWorld::ColumnBase::Display_RecordState, - new CSVWorld::RecordStatusDelegateFactory() ); + static const Mapping sMapping[] = + { + { CSMWorld::ColumnBase::Display_Specialisation, CSMWorld::Columns::ColumnId_Specialisation, false }, + { CSMWorld::ColumnBase::Display_Attribute, CSMWorld::Columns::ColumnId_Attribute, true }, + { CSMWorld::ColumnBase::Display_SpellType, CSMWorld::Columns::ColumnId_SpellType, false }, + { CSMWorld::ColumnBase::Display_ApparatusType, CSMWorld::Columns::ColumnId_ApparatusType, false }, + { CSMWorld::ColumnBase::Display_ArmorType, CSMWorld::Columns::ColumnId_ArmorType, false }, + { CSMWorld::ColumnBase::Display_ClothingType, CSMWorld::Columns::ColumnId_ClothingType, false }, + { CSMWorld::ColumnBase::Display_CreatureType, CSMWorld::Columns::ColumnId_CreatureType, false }, + { CSMWorld::ColumnBase::Display_WeaponType, CSMWorld::Columns::ColumnId_WeaponType, false } + }; - mDelegateFactories->add (CSMWorld::ColumnBase::Display_RefRecordType, - new CSVWorld::RefIdTypeDelegateFactory() ); + for (std::size_t i=0; iadd (sMapping[i].mDisplay, new CSVWorld::EnumDelegateFactory ( + CSMWorld::Columns::getEnums (sMapping[i].mColumnId), sMapping[i].mAllowNone)); connect (&CSMSettings::UserSettings::instance(), SIGNAL (signalUpdateEditorSetting (const QString &, const QString &)), - this, SLOT (slotUpdateEditorSetting (const QString &, const QString &))); + this, SLOT (slotUpdateEditorSetting (const QString &, const QString &))); } CSVDoc::ViewManager::~ViewManager() @@ -152,13 +107,14 @@ CSVDoc::View *CSVDoc::ViewManager::addView (CSMDoc::Document *document) View *view = new View (*this, document, countViews (document)+1); - mViews.push_back (view); view->show(); - connect (view, SIGNAL (newDocumentRequest ()), this, SIGNAL (newDocumentRequest())); + connect (view, SIGNAL (newGameRequest ()), this, SIGNAL (newGameRequest())); + connect (view, SIGNAL (newAddonRequest ()), this, SIGNAL (newAddonRequest())); connect (view, SIGNAL (loadDocumentRequest ()), this, SIGNAL (loadDocumentRequest())); + connect (view, SIGNAL (editSettingsRequest()), this, SIGNAL (editSettingsRequest())); updateIndices(); diff --git a/apps/opencs/view/doc/viewmanager.hpp b/apps/opencs/view/doc/viewmanager.hpp index 1f4dcd51b..01f495186 100644 --- a/apps/opencs/view/doc/viewmanager.hpp +++ b/apps/opencs/view/doc/viewmanager.hpp @@ -55,12 +55,16 @@ namespace CSVDoc signals: - void newDocumentRequest(); + void newGameRequest(); + + void newAddonRequest(); void loadDocumentRequest(); void closeMessageBox(); + void editSettingsRequest(); + public slots: void exitApplication (CSVDoc::View *view); diff --git a/apps/opencs/view/filter/editwidget.cpp b/apps/opencs/view/filter/editwidget.cpp new file mode 100644 index 000000000..708d45032 --- /dev/null +++ b/apps/opencs/view/filter/editwidget.cpp @@ -0,0 +1,58 @@ + +#include "editwidget.hpp" + +#include + +#include "../../model/world/data.hpp" + +CSVFilter::EditWidget::EditWidget (CSMWorld::Data& data, QWidget *parent) +: QLineEdit (parent), mParser (data) +{ + mPalette = palette(); + connect (this, SIGNAL (textChanged (const QString&)), this, SLOT (textChanged (const QString&))); + + QAbstractItemModel *model = data.getTableModel (CSMWorld::UniversalId::Type_Filters); + + connect (model, SIGNAL (dataChanged (const QModelIndex &, const QModelIndex&)), + this, SLOT (filterDataChanged (const QModelIndex &, const QModelIndex&)), + Qt::QueuedConnection); + connect (model, SIGNAL (rowsRemoved (const QModelIndex&, int, int)), + this, SLOT (filterRowsRemoved (const QModelIndex&, int, int)), + Qt::QueuedConnection); + connect (model, SIGNAL (rowsInserted (const QModelIndex&, int, int)), + this, SLOT (filterRowsInserted (const QModelIndex&, int, int)), + Qt::QueuedConnection); +} + +void CSVFilter::EditWidget::textChanged (const QString& text) +{ + if (mParser.parse (text.toUtf8().constData())) + { + setPalette (mPalette); + emit filterChanged (mParser.getFilter()); + } + else + { + QPalette palette (mPalette); + palette.setColor (QPalette::Text, Qt::red); + setPalette (palette); + + /// \todo improve error reporting; mark only the faulty part + } +} + +void CSVFilter::EditWidget::filterDataChanged (const QModelIndex& topLeft, + const QModelIndex& bottomRight) +{ + textChanged (text()); +} + +void CSVFilter::EditWidget::filterRowsRemoved (const QModelIndex& parent, int start, int end) +{ + textChanged (text()); +} + +void CSVFilter::EditWidget::filterRowsInserted (const QModelIndex& parent, int start, int end) +{ + textChanged (text()); +} diff --git a/apps/opencs/view/filter/editwidget.hpp b/apps/opencs/view/filter/editwidget.hpp new file mode 100644 index 000000000..31904e624 --- /dev/null +++ b/apps/opencs/view/filter/editwidget.hpp @@ -0,0 +1,48 @@ +#ifndef CSV_FILTER_EDITWIDGET_H +#define CSV_FILTER_EDITWIDGET_H + +#include + +#include +#include + +#include "../../model/filter/parser.hpp" +#include "../../model/filter/node.hpp" + +class QModelIndex; + +namespace CSMWorld +{ + class Data; +} + +namespace CSVFilter +{ + class EditWidget : public QLineEdit + { + Q_OBJECT + + CSMFilter::Parser mParser; + QPalette mPalette; + + public: + + EditWidget (CSMWorld::Data& data, QWidget *parent = 0); + + signals: + + void filterChanged (boost::shared_ptr filter); + + private slots: + + void textChanged (const QString& text); + + void filterDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + void filterRowsRemoved (const QModelIndex& parent, int start, int end); + + void filterRowsInserted (const QModelIndex& parent, int start, int end); + }; +} + +#endif diff --git a/apps/opencs/view/filter/filterbox.cpp b/apps/opencs/view/filter/filterbox.cpp new file mode 100644 index 000000000..273170884 --- /dev/null +++ b/apps/opencs/view/filter/filterbox.cpp @@ -0,0 +1,24 @@ + +#include "filterbox.hpp" + +#include + +#include "recordfilterbox.hpp" + +CSVFilter::FilterBox::FilterBox (CSMWorld::Data& data, QWidget *parent) +: QWidget (parent) +{ + QHBoxLayout *layout = new QHBoxLayout (this); + + layout->setContentsMargins (0, 0, 0, 0); + + RecordFilterBox *recordFilterBox = new RecordFilterBox (data, this); + + layout->addWidget (recordFilterBox); + + setLayout (layout); + + connect (recordFilterBox, + SIGNAL (filterChanged (boost::shared_ptr)), + this, SIGNAL (recordFilterChanged (boost::shared_ptr))); +} \ No newline at end of file diff --git a/apps/opencs/view/filter/filterbox.hpp b/apps/opencs/view/filter/filterbox.hpp new file mode 100644 index 000000000..2524fa0a3 --- /dev/null +++ b/apps/opencs/view/filter/filterbox.hpp @@ -0,0 +1,30 @@ +#ifndef CSV_FILTER_FILTERBOX_H +#define CSV_FILTER_FILTERBOX_H + +#include + +#include "../../model/filter/node.hpp" + +namespace CSMWorld +{ + class Data; +} + +namespace CSVFilter +{ + class FilterBox : public QWidget + { + Q_OBJECT + + public: + + FilterBox (CSMWorld::Data& data, QWidget *parent = 0); + + signals: + + void recordFilterChanged (boost::shared_ptr filter); + }; + +} + +#endif diff --git a/apps/opencs/view/filter/filtercreator.cpp b/apps/opencs/view/filter/filtercreator.cpp new file mode 100644 index 000000000..47925ea57 --- /dev/null +++ b/apps/opencs/view/filter/filtercreator.cpp @@ -0,0 +1,63 @@ + +#include "filtercreator.hpp" + +#include +#include + +#include "../../model/filter/filter.hpp" + +std::string CSVFilter::FilterCreator::getNamespace() const +{ + switch (mScope->currentIndex()) + { + case CSMFilter::Filter::Scope_Project: return "project::"; + case CSMFilter::Filter::Scope_Session: return "session::"; + } + + return ""; +} + +void CSVFilter::FilterCreator::update() +{ + mNamespace->setText (QString::fromUtf8 (getNamespace().c_str())); + GenericCreator::update(); +} + +std::string CSVFilter::FilterCreator::getId() const +{ + return getNamespace() + GenericCreator::getId(); +} + +CSVFilter::FilterCreator::FilterCreator (CSMWorld::Data& data, QUndoStack& undoStack, + const CSMWorld::UniversalId& id) +: GenericCreator (data, undoStack, id) +{ + mNamespace = new QLabel ("::", this); + insertAtBeginning (mNamespace, false); + + mScope = new QComboBox (this); + + mScope->addItem ("Project"); + mScope->addItem ("Session"); + /// \ŧodo re-enable for OpenMW 1.1 + // mScope->addItem ("Content"); + + connect (mScope, SIGNAL (currentIndexChanged (int)), this, SLOT (setScope (int))); + + insertAtBeginning (mScope, false); + + QLabel *label = new QLabel ("Scope", this); + insertAtBeginning (label, false); + + mScope->setCurrentIndex (1); +} + +void CSVFilter::FilterCreator::reset() +{ + GenericCreator::reset(); +} + +void CSVFilter::FilterCreator::setScope (int index) +{ + update(); +} diff --git a/apps/opencs/view/filter/filtercreator.hpp b/apps/opencs/view/filter/filtercreator.hpp new file mode 100644 index 000000000..82d38d22c --- /dev/null +++ b/apps/opencs/view/filter/filtercreator.hpp @@ -0,0 +1,41 @@ +#ifndef CSV_FILTER_FILTERCREATOR_H +#define CSV_FILTER_FILTERCREATOR_H + +class QComboBox; +class QLabel; + +#include "../world/genericcreator.hpp" + +namespace CSVFilter +{ + class FilterCreator : public CSVWorld::GenericCreator + { + Q_OBJECT + + QComboBox *mScope; + QLabel *mNamespace; + + private: + + std::string getNamespace() const; + + protected: + + void update(); + + virtual std::string getId() const; + + public: + + FilterCreator (CSMWorld::Data& data, QUndoStack& undoStack, + const CSMWorld::UniversalId& id); + + virtual void reset(); + + private slots: + + void setScope (int index); + }; +} + +#endif diff --git a/apps/opencs/view/filter/recordfilterbox.cpp b/apps/opencs/view/filter/recordfilterbox.cpp new file mode 100644 index 000000000..c405177b0 --- /dev/null +++ b/apps/opencs/view/filter/recordfilterbox.cpp @@ -0,0 +1,27 @@ + +#include "recordfilterbox.hpp" + +#include +#include + +#include "editwidget.hpp" + +CSVFilter::RecordFilterBox::RecordFilterBox (CSMWorld::Data& data, QWidget *parent) +: QWidget (parent) +{ + QHBoxLayout *layout = new QHBoxLayout (this); + + layout->setContentsMargins (0, 0, 0, 0); + + layout->addWidget (new QLabel ("Record Filter", this)); + + EditWidget *editWidget = new EditWidget (data, this); + + layout->addWidget (editWidget); + + setLayout (layout); + + connect ( + editWidget, SIGNAL (filterChanged (boost::shared_ptr)), + this, SIGNAL (filterChanged (boost::shared_ptr))); +} diff --git a/apps/opencs/view/filter/recordfilterbox.hpp b/apps/opencs/view/filter/recordfilterbox.hpp new file mode 100644 index 000000000..057d69518 --- /dev/null +++ b/apps/opencs/view/filter/recordfilterbox.hpp @@ -0,0 +1,34 @@ +#ifndef CSV_FILTER_RECORDFILTERBOX_H +#define CSV_FILTER_RECORDFILTERBOX_H + +#include + +#include + +#include + +#include "../../model/filter/node.hpp" + +namespace CSMWorld +{ + class Data; +} + +namespace CSVFilter +{ + class RecordFilterBox : public QWidget + { + Q_OBJECT + + public: + + RecordFilterBox (CSMWorld::Data& data, QWidget *parent = 0); + + signals: + + void filterChanged (boost::shared_ptr filter); + }; + +} + +#endif \ No newline at end of file diff --git a/apps/opencs/view/settings/usersettingsdialog.cpp b/apps/opencs/view/settings/usersettingsdialog.cpp index 21311c2da..e73e24dcb 100644 --- a/apps/opencs/view/settings/usersettingsdialog.cpp +++ b/apps/opencs/view/settings/usersettingsdialog.cpp @@ -1,5 +1,7 @@ #include "usersettingsdialog.hpp" +#include + #include #include #include @@ -9,14 +11,14 @@ #include #include #include - #include +#include +#include + +#include "../../model/settings/support.hpp" #include "datadisplayformatpage.hpp" #include "windowpage.hpp" - -#include "../../model/settings/support.hpp" -#include #include "settingwidget.hpp" CSVSettings::UserSettingsDialog::UserSettingsDialog(QMainWindow *parent) : @@ -29,7 +31,11 @@ CSVSettings::UserSettingsDialog::UserSettingsDialog(QMainWindow *parent) : connect (mListWidget, SIGNAL (currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, - SLOT (slotChangePage (QListWidgetItem*, QListWidgetItem*))); + SLOT (slotChangePage (QListWidgetItem*, QListWidgetItem*))); + + QRect scr = QApplication::desktop()->screenGeometry(); + QRect rect = geometry(); + move (scr.center().x() - rect.center().x(), scr.center().y() - rect.center().y()); } CSVSettings::UserSettingsDialog::~UserSettingsDialog() diff --git a/apps/opencs/view/world/enumdelegate.cpp b/apps/opencs/view/world/enumdelegate.cpp index dd194abe9..fc9b7ee3b 100644 --- a/apps/opencs/view/world/enumdelegate.cpp +++ b/apps/opencs/view/world/enumdelegate.cpp @@ -109,6 +109,18 @@ CSVWorld::EnumDelegateFactory::EnumDelegateFactory (const char **names, bool all add (i, names[i]); } +CSVWorld::EnumDelegateFactory::EnumDelegateFactory (const std::vector& names, + bool allowNone) +{ + if (allowNone) + add (-1, ""); + + int size = static_cast (names.size()); + + for (int i=0; i& names, bool allowNone = false); + /// \param allowNone Use value of -1 for "none selected" (empty string) + virtual CommandDelegate *makeDelegate (QUndoStack& undoStack, QObject *parent) const; ///< The ownership of the returned CommandDelegate is transferred to the caller. diff --git a/apps/opencs/view/world/recordstatusdelegate.cpp b/apps/opencs/view/world/recordstatusdelegate.cpp index a0ffd3063..8085ec7be 100644 --- a/apps/opencs/view/world/recordstatusdelegate.cpp +++ b/apps/opencs/view/world/recordstatusdelegate.cpp @@ -1,8 +1,11 @@ #include "recordstatusdelegate.hpp" + #include #include #include + #include "../../model/settings/usersettings.hpp" +#include "../../model/world/columns.hpp" CSVWorld::RecordStatusDelegate::RecordStatusDelegate(const ValueList& values, const IconList & icons, @@ -37,9 +40,14 @@ bool CSVWorld::RecordStatusDelegate::updateEditorSetting (const QString &setting CSVWorld::RecordStatusDelegateFactory::RecordStatusDelegateFactory() { - DataDisplayDelegateFactory::add ( CSMWorld::RecordBase::State_BaseOnly, "Base", ":./base.png"); - DataDisplayDelegateFactory::add ( CSMWorld::RecordBase::State_Deleted, "Deleted", ":./removed.png"); - DataDisplayDelegateFactory::add ( CSMWorld::RecordBase::State_Erased, "Deleted", ":./removed.png"); - DataDisplayDelegateFactory::add ( CSMWorld::RecordBase::State_Modified, "Modified", ":./modified.png"); - DataDisplayDelegateFactory::add ( CSMWorld::RecordBase::State_ModifiedOnly, "Added", ":./added.png"); + std::vector enums = + CSMWorld::Columns::getEnums (CSMWorld::Columns::ColumnId_Modification); + + static const char *sIcons[] = + { + ":./base.png", ":./modified.png", ":./added.png", ":./removed.png", ":./removed.png", 0 + }; + + for (int i=0; sIcons[i]; ++i) + add (i, enums.at (i).c_str(), sIcons[i]); } diff --git a/apps/opencs/view/world/refidtypedelegate.cpp b/apps/opencs/view/world/refidtypedelegate.cpp index bf3acbb20..7cffbf3dd 100755 --- a/apps/opencs/view/world/refidtypedelegate.cpp +++ b/apps/opencs/view/world/refidtypedelegate.cpp @@ -1,4 +1,5 @@ #include "refidtypedelegate.hpp" + #include "../../model/world/universalid.hpp" CSVWorld::RefIdTypeDelegate::RefIdTypeDelegate @@ -6,6 +7,26 @@ CSVWorld::RefIdTypeDelegate::RefIdTypeDelegate : DataDisplayDelegate (values, icons, undoStack, parent) {} +bool CSVWorld::RefIdTypeDelegate::updateEditorSetting (const QString &settingName, const QString &settingValue) +{ + if (settingName == "Referenceable ID Type Display") + { + if (settingValue == "Icon and Text") + mDisplayMode = Mode_IconAndText; + + else if (settingValue == "Icon Only") + mDisplayMode = Mode_IconOnly; + + else if (settingValue == "Text Only") + mDisplayMode = Mode_TextOnly; + + return true; + } + + return false; +} + + CSVWorld::RefIdTypeDelegateFactory::RefIdTypeDelegateFactory() { UidTypeList uIdList = buildUidTypeList(); @@ -39,22 +60,3 @@ CSVWorld::RefIdTypeDelegateFactory::UidTypeList CSVWorld::RefIdTypeDelegateFacto return list; } - -bool CSVWorld::RefIdTypeDelegate::updateEditorSetting (const QString &settingName, const QString &settingValue) -{ - if (settingName == "Referenceable ID Type Display") - { - if (settingValue == "Icon and Text") - mDisplayMode = Mode_IconAndText; - - else if (settingValue == "Icon Only") - mDisplayMode = Mode_IconOnly; - - else if (settingValue == "Text Only") - mDisplayMode = Mode_TextOnly; - - return true; - } - - return false; -} diff --git a/apps/opencs/view/world/refrecordtypedelegate.cpp b/apps/opencs/view/world/refrecordtypedelegate.cpp deleted file mode 100644 index 2bcb7ca50..000000000 --- a/apps/opencs/view/world/refrecordtypedelegate.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include "refrecordtypedelegate.hpp" -#include "../../model/world/universalid.hpp" - -CSVWorld::RefRecordTypeDelegate::RefRecordTypeDelegate - (const std::vector > &values, QUndoStack& undoStack, QObject *parent) - : EnumDelegate (values, undoStack, parent) -{} - -CSVWorld::RefRecordTypeDelegateFactory::RefRecordTypeDelegateFactory() -{ - unsigned int argSize = CSMWorld::UniversalId::getIdArgSize(); - - for (unsigned int i = 0; i < argSize; i++) - { - std::pair idPair = CSMWorld::UniversalId::getIdArgPair(i); - - mValues.push_back (std::pair(idPair.first, QString::fromUtf8(idPair.second))); - } -} - -CSVWorld::CommandDelegate *CSVWorld::RefRecordTypeDelegateFactory::makeDelegate (QUndoStack& undoStack, - QObject *parent) const -{ - return new RefRecordTypeDelegate (mValues, undoStack, parent); -} diff --git a/apps/opencs/view/world/refrecordtypedelegate.hpp b/apps/opencs/view/world/refrecordtypedelegate.hpp deleted file mode 100644 index baec2cc2e..000000000 --- a/apps/opencs/view/world/refrecordtypedelegate.hpp +++ /dev/null @@ -1,58 +0,0 @@ -#ifndef REFRECORDTYPEDELEGATE_HPP -#define REFRECORDTYPEDELEGATE_HPP - -#include "enumdelegate.hpp" -#include "util.hpp" - -namespace CSVWorld -{ - class RefRecordTypeDelegate : public EnumDelegate - { - public: - RefRecordTypeDelegate (const std::vector > &mValues, QUndoStack& undoStack, QObject *parent); - }; - - class RefRecordTypeDelegateFactory : public CommandDelegateFactory - { - - std::vector > mValues; - - public: - RefRecordTypeDelegateFactory(); - - virtual CommandDelegate *makeDelegate (QUndoStack& undoStack, QObject *parent) const; - ///< The ownership of the returned CommandDelegate is transferred to the caller. - }; -} -/* - class VarTypeDelegate : public EnumDelegate - { - private: - - virtual void addCommands (QAbstractItemModel *model, - const QModelIndex& index, int type) const; - - public: - - VarTypeDelegate (const std::vector >& values, - QUndoStack& undoStack, QObject *parent); - }; - - class VarTypeDelegateFactory : public CommandDelegateFactory - { - std::vector > mValues; - - public: - - VarTypeDelegateFactory (ESM::VarType type0 = ESM::VT_Unknown, - ESM::VarType type1 = ESM::VT_Unknown, ESM::VarType type2 = ESM::VT_Unknown, - ESM::VarType type3 = ESM::VT_Unknown); - - virtual CommandDelegate *makeDelegate (QUndoStack& undoStack, QObject *parent) const; - ///< The ownership of the returned CommandDelegate is transferred to the caller. - - void add (ESM::VarType type); - }; -*/ - -#endif // REFRECORDTYPEDELEGATE_HPP diff --git a/apps/opencs/view/world/subviews.cpp b/apps/opencs/view/world/subviews.cpp index 2ca711a59..d22e07d89 100644 --- a/apps/opencs/view/world/subviews.cpp +++ b/apps/opencs/view/world/subviews.cpp @@ -3,6 +3,8 @@ #include "../doc/subviewfactoryimp.hpp" +#include "../filter/filtercreator.hpp" + #include "tablesubview.hpp" #include "dialoguesubview.hpp" #include "scriptsubview.hpp" @@ -33,7 +35,6 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) CSMWorld::UniversalId::Type_Regions, CSMWorld::UniversalId::Type_Birthsigns, CSMWorld::UniversalId::Type_Spells, - CSMWorld::UniversalId::Type_Filters, CSMWorld::UniversalId::Type_None // end marker }; @@ -56,4 +57,9 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) // Other stuff (combined record tables) manager.add (CSMWorld::UniversalId::Type_RegionMap, new CSVDoc::SubViewFactory); + + manager.add (CSMWorld::UniversalId::Type_Filters, + new CSVDoc::SubViewFactoryWithCreator >); + } \ No newline at end of file diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 4ae25d10b..72e78c738 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -257,9 +257,9 @@ void CSVWorld::Table::tableSizeUpdate() int deleted = 0; int modified = 0; - if (mModel->columnCount()>0) + if (mProxyModel->columnCount()>0) { - int rows = mModel->rowCount(); + int rows = mProxyModel->rowCount(); for (int i=0; i filter) +{ + mProxyModel->setFilter (filter); } \ No newline at end of file diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index 0c24e7b54..d93109056 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -6,6 +6,8 @@ #include +#include "../../model/filter/node.hpp" + class QUndoStack; class QAction; @@ -85,6 +87,7 @@ namespace CSVWorld void requestFocus (const std::string& id); + void recordFilterChanged (boost::shared_ptr filter); }; } diff --git a/apps/opencs/view/world/tablebottombox.cpp b/apps/opencs/view/world/tablebottombox.cpp index 6cf21a132..3edf9af31 100644 --- a/apps/opencs/view/world/tablebottombox.cpp +++ b/apps/opencs/view/world/tablebottombox.cpp @@ -63,12 +63,15 @@ CSVWorld::TableBottomBox::TableBottomBox (const CreatorFactoryBase& creatorFacto mCreator = creatorFactory.makeCreator (data, undoStack, id); - mLayout->addWidget (mCreator); + if (mCreator) + { + mLayout->addWidget (mCreator); - connect (mCreator, SIGNAL (done()), this, SLOT (createRequestDone())); + connect (mCreator, SIGNAL (done()), this, SLOT (createRequestDone())); - connect (mCreator, SIGNAL (requestFocus (const std::string&)), - this, SIGNAL (requestFocus (const std::string&))); + connect (mCreator, SIGNAL (requestFocus (const std::string&)), + this, SIGNAL (requestFocus (const std::string&))); + } } void CSVWorld::TableBottomBox::setEditLock (bool locked) diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index af3d186e8..1e05fbf51 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -5,6 +5,8 @@ #include "../../model/doc/document.hpp" +#include "../filter/filterbox.hpp" + #include "table.hpp" #include "tablebottombox.hpp" #include "creator.hpp" @@ -23,6 +25,10 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D layout->insertWidget (0, mTable = new Table (id, document.getData(), document.getUndoStack(), mBottom->canCreateAndDelete()), 2); + CSVFilter::FilterBox *filterBox = new CSVFilter::FilterBox (document.getData(), this); + + layout->insertWidget (0, filterBox); + QWidget *widget = new QWidget; widget->setLayout (layout); @@ -44,6 +50,10 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D connect (mBottom, SIGNAL (requestFocus (const std::string&)), mTable, SLOT (requestFocus (const std::string&))); + + connect (filterBox, + SIGNAL (recordFilterChanged (boost::shared_ptr)), + mTable, SLOT (recordFilterChanged (boost::shared_ptr))); } void CSVWorld::TableSubView::setEditLock (bool locked) diff --git a/apps/opencs/view/world/vartypedelegate.cpp b/apps/opencs/view/world/vartypedelegate.cpp index 72cbaae42..15ce2dbaf 100644 --- a/apps/opencs/view/world/vartypedelegate.cpp +++ b/apps/opencs/view/world/vartypedelegate.cpp @@ -4,6 +4,7 @@ #include #include "../../model/world/commands.hpp" +#include "../../model/world/columns.hpp" void CSVWorld::VarTypeDelegate::addCommands (QAbstractItemModel *model, const QModelIndex& index, int type) const @@ -75,29 +76,11 @@ CSVWorld::CommandDelegate *CSVWorld::VarTypeDelegateFactory::makeDelegate (QUndo void CSVWorld::VarTypeDelegateFactory::add (ESM::VarType type) { - struct Name - { - ESM::VarType mType; - const char *mName; - }; + std::vector enums = + CSMWorld::Columns::getEnums (CSMWorld::Columns::ColumnId_ValueType); - static const Name sNames[] = - { - { ESM::VT_None, "empty" }, - { ESM::VT_Short, "short" }, - { ESM::VT_Int, "integer" }, - { ESM::VT_Long, "long" }, - { ESM::VT_Float, "float" }, - { ESM::VT_String, "string" }, - { ESM::VT_Unknown, 0 } // end marker - }; - - for (int i=0; sNames[i].mName; ++i) - if (sNames[i].mType==type) - { - mValues.push_back (std::make_pair (type, sNames[i].mName)); - return; - } - - throw std::logic_error ("Unsupported variable type"); + if (type<0 && type>=enums.size()) + throw std::logic_error ("Unsupported variable type"); + + mValues.push_back (std::make_pair (type, QString::fromUtf8 (enums[type].c_str()))); } diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index eebecc09c..b367e2a1e 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -15,8 +15,9 @@ source_group(game FILES ${GAME} ${GAME_HEADER}) add_openmw_dir (mwrender renderingmanager debugging sky camera animation npcanimation creatureanimation activatoranimation - actors objects renderinginterface localmap occlusionquery terrain terrainmaterial water shadows + actors objects renderinginterface localmap occlusionquery water shadows compositors characterpreview externalrendering globalmap videoplayer ripplesimulation refraction + terrainstorage ) add_openmw_dir (mwinput @@ -104,7 +105,6 @@ add_definitions(${SOUND_DEFINE}) target_link_libraries(openmw ${OGRE_LIBRARIES} - ${OGRE_Terrain_LIBRARY} ${OGRE_STATIC_PLUGINS} ${Boost_LIBRARIES} ${OPENAL_LIBRARY} @@ -119,6 +119,10 @@ target_link_libraries(openmw components ) +if (USE_SYSTEM_TINYXML) + target_link_libraries(openmw ${TINYXML_LIBRARIES}) +endif() + if (NOT UNIX) target_link_libraries(openmw ${SDL2MAIN_LIBRARY}) endif() diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 9516da5ae..a2eccbaf9 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -69,8 +69,8 @@ void OMW::Engine::setAnimationVerbose(bool animverbose) bool OMW::Engine::frameStarted (const Ogre::FrameEvent& evt) { - if (!MWBase::Environment::get().getWindowManager()->isGuiMode()) - MWBase::Environment::get().getWorld()->frameStarted(evt.timeSinceLastFrame); + bool paused = MWBase::Environment::get().getWindowManager()->isGuiMode(); + MWBase::Environment::get().getWorld()->frameStarted(evt.timeSinceLastFrame, paused); MWBase::Environment::get().getWindowManager ()->frameStarted(evt.timeSinceLastFrame); return true; } @@ -123,6 +123,7 @@ bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt) MWBase::Environment::get().getWindowManager()->wmUpdateFps(window->getLastFPS(), tri, batch); MWBase::Environment::get().getWindowManager()->onFrame(frametime); + MWBase::Environment::get().getWindowManager()->update(); } catch (const std::exception& e) { @@ -385,26 +386,39 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) loadBSA(); + + // Create input and UI first to set up a bootstrapping environment for + // showing a loading screen and keeping the window responsive while doing so + + std::string keybinderUser = (mCfgMgr.getUserPath() / "input.xml").string(); + bool keybinderUserExists = boost::filesystem::exists(keybinderUser); + MWInput::InputManager* input = new MWInput::InputManager (*mOgre, *this, keybinderUser, keybinderUserExists); + mEnvironment.setInputManager (input); + + MWGui::WindowManager* window = new MWGui::WindowManager( + mExtensions, mFpsLevel, mOgre, mCfgMgr.getLogPath().string() + std::string("/"), + mCfgMgr.getCachePath ().string(), mScriptConsoleMode, mTranslationDataStorage, mEncoding); + mEnvironment.setWindowManager (window); + if (mNewGame) + mEnvironment.getWindowManager()->setNewGame(true); + // Create the world mEnvironment.setWorld( new MWWorld::World (*mOgre, mFileCollections, mMaster, mPlugins, mResDir, mCfgMgr.getCachePath(), mEncoder, mFallbackMap, mActivationDistanceOverride)); MWBase::Environment::get().getWorld()->setupPlayer(); + input->setPlayer(&mEnvironment.getWorld()->getPlayer()); + + window->initUI(); + window->renderWorldMap(); //Load translation data mTranslationDataStorage.setEncoder(mEncoder); for (size_t i = 0; i < mMaster.size(); i++) mTranslationDataStorage.loadTranslationData(mFileCollections, mMaster[i]); - // Create window manager - this manages all the MW-specific GUI windows Compiler::registerExtensions (mExtensions); - mEnvironment.setWindowManager (new MWGui::WindowManager( - mExtensions, mFpsLevel, mOgre, mCfgMgr.getLogPath().string() + std::string("/"), - mCfgMgr.getCachePath ().string(), mScriptConsoleMode, mTranslationDataStorage, mEncoding)); - if (mNewGame) - mEnvironment.getWindowManager()->setNewGame(true); - // Create sound system mEnvironment.setSoundManager (new MWSound::SoundManager(mUseSound)); @@ -422,16 +436,6 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mEnvironment.setJournal (new MWDialogue::Journal); mEnvironment.setDialogueManager (new MWDialogue::DialogueManager (mExtensions, mVerboseScripts, mTranslationDataStorage)); - // Sets up the input system - - // Get the path for the keybinder xml file - std::string keybinderUser = (mCfgMgr.getUserPath() / "input.xml").string(); - bool keybinderUserExists = boost::filesystem::exists(keybinderUser); - - mEnvironment.setInputManager (new MWInput::InputManager (*mOgre, - MWBase::Environment::get().getWorld()->getPlayer(), - *MWBase::Environment::get().getWindowManager(), *this, keybinderUser, keybinderUserExists)); - mEnvironment.getWorld()->renderPlayer(); if (!mNewGame) @@ -597,4 +601,4 @@ void OMW::Engine::setStartupScript (const std::string& path) void OMW::Engine::setActivationDistanceOverride (int distance) { mActivationDistanceOverride = distance; -} \ No newline at end of file +} diff --git a/apps/openmw/mwbase/soundmanager.hpp b/apps/openmw/mwbase/soundmanager.hpp index b75f6753d..4d764597c 100644 --- a/apps/openmw/mwbase/soundmanager.hpp +++ b/apps/openmw/mwbase/soundmanager.hpp @@ -39,9 +39,11 @@ namespace MWBase Play_Normal = 0, /* tracked, non-looping, multi-instance, environment */ Play_Loop = 1<<0, /* Sound will continually loop until explicitly stopped */ Play_NoEnv = 1<<1, /* Do not apply environment effects (eg, underwater filters) */ - Play_NoTrack = 1<<2 /* (3D only) Play the sound at the given object's position + Play_NoTrack = 1<<2, /* (3D only) Play the sound at the given object's position * but do not keep it updated (the sound will not move with * the object and will not stop when the object is deleted. */ + + Play_LoopNoEnv = Play_Loop | Play_NoEnv }; enum PlayType { Play_TypeSfx = 1<<3, /* Normal SFX sound */ diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 77941a43a..1cd867223 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -9,6 +9,8 @@ #include +#include + #include "../mwmechanics/stat.hpp" #include "../mwgui/mode.hpp" @@ -253,9 +255,6 @@ namespace MWBase virtual void executeInConsole (const std::string& path) = 0; - virtual void setLoadingProgress (const std::string& stage, int depth, int current, int total) = 0; - virtual void loadingDone() = 0; - virtual void enableRest() = 0; virtual bool getRestEnabled() = 0; virtual bool getJournalAllowed() = 0; @@ -282,6 +281,8 @@ namespace MWBase virtual const Translation::Storage& getTranslationDataStorage() const = 0; virtual void setKeyFocusWidget (MyGUI::Widget* widget) = 0; + + virtual Loading::Listener* getLoadingScreen() = 0; }; } diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 4b4ffd0d3..6101358de 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -220,9 +220,10 @@ namespace MWBase virtual MWWorld::Ptr getFacedObject() = 0; ///< Return pointer to the object the player is looking at, if it is within activation range - /// Returns a pointer to the object the provided object is facing (if within the - /// specified distance). This will attempt to use the "Bip01 Head" node as a basis. - virtual MWWorld::Ptr getFacedObject(const MWWorld::Ptr &ptr, float distance) = 0; + /// Returns a pointer to the object the provided object would hit (if within the + /// specified distance), and the point where the hit occurs. This will attempt to + /// use the "Head" node as a basis. + virtual std::pair getHitContact(const MWWorld::Ptr &ptr, float distance) = 0; virtual void adjustPosition (const MWWorld::Ptr& ptr) = 0; ///< Adjust position after load to be on ground. Must be called after model load. @@ -250,8 +251,9 @@ namespace MWBase virtual void positionToIndex (float x, float y, int &cellX, int &cellY) const = 0; ///< Convert position to cell numbers - virtual void doPhysics (const MWWorld::PtrMovementList &actors, float duration) = 0; - ///< Run physics simulation and modify \a world accordingly. + virtual void queueMovement(const MWWorld::Ptr &ptr, const Ogre::Vector3 &velocity) = 0; + ///< Queues movement for \a ptr (in local space), to be applied in the next call to + /// doPhysics. virtual bool castRay (float x1, float y1, float z1, float x2, float y2, float z2) = 0; ///< cast a Ray and return true if there is an object in the ray path. @@ -371,7 +373,7 @@ namespace MWBase /// \todo this does not belong here virtual void playVideo(const std::string& name, bool allowSkipping) = 0; virtual void stopVideo() = 0; - virtual void frameStarted (float dt) = 0; + virtual void frameStarted (float dt, bool paused) = 0; /// Find default position inside exterior cell specified by name /// \return false if exterior with given name not exists, true otherwise @@ -393,6 +395,8 @@ namespace MWBase /// Sets the NPC's Acrobatics skill to match the fWerewolfAcrobatics GMST. /// It only applies to the current form the NPC is in. virtual void applyWerewolfAcrobatics(const MWWorld::Ptr& actor) = 0; + + virtual bool toggleGodMode() = 0; }; } diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index 3a60d9c39..583cb08d3 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -6,19 +6,26 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwworld//cellstore.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/physicssystem.hpp" +#include "../mwworld/action.hpp" +#include "../mwworld/failedaction.hpp" +#include "../mwworld/nullaction.hpp" #include "../mwrender/actors.hpp" #include "../mwrender/renderinginterface.hpp" #include "../mwgui/tooltips.hpp" +#include "../mwmechanics/npcstats.hpp" + + namespace MWClass { - void Activator::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const + void Activator::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { const std::string model = getModel(ptr); if (!model.empty()) { @@ -94,7 +101,23 @@ namespace MWClass return info; } - + + boost::shared_ptr Activator::activate(const MWWorld::Ptr &ptr, const MWWorld::Ptr &actor) const + { + if(get(actor).isNpc() && get(actor).getNpcStats(actor).isWerewolf()) + { + const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + const ESM::Sound *sound = store.get().searchRandom("WolfActivator"); + + boost::shared_ptr action(new MWWorld::FailedAction("#{sWerewolfRefusal}")); + if(sound) action->setSound(sound->mId); + + return action; + } + return boost::shared_ptr(new MWWorld::NullAction); + } + + MWWorld::Ptr Activator::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const { diff --git a/apps/openmw/mwclass/activator.hpp b/apps/openmw/mwclass/activator.hpp index 4165fbc08..1e772ef4f 100644 --- a/apps/openmw/mwclass/activator.hpp +++ b/apps/openmw/mwclass/activator.hpp @@ -31,6 +31,9 @@ namespace MWClass virtual std::string getScript (const MWWorld::Ptr& ptr) const; ///< Return name of the script attached to ptr + virtual boost::shared_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const; + ///< Generate action for activation + static void registerSelf(); virtual std::string getModel(const MWWorld::Ptr &ptr) const; diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 620bfaaca..073d1b1b9 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -332,14 +332,15 @@ namespace MWClass float dist = 100.0f * (!weapon.isEmpty() ? weapon.get()->mBase->mData.mReach : gmst.find("fHandToHandReach")->getFloat()); - MWWorld::Ptr victim = world->getFacedObject(ptr, dist); + // TODO: Use second to work out the hit angle and where to spawn the blood effect + MWWorld::Ptr victim = world->getHitContact(ptr, dist).first; if(victim.isEmpty()) // Didn't hit anything return; const MWWorld::Class &othercls = MWWorld::Class::get(victim); if(!othercls.isActor()) // Can't hit non-actors return; - MWMechanics::CreatureStats &otherstats = getCreatureStats(victim); + MWMechanics::CreatureStats &otherstats = victim.getClass().getCreatureStats(victim); if(otherstats.isDead()) // Can't hit dead actors return; @@ -1021,6 +1022,16 @@ namespace MWClass } return ""; } + if(name == "land") + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + Ogre::Vector3 pos(ptr.getRefData().getPosition().pos); + if(world->isUnderwater(ptr.getCell(), pos)) + return "DefaultLandWater"; + if(world->isOnGround(ptr)) + return "Body Fall Medium"; + return ""; + } if(name == "swimleft") return "Swim Left"; if(name == "swimright") @@ -1034,8 +1045,6 @@ namespace MWClass return ""; if(name == "scream") return ""; - if(name == "land") - return ""; throw std::runtime_error(std::string("Unexpected soundgen type: ")+name); } diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index 85c2cf5fb..bc869e5fe 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -88,8 +88,13 @@ namespace MWGui mDragAndDropWidget->setVisible(false); - targetModel->copyItem(mItem, mDraggedCount); - mSourceModel->removeItem(mItem, mDraggedCount); + // If item is dropped where it was taken from, we don't need to do anything - + // otherwise, do the transfer + if (targetModel != mSourceModel) + { + targetModel->copyItem(mItem, mDraggedCount); + mSourceModel->removeItem(mItem, mDraggedCount); + } mSourceModel->update(); diff --git a/apps/openmw/mwgui/containeritemmodel.cpp b/apps/openmw/mwgui/containeritemmodel.cpp index e23b4f77f..eff8fbcc1 100644 --- a/apps/openmw/mwgui/containeritemmodel.cpp +++ b/apps/openmw/mwgui/containeritemmodel.cpp @@ -74,13 +74,12 @@ ItemModel::ModelIndex ContainerItemModel::getIndex (ItemStack item) void ContainerItemModel::copyItem (const ItemStack& item, size_t count) { const MWWorld::Ptr& source = mItemSources[mItemSources.size()-1]; + if (item.mBase.getContainerStore() == &source.getClass().getContainerStore(source)) + throw std::runtime_error("Item to copy needs to be from a different container!"); int origCount = item.mBase.getRefData().getCount(); item.mBase.getRefData().setCount(count); - MWWorld::ContainerStoreIterator it = MWWorld::Class::get(source).getContainerStore(source).add(item.mBase, source); - if (*it != item.mBase) - item.mBase.getRefData().setCount(origCount); - else - item.mBase.getRefData().setCount(origCount + count); // item copied onto itself + source.getClass().getContainerStore(source).add(item.mBase, source); + item.mBase.getRefData().setCount(origCount); } void ContainerItemModel::removeItem (const ItemStack& item, size_t count) diff --git a/apps/openmw/mwgui/fontloader.cpp b/apps/openmw/mwgui/fontloader.cpp index ff160105a..7a6317c16 100644 --- a/apps/openmw/mwgui/fontloader.cpp +++ b/apps/openmw/mwgui/fontloader.cpp @@ -9,6 +9,7 @@ #include #include + #include namespace @@ -62,6 +63,58 @@ namespace return unicode; } + + std::string getUtf8 (unsigned char c, ToUTF8::Utf8Encoder& encoder, ToUTF8::FromType encoding) + { + if (encoding == ToUTF8::WINDOWS_1250) + { + unsigned char win1250; + std::map conv; + conv[0x80] = 0xc6; + conv[0x81] = 0x9c; + conv[0x82] = 0xe6; + conv[0x83] = 0xb3; + conv[0x84] = 0xf1; + conv[0x85] = 0xb9; + conv[0x86] = 0xbf; + conv[0x87] = 0x9f; + conv[0x88] = 0xea; + conv[0x89] = 0xea; + conv[0x8a] = 0x0; // not contained in win1250 + conv[0x8b] = 0x0; // not contained in win1250 + conv[0x8c] = 0x8f; + conv[0x8d] = 0xaf; + conv[0x8e] = 0xa5; + conv[0x8f] = 0x8c; + conv[0x90] = 0xca; + conv[0x93] = 0xa3; + conv[0x94] = 0xf6; + conv[0x95] = 0xf3; + conv[0x96] = 0xaf; + conv[0x97] = 0x8f; + conv[0x99] = 0xd3; + conv[0x9a] = 0xd1; + conv[0x9c] = 0x0; // not contained in win1250 + conv[0xa0] = 0xb9; + conv[0xa1] = 0xaf; + conv[0xa2] = 0xf3; + conv[0xa3] = 0xbf; + conv[0xa4] = 0x0; // not contained in win1250 + conv[0xe1] = 0x8c; + conv[0xe1] = 0x8c; + conv[0xe3] = 0x0; // not contained in win1250 + conv[0xf5] = 0x0; // not contained in win1250 + + if (conv.find(c) != conv.end()) + win1250 = conv[c]; + else + win1250 = c; + return encoder.getUtf8(std::string(1, win1250)); + } + else + return encoder.getUtf8(std::string(1, c)); + } + } namespace MWGui @@ -184,7 +237,7 @@ namespace MWGui int h = data[i].bottom_left.y*height - y1; ToUTF8::Utf8Encoder encoder(mEncoding); - unsigned long unicodeVal = utf8ToUnicode(encoder.getUtf8(std::string(1, (unsigned char)(i)))); + unsigned long unicodeVal = utf8ToUnicode(getUtf8(i, encoder, mEncoding)); MyGUI::xml::ElementPtr code = codes->createChild("Code"); code->addAttribute("index", unicodeVal); diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index f9d31bdcd..edcd49738 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -250,6 +250,7 @@ namespace MWGui // remove object from the container it was coming from mDragAndDrop->mSourceModel->removeItem(mDragAndDrop->mItem, mDragAndDrop->mDraggedCount); mDragAndDrop->finish(); + mDragAndDrop->mSourceModel->update(); } else { diff --git a/apps/openmw/mwgui/inventoryitemmodel.cpp b/apps/openmw/mwgui/inventoryitemmodel.cpp index ec3bb3b30..62a5a75f0 100644 --- a/apps/openmw/mwgui/inventoryitemmodel.cpp +++ b/apps/openmw/mwgui/inventoryitemmodel.cpp @@ -40,13 +40,12 @@ ItemModel::ModelIndex InventoryItemModel::getIndex (ItemStack item) void InventoryItemModel::copyItem (const ItemStack& item, size_t count) { + if (item.mBase.getContainerStore() == &mActor.getClass().getContainerStore(mActor)) + throw std::runtime_error("Item to copy needs to be from a different container!"); int origCount = item.mBase.getRefData().getCount(); item.mBase.getRefData().setCount(count); - MWWorld::ContainerStoreIterator it = MWWorld::Class::get(mActor).getContainerStore(mActor).add(item.mBase, mActor); - if (*it != item.mBase) - item.mBase.getRefData().setCount(origCount); - else - item.mBase.getRefData().setCount(origCount + count); // item copied onto itself + mActor.getClass().getContainerStore(mActor).add(item.mBase, mActor); + item.mBase.getRefData().setCount(origCount); } diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 547d2fe29..9b63dfa76 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -19,20 +19,16 @@ namespace MWGui : mSceneMgr(sceneMgr) , mWindow(rw) , WindowBase("openmw_loading_screen.layout") - , mLoadingOn(false) , mLastRenderTime(0.f) , mLastWallpaperChangeTime(0.f) , mFirstLoad(true) - , mTotalRefsLoading(0) - , mCurrentCellLoading(0) - , mTotalCellsLoading(0) - , mCurrentRefLoading(0) - , mCurrentRefList(0) + , mProgress(0) { getWidget(mLoadingText, "LoadingText"); getWidget(mProgressBar, "ProgressBar"); getWidget(mBackgroundImage, "BackgroundImage"); + mProgressBar->setScrollViewPage(1); mBackgroundMaterial = Ogre::MaterialManager::getSingleton().create("BackgroundMaterial", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); mBackgroundMaterial->getTechnique(0)->getPass(0)->setLightingEnabled(false); @@ -54,6 +50,11 @@ namespace MWGui mRectangle->setVisible(false); } + void LoadingScreen::setLabel(const std::string &label) + { + mLoadingText->setCaptionWithReplacing(label); + } + LoadingScreen::~LoadingScreen() { delete mRectangle; @@ -64,56 +65,108 @@ namespace MWGui setCoord(0,0,w,h); } - void LoadingScreen::setLoadingProgress (const std::string& stage, int depth, int current, int total) + void LoadingScreen::loadingOn() { - if (!mLoadingOn) - loadingOn(); - - const int numRefLists = 20; + setVisible(true); - if (depth == 0) + if (mFirstLoad) { - mCurrentCellLoading = current; - mTotalCellsLoading = total; - - mCurrentRefLoading = 0; - mCurrentRefList = 0; + changeWallpaper(); } - else if (depth == 1) + else { - mCurrentRefLoading = current; - mTotalRefsLoading = total; + mBackgroundImage->setImageTexture(""); } - assert (mTotalCellsLoading != 0); + MWBase::Environment::get().getWindowManager()->pushGuiMode(mFirstLoad ? GM_LoadingWallpaper : GM_Loading); + } + + void LoadingScreen::loadingOff() + { + setVisible(false); + + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Loading); + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_LoadingWallpaper); + } - float refProgress; - if (mTotalRefsLoading <= 1) - refProgress = 1; + void LoadingScreen::changeWallpaper () + { + if (mResources.empty()) + { + Ogre::StringVector groups = Ogre::ResourceGroupManager::getSingleton().getResourceGroups (); + for (Ogre::StringVector::iterator it = groups.begin(); it != groups.end(); ++it) + { + Ogre::StringVectorPtr resourcesInThisGroup = Ogre::ResourceGroupManager::getSingleton ().findResourceNames (*it, "Splash_*.tga"); + mResources.insert(mResources.end(), resourcesInThisGroup->begin(), resourcesInThisGroup->end()); + } + } + + if (!mResources.empty()) + { + std::string const & randomSplash = mResources.at (rand() % mResources.size()); + + Ogre::TexturePtr tex = Ogre::TextureManager::getSingleton ().load (randomSplash, Ogre::ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME); + + mBackgroundImage->setImageTexture (randomSplash); + } else - refProgress = float(mCurrentRefLoading) / float(mTotalRefsLoading-1); - refProgress += mCurrentRefList; - refProgress /= numRefLists; + std::cerr << "No loading screens found!" << std::endl; + } - assert(refProgress <= 1 && refProgress >= 0); + void LoadingScreen::setProgressRange (size_t range) + { + mProgressBar->setScrollRange(range+1); + mProgressBar->setScrollPosition(0); + mProgressBar->setTrackSize(0); + mProgress = 0; + } - if (depth == 1 && mCurrentRefLoading == mTotalRefsLoading-1) - ++mCurrentRefList; + void LoadingScreen::setProgress (size_t value) + { + assert(value < mProgressBar->getScrollRange()); + if (value - mProgress < mProgressBar->getScrollRange()/100.f) + return; + mProgress = value; + mProgressBar->setScrollPosition(0); + mProgressBar->setTrackSize(value / (float)(mProgressBar->getScrollRange()) * mProgressBar->getLineSize()); + draw(); + } - float progress = (float(mCurrentCellLoading)+refProgress) / float(mTotalCellsLoading); - assert(progress <= 1 && progress >= 0); + void LoadingScreen::increaseProgress (size_t increase) + { + mProgressBar->setScrollPosition(0); + size_t value = mProgress + increase; + mProgress = value; + assert(mProgress < mProgressBar->getScrollRange()); + mProgressBar->setTrackSize(value / (float)(mProgressBar->getScrollRange()) * mProgressBar->getLineSize()); + draw(); + } - mLoadingText->setCaption(stage); - mProgressBar->setProgressPosition (static_cast(progress * 1000)); + void LoadingScreen::indicateProgress() + { + float time = (mTimer.getMilliseconds() % 2001) / 1000.f; + if (time > 1) + time = (time-2)*-1; - static float loadingScreenFps = 30.f; + mProgressBar->setTrackSize(50); + mProgressBar->setScrollPosition(time * (mProgressBar->getScrollRange()-1)); + draw(); + } + + void LoadingScreen::removeWallpaper() + { + mFirstLoad = false; + } + + void LoadingScreen::draw() + { + const float loadingScreenFps = 20.f; if (mTimer.getMilliseconds () > mLastRenderTime + (1.f/loadingScreenFps) * 1000.f) { - float dt = mTimer.getMilliseconds () - mLastRenderTime; mLastRenderTime = mTimer.getMilliseconds (); - if (mFirstLoad && mTimer.getMilliseconds () > mLastWallpaperChangeTime + 3000*1) + if (mFirstLoad && mTimer.getMilliseconds () > mLastWallpaperChangeTime + 5000*1) { mLastWallpaperChangeTime = mTimer.getMilliseconds (); changeWallpaper(); @@ -129,8 +182,6 @@ namespace MWGui } mSceneMgr->setSpecialCaseRenderQueueMode(Ogre::SceneManager::SCRQM_EXCLUDE); - // always update input before rendering something, otherwise mygui goes crazy when something was entered in the frame before - // (e.g. when using "coc" console command, it would enter an infinite loop and crash due to overflow) MWBase::Environment::get().getInputManager()->update(0, true); Ogre::CompositorChain* chain = Ogre::CompositorManager::getSingleton().getCompositorChain(mWindow->getViewport(0)); @@ -156,9 +207,13 @@ namespace MWGui } } - MWBase::Environment::get().getWorld ()->getFader ()->update (dt); - - mWindow->update(); + // First, swap buffers from last draw, then, queue an update of the + // window contents, but don't swap buffers (which would have + // caused a sync / flush and would be expensive). + // We're doing this so we can do some actual loading while the GPU is busy with the render. + // This means the render is lagging a frame behind, but this is hardly noticable. + mWindow->swapBuffers(false); // never Vsync, makes no sense here + mWindow->update(false); if (!hasCompositor) mWindow->getViewport(0)->setClearEveryFrame(true); @@ -177,62 +232,4 @@ namespace MWGui mSceneMgr->setSpecialCaseRenderQueueMode(Ogre::SceneManager::SCRQM_EXCLUDE); } } - - void LoadingScreen::loadingDone() - { - loadingOff(); - } - - void LoadingScreen::loadingOn() - { - setVisible(true); - mLoadingOn = true; - - if (mFirstLoad) - { - changeWallpaper(); - - MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_LoadingWallpaper); - } - else - { - mBackgroundImage->setImageTexture(""); - MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Loading); - } - } - - - void LoadingScreen::loadingOff() - { - setVisible(false); - mLoadingOn = false; - mFirstLoad = false; - - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Loading); - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_LoadingWallpaper); - } - - void LoadingScreen::changeWallpaper () - { - if (mResources.empty()) - { - Ogre::StringVector groups = Ogre::ResourceGroupManager::getSingleton().getResourceGroups (); - for (Ogre::StringVector::iterator it = groups.begin(); it != groups.end(); ++it) - { - Ogre::StringVectorPtr resourcesInThisGroup = Ogre::ResourceGroupManager::getSingleton ().findResourceNames (*it, "Splash_*.tga"); - mResources.insert(mResources.end(), resourcesInThisGroup->begin(), resourcesInThisGroup->end()); - } - } - - if (!mResources.empty()) - { - std::string const & randomSplash = mResources.at (rand() % mResources.size()); - - Ogre::TexturePtr tex = Ogre::TextureManager::getSingleton ().load (randomSplash, Ogre::ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME); - - mBackgroundImage->setImageTexture (randomSplash); - } - else - std::cerr << "No loading screens found!" << std::endl; - } } diff --git a/apps/openmw/mwgui/loadingscreen.hpp b/apps/openmw/mwgui/loadingscreen.hpp index 87cedaa98..dde8ff63a 100644 --- a/apps/openmw/mwgui/loadingscreen.hpp +++ b/apps/openmw/mwgui/loadingscreen.hpp @@ -5,11 +5,27 @@ #include "windowbase.hpp" +#include + namespace MWGui { - class LoadingScreen : public WindowBase + class LoadingScreen : public WindowBase, public Loading::Listener { public: + virtual void setLabel (const std::string& label); + + /// Indicate that some progress has been made, without specifying how much + virtual void indicateProgress (); + + virtual void loadingOn(); + virtual void loadingOff(); + + virtual void setProgressRange (size_t range); + virtual void setProgress (size_t value); + virtual void increaseProgress (size_t increase); + + virtual void removeWallpaper(); + LoadingScreen(Ogre::SceneManager* sceneMgr, Ogre::RenderWindow* rw); virtual ~LoadingScreen(); @@ -30,27 +46,20 @@ namespace MWGui unsigned long mLastRenderTime; Ogre::Timer mTimer; + size_t mProgress; + MyGUI::TextBox* mLoadingText; - MyGUI::ProgressBar* mProgressBar; + MyGUI::ScrollBar* mProgressBar; MyGUI::ImageBox* mBackgroundImage; - int mCurrentCellLoading; - int mTotalCellsLoading; - int mCurrentRefLoading; - int mTotalRefsLoading; - int mCurrentRefList; - Ogre::Rectangle2D* mRectangle; Ogre::MaterialPtr mBackgroundMaterial; Ogre::StringVector mResources; - bool mLoadingOn; - - void loadingOn(); - void loadingOff(); - void changeWallpaper(); + + void draw(); }; } diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index 0ccfb7e88..5ed002d7b 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -260,12 +260,10 @@ namespace MWGui MapWindow::MapWindow(const std::string& cacheDir) : MWGui::WindowPinnableBase("openmw_map_window.layout") , mGlobal(false) + , mGlobalMap(0) { setCoord(500,0,320,300); - mGlobalMapRender = new MWRender::GlobalMap(cacheDir); - mGlobalMapRender->render(); - getWidget(mLocalMap, "LocalMap"); getWidget(mGlobalMap, "GlobalMap"); getWidget(mGlobalMapImage, "GlobalMapImage"); @@ -273,9 +271,6 @@ namespace MWGui getWidget(mPlayerArrowLocal, "CompassLocal"); getWidget(mPlayerArrowGlobal, "CompassGlobal"); - mGlobalMapImage->setImageTexture("GlobalMap.png"); - mGlobalMapOverlay->setImageTexture("GlobalMapOverlay"); - mGlobalMap->setVisible (false); getWidget(mButton, "WorldButton"); @@ -292,6 +287,14 @@ namespace MWGui LocalMapBase::init(mLocalMap, mPlayerArrowLocal, this); } + void MapWindow::renderGlobalMap(Loading::Listener* loadingListener) + { + mGlobalMapRender = new MWRender::GlobalMap(""); + mGlobalMapRender->render(loadingListener); + mGlobalMapImage->setImageTexture("GlobalMap.png"); + mGlobalMapOverlay->setImageTexture("GlobalMapOverlay"); + } + MapWindow::~MapWindow() { delete mGlobalMapRender; diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index 3aefc398c..5518ab4a8 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -8,6 +8,11 @@ namespace MWRender class GlobalMap; } +namespace Loading +{ + class Listener; +} + namespace MWGui { class LocalMapBase @@ -71,6 +76,8 @@ namespace MWGui void setCellName(const std::string& cellName); + void renderGlobalMap(Loading::Listener* loadingListener); + void addVisitedLocation(const std::string& name, int x, int y); // adds the marker to the global map void cellExplored(int x, int y); diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index a5fc2ca5d..5f749d3d3 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -605,7 +605,6 @@ namespace MWGui void MagicSelectionDialog::onEnchantedItemSelected(MyGUI::Widget* _sender) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); MWWorld::Ptr item = *_sender->getUserData(); mParent->onAssignMagicItem (item); diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 4ce6ac0bf..3dfa17bad 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -125,7 +125,7 @@ namespace MWGui getWidget(mActorShadows, "ActorShadows"); getWidget(mStaticsShadows, "StaticsShadows"); getWidget(mMiscShadows, "MiscShadows"); - getWidget(mShadowsDebug, "ShadowsDebug"); + getWidget(mTerrainShadows, "TerrainShadows"); getWidget(mControlsBox, "ControlsBox"); getWidget(mResetControlsButton, "ResetControlsButton"); getWidget(mInvertYButton, "InvertYButton"); @@ -161,7 +161,7 @@ namespace MWGui mActorShadows->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mStaticsShadows->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mMiscShadows->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); - mShadowsDebug->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); + mTerrainShadows->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mMasterVolumeSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); mVoiceVolumeSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); @@ -238,7 +238,7 @@ namespace MWGui mActorShadows->setCaptionWithReplacing(Settings::Manager::getBool("actor shadows", "Shadows") ? "#{sOn}" : "#{sOff}"); mStaticsShadows->setCaptionWithReplacing(Settings::Manager::getBool("statics shadows", "Shadows") ? "#{sOn}" : "#{sOff}"); mMiscShadows->setCaptionWithReplacing(Settings::Manager::getBool("misc shadows", "Shadows") ? "#{sOn}" : "#{sOff}"); - mShadowsDebug->setCaptionWithReplacing(Settings::Manager::getBool("debug", "Shadows") ? "#{sOn}" : "#{sOff}"); + mTerrainShadows->setCaptionWithReplacing(Settings::Manager::getBool("terrain shadows", "Shadows") ? "#{sOn}" : "#{sOff}"); float cameraSens = (Settings::Manager::getFloat("camera sensitivity", "Input")-0.2)/(5.0-0.2); mCameraSensitivitySlider->setScrollPosition (cameraSens * (mCameraSensitivitySlider->getScrollRange()-1)); @@ -394,8 +394,8 @@ namespace MWGui Settings::Manager::setBool("statics shadows", "Shadows", newState); else if (_sender == mMiscShadows) Settings::Manager::setBool("misc shadows", "Shadows", newState); - else if (_sender == mShadowsDebug) - Settings::Manager::setBool("debug", "Shadows", newState); + else if (_sender == mTerrainShadows) + Settings::Manager::setBool("terrain shadows", "Shadows", newState); else if (_sender == mInvertYButton) Settings::Manager::setBool("invert y axis", "Input", newState); else if (_sender == mCrosshairButton) diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index 42ed5bf6d..a585bda7e 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -59,7 +59,7 @@ namespace MWGui MyGUI::Button* mActorShadows; MyGUI::Button* mStaticsShadows; MyGUI::Button* mMiscShadows; - MyGUI::Button* mShadowsDebug; + MyGUI::Button* mTerrainShadows; // audio MyGUI::ScrollBar* mMasterVolumeSlider; diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp index 83e158e4a..93ac8299d 100644 --- a/apps/openmw/mwgui/travelwindow.cpp +++ b/apps/openmw/mwgui/travelwindow.cpp @@ -100,8 +100,6 @@ namespace MWGui mPtr = actor; clearDestinations(); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); - for(unsigned int i = 0;i()->mBase->mTransport.size();i++) { std::string cellname = mPtr.get()->mBase->mTransport[i].mCellName; diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index c0a30206b..bf8b664da 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -62,6 +62,7 @@ namespace MWGui const std::string& logpath, const std::string& cacheDir, bool consoleOnlyScripts, Translation::Storage& translationDataStorage, ToUTF8::FromType encoding) : mGuiManager(NULL) + , mConsoleOnlyScripts(consoleOnlyScripts) , mRendering(ogre) , mHud(NULL) , mMap(NULL) @@ -156,7 +157,28 @@ namespace MWGui MyGUI::LanguageManager::getInstance().eventRequestTag = MyGUI::newDelegate(this, &WindowManager::onRetrieveTag); // Get size info from the Gui object - assert(mGui); + int w = MyGUI::RenderManager::getInstance().getViewSize().width; + int h = MyGUI::RenderManager::getInstance().getViewSize().height; + + mLoadingScreen = new LoadingScreen(mRendering->getScene (), mRendering->getWindow ()); + mLoadingScreen->onResChange (w,h); + + //set up the hardware cursor manager + mSoftwareCursor = new Cursor(); + mCursorManager = new SFO::SDLCursorManager(); + + MyGUI::PointerManager::getInstance().eventChangeMousePointer += MyGUI::newDelegate(this, &WindowManager::onCursorChange); + + MyGUI::InputManager::getInstance().eventChangeKeyFocus += MyGUI::newDelegate(this, &WindowManager::onKeyFocusChanged); + + setUseHardwareCursors(mUseHardwareCursors); + onCursorChange(MyGUI::PointerManager::getInstance().getDefaultPointer()); + mCursorManager->cursorVisibilityChange(false); + } + + void WindowManager::initUI() + { + // Get size info from the Gui object int w = MyGUI::RenderManager::getInstance().getViewSize().width; int h = MyGUI::RenderManager::getInstance().getViewSize().height; @@ -169,9 +191,9 @@ namespace MWGui mDragAndDrop->mDragAndDropWidget = dragAndDropWidget; mMenu = new MainMenu(w,h); - mMap = new MapWindow(cacheDir); + mMap = new MapWindow(""); mStatsWindow = new StatsWindow(); - mConsole = new Console(w,h, consoleOnlyScripts); + mConsole = new Console(w,h, mConsoleOnlyScripts); mJournal = JournalWindow::create(JournalViewModel::create ()); mMessageBoxManager = new MessageBoxManager(); mInventoryWindow = new InventoryWindow(mDragAndDrop); @@ -200,13 +222,8 @@ namespace MWGui mSoulgemDialog = new SoulgemDialog(mMessageBoxManager); mCompanionWindow = new CompanionWindow(mDragAndDrop, mMessageBoxManager); - mLoadingScreen = new LoadingScreen(mRendering->getScene (), mRendering->getWindow ()); - mLoadingScreen->onResChange (w,h); - mInputBlocker = mGui->createWidget("",0,0,w,h,MyGUI::Align::Default,"Windows",""); - mSoftwareCursor = new Cursor(); - mHud->setVisible(mHudEnabled); mCharGen = new CharacterCreation(); @@ -225,19 +242,15 @@ namespace MWGui unsetSelectedSpell(); unsetSelectedWeapon(); - //set up the hardware cursor manager - mCursorManager = new SFO::SDLCursorManager(); - - MyGUI::PointerManager::getInstance().eventChangeMousePointer += MyGUI::newDelegate(this, &WindowManager::onCursorChange); - - MyGUI::InputManager::getInstance().eventChangeKeyFocus += MyGUI::newDelegate(this, &WindowManager::onKeyFocusChanged); - - setUseHardwareCursors(mUseHardwareCursors); - onCursorChange(MyGUI::PointerManager::getInstance().getDefaultPointer()); - mCursorManager->cursorVisibilityChange(false); - // Set up visibility updateVisible(); + + MWBase::Environment::get().getInputManager()->changeInputMode(false); + } + + void WindowManager::renderWorldMap() + { + mMap->renderGlobalMap(mLoadingScreen); } void WindowManager::setNewGame(bool newgame) @@ -248,6 +261,9 @@ namespace MWGui delete mCharGen; mCharGen = new CharacterCreation(); mGuiModes.clear(); + mHud->unsetSelectedWeapon(); + mHud->unsetSelectedSpell(); + unsetForceHide(GW_ALL); } else allow(GW_ALL); @@ -326,6 +342,8 @@ namespace MWGui void WindowManager::updateVisible() { + if (!mMap) + return; // UI not created yet // Start out by hiding everything except the HUD mMap->setVisible(false); mMenu->setVisible(false); @@ -911,6 +929,10 @@ namespace MWGui void WindowManager::windowResized(int x, int y) { + mGuiManager->windowResized(); + mLoadingScreen->onResChange (x,y); + if (!mHud) + return; // UI not initialized yet mHud->onResChange(x, y); mConsole->onResChange(x, y); mMenu->onResChange(x, y); @@ -920,10 +942,8 @@ namespace MWGui mBookWindow->center(); mQuickKeysMenu->center(); mSpellBuyingWindow->center(); - mLoadingScreen->onResChange (x,y); mDragAndDrop->mDragAndDropWidget->setSize(MyGUI::IntSize(x, y)); mInputBlocker->setSize(MyGUI::IntSize(x,y)); - mGuiManager->windowResized(); } void WindowManager::pushGuiMode(GuiMode mode) @@ -1145,7 +1165,7 @@ namespace MWGui bool WindowManager::isGuiMode() const { - return !mGuiModes.empty() || mMessageBoxManager->isInteractiveMessageBox(); + return !mGuiModes.empty() || (mMessageBoxManager && mMessageBoxManager->isInteractiveMessageBox()); } bool WindowManager::isConsoleMode() const @@ -1208,7 +1228,8 @@ namespace MWGui void WindowManager::showCrosshair (bool show) { - mHud->setCrosshairVisible (show && mCrosshairEnabled); + if (mHud) + mHud->setCrosshairVisible (show && mCrosshairEnabled); } void WindowManager::activateQuickKey (int index) @@ -1227,15 +1248,6 @@ namespace MWGui mHud->setVisible (mHudEnabled); } - void WindowManager::setLoadingProgress (const std::string& stage, int depth, int current, int total) - { - mLoadingScreen->setLoadingProgress (stage, depth, current, total); - } - - void WindowManager::loadingDone () - { - mLoadingScreen->loadingDone (); - } bool WindowManager::getRestEnabled() { //Enable rest dialogue if character creation finished @@ -1342,4 +1354,9 @@ namespace MWGui mHud->setEnemy(enemy); } + Loading::Listener* WindowManager::getLoadingScreen() + { + return mLoadingScreen; + } + } diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index e49b33647..badb333a7 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -91,6 +91,11 @@ namespace MWGui Translation::Storage& translationDataStorage, ToUTF8::FromType encoding); virtual ~WindowManager(); + void initUI(); + void renderWorldMap(); + + virtual Loading::Listener* getLoadingScreen(); + /** * Should be called each frame to update windows/gui elements. * This could mean updating sizes of gui elements or opening @@ -241,9 +246,6 @@ namespace MWGui virtual void executeInConsole (const std::string& path); - virtual void setLoadingProgress (const std::string& stage, int depth, int current, int total); - virtual void loadingDone(); - virtual void enableRest() { mRestAllowed = true; } virtual bool getRestEnabled(); @@ -275,6 +277,8 @@ namespace MWGui void onSoulgemDialogButtonPressed (int button); private: + bool mConsoleOnlyScripts; + OEngine::GUI::MyGUIManager *mGuiManager; OEngine::Render::OgreRenderer *mRendering; HUD *mHud; diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 573fe389c..1039a0dce 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -86,13 +86,10 @@ namespace namespace MWInput { InputManager::InputManager(OEngine::Render::OgreRenderer &ogre, - MWWorld::Player& player, - MWBase::WindowManager &windows, OMW::Engine& engine, const std::string& userFile, bool userFileExists) : mOgre(ogre) - , mPlayer(player) - , mWindows(windows) + , mPlayer(NULL) , mEngine(engine) , mMouseLookEnabled(true) , mMouseX(ogre.getWindow()->getWidth ()/2.f) @@ -124,8 +121,6 @@ namespace MWInput adjustMouseRegion (window->getWidth(), window->getHeight()); - MyGUI::InputManager::getInstance().injectMouseMove(mMouseX, mMouseY, 0); - loadKeyDefaults(); for (int i = 0; i < A_Last; ++i) @@ -140,8 +135,6 @@ namespace MWInput mControlSwitch["playermagic"] = true; mControlSwitch["playerviewswitch"] = true; mControlSwitch["vanitymode"] = true; - - changeInputMode(false); } InputManager::~InputManager() @@ -164,7 +157,7 @@ namespace MWInput if (action == A_Use) { - MWWorld::Class::get(mPlayer.getPlayer()).getCreatureStats(mPlayer.getPlayer()).setAttackingOrSpell(currentValue); + MWWorld::Class::get(mPlayer->getPlayer()).getCreatureStats(mPlayer->getPlayer()).setAttackingOrSpell(currentValue); if (currentValue == 1) { int type = MWMechanics::CreatureStats::AT_Chop; @@ -177,7 +170,7 @@ namespace MWInput if (forward && !side) type = MWMechanics::CreatureStats::AT_Thrust; - MWWorld::Class::get(mPlayer.getPlayer()).getCreatureStats(mPlayer.getPlayer()).setAttackType(type); + MWWorld::Class::get(mPlayer->getPlayer()).getCreatureStats(mPlayer->getPlayer()).setAttackType(type); } } @@ -204,9 +197,9 @@ namespace MWInput case A_Activate: resetIdleTime(); - if (mWindows.isGuiMode()) + if (MWBase::Environment::get().getWindowManager()->isGuiMode()) { - if (mWindows.getMode() == MWGui::GM_Container) + if (MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_Container) toggleContainer (); else MWBase::Environment::get().getWindowManager()->activateKeyPressed(); @@ -266,7 +259,7 @@ namespace MWInput showQuickKeysMenu(); break; case A_ToggleHUD: - mWindows.toggleHud(); + MWBase::Environment::get().getWindowManager()->toggleHud(); break; } } @@ -274,8 +267,7 @@ namespace MWInput void InputManager::update(float dt, bool loading) { - // Tell OIS to handle all input events - mInputManager->capture(); + mInputManager->capture(loading); // inject some fake mouse movement to force updating MyGUI's widget states // this shouldn't do any harm since we're moving back to the original position afterwards MyGUI::InputManager::getInstance().injectMouseMove( int(mMouseX+1), int(mMouseY+1), mMouseWheel); @@ -285,18 +277,10 @@ namespace MWInput if (!loading) mInputBinder->update(dt); - // Update windows/gui as a result of input events - // For instance this could mean opening a new window/dialog, - // by doing this after the input events are handled we - // ensure that window/gui changes appear quickly while - // avoiding that window/gui changes does not happen in - // event callbacks (which may crash) - mWindows.update(); - - bool main_menu = mWindows.containsMode(MWGui::GM_MainMenu); + bool main_menu = MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu); bool was_relative = mInputManager->getMouseRelative(); - bool is_relative = !mWindows.isGuiMode(); + bool is_relative = !MWBase::Environment::get().getWindowManager()->isGuiMode(); // don't keep the pointer away from the window edge in gui mode // stop using raw mouse motions and switch to system cursor movements @@ -312,8 +296,11 @@ namespace MWInput mInputManager->warpMouse(mMouseX, mMouseY); } + if (loading) + return; + // Disable movement in Gui mode - if (mWindows.isGuiMode()) return; + if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return; // Configure player movement according to keyboard input. Actual movement will @@ -324,45 +311,45 @@ namespace MWInput if (actionIsActive(A_MoveLeft)) { triedToMove = true; - mPlayer.setLeftRight (-1); + mPlayer->setLeftRight (-1); } else if (actionIsActive(A_MoveRight)) { triedToMove = true; - mPlayer.setLeftRight (1); + mPlayer->setLeftRight (1); } if (actionIsActive(A_MoveForward)) { triedToMove = true; - mPlayer.setAutoMove (false); - mPlayer.setForwardBackward (1); + mPlayer->setAutoMove (false); + mPlayer->setForwardBackward (1); } else if (actionIsActive(A_MoveBackward)) { triedToMove = true; - mPlayer.setAutoMove (false); - mPlayer.setForwardBackward (-1); + mPlayer->setAutoMove (false); + mPlayer->setForwardBackward (-1); } - else if(mPlayer.getAutoMove()) + else if(mPlayer->getAutoMove()) { triedToMove = true; - mPlayer.setForwardBackward (1); + mPlayer->setForwardBackward (1); } - mPlayer.setSneak(actionIsActive(A_Sneak)); + mPlayer->setSneak(actionIsActive(A_Sneak)); if (actionIsActive(A_Jump) && mControlSwitch["playerjumping"]) { - mPlayer.setUpDown (1); + mPlayer->setUpDown (1); triedToMove = true; } if (mAlwaysRunActive) - mPlayer.setRunState(!actionIsActive(A_Run)); + mPlayer->setRunState(!actionIsActive(A_Run)); else - mPlayer.setRunState(actionIsActive(A_Run)); + mPlayer->setRunState(actionIsActive(A_Run)); // if player tried to start moving, but can't (due to being overencumbered), display a notification. if (triedToMove) @@ -371,7 +358,7 @@ namespace MWInput mOverencumberedMessageDelay -= dt; if (MWWorld::Class::get(player).getEncumbrance(player) >= MWWorld::Class::get(player).getCapacity(player)) { - mPlayer.setAutoMove (false); + mPlayer->setAutoMove (false); if (mOverencumberedMessageDelay <= 0) { MWBase::Environment::get().getWindowManager ()->messageBox("#{sNotifyMessage59}"); @@ -425,8 +412,8 @@ namespace MWInput mGuiCursorEnabled = guiMode; mMouseLookEnabled = !guiMode; if (guiMode) - mWindows.showCrosshair(false); - mWindows.setCursorVisible(guiMode); + MWBase::Environment::get().getWindowManager()->showCrosshair(false); + MWBase::Environment::get().getWindowManager()->setCursorVisible(guiMode); // if not in gui mode, the camera decides whether to show crosshair or not. } @@ -459,13 +446,13 @@ namespace MWInput } /// \note 7 switches at all, if-else is relevant if (sw == "playercontrols" && !value) { - mPlayer.setLeftRight(0); - mPlayer.setForwardBackward(0); - mPlayer.setAutoMove(false); - mPlayer.setUpDown(0); + mPlayer->setLeftRight(0); + mPlayer->setForwardBackward(0); + mPlayer->setAutoMove(false); + mPlayer->setUpDown(0); } else if (sw == "playerjumping" && !value) { /// \fixme maybe crouching at this time - mPlayer.setUpDown(0); + mPlayer->setUpDown(0); } else if (sw == "vanitymode") { MWBase::Environment::get().getWorld()->allowVanityMode(value); } else if (sw == "playerlooking") { @@ -594,8 +581,8 @@ namespace MWInput // Only actually turn player when we're not in vanity mode if(!MWBase::Environment::get().getWorld()->vanityRotateCamera(rot)) { - mPlayer.yaw(x/scale); - mPlayer.pitch(-y/scale); + mPlayer->yaw(x/scale); + mPlayer->pitch(-y/scale); } if (arg.zrel) @@ -627,51 +614,51 @@ namespace MWInput if (MyGUI::InputManager::getInstance ().isModalAny()) return; - if (mWindows.isGuiMode () && mWindows.getMode () == MWGui::GM_Video) + if (MWBase::Environment::get().getWindowManager()->isGuiMode () && MWBase::Environment::get().getWindowManager()->getMode () == MWGui::GM_Video) MWBase::Environment::get().getWorld ()->stopVideo (); - else if (mWindows.containsMode(MWGui::GM_MainMenu)) - mWindows.popGuiMode(); + else if (MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu)) + MWBase::Environment::get().getWindowManager()->popGuiMode(); else - mWindows.pushGuiMode (MWGui::GM_MainMenu); + MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); } void InputManager::toggleSpell() { - if (mWindows.isGuiMode()) return; + if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return; // Not allowed before the magic window is accessible - if (!mWindows.isAllowed(MWGui::GW_Magic)) + if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Magic)) return; - MWMechanics::DrawState_ state = mPlayer.getDrawState(); + MWMechanics::DrawState_ state = mPlayer->getDrawState(); if (state == MWMechanics::DrawState_Weapon || state == MWMechanics::DrawState_Nothing) - mPlayer.setDrawState(MWMechanics::DrawState_Spell); + mPlayer->setDrawState(MWMechanics::DrawState_Spell); else - mPlayer.setDrawState(MWMechanics::DrawState_Nothing); + mPlayer->setDrawState(MWMechanics::DrawState_Nothing); } void InputManager::toggleWeapon() { - if (mWindows.isGuiMode()) return; + if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return; // Not allowed before the inventory window is accessible - if (!mWindows.isAllowed(MWGui::GW_Inventory)) + if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) return; - MWMechanics::DrawState_ state = mPlayer.getDrawState(); + MWMechanics::DrawState_ state = mPlayer->getDrawState(); if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing) - mPlayer.setDrawState(MWMechanics::DrawState_Weapon); + mPlayer->setDrawState(MWMechanics::DrawState_Weapon); else - mPlayer.setDrawState(MWMechanics::DrawState_Nothing); + mPlayer->setDrawState(MWMechanics::DrawState_Nothing); } void InputManager::rest() { - if (!mWindows.getRestEnabled () || mWindows.isGuiMode ()) + if (!MWBase::Environment::get().getWindowManager()->getRestEnabled () || MWBase::Environment::get().getWindowManager()->isGuiMode ()) return; /// \todo check if resting is currently allowed (enemies nearby?) - mWindows.pushGuiMode (MWGui::GM_Rest); + MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_Rest); } void InputManager::screenshot() @@ -679,7 +666,7 @@ namespace MWInput mEngine.screenshot(); std::vector empty; - mWindows.messageBox ("Screenshot saved", empty); + MWBase::Environment::get().getWindowManager()->messageBox ("Screenshot saved", empty); } void InputManager::toggleInventory() @@ -688,13 +675,13 @@ namespace MWInput return; // Toggle between game mode and inventory mode - if(!mWindows.isGuiMode()) - mWindows.pushGuiMode(MWGui::GM_Inventory); + if(!MWBase::Environment::get().getWindowManager()->isGuiMode()) + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Inventory); else { - MWGui::GuiMode mode = mWindows.getMode(); + MWGui::GuiMode mode = MWBase::Environment::get().getWindowManager()->getMode(); if(mode == MWGui::GM_Inventory || mode == MWGui::GM_Container) - mWindows.popGuiMode(); + MWBase::Environment::get().getWindowManager()->popGuiMode(); } // .. but don't touch any other mode, except container. @@ -705,12 +692,12 @@ namespace MWInput if (MyGUI::InputManager::getInstance ().isModalAny()) return; - if(mWindows.isGuiMode()) + if(MWBase::Environment::get().getWindowManager()->isGuiMode()) { - if (mWindows.getMode() == MWGui::GM_Container) - mWindows.popGuiMode(); + if (MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_Container) + MWBase::Environment::get().getWindowManager()->popGuiMode(); else - mWindows.pushGuiMode(MWGui::GM_Container); + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Container); } } @@ -722,15 +709,15 @@ namespace MWInput // Switch to console mode no matter what mode we are currently // in, except of course if we are already in console mode - if (mWindows.isGuiMode()) + if (MWBase::Environment::get().getWindowManager()->isGuiMode()) { - if (mWindows.getMode() == MWGui::GM_Console) - mWindows.popGuiMode(); + if (MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_Console) + MWBase::Environment::get().getWindowManager()->popGuiMode(); else - mWindows.pushGuiMode(MWGui::GM_Console); + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Console); } else - mWindows.pushGuiMode(MWGui::GM_Console); + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Console); } void InputManager::toggleJournal() @@ -739,31 +726,31 @@ namespace MWInput return; // Toggle between game mode and journal mode - if(!mWindows.isGuiMode() && MWBase::Environment::get().getWindowManager ()->getJournalAllowed()) + if(!MWBase::Environment::get().getWindowManager()->isGuiMode() && MWBase::Environment::get().getWindowManager ()->getJournalAllowed()) { MWBase::Environment::get().getSoundManager()->playSound ("book open", 1.0, 1.0); - mWindows.pushGuiMode(MWGui::GM_Journal); + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Journal); } - else if(mWindows.getMode() == MWGui::GM_Journal) + else if(MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_Journal) { MWBase::Environment::get().getSoundManager()->playSound ("book close", 1.0, 1.0); - mWindows.popGuiMode(); + MWBase::Environment::get().getWindowManager()->popGuiMode(); } // .. but don't touch any other mode. } void InputManager::quickKey (int index) { - if (!mWindows.isGuiMode()) - mWindows.activateQuickKey (index); + if (!MWBase::Environment::get().getWindowManager()->isGuiMode()) + MWBase::Environment::get().getWindowManager()->activateQuickKey (index); } void InputManager::showQuickKeysMenu() { - if (!mWindows.isGuiMode ()) - mWindows.pushGuiMode (MWGui::GM_QuickKeysMenu); - else if (mWindows.getMode () == MWGui::GM_QuickKeysMenu) - mWindows.removeGuiMode (MWGui::GM_QuickKeysMenu); + if (!MWBase::Environment::get().getWindowManager()->isGuiMode ()) + MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_QuickKeysMenu); + else if (MWBase::Environment::get().getWindowManager()->getMode () == MWGui::GM_QuickKeysMenu) + MWBase::Environment::get().getWindowManager()->removeGuiMode (MWGui::GM_QuickKeysMenu); } void InputManager::activate() @@ -774,22 +761,22 @@ namespace MWInput void InputManager::toggleAutoMove() { - if (mWindows.isGuiMode()) return; + if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return; if (mControlSwitch["playercontrols"]) - mPlayer.setAutoMove (!mPlayer.getAutoMove()); + mPlayer->setAutoMove (!mPlayer->getAutoMove()); } void InputManager::toggleWalking() { - if (mWindows.isGuiMode()) return; + if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return; mAlwaysRunActive = !mAlwaysRunActive; } // Exit program now button (which is disabled in GUI mode) void InputManager::exitNow() { - if(!mWindows.isGuiMode()) + if(!MWBase::Environment::get().getWindowManager()->isGuiMode()) Ogre::Root::getSingleton().queueEndRendering (); } diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index ef7ef75a8..5f9a752d7 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -60,8 +60,6 @@ namespace MWInput { public: InputManager(OEngine::Render::OgreRenderer &_ogre, - MWWorld::Player&_player, - MWBase::WindowManager &_windows, OMW::Engine& engine, const std::string& userFile, bool userFileExists); @@ -69,6 +67,8 @@ namespace MWInput virtual void update(float dt, bool loading); + void setPlayer (MWWorld::Player* player) { mPlayer = player; } + virtual void changeInputMode(bool guiMode); virtual void processChangedSettings(const Settings::CategorySettingVector& changed); @@ -125,8 +125,7 @@ namespace MWInput private: OEngine::Render::OgreRenderer &mOgre; - MWWorld::Player& mPlayer; - MWBase::WindowManager &mWindows; + MWWorld::Player* mPlayer; OMW::Engine& mEngine; ICS::InputControlSystem* mInputBinder; diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 10d609384..566d4bc50 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -11,10 +11,12 @@ #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" +#include "../mwworld/player.hpp" #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/soundmanager.hpp" #include "npcstats.hpp" #include "creaturestats.hpp" @@ -68,22 +70,24 @@ namespace MWMechanics { CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr); - int strength = creatureStats.getAttribute(0).getBase(); - int intelligence = creatureStats.getAttribute(1).getBase(); - int willpower = creatureStats.getAttribute(2).getBase(); - int agility = creatureStats.getAttribute(3).getBase(); - int endurance = creatureStats.getAttribute(5).getBase(); + int strength = creatureStats.getAttribute(ESM::Attribute::Strength).getBase(); + int intelligence = creatureStats.getAttribute(ESM::Attribute::Intelligence).getBase(); + int willpower = creatureStats.getAttribute(ESM::Attribute::Willpower).getBase(); + int agility = creatureStats.getAttribute(ESM::Attribute::Agility).getBase(); + int endurance = creatureStats.getAttribute(ESM::Attribute::Endurance).getBase(); double magickaFactor = creatureStats.getMagicEffects().get (EffectKey (ESM::MagicEffect::FortifyMaximumMagicka)).mMagnitude * 0.1 + 0.5; DynamicStat magicka = creatureStats.getMagicka(); - magicka.setBase (static_cast (intelligence + magickaFactor * intelligence)); - creatureStats.setMagicka (magicka); + float diff = (static_cast(intelligence + magickaFactor*intelligence)) - magicka.getBase(); + magicka.modify(diff); + creatureStats.setMagicka(magicka); DynamicStat fatigue = creatureStats.getFatigue(); - fatigue.setBase (strength+willpower+agility+endurance); - creatureStats.setFatigue (fatigue); + diff = (strength+willpower+agility+endurance) - fatigue.getBase(); + fatigue.modify(diff); + creatureStats.setFatigue(fatigue); } void Actors::calculateRestoration (const MWWorld::Ptr& ptr, float duration) @@ -133,62 +137,63 @@ namespace MWMechanics void Actors::calculateCreatureStatModifiers (const MWWorld::Ptr& ptr) { - CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr); + CreatureStats &creatureStats = MWWorld::Class::get(ptr).getCreatureStats(ptr); + const MagicEffects &effects = creatureStats.getMagicEffects(); // attributes - for (int i=0; i<8; ++i) + for(int i = 0;i < ESM::Attribute::Length;++i) { - int modifier = - creatureStats.getMagicEffects().get (EffectKey (ESM::MagicEffect::FortifyAttribute, i)).mMagnitude; - - modifier -= creatureStats.getMagicEffects().get (EffectKey (ESM::MagicEffect::DrainAttribute, i)).mMagnitude; + Stat stat = creatureStats.getAttribute(i); + stat.setModifier(effects.get(EffectKey(ESM::MagicEffect::FortifyAttribute, i)).mMagnitude - + effects.get(EffectKey(ESM::MagicEffect::DrainAttribute, i)).mMagnitude); - creatureStats.getAttribute(i).setModifier (modifier); + creatureStats.setAttribute(i, stat); } // dynamic stats - MagicEffects effects = creatureStats.getMagicEffects(); - - for (int i=0; i<3; ++i) + for(int i = 0;i < 3;++i) { - DynamicStat stat = creatureStats.getDynamic (i); + DynamicStat stat = creatureStats.getDynamic(i); + stat.setModifier(effects.get(EffectKey(80+i)).mMagnitude - + effects.get(EffectKey(18+i)).mMagnitude); - stat.setModifier ( - effects.get (EffectKey(80+i)).mMagnitude - effects.get (EffectKey(18+i)).mMagnitude); - - creatureStats.setDynamic (i, stat); + creatureStats.setDynamic(i, stat); } } void Actors::updateDrowning(const MWWorld::Ptr& ptr, float duration) { - NpcStats &stats = MWWorld::Class::get(ptr).getNpcStats(ptr); - if(MWBase::Environment::get().getWorld()->isSubmerged(ptr) && + MWBase::World *world = MWBase::Environment::get().getWorld(); + NpcStats &stats = ptr.getClass().getNpcStats(ptr); + if(world->isSubmerged(ptr) && stats.getMagicEffects().get(ESM::MagicEffect::WaterBreathing).mMagnitude == 0) { + float timeLeft = 0.0f; if(stats.getFatigue().getCurrent() == 0) stats.setTimeToStartDrowning(0); - - float timeLeft = stats.getTimeToStartDrowning()-duration; - if(timeLeft < 0.0f) - timeLeft = 0.0f; - - stats.setTimeToStartDrowning(timeLeft); + else + { + timeLeft = stats.getTimeToStartDrowning() - duration; + if(timeLeft < 0.0f) + timeLeft = 0.0f; + stats.setTimeToStartDrowning(timeLeft); + } if(timeLeft == 0.0f) - stats.setLastDrowningHitTime(stats.getLastDrowningHitTime()+duration); + { + // If drowning, apply 3 points of damage per second + ptr.getClass().setActorHealth(ptr, stats.getHealth().getCurrent() - 3.0f*duration); + + // Play a drowning sound as necessary for the player + if(ptr == world->getPlayer().getPlayer()) + { + MWBase::SoundManager *sndmgr = MWBase::Environment::get().getSoundManager(); + if(!sndmgr->getSoundPlaying(MWWorld::Ptr(), "drown")) + sndmgr->playSound("drown", 1.0f, 1.0f); + } + } } else - { stats.setTimeToStartDrowning(20); - stats.setLastDrowningHitTime(0); - } - //if npc is drowning and it's time to hit, then hit - while(stats.getTimeToStartDrowning() == 0.0f && stats.getLastDrowningHitTime() > 0.33f) - { - stats.setLastDrowningHitTime(stats.getLastDrowningHitTime()-0.33f); - //fixme: replace it with something different once screen hit effects are implemented (blood on screen) - MWWorld::Class::get(ptr).setActorHealth(ptr, stats.getHealth().getCurrent()-1.0f); - } } Actors::Actors() : mDuration (0) {} @@ -300,17 +305,8 @@ namespace MWMechanics if(!paused) { - mMovement.reserve(mActors.size()); - for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter) - { - Movement movement; - iter->second->update(duration, movement); - mMovement.push_back(std::make_pair(iter->first, movement)); - } - MWBase::Environment::get().getWorld()->doPhysics(mMovement, duration); - - mMovement.clear(); + iter->second->update(duration); } } diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index a289a9174..69878a000 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -28,8 +28,6 @@ namespace MWMechanics typedef std::map PtrControllerMap; PtrControllerMap mActors; - MWWorld::PtrMovementList mMovement; - std::map mDeathCount; float mDuration; diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index 6b43a36f6..d47a49c70 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -33,46 +33,48 @@ namespace MWMechanics bool AiTravel::execute (const MWWorld::Ptr& actor) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWBase::World *world = MWBase::Environment::get().getWorld(); ESM::Position pos = actor.getRefData().getPosition(); - bool cellChange = actor.getCell()->mCell->mData.mX != cellX || actor.getCell()->mCell->mData.mY != cellY; - const ESM::Pathgrid *pathgrid = - MWBase::Environment::get().getWorld()->getStore().get().search(*actor.getCell()->mCell); + Movement &movement = actor.getClass().getMovementSettings(actor); + const ESM::Cell *cell = actor.getCell()->mCell; - if(actor.getCell()->mCell->mData.mX != player.getCell()->mCell->mData.mX) + MWWorld::Ptr player = world->getPlayer().getPlayer(); + if(cell->mData.mX != player.getCell()->mCell->mData.mX) { - int sideX = sgn(actor.getCell()->mCell->mData.mX - player.getCell()->mCell->mData.mX); - //check if actor is near the border of an inactive cell. If so, disable aitravel. - if(sideX * (pos.pos[0] - actor.getCell()->mCell->mData.mX * ESM::Land::REAL_SIZE) > sideX * (ESM::Land::REAL_SIZE / - 2.0 - 200)) + int sideX = sgn(cell->mData.mX - player.getCell()->mCell->mData.mX); + //check if actor is near the border of an inactive cell. If so, stop walking. + if(sideX * (pos.pos[0] - cell->mData.mX*ESM::Land::REAL_SIZE) > + sideX * (ESM::Land::REAL_SIZE/2.0f - 200.0f)) { - MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0; - return true; + movement.mPosition[1] = 0; + return false; } } - if(actor.getCell()->mCell->mData.mY != player.getCell()->mCell->mData.mY) + if(cell->mData.mY != player.getCell()->mCell->mData.mY) { - int sideY = sgn(actor.getCell()->mCell->mData.mY - player.getCell()->mCell->mData.mY); - //check if actor is near the border of an inactive cell. If so, disable aitravel. - if(sideY * (pos.pos[1] - actor.getCell()->mCell->mData.mY * ESM::Land::REAL_SIZE) > sideY * (ESM::Land::REAL_SIZE / - 2.0 - 200)) + int sideY = sgn(cell->mData.mY - player.getCell()->mCell->mData.mY); + //check if actor is near the border of an inactive cell. If so, stop walking. + if(sideY * (pos.pos[1] - cell->mData.mY*ESM::Land::REAL_SIZE) > + sideY * (ESM::Land::REAL_SIZE/2.0f - 200.0f)) { - MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0; - return true; + movement.mPosition[1] = 0; + return false; } } + const ESM::Pathgrid *pathgrid = world->getStore().get().search(*cell); + bool cellChange = cell->mData.mX != cellX || cell->mData.mY != cellY; if(!mPathFinder.isPathConstructed() || cellChange) { - cellX = actor.getCell()->mCell->mData.mX; - cellY = actor.getCell()->mCell->mData.mY; + cellX = cell->mData.mX; + cellY = cell->mData.mY; float xCell = 0; float yCell = 0; - if (actor.getCell()->mCell->isExterior()) + if(cell->isExterior()) { - xCell = actor.getCell()->mCell->mData.mX * ESM::Land::REAL_SIZE; - yCell = actor.getCell()->mCell->mData.mY * ESM::Land::REAL_SIZE; + xCell = cell->mData.mX * ESM::Land::REAL_SIZE; + yCell = cell->mData.mY * ESM::Land::REAL_SIZE; } ESM::Pathgrid::Point dest; @@ -90,13 +92,13 @@ namespace MWMechanics if(mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2])) { - MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0; + movement.mPosition[1] = 0; return true; } float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); - MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); - MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; + world->rotateObject(actor, 0, 0, zAngle, false); + movement.mPosition[1] = 1; return false; } diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index c4f32f5dc..96a41883b 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -65,10 +65,11 @@ namespace MWMechanics bool AiWander::execute (const MWWorld::Ptr& actor) { + MWBase::World *world = MWBase::Environment::get().getWorld(); if(mDuration) { // End package if duration is complete or mid-night hits: - MWWorld::TimeStamp currentTime = MWBase::Environment::get().getWorld()->getTimeStamp(); + MWWorld::TimeStamp currentTime = world->getTimeStamp(); if(currentTime.getHour() >= mStartTime.getHour() + mDuration) { if(!mRepeat) @@ -96,8 +97,7 @@ namespace MWMechanics if(!mStoredAvailableNodes) { mStoredAvailableNodes = true; - mPathgrid = - MWBase::Environment::get().getWorld()->getStore().get().search(*actor.getCell()->mCell); + mPathgrid = world->getStore().get().search(*actor.getCell()->mCell); mCellX = actor.getCell()->mCell->mData.mX; mCellY = actor.getCell()->mCell->mData.mY; @@ -150,37 +150,8 @@ namespace MWMechanics } } - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); - bool cellChange = actor.getCell()->mCell->mData.mX != mCellX || actor.getCell()->mCell->mData.mY != mCellY; - - if(actor.getCell()->mCell->mData.mX != player.getCell()->mCell->mData.mX) - { - int sideX = sgn(actor.getCell()->mCell->mData.mX - player.getCell()->mCell->mData.mX); - // Check if actor is near the border of an inactive cell. If so, disable AiWander. - // FIXME: This *should* pause the AiWander package instead of terminating it. - if(sideX * (pos.pos[0] - actor.getCell()->mCell->mData.mX * ESM::Land::REAL_SIZE) > sideX * (ESM::Land::REAL_SIZE / - 2.0 - 200)) - { - stopWalking(actor); - return true; - } - } - - if(actor.getCell()->mCell->mData.mY != player.getCell()->mCell->mData.mY) - { - int sideY = sgn(actor.getCell()->mCell->mData.mY - player.getCell()->mCell->mData.mY); - // Check if actor is near the border of an inactive cell. If so, disable AiWander. - // FIXME: This *should* pause the AiWander package instead of terminating it. - if(sideY * (pos.pos[1] - actor.getCell()->mCell->mData.mY * ESM::Land::REAL_SIZE) > sideY * (ESM::Land::REAL_SIZE / - 2.0 - 200)) - { - stopWalking(actor); - return true; - } - } - // Don't try to move if you are in a new cell (ie: positioncell command called) but still play idles. - if(mDistance && (cellChange || (mCellX != actor.getCell()->mCell->mData.mX || mCellY != actor.getCell()->mCell->mData.mY))) + if(mDistance && (mCellX != actor.getCell()->mCell->mData.mX || mCellY != actor.getCell()->mCell->mData.mY)) mDistance = 0; if(mChooseAction) @@ -207,7 +178,7 @@ namespace MWMechanics else { // Play idle animation and recreate vanilla (broken?) behavior of resetting start time of AIWander: - MWWorld::TimeStamp currentTime = MWBase::Environment::get().getWorld()->getTimeStamp(); + MWWorld::TimeStamp currentTime = world->getTimeStamp(); mStartTime = currentTime; playIdle(actor, mPlayedIdle); mChooseAction = false; @@ -264,18 +235,10 @@ namespace MWMechanics if(mWalking) { float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); - MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle,false); + world->rotateObject(actor, 0, 0, zAngle, false); MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; - // Unclog path nodes by allowing the NPC to be a small distance away from the center. This way two NPCs can be - // at the same path node at the same time and both will complete instead of endlessly walking into eachother: - Ogre::Vector3 destNodePos(mCurrentNode.mX, mCurrentNode.mY, mCurrentNode.mZ); - Ogre::Vector3 actorPos(actor.getRefData().getPosition().pos); - actorPos[0] = actorPos[0] - mXCell; - actorPos[1] = actorPos[1] - mYCell; - float distance = actorPos.squaredDistance(destNodePos); - - if(distance < 1200 || mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2])) + if(mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2])) { stopWalking(actor); mMoveNow = false; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 1b220e65f..ec2bb1b59 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -99,6 +99,8 @@ static const StateInfo sMovementList[] = { { CharState_SneakLeft, "sneakleft" }, { CharState_SneakRight, "sneakright" }, + { CharState_Jump, "jump" }, + { CharState_TurnLeft, "turnleft" }, { CharState_TurnRight, "turnright" }, }; @@ -177,6 +179,42 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat 1.0f, "start", "stop", 0.0f, ~0ul); } + if(force && mJumpState != JumpState_None) + { + std::string jump; + MWRender::Animation::Group jumpgroup = MWRender::Animation::Group_All; + if(mJumpState != JumpState_None) + { + jump = "jump"; + if(weap != sWeaponTypeListEnd) + { + jump += weap->shortgroup; + if(!mAnimation->hasAnimation(jump)) + { + jumpgroup = MWRender::Animation::Group_LowerBody; + jump = "jump"; + } + } + } + + if(mJumpState == JumpState_Falling) + { + int mode = ((jump == mCurrentJump) ? 2 : 1); + + mAnimation->disable(mCurrentJump); + mCurrentJump = jump; + mAnimation->play(mCurrentJump, Priority_Jump, jumpgroup, false, + 1.0f, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul); + } + else + { + mAnimation->disable(mCurrentJump); + mCurrentJump.clear(); + mAnimation->play(jump, Priority_Jump, jumpgroup, true, + 1.0f, "loop stop", "stop", 0.0f, 0); + } + } + if(force || movement != mMovementState) { mMovementState = movement; @@ -214,7 +252,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat /* If we're playing the same animation, restart from the loop start instead of the * beginning. */ - int mode = ((movement != mCurrentMovement) ? 1 : 2); + int mode = ((movement == mCurrentMovement) ? 2 : 1); mAnimation->disable(mCurrentMovement); mCurrentMovement = movement; @@ -224,7 +262,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat if(mMovementSpeed > 0.0f && (vel=mAnimation->getVelocity(mCurrentMovement)) > 1.0f) speedmult = mMovementSpeed / vel; mAnimation->play(mCurrentMovement, Priority_Movement, movegroup, false, - speedmult, ((mode==2)?"loop start":"start"), "stop", 0.0f, ~0ul); + speedmult, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul); } } } @@ -307,6 +345,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim , mMovementSpeed(0.0f) , mDeathState(CharState_None) , mUpperBodyState(UpperCharState_Nothing) + , mJumpState(JumpState_None) , mWeaponType(WeapType_None) , mSkipAnim(false) , mSecondsOfRunning(0) @@ -663,9 +702,11 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun return forcestateupdate; } -void CharacterController::update(float duration, Movement &movement) +void CharacterController::update(float duration) { + MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::Class &cls = MWWorld::Class::get(mPtr); + Ogre::Vector3 movement(0.0f); if(!cls.isActor()) { @@ -684,44 +725,21 @@ void CharacterController::update(float duration, Movement &movement) } else if(!cls.getCreatureStats(mPtr).isDead()) { - MWBase::World *world = MWBase::Environment::get().getWorld(); - bool onground = world->isOnGround(mPtr); bool inwater = world->isSwimming(mPtr); bool isrunning = cls.getStance(mPtr, MWWorld::Class::Run); bool sneak = cls.getStance(mPtr, MWWorld::Class::Sneak); + bool flying = world->isFlying(mPtr); Ogre::Vector3 vec = cls.getMovementVector(mPtr); Ogre::Vector3 rot = cls.getRotationVector(mPtr); mMovementSpeed = cls.getSpeed(mPtr); - CharacterState movestate = CharState_None; - CharacterState idlestate = CharState_SpecialIdle; - bool forcestateupdate = false; - vec.x *= mMovementSpeed; vec.y *= mMovementSpeed; - /* FIXME: The state should be set to Jump, and X/Y movement should be disallowed except - * for the initial thrust (which would be carried by "physics" until landing). */ - if(!onground || sneak) - vec.z = 0.0f; - else if(vec.z > 0.0f) - { - float z = cls.getJump(mPtr); - - if(vec.x == 0 && vec.y == 0) - vec.z *= z; - else - { - /* FIXME: this would be more correct if we were going into a jumping state, - * rather than normal walking/idle states. */ - //Ogre::Vector3 lat = Ogre::Vector3(vec.x, vec.y, 0.0f).normalisedCopy(); - //vec *= Ogre::Vector3(lat.x, lat.y, 1.0f) * z * 0.707f; - vec.z *= z * 0.707f; - } - - //decrease fatigue by fFatigueJumpBase + (1 - normalizedEncumbrance) * fFatigueJumpMult; - } + CharacterState movestate = CharState_None; + CharacterState idlestate = CharState_SpecialIdle; + bool forcestateupdate = false; isrunning = isrunning && std::abs(vec[0])+std::abs(vec[1]) > 0.0f; @@ -748,34 +766,86 @@ void CharacterController::update(float duration, Movement &movement) } } - if(std::abs(vec.x/2.0f) > std::abs(vec.y)) + if(sneak || inwater || flying) + vec.z = 0.0f; + + if(!onground && !flying && !inwater) { - if(vec.x > 0.0f) - movestate = (inwater ? (isrunning ? CharState_SwimRunRight : CharState_SwimWalkRight) - : (sneak ? CharState_SneakRight - : (isrunning ? CharState_RunRight : CharState_WalkRight))); - else if(vec.x < 0.0f) - movestate = (inwater ? (isrunning ? CharState_SwimRunLeft : CharState_SwimWalkLeft) - : (sneak ? CharState_SneakLeft - : (isrunning ? CharState_RunLeft : CharState_WalkLeft))); + const MWWorld::Store &gmst = world->getStore().get(); + + forcestateupdate = (mJumpState != JumpState_Falling); + mJumpState = JumpState_Falling; + + // This is a guess. All that seems to be known is that "While the player is in the + // air, fJumpMoveBase and fJumpMoveMult governs air control." Assuming Acrobatics + // plays a role, this makes the most sense. + float mult = 0.0f; + if(cls.isNpc()) + { + const NpcStats &stats = cls.getNpcStats(mPtr); + mult = gmst.find("fJumpMoveBase")->getFloat() + + (stats.getSkill(ESM::Skill::Acrobatics).getModified()/100.0f * + gmst.find("fJumpMoveMult")->getFloat()); + } + + vec.x *= mult; + vec.y *= mult; + vec.z = 0.0f; + } + else if(vec.z > 0.0f && mJumpState == JumpState_None) + { + float z = cls.getJump(mPtr); + if(vec.x == 0 && vec.y == 0) + vec = Ogre::Vector3(0.0f, 0.0f, z); + else + { + Ogre::Vector3 lat = Ogre::Vector3(vec.x, vec.y, 0.0f).normalisedCopy(); + vec = Ogre::Vector3(lat.x, lat.y, 1.0f) * z * 0.707f; + } + + //decrease fatigue by fFatigueJumpBase + (1 - normalizedEncumbrance) * fFatigueJumpMult; } - else if(vec.y != 0.0f) + else if(mJumpState == JumpState_Falling) { - if(vec.y > 0.0f) - movestate = (inwater ? (isrunning ? CharState_SwimRunForward : CharState_SwimWalkForward) - : (sneak ? CharState_SneakForward - : (isrunning ? CharState_RunForward : CharState_WalkForward))); - else if(vec.y < 0.0f) - movestate = (inwater ? (isrunning ? CharState_SwimRunBack : CharState_SwimWalkBack) - : (sneak ? CharState_SneakBack - : (isrunning ? CharState_RunBack : CharState_WalkBack))); + forcestateupdate = true; + mJumpState = JumpState_Landing; + vec.z = 0.0f; } - else if(rot.z != 0.0f && !inwater && !sneak) + else { - if(rot.z > 0.0f) - movestate = CharState_TurnRight; - else if(rot.z < 0.0f) - movestate = CharState_TurnLeft; + if(!(vec.z > 0.0f)) + mJumpState = JumpState_None; + vec.z = 0.0f; + + if(std::abs(vec.x/2.0f) > std::abs(vec.y)) + { + if(vec.x > 0.0f) + movestate = (inwater ? (isrunning ? CharState_SwimRunRight : CharState_SwimWalkRight) + : (sneak ? CharState_SneakRight + : (isrunning ? CharState_RunRight : CharState_WalkRight))); + else if(vec.x < 0.0f) + movestate = (inwater ? (isrunning ? CharState_SwimRunLeft : CharState_SwimWalkLeft) + : (sneak ? CharState_SneakLeft + : (isrunning ? CharState_RunLeft : CharState_WalkLeft))); + } + else if(vec.y != 0.0f) + { + if(vec.y > 0.0f) + movestate = (inwater ? (isrunning ? CharState_SwimRunForward : CharState_SwimWalkForward) + : (sneak ? CharState_SneakForward + : (isrunning ? CharState_RunForward : CharState_WalkForward))); + else if(vec.y < 0.0f) + movestate = (inwater ? (isrunning ? CharState_SwimRunBack : CharState_SwimWalkBack) + : (sneak ? CharState_SneakBack + : (isrunning ? CharState_RunBack : CharState_WalkBack))); + } + else if(rot.z != 0.0f && !inwater && !sneak) + { + if(rot.z > 0.0f) + movestate = CharState_TurnRight; + else if(rot.z < 0.0f) + movestate = CharState_TurnLeft; + } } if(movestate != CharState_None) @@ -796,45 +866,47 @@ void CharacterController::update(float duration, Movement &movement) } } - vec *= duration; - movement.mPosition[0] += vec.x; - movement.mPosition[1] += vec.y; - movement.mPosition[2] += vec.z; - rot *= duration; - movement.mRotation[0] += rot.x; - movement.mRotation[1] += rot.y; - movement.mRotation[2] += rot.z; - if(cls.isNpc()) - forcestateupdate = updateNpcState(onground, inwater, isrunning, sneak); + forcestateupdate = updateNpcState(onground, inwater, isrunning, sneak) || forcestateupdate; refreshCurrentAnims(idlestate, movestate, forcestateupdate); + + rot *= duration * Ogre::Math::RadiansToDegrees(1.0f); + world->rotateObject(mPtr, rot.x, rot.y, rot.z, true); + + world->queueMovement(mPtr, vec); + movement = vec; } else if(cls.getCreatureStats(mPtr).isDead()) { MWBase::Environment::get().getWorld()->enableActorCollision(mPtr, false); + world->queueMovement(mPtr, Ogre::Vector3(0.0f)); } if(mAnimation && !mSkipAnim) { Ogre::Vector3 moved = mAnimation->runAnimation(duration); + if(duration > 0.0f) + moved /= duration; + else + moved = Ogre::Vector3(0.0f); + // Ensure we're moving in generally the right direction if(mMovementSpeed > 0.f) { - if((movement.mPosition[0] < 0.0f && movement.mPosition[0] < moved.x*2.0f) || - (movement.mPosition[0] > 0.0f && movement.mPosition[0] > moved.x*2.0f)) - moved.x = movement.mPosition[0]; - if((movement.mPosition[1] < 0.0f && movement.mPosition[1] < moved.y*2.0f) || - (movement.mPosition[1] > 0.0f && movement.mPosition[1] > moved.y*2.0f)) - moved.y = movement.mPosition[1]; - if((movement.mPosition[2] < 0.0f && movement.mPosition[2] < moved.z*2.0f) || - (movement.mPosition[2] > 0.0f && movement.mPosition[2] > moved.z*2.0f)) - moved.z = movement.mPosition[2]; + if((movement.x < 0.0f && movement.x < moved.x*2.0f) || + (movement.x > 0.0f && movement.x > moved.x*2.0f)) + moved.x = movement.x; + if((movement.y < 0.0f && movement.y < moved.y*2.0f) || + (movement.y > 0.0f && movement.y > moved.y*2.0f)) + moved.y = movement.y; + if((movement.z < 0.0f && movement.z < moved.z*2.0f) || + (movement.z > 0.0f && movement.z > moved.z*2.0f)) + moved.z = movement.z; } - - movement.mPosition[0] = moved.x; - movement.mPosition[1] = moved.y; - movement.mPosition[2] = moved.z; + // Update movement + if(moved.squaredLength() > 1.0f) + world->queueMovement(mPtr, moved); } mSkipAnim = false; } diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 34d778fea..c943b9597 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -24,6 +24,7 @@ class NpcStats; enum Priority { Priority_Default, + Priority_Jump, Priority_Movement, Priority_Weapon, Priority_Torch, @@ -115,6 +116,12 @@ enum UpperBodyCharacterState { UpperCharState_CastingSpell }; +enum JumpingState { + JumpState_None, + JumpState_Falling, + JumpState_Landing +}; + class CharacterController { MWWorld::Ptr mPtr; @@ -135,6 +142,9 @@ class CharacterController UpperBodyCharacterState mUpperBodyState; + JumpingState mJumpState; + std::string mCurrentJump; + WeaponType mWeaponType; std::string mCurrentWeapon; @@ -164,7 +174,7 @@ public: void updatePtr(const MWWorld::Ptr &ptr); - void update(float duration, Movement &movement); + void update(float duration); void playGroup(const std::string &groupname, int mode, int count); void skipAnim(); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index c559e4445..8c13db790 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -8,7 +8,6 @@ #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/dialoguemanager.hpp" -#include "../mwbase/soundmanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" @@ -189,6 +188,9 @@ namespace MWMechanics void MechanicsManager::updateCell(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr) { + if(old == mWatched) + mWatched = ptr; + if(MWWorld::Class::get(ptr).isActor()) mActors.updateActor(old, ptr); else @@ -213,98 +215,76 @@ namespace MWMechanics void MechanicsManager::update(float duration, bool paused) { - if (!mWatched.isEmpty()) + if(!mWatched.isEmpty()) { - MWMechanics::CreatureStats& stats = - MWWorld::Class::get (mWatched).getCreatureStats (mWatched); - - MWMechanics::NpcStats& npcStats = - MWWorld::Class::get (mWatched).getNpcStats (mWatched); - - static const char *attributeNames[8] = - { - "AttribVal1", "AttribVal2", "AttribVal3", "AttribVal4", "AttribVal5", - "AttribVal6", "AttribVal7", "AttribVal8" - }; - - static const char *dynamicNames[3] = + MWBase::WindowManager *winMgr = MWBase::Environment::get().getWindowManager(); + const MWMechanics::NpcStats &stats = mWatched.getClass().getNpcStats(mWatched); + for(int i = 0;i < ESM::Attribute::Length;++i) { - "HBar", "MBar", "FBar" - }; - - for (int i=0; i<8; ++i) - { - if (stats.getAttribute(i)!=mWatchedCreature.getAttribute(i)) + if(stats.getAttribute(i) != mWatchedStats.getAttribute(i)) { - mWatchedCreature.setAttribute(i, stats.getAttribute(i)); + std::stringstream attrname; + attrname << "AttribVal"<<(i+1); - MWBase::Environment::get().getWindowManager()->setValue (attributeNames[i], stats.getAttribute(i)); + mWatchedStats.setAttribute(i, stats.getAttribute(i)); + winMgr->setValue(attrname.str(), stats.getAttribute(i)); } } - if (stats.getHealth() != mWatchedCreature.getHealth()) { - mWatchedCreature.setHealth(stats.getHealth()); - MWBase::Environment::get().getWindowManager()->setValue(dynamicNames[0], stats.getHealth()); + if(stats.getHealth() != mWatchedStats.getHealth()) + { + static const std::string hbar("HBar"); + mWatchedStats.setHealth(stats.getHealth()); + winMgr->setValue(hbar, stats.getHealth()); } - if (stats.getMagicka() != mWatchedCreature.getMagicka()) { - mWatchedCreature.setMagicka(stats.getMagicka()); - MWBase::Environment::get().getWindowManager()->setValue(dynamicNames[1], stats.getMagicka()); + if(stats.getMagicka() != mWatchedStats.getMagicka()) + { + static const std::string mbar("MBar"); + mWatchedStats.setMagicka(stats.getMagicka()); + winMgr->setValue(mbar, stats.getMagicka()); } - if (stats.getFatigue() != mWatchedCreature.getFatigue()) { - mWatchedCreature.setFatigue(stats.getFatigue()); - MWBase::Environment::get().getWindowManager()->setValue(dynamicNames[2], stats.getFatigue()); + if(stats.getFatigue() != mWatchedStats.getFatigue()) + { + static const std::string fbar("FBar"); + mWatchedStats.setFatigue(stats.getFatigue()); + winMgr->setValue(fbar, stats.getFatigue()); } - if(npcStats.getTimeToStartDrowning() != mWatchedNpc.getTimeToStartDrowning()) + if(stats.getTimeToStartDrowning() != mWatchedStats.getTimeToStartDrowning()) { - mWatchedNpc.setTimeToStartDrowning(npcStats.getTimeToStartDrowning()); - if(npcStats.getTimeToStartDrowning()>=20.0) - { - MWBase::Environment::get().getWindowManager()->setDrowningBarVisibility(false); - } + mWatchedStats.setTimeToStartDrowning(stats.getTimeToStartDrowning()); + if(stats.getTimeToStartDrowning() >= 20.0f) + winMgr->setDrowningBarVisibility(false); else { - MWBase::Environment::get().getWindowManager()->setDrowningBarVisibility(true); - MWBase::Environment::get().getWindowManager()->setDrowningTimeLeft(npcStats.getTimeToStartDrowning()); + winMgr->setDrowningBarVisibility(true); + winMgr->setDrowningTimeLeft(stats.getTimeToStartDrowning()); } } bool update = false; //Loop over ESM::Skill::SkillEnum - for(int i = 0; i < 27; ++i) + for(int i = 0; i < ESM::Skill::Length; ++i) { - if(npcStats.getSkill (i) != mWatchedNpc.getSkill (i)) + if(stats.getSkill(i) != mWatchedStats.getSkill(i)) { update = true; - mWatchedNpc.getSkill (i) = npcStats.getSkill (i); - MWBase::Environment::get().getWindowManager()->setValue((ESM::Skill::SkillEnum)i, npcStats.getSkill (i)); + mWatchedStats.getSkill(i) = stats.getSkill(i); + winMgr->setValue((ESM::Skill::SkillEnum)i, stats.getSkill(i)); } } - if (update) - MWBase::Environment::get().getWindowManager()->updateSkillArea(); - - MWBase::Environment::get().getWindowManager()->setValue ("level", stats.getLevel()); - } + if(update) + winMgr->updateSkillArea(); - //update drowning sound - MWBase::World *world = MWBase::Environment::get().getWorld(); - MWBase::SoundManager * sndmgr = MWBase::Environment::get().getSoundManager(); - MWWorld::Ptr playerPtr = world->getPlayer().getPlayer(); - NpcStats& playerStats = MWWorld::Class::get(playerPtr).getNpcStats(playerPtr); - if(!sndmgr->getSoundPlaying(MWWorld::Ptr(), "drown") && playerStats.getTimeToStartDrowning()==0.0) - { - sndmgr->playSound("drown",1.0,1.0,MWBase::SoundManager::Play_TypeSfx,MWBase::SoundManager::Play_Loop); - } - if(playerStats.getTimeToStartDrowning()>0.0) - { - //no need to check if it's playing, stop sound does nothing in that case - sndmgr->stopSound("drown"); + winMgr->setValue("level", stats.getLevel()); } if (mUpdatePlayer) { + MWBase::World *world = MWBase::Environment::get().getWorld(); + // basic player profile; should not change anymore after the creation phase is finished. MWBase::WindowManager *winMgr = MWBase::Environment::get().getWindowManager(); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 95f760d11..ad07562c7 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -25,8 +25,7 @@ namespace MWMechanics class MechanicsManager : public MWBase::MechanicsManager { MWWorld::Ptr mWatched; - CreatureStats mWatchedCreature; - NpcStats mWatchedNpc; + NpcStats mWatchedStats; bool mUpdatePlayer; bool mClassSelected; bool mRaceSelected; diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 6a5e5a98f..0b3698289 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -422,7 +422,7 @@ void MWMechanics::NpcStats::modifyProfit(int diff) mProfit += diff; } -float MWMechanics::NpcStats::getTimeToStartDrowning() +float MWMechanics::NpcStats::getTimeToStartDrowning() const { return mTimeToStartDrowning; } @@ -431,13 +431,3 @@ void MWMechanics::NpcStats::setTimeToStartDrowning(float time) assert(time>=0 && time<=20); mTimeToStartDrowning=time; } - -float MWMechanics::NpcStats::getLastDrowningHitTime() -{ - return mLastDrowningHit; -} - -void MWMechanics::NpcStats::setLastDrowningHitTime(float time) -{ - mLastDrowningHit=time; -} diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index d3fe61829..6b7efa5b7 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -147,15 +147,10 @@ namespace MWMechanics int getWerewolfKills() const; - float getTimeToStartDrowning(); + float getTimeToStartDrowning() const; /// Sets time left for the creature to drown if it stays underwater. /// @param time value from [0,20] void setTimeToStartDrowning(float time); - - float getLastDrowningHitTime(); - /// Sets time since last hit caused by drowning. - /// @param time value from [0,0.33] - void setLastDrowningHitTime(float time); }; } diff --git a/apps/openmw/mwmechanics/objects.cpp b/apps/openmw/mwmechanics/objects.cpp index e0c0c756b..694987855 100644 --- a/apps/openmw/mwmechanics/objects.cpp +++ b/apps/openmw/mwmechanics/objects.cpp @@ -65,10 +65,7 @@ void Objects::update(float duration, bool paused) if(!paused) { for(PtrControllerMap::iterator iter(mObjects.begin());iter != mObjects.end();++iter) - { - Movement movement; - iter->second->update(duration, movement); - } + iter->second->update(duration); } } diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index da9d52e44..8ef0edab8 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -147,13 +147,13 @@ namespace MWMechanics mIsPathConstructed = false; } - void PathFinder::buildPath(ESM::Pathgrid::Point startPoint, ESM::Pathgrid::Point endPoint, - const ESM::Pathgrid* pathGrid, float xCell, float yCell, bool allowShortcuts) + void PathFinder::buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, + const ESM::Pathgrid *pathGrid, float xCell, float yCell, bool allowShortcuts) { if(allowShortcuts) { - if(MWBase::Environment::get().getWorld()->castRay(startPoint.mX, startPoint.mY, startPoint.mZ, endPoint.mX, endPoint.mY, - endPoint.mZ)) + if(MWBase::Environment::get().getWorld()->castRay(startPoint.mX, startPoint.mY, startPoint.mZ, + endPoint.mX, endPoint.mY, endPoint.mZ)) allowShortcuts = false; } @@ -184,14 +184,14 @@ namespace MWMechanics mIsPathConstructed = false; } - float PathFinder::getZAngleToNext(float x, float y) + float PathFinder::getZAngleToNext(float x, float y) const { // This should never happen (programmers should have an if statement checking mIsPathConstructed that prevents this call // if otherwise). if(mPath.empty()) return 0; - ESM::Pathgrid::Point nextPoint = *mPath.begin(); + const ESM::Pathgrid::Point &nextPoint = *mPath.begin(); float directionX = nextPoint.mX - x; float directionY = nextPoint.mY - y; float directionResult = sqrt(directionX * directionX + directionY * directionY); @@ -205,7 +205,7 @@ namespace MWMechanics return true; ESM::Pathgrid::Point nextPoint = *mPath.begin(); - if(distanceZCorrected(nextPoint, x, y, z) < 40) + if(distanceZCorrected(nextPoint, x, y, z) < 64) { mPath.pop_front(); if(mPath.empty()) @@ -217,15 +217,5 @@ namespace MWMechanics return false; } - - std::list PathFinder::getPath() - { - return mPath; - } - - bool PathFinder::isPathConstructed() - { - return mIsPathConstructed; - } } diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 1727c650f..35e0fa908 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -12,15 +12,18 @@ namespace MWMechanics PathFinder(); void clearPath(); - void buildPath(ESM::Pathgrid::Point startPoint, ESM::Pathgrid::Point endPoint, - const ESM::Pathgrid* pathGrid, float xCell = 0, float yCell = 0, bool allowShortcuts = 1); + void buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, + const ESM::Pathgrid* pathGrid, float xCell = 0, float yCell = 0, + bool allowShortcuts = true); bool checkPathCompleted(float x, float y, float z); ///< \Returns true if the last point of the path has been reached. - float getZAngleToNext(float x, float y); + float getZAngleToNext(float x, float y) const; - std::list getPath(); - bool isPathConstructed(); + bool isPathConstructed() const + { + return mIsPathConstructed; + } private: std::list mPath; diff --git a/apps/openmw/mwmechanics/stat.hpp b/apps/openmw/mwmechanics/stat.hpp index 606671389..65d47c9c0 100644 --- a/apps/openmw/mwmechanics/stat.hpp +++ b/apps/openmw/mwmechanics/stat.hpp @@ -42,6 +42,18 @@ namespace MWMechanics mBase = mModified = value; } + void modify(const T& diff) + { + mBase += diff; + if(mBase >= static_cast(0)) + mModified += diff; + else + { + mModified += diff - mBase; + mBase = static_cast(0); + } + } + /// Set base and adjust modified accordingly. void setBase (const T& value) { diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 37cebcf0b..545060fe3 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -64,7 +64,7 @@ void Animation::destroyObjectList(Ogre::SceneManager *sceneMgr, NifOgre::ObjectL Animation::Animation(const MWWorld::Ptr &ptr, Ogre::SceneNode *node) : mPtr(ptr) , mCamera(NULL) - , mInsert(NULL) + , mInsert(node) , mSkelBase(NULL) , mAccumRoot(NULL) , mNonAccumRoot(NULL) @@ -74,20 +74,14 @@ Animation::Animation(const MWWorld::Ptr &ptr, Ogre::SceneNode *node) { for(size_t i = 0;i < sNumGroups;i++) mAnimationValuePtr[i].bind(OGRE_NEW AnimationValue(this)); - mInsert = node->createChildSceneNode(); } Animation::~Animation() { - if(mInsert) - { - mAnimSources.clear(); - - Ogre::SceneManager *sceneMgr = mInsert->getCreator(); - destroyObjectList(sceneMgr, mObjectRoot); + mAnimSources.clear(); - sceneMgr->destroySceneNode(mInsert); - } + Ogre::SceneManager *sceneMgr = mInsert->getCreator(); + destroyObjectList(sceneMgr, mObjectRoot); } @@ -268,8 +262,13 @@ void Animation::addAnimSource(const std::string &model) if(!mAccumRoot && grp == 0) { - mAccumRoot = mInsert; mNonAccumRoot = dstval->getNode(); + mAccumRoot = mNonAccumRoot->getParent(); + if(!mAccumRoot) + { + std::cerr<< "Non-Accum root for "<= state.mLoopStopTime && state.mLoopCount > 0) + goto handle_loop; + + targetTime = state.mTime + timepassed; if(textkey == textkeys.end() || textkey->first > targetTime) { if(mNonAccumCtrl && stateiter->first == mAnimationValuePtr[0]->getAnimName()) @@ -858,11 +862,10 @@ Ogre::Vector3 Animation::runAnimation(float duration) if(state.mTime >= state.mLoopStopTime && state.mLoopCount > 0) { + handle_loop: state.mLoopCount--; state.mTime = state.mLoopStartTime; state.mPlaying = true; - if(state.mTime >= state.mLoopStopTime) - break; textkey = textkeys.lower_bound(state.mTime); while(textkey != textkeys.end() && textkey->first <= state.mTime) @@ -870,6 +873,9 @@ Ogre::Vector3 Animation::runAnimation(float duration) handleTextKey(state, stateiter->first, textkey); textkey++; } + + if(state.mTime >= state.mLoopStopTime) + break; } if(timepassed <= 0.0f) @@ -977,10 +983,7 @@ ObjectAnimation::ObjectAnimation(const MWWorld::Ptr& ptr, const std::string &mod { setObjectRoot(model, false); - Ogre::AxisAlignedBox bounds = getWorldBounds(); - - Ogre::Vector3 extents = bounds.getSize(); - extents *= mInsert->getParentSceneNode()->getScale(); + Ogre::Vector3 extents = getWorldBounds().getSize(); float size = std::max(std::max(extents.x, extents.y), extents.z); bool small = (size < Settings::Manager::getInt("small object size", "Viewing distance")) && diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index 1ff99dda8..120a83fae 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -10,6 +10,8 @@ #include #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -28,7 +30,7 @@ namespace MWRender } - void GlobalMap::render () + void GlobalMap::render (Loading::Listener* loadingListener) { Ogre::TexturePtr tex; @@ -53,13 +55,16 @@ namespace MWRender mWidth = cellSize*(mMaxX-mMinX+1); mHeight = cellSize*(mMaxY-mMinY+1); + loadingListener->loadingOn(); + loadingListener->setLabel("Creating map"); + loadingListener->setProgressRange((mMaxX-mMinX+1) * (mMaxY-mMinY+1)); + loadingListener->setProgress(0); + mExploredBuffer.resize((mMaxX-mMinX+1) * (mMaxY-mMinY+1) * 4); //if (!boost::filesystem::exists(mCacheDir + "/GlobalMap.png")) if (1) { - Ogre::Image image; - std::vector data (mWidth * mHeight * 3); for (int x = mMinX; x <= mMaxX; ++x) @@ -144,24 +149,20 @@ namespace MWRender b = waterDeepColour.b * 255; } - // uncomment this line to outline cell borders - //if (cellX == 0 || cellX == cellSize-1 || cellY == 0|| cellY == cellSize-1) r = 255; - data[texelY * mWidth * 3 + texelX * 3] = r; data[texelY * mWidth * 3 + texelX * 3+1] = g; data[texelY * mWidth * 3 + texelX * 3+2] = b; } } + loadingListener->increaseProgress(1); } } - image.loadDynamicImage (&data[0], mWidth, mHeight, Ogre::PF_B8G8R8); - - //image.save (mCacheDir + "/GlobalMap.png"); + Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(&data[0], data.size())); tex = Ogre::TextureManager::getSingleton ().createManual ("GlobalMap.png", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, mWidth, mHeight, 0, Ogre::PF_B8G8R8, Ogre::TU_STATIC); - tex->loadImage(image); + tex->loadRawData(stream, mWidth, mHeight, Ogre::PF_B8G8R8); } else tex = Ogre::TextureManager::getSingleton ().getByName ("GlobalMap.png"); @@ -184,6 +185,8 @@ namespace MWRender memcpy(mOverlayTexture->getBuffer()->lock(Ogre::HardwareBuffer::HBL_DISCARD), &buffer[0], mWidth*mHeight*4); mOverlayTexture->getBuffer()->unlock(); + + loadingListener->loadingOff(); } void GlobalMap::worldPosToImageSpace(float x, float z, float& imageX, float& imageY) diff --git a/apps/openmw/mwrender/globalmap.hpp b/apps/openmw/mwrender/globalmap.hpp index c01182c5b..dd3787b62 100644 --- a/apps/openmw/mwrender/globalmap.hpp +++ b/apps/openmw/mwrender/globalmap.hpp @@ -5,6 +5,11 @@ #include +namespace Loading +{ + class Listener; +} + namespace MWRender { @@ -13,7 +18,7 @@ namespace MWRender public: GlobalMap(const std::string& cacheDir); - void render(); + void render(Loading::Listener* loadingListener); int getWidth() { return mWidth; } int getHeight() { return mHeight; } diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 8043f8b12..5f4128978 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -2,6 +2,10 @@ #include #include +#include +#include +#include +#include #include "../mwworld/esmstore.hpp" @@ -104,7 +108,7 @@ void LocalMap::saveFogOfWar(MWWorld::Ptr::CellStore* cell) } } -void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell) +void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell, float zMin, float zMax) { mInterior = false; @@ -118,7 +122,7 @@ void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell) mCameraPosNode->setPosition(Vector3(0,0,0)); - render((x+0.5)*sSize, (y+0.5)*sSize, -10000, 10000, sSize, sSize, name); + render((x+0.5)*sSize, (y+0.5)*sSize, zMin, zMax, sSize, sSize, name); } void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell, @@ -366,8 +370,8 @@ void LocalMap::updatePlayer (const Ogre::Vector3& position, const Ogre::Quaterni MWBase::Environment::get().getWindowManager()->setPlayerDir(playerdirection.x, playerdirection.y); // explore radius (squared) - const float sqrExploreRadius = (mInterior ? 0.01 : 0.09) * sFogOfWarResolution*sFogOfWarResolution; - const float exploreRadius = (mInterior ? 0.1 : 0.3) * sFogOfWarResolution; // explore radius from 0 to sFogOfWarResolution + const float exploreRadius = (mInterior ? 0.1 : 0.3) * (sFogOfWarResolution-1); // explore radius from 0 to sFogOfWarResolution-1 + const float sqrExploreRadius = Math::Sqr(exploreRadius); const float exploreRadiusUV = exploreRadius / sFogOfWarResolution; // explore radius from 0 to 1 (UV space) // change the affected fog of war textures (in a 3x3 grid around the player) @@ -402,11 +406,8 @@ void LocalMap::updatePlayer (const Ogre::Vector3& position, const Ogre::Quaterni { for (int texU = 0; texU> 24); alpha = std::min( alpha, (uint8) (std::max(0.f, std::min(1.f, (sqrDist/sqrExploreRadius)))*255) ); diff --git a/apps/openmw/mwrender/localmap.hpp b/apps/openmw/mwrender/localmap.hpp index 72e637d9a..538489640 100644 --- a/apps/openmw/mwrender/localmap.hpp +++ b/apps/openmw/mwrender/localmap.hpp @@ -28,16 +28,18 @@ namespace MWRender * Request the local map for an exterior cell. * @remarks It will either be loaded from a disk cache, * or rendered if it is not already cached. - * @param exterior cell + * @param cell exterior cell + * @param zMin min height of objects or terrain in cell + * @param zMax max height of objects or terrain in cell */ - void requestMap (MWWorld::CellStore* cell); + void requestMap (MWWorld::CellStore* cell, float zMin, float zMax); /** * Request the local map for an interior cell. * @remarks It will either be loaded from a disk cache, * or rendered if it is not already cached. - * @param interior cell - * @param bounding box of the cell + * @param cell interior cell + * @param bounds bounding box of the cell */ void requestMap (MWWorld::CellStore* cell, Ogre::AxisAlignedBox bounds); diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index 337327dc4..fd81baf6e 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -130,7 +130,10 @@ void Objects::insertModel(const MWWorld::Ptr &ptr, const std::string &mesh) // - the culling will be more inefficient // If it is set too low: // - there will be too many batches. - sg->setRegionDimensions(Ogre::Vector3(2500,2500,2500)); + if(ptr.getCell()->isExterior()) + sg->setRegionDimensions(Ogre::Vector3(2048,2048,2048)); + else + sg->setRegionDimensions(Ogre::Vector3(1024,1024,1024)); sg->setVisibilityFlags(small ? RV_StaticsSmall : RV_Statics); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index f3e518800..e03b2ccfc 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -25,6 +25,8 @@ #include #include +#include + #include "../mwworld/esmstore.hpp" #include "../mwworld/class.hpp" @@ -46,6 +48,7 @@ #include "externalrendering.hpp" #include "globalmap.hpp" #include "videoplayer.hpp" +#include "terrainstorage.hpp" using namespace MWRender; using namespace Ogre; @@ -63,6 +66,7 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b , mAmbientMode(0) , mSunEnabled(0) , mPhysicsEngine(engine) + , mTerrain(NULL) { // select best shader mode bool openGL = (Ogre::Root::getSingleton ().getRenderSystem ()->getName().find("OpenGL") != std::string::npos); @@ -78,7 +82,7 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b Settings::Manager::setString("shader mode", "General", openGL ? (glES ? "glsles" : "glsl") : "hlsl"); } - mRendering.createScene("PlayerCam", Settings::Manager::getFloat("field of view", "General"), 5); + mRendering.adjustCamera(Settings::Manager::getFloat("field of view", "General"), 5); mRendering.getWindow()->addListener(this); mRendering.setWindowListener(this); @@ -141,7 +145,6 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b sh::Factory::getInstance ().setGlobalSetting ("fog", "true"); sh::Factory::getInstance ().setGlobalSetting ("num_lights", Settings::Manager::getString ("num lights", "Objects")); - sh::Factory::getInstance ().setGlobalSetting ("terrain_num_lights", Settings::Manager::getString ("num lights", "Terrain")); sh::Factory::getInstance ().setGlobalSetting ("simple_water", Settings::Manager::getBool("shader", "Water") ? "false" : "true"); sh::Factory::getInstance ().setGlobalSetting ("render_refraction", "false"); @@ -166,8 +169,6 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b mShadows = new Shadows(&mRendering); - mTerrainManager = new TerrainManager(mRendering.getScene(), this); - mSkyManager = new SkyManager(mRootNode, mRendering.getCamera()); mOcclusionQuery = new OcclusionQuery(&mRendering, mSkyManager->getSunNode()); @@ -194,7 +195,7 @@ RenderingManager::~RenderingManager () delete mSkyManager; delete mDebugging; delete mShadows; - delete mTerrainManager; + delete mTerrain; delete mLocalMap; delete mOcclusionQuery; delete mCompositors; @@ -225,8 +226,6 @@ void RenderingManager::removeCell (MWWorld::Ptr::CellStore *store) mObjects.removeCell(store); mActors.removeCell(store); mDebugging->cellRemoved(store); - if (store->mCell->isExterior()) - mTerrainManager->cellRemoved(store); } void RenderingManager::removeWater () @@ -244,8 +243,6 @@ void RenderingManager::cellAdded (MWWorld::Ptr::CellStore *store) mObjects.buildStaticGeometry (*store); sh::Factory::getInstance().unloadUnreferencedMaterials(); mDebugging->cellAdded(store); - if (store->mCell->isExterior()) - mTerrainManager->cellAdded(store); waterAdded(store); } @@ -548,15 +545,10 @@ void RenderingManager::setAmbientMode() } } -float RenderingManager::getTerrainHeightAt(Ogre::Vector3 worldPos) -{ - return mTerrainManager->getTerrainHeightAt(worldPos); -} - - void RenderingManager::configureAmbient(MWWorld::Ptr::CellStore &mCell) { - mAmbientColor.setAsABGR (mCell.mCell->mAmbi.mAmbient); + if (mCell.mCell->mData.mFlags & ESM::Cell::Interior) + mAmbientColor.setAsABGR (mCell.mCell->mAmbi.mAmbient); setAmbientMode(); // Create a "sun" that shines light downwards. It doesn't look @@ -564,12 +556,15 @@ void RenderingManager::configureAmbient(MWWorld::Ptr::CellStore &mCell) if(!mSun) { mSun = mRendering.getScene()->createLight(); + mSun->setType(Ogre::Light::LT_DIRECTIONAL); + } + if (mCell.mCell->mData.mFlags & ESM::Cell::Interior) + { + Ogre::ColourValue colour; + colour.setAsABGR (mCell.mCell->mAmbi.mSunlight); + mSun->setDiffuseColour (colour); + mSun->setDirection(0,-1,0); } - Ogre::ColourValue colour; - colour.setAsABGR (mCell.mCell->mAmbi.mSunlight); - mSun->setDiffuseColour (colour); - mSun->setType(Ogre::Light::LT_DIRECTIONAL); - mSun->setDirection(0,-1,0); } // Switch through lighting modes. @@ -595,7 +590,6 @@ void RenderingManager::setSunColour(const Ogre::ColourValue& colour) if (!mSunEnabled) return; mSun->setDiffuseColour(colour); mSun->setSpecularColour(colour); - mTerrainManager->setDiffuse(colour); } void RenderingManager::setAmbientColour(const Ogre::ColourValue& colour) @@ -608,7 +602,6 @@ void RenderingManager::setAmbientColour(const Ogre::ColourValue& colour) final += Ogre::ColourValue(0.7,0.7,0.7,0) * std::min(1.f, (nightEye/100.f)); mRendering.getScene()->setAmbientLight(final); - mTerrainManager->setAmbient(final); } void RenderingManager::sunEnable(bool real) @@ -652,7 +645,18 @@ void RenderingManager::setGlare(bool glare) void RenderingManager::requestMap(MWWorld::Ptr::CellStore* cell) { if (cell->mCell->isExterior()) - mLocalMap->requestMap(cell); + { + assert(mTerrain); + + Ogre::AxisAlignedBox dims = mObjects.getDimensions(cell); + Ogre::Vector2 center(cell->mCell->getGridX() + 0.5, cell->mCell->getGridY() + 0.5); + dims.merge(mTerrain->getWorldBoundingBox(center)); + + if (dims.isFinite()) + mTerrain->update(dims.getCenter()); + + mLocalMap->requestMap(cell, dims.getMinimum().z, dims.getMaximum().z); + } else mLocalMap->requestMap(cell, mObjects.getDimensions(cell)); } @@ -838,7 +842,12 @@ void RenderingManager::processChangedSettings(const Settings::CategorySettingVec mWater->processChangedSettings(settings); if (rebuild) + { mObjects.rebuildStaticGeometry(); + if (mTerrain) + mTerrain->applyMaterials(Settings::Manager::getBool("enabled", "Shadows"), + Settings::Manager::getBool("split", "Shadows")); + } } void RenderingManager::setMenuTransparency(float val) @@ -982,9 +991,13 @@ void RenderingManager::updateWaterRippleEmitterPtr (const MWWorld::Ptr& old, con mWater->updateEmitterPtr(old, ptr); } -void RenderingManager::frameStarted(float dt) +void RenderingManager::frameStarted(float dt, bool paused) { - mWater->frameStarted(dt); + if (mTerrain) + mTerrain->update(mRendering.getCamera()->getRealPosition()); + + if (!paused) + mWater->frameStarted(dt); } void RenderingManager::resetCamera() @@ -992,4 +1005,34 @@ void RenderingManager::resetCamera() mCamera->reset(); } +float RenderingManager::getTerrainHeightAt(Ogre::Vector3 worldPos) +{ + if (!mTerrain || !mTerrain->getVisible()) + return -std::numeric_limits::max(); + return mTerrain->getHeightAt(worldPos); +} + +void RenderingManager::enableTerrain(bool enable) +{ + if (enable) + { + if (!mTerrain) + { + Loading::Listener* listener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); + Loading::ScopedLoad load(listener); + mTerrain = new Terrain::World(listener, mRendering.getScene(), new MWRender::TerrainStorage(), RV_Terrain, + Settings::Manager::getBool("distant land", "Terrain"), + Settings::Manager::getBool("shader", "Terrain")); + mTerrain->applyMaterials(Settings::Manager::getBool("enabled", "Shadows"), + Settings::Manager::getBool("split", "Shadows")); + mTerrain->update(mRendering.getCamera()->getRealPosition()); + mTerrain->setLoadingListener(NULL); + } + mTerrain->setVisible(true); + } + else + if (mTerrain) + mTerrain->setVisible(false); +} + } // namespace diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 17cdfff4a..2d0813912 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -2,7 +2,6 @@ #define _GAME_RENDERING_MANAGER_H #include "sky.hpp" -#include "terrain.hpp" #include "debugging.hpp" #include @@ -39,6 +38,11 @@ namespace sh class Factory; } +namespace Terrain +{ + class World; +} + namespace MWRender { class Shadows; @@ -106,6 +110,8 @@ public: void cellAdded (MWWorld::CellStore *store); void waterAdded(MWWorld::CellStore *store); + void enableTerrain(bool enable); + void removeWater(); void preCellChange (MWWorld::CellStore* store); @@ -153,6 +159,8 @@ public: bool occlusionQuerySupported() { return mOcclusionQuery->supported(); } OcclusionQuery* getOcclusionQuery() { return mOcclusionQuery; } + float getTerrainHeightAt (Ogre::Vector3 worldPos); + Shadows* getShadows(); void switchToInterior(); @@ -160,8 +168,6 @@ public: void getTriangleBatchCount(unsigned int &triangles, unsigned int &batches); - float getTerrainHeightAt (Ogre::Vector3 worldPos); - void setGlare(bool glare); void skyEnable (); void skyDisable (); @@ -205,7 +211,7 @@ public: void playVideo(const std::string& name, bool allowSkipping); void stopVideo(); - void frameStarted(float dt); + void frameStarted(float dt, bool paused); protected: virtual void windowResized(int x, int y); @@ -228,7 +234,7 @@ private: OcclusionQuery* mOcclusionQuery; - TerrainManager* mTerrainManager; + Terrain::World* mTerrain; MWRender::Water *mWater; diff --git a/apps/openmw/mwrender/shadows.cpp b/apps/openmw/mwrender/shadows.cpp index 0d066a0ec..21bbe51b6 100644 --- a/apps/openmw/mwrender/shadows.cpp +++ b/apps/openmw/mwrender/shadows.cpp @@ -107,8 +107,8 @@ void Shadows::recreate() // Set visibility mask for the shadow render textures int visibilityMask = RV_Actors * Settings::Manager::getBool("actor shadows", "Shadows") + (RV_Statics + RV_StaticsSmall) * Settings::Manager::getBool("statics shadows", "Shadows") - + RV_Misc * Settings::Manager::getBool("misc shadows", "Shadows"); - + + RV_Misc * Settings::Manager::getBool("misc shadows", "Shadows") + + RV_Terrain * (Settings::Manager::getBool("terrain shadows", "Shadows")); for (int i = 0; i < (split ? 3 : 1); ++i) { TexturePtr shadowTexture = mSceneMgr->getShadowTexture(i); diff --git a/apps/openmw/mwrender/terrain.cpp b/apps/openmw/mwrender/terrain.cpp deleted file mode 100644 index 1b829a2c2..000000000 --- a/apps/openmw/mwrender/terrain.cpp +++ /dev/null @@ -1,531 +0,0 @@ -#include - -#include -#include -#include -#include - -#include "../mwworld/esmstore.hpp" - -#include - -#include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" - -#include "terrainmaterial.hpp" -#include "terrain.hpp" -#include "renderconst.hpp" -#include "shadows.hpp" -#include "renderingmanager.hpp" - -using namespace Ogre; - -namespace MWRender -{ - - //---------------------------------------------------------------------------------------------- - - TerrainManager::TerrainManager(Ogre::SceneManager* mgr, RenderingManager* rend) : - mTerrainGroup(TerrainGroup(mgr, Terrain::ALIGN_X_Y, mLandSize, mWorldSize)), mRendering(rend) - { - mTerrainGlobals = OGRE_NEW TerrainGlobalOptions(); - - TerrainMaterialGeneratorPtr matGen; - TerrainMaterial* matGenP = new TerrainMaterial(); - matGen.bind(matGenP); - mTerrainGlobals->setDefaultMaterialGenerator(matGen); - - TerrainMaterialGenerator::Profile* const activeProfile = - mTerrainGlobals->getDefaultMaterialGenerator() - ->getActiveProfile(); - mActiveProfile = static_cast(activeProfile); - - // We don't want any pixel error at all. Really, LOD makes no sense here - morrowind uses 65x65 verts in one cell, - // so applying LOD is most certainly slower than doing no LOD at all. - // Setting this to 0 seems to cause glitches though. :/ - mTerrainGlobals->setMaxPixelError(1); - - mTerrainGlobals->setLayerBlendMapSize(ESM::Land::LAND_TEXTURE_SIZE/2 + 1); - - //10 (default) didn't seem to be quite enough - mTerrainGlobals->setSkirtSize(128); - - //due to the sudden flick between composite and non composite textures, - //this seemed the distance where it wasn't too noticeable - mTerrainGlobals->setCompositeMapDistance(mWorldSize*2); - - mTerrainGroup.setOrigin(Vector3(mWorldSize/2, - mWorldSize/2, - 0)); - - Terrain::ImportData& importSettings = mTerrainGroup.getDefaultImportSettings(); - - importSettings.inputBias = 0; - importSettings.terrainSize = mLandSize; - importSettings.worldSize = mWorldSize; - importSettings.minBatchSize = 9; - importSettings.maxBatchSize = mLandSize; - - importSettings.deleteInputData = true; - } - - //---------------------------------------------------------------------------------------------- - - float TerrainManager::getTerrainHeightAt(Vector3 worldPos) - { - Ogre::Terrain* terrain = NULL; - float height = mTerrainGroup.getHeightAtWorldPosition(worldPos, &terrain); - if (terrain == NULL) - return std::numeric_limits().min(); - return height; - } - - //---------------------------------------------------------------------------------------------- - - TerrainManager::~TerrainManager() - { - OGRE_DELETE mTerrainGlobals; - } - - //---------------------------------------------------------------------------------------------- - - void TerrainManager::setDiffuse(const ColourValue& diffuse) - { - mTerrainGlobals->setCompositeMapDiffuse(diffuse); - } - - //---------------------------------------------------------------------------------------------- - - void TerrainManager::setAmbient(const ColourValue& ambient) - { - mTerrainGlobals->setCompositeMapAmbient(ambient); - } - - //---------------------------------------------------------------------------------------------- - - void TerrainManager::cellAdded(MWWorld::Ptr::CellStore *store) - { - const int cellX = store->mCell->getGridX(); - const int cellY = store->mCell->getGridY(); - - ESM::Land* land = - MWBase::Environment::get().getWorld()->getStore().get().search(cellX, cellY); - if (land == NULL) // no land data means we're not going to create any terrain. - return; - - int dataRequired = ESM::Land::DATA_VHGT | ESM::Land::DATA_VCLR; - if (!land->isDataLoaded(dataRequired)) - { - land->loadData(dataRequired); - } - - //split the cell terrain into four segments - const int numTextures = ESM::Land::LAND_TEXTURE_SIZE/2; - - for ( int x = 0; x < 2; x++ ) - { - for ( int y = 0; y < 2; y++ ) - { - Terrain::ImportData terrainData = - mTerrainGroup.getDefaultImportSettings(); - - const int terrainX = cellX * 2 + x; - const int terrainY = cellY * 2 + y; - - //it makes far more sense to reallocate the memory here, - //and let Ogre deal with it due to the issues with deleting - //it at the wrong time if using threads (Which Terrain does) - terrainData.inputFloat = OGRE_ALLOC_T(float, - mLandSize*mLandSize, - MEMCATEGORY_GEOMETRY); - - //copy the height data row by row - for ( int terrainCopyY = 0; terrainCopyY < mLandSize; terrainCopyY++ ) - { - //the offset of the current segment - const size_t yOffset = y * (mLandSize-1) * ESM::Land::LAND_SIZE + - //offset of the row - terrainCopyY * ESM::Land::LAND_SIZE; - const size_t xOffset = x * (mLandSize-1); - - memcpy(&terrainData.inputFloat[terrainCopyY*mLandSize], - &land->mLandData->mHeights[yOffset + xOffset], - mLandSize*sizeof(float)); - } - - std::map indexes; - initTerrainTextures(&terrainData, cellX, cellY, - x * numTextures, y * numTextures, - numTextures, indexes, land->mPlugin); - - if (mTerrainGroup.getTerrain(terrainX, terrainY) == NULL) - { - mTerrainGroup.defineTerrain(terrainX, terrainY, &terrainData); - - mTerrainGroup.loadTerrain(terrainX, terrainY, true); - - Terrain* terrain = mTerrainGroup.getTerrain(terrainX, terrainY); - initTerrainBlendMaps(terrain, - cellX, cellY, - x * numTextures, y * numTextures, - numTextures, - indexes); - terrain->setVisibilityFlags(RV_Terrain); - terrain->setRenderQueueGroup(RQG_Main); - - // disable or enable global colour map (depends on available vertex colours) - if ( land->mLandData->mUsingColours ) - { - TexturePtr vertex = getVertexColours(land, - cellX, cellY, - x*(mLandSize-1), - y*(mLandSize-1), - mLandSize); - - mActiveProfile->setGlobalColourMapEnabled(true); - mActiveProfile->setGlobalColourMap (terrain, vertex->getName()); - } - else - mActiveProfile->setGlobalColourMapEnabled (false); - } - } - } - - // when loading from a heightmap, Ogre::Terrain does not update the derived data (normal map, LOD) - // synchronously, even if we supply synchronous = true parameter to loadTerrain. - // the following to be the only way to make sure derived data is ready when rendering the next frame. - while (mTerrainGroup.isDerivedDataUpdateInProgress()) - { - // we need to wait for this to finish - OGRE_THREAD_SLEEP(5); - Root::getSingleton().getWorkQueue()->processResponses(); - } - - mTerrainGroup.freeTemporaryResources(); - } - - //---------------------------------------------------------------------------------------------- - - void TerrainManager::cellRemoved(MWWorld::Ptr::CellStore *store) - { - for ( int x = 0; x < 2; x++ ) - { - for ( int y = 0; y < 2; y++ ) - { - int terrainX = store->mCell->getGridX() * 2 + x; - int terrainY = store->mCell->getGridY() * 2 + y; - if (mTerrainGroup.getTerrain(terrainX, terrainY) != NULL) - mTerrainGroup.unloadTerrain(terrainX, terrainY); - } - } - } - - //---------------------------------------------------------------------------------------------- - - void TerrainManager::initTerrainTextures(Terrain::ImportData* terrainData, - int cellX, int cellY, - int fromX, int fromY, int size, - std::map& indexes, size_t plugin) - { - // FIXME: In a multiple esm configuration, we have multiple palettes. Since this code - // crosses cell boundaries, we no longer have a unique terrain palette. Instead, we need - // to adopt the following code for a dynamic palette. And this is evil - the current design - // does not work well for this task... - - assert(terrainData != NULL && "Must have valid terrain data"); - assert(fromX >= 0 && fromY >= 0 && - "Can't get a terrain texture on terrain outside the current cell"); - assert(fromX+size <= ESM::Land::LAND_TEXTURE_SIZE && - fromY+size <= ESM::Land::LAND_TEXTURE_SIZE && - "Can't get a terrain texture on terrain outside the current cell"); - - //this ensures that the ltex indexes are sorted (or retrived as sorted - //which simplifies shading between cells). - // - //If we don't sort the ltex indexes, the splatting order may differ between - //cells which may lead to inconsistent results when shading between cells - int num = MWBase::Environment::get().getWorld()->getStore().get().getSize(plugin); - std::set ltexIndexes; - for ( int y = fromY; y < fromY + size + 1; y++ ) - { - for ( int x = fromX - 1; x < fromX + size; x++ ) // NB we wrap X from the other side because Y is reversed - { - int idx = getLtexIndexAt(cellX, cellY, x, y); - // This is a quick hack to prevent the program from trying to fetch textures - // from a neighboring cell, which might originate from a different plugin, - // and use a separate texture palette. Right now, we simply cast it to the - // default texture (i.e. 0). - if (idx > num) - idx = 0; - ltexIndexes.insert(idx); - } - } - - //there is one texture that we want to use as a base (i.e. it won't have - //a blend map). This holds the ltex index of that base texture so that - //we know not to include it in the output map - int baseTexture = -1; - for ( std::set::iterator iter = ltexIndexes.begin(); - iter != ltexIndexes.end(); - ++iter ) - { - uint16_t ltexIndex = *iter; - //this is the base texture, so we can ignore this at present - if ( ltexIndex == baseTexture ) - { - continue; - } - - const std::map::const_iterator it = indexes.find(ltexIndex); - - if ( it == indexes.end() ) - { - //NB: All vtex ids are +1 compared to the ltex ids - - const MWWorld::Store <exStore = - MWBase::Environment::get().getWorld()->getStore().get(); - - // NOTE: using the quick hack above, we should no longer end up with textures indices - // that are out of bounds. However, I haven't updated the test to a multi-palette - // system yet. We probably need more work here, so we skip it for now. - //assert( (int)ltexStore.getSize() >= (int)ltexIndex - 1 && - //"LAND.VTEX must be within the bounds of the LTEX array"); - - std::string texture; - if ( ltexIndex == 0 ) - { - texture = "_land_default.dds"; - } - else - { - texture = ltexStore.search(ltexIndex-1, plugin)->mTexture; - //TODO this is needed due to MWs messed up texture handling - texture = texture.substr(0, texture.rfind(".")) + ".dds"; - } - - const size_t position = terrainData->layerList.size(); - terrainData->layerList.push_back(Terrain::LayerInstance()); - - terrainData->layerList[position].worldSize = 256; - terrainData->layerList[position].textureNames.push_back("textures\\" + texture); - - if ( baseTexture == -1 ) - { - baseTexture = ltexIndex; - } - else - { - indexes[ltexIndex] = position; - } - } - } - } - - //---------------------------------------------------------------------------------------------- - - void TerrainManager::initTerrainBlendMaps(Terrain* terrain, - int cellX, int cellY, - int fromX, int fromY, int size, - const std::map& indexes) - { - assert(terrain != NULL && "Must have valid terrain"); - assert(fromX >= 0 && fromY >= 0 && - "Can't get a terrain texture on terrain outside the current cell"); - assert(fromX+size <= ESM::Land::LAND_TEXTURE_SIZE && - fromY+size <= ESM::Land::LAND_TEXTURE_SIZE && - "Can't get a terrain texture on terrain outside the current cell"); - - //size must be a power of 2 as we do divisions with a power of 2 number - //that need to result in an integer for correct splatting - assert( (size & (size - 1)) == 0 && "Size must be a power of 2"); - - const int blendMapSize = terrain->getLayerBlendMapSize(); - - //zero out every map - std::map::const_iterator iter; - for ( iter = indexes.begin(); iter != indexes.end(); ++iter ) - { - float* pBlend = terrain->getLayerBlendMap(iter->second) - ->getBlendPointer(); - memset(pBlend, 0, sizeof(float) * blendMapSize * blendMapSize); - } - - //covert the ltex data into a set of blend maps - for ( int texY = fromY; texY < fromY + size + 1; texY++ ) - { - for ( int texX = fromX - 1; texX < fromX + size; texX++ ) // NB we wrap X from the other side because Y is reversed - { - const uint16_t ltexIndex = getLtexIndexAt(cellX, cellY, texX, texY); - - //check if it is the base texture (which isn't in the map) and - //if it is don't bother altering the blend map for it - if ( indexes.find(ltexIndex) == indexes.end() ) - { - continue; - } - - //while texX is the splat index relative to the entire cell, - //relX is relative to the current segment we are splatting - const int relX = texX - fromX + 1; - const int relY = texY - fromY; - - const int layerIndex = indexes.find(ltexIndex)->second; - - float* const pBlend = terrain->getLayerBlendMap(layerIndex) - ->getBlendPointer(); - - //Note: Y is reversed - const int splatY = blendMapSize - relY - 1; - const int splatX = relX; - - assert(splatX >= 0 && splatX < blendMapSize); - assert(splatY >= 0 && splatY < blendMapSize); - - const int index = (splatY)*blendMapSize + splatX; - pBlend[index] = 1; - } - } - - for ( int i = 1; i < terrain->getLayerCount(); i++ ) - { - TerrainLayerBlendMap* blend = terrain->getLayerBlendMap(i); - blend->dirty(); - blend->update(); - } - - } - - //---------------------------------------------------------------------------------------------- - - int TerrainManager::getLtexIndexAt(int cellX, int cellY, - int x, int y) - { - //check texture index falls within the 9 cell bounds - //as this function can't cope with anything above that - assert(x >= -ESM::Land::LAND_TEXTURE_SIZE && - y >= -ESM::Land::LAND_TEXTURE_SIZE && - "Trying to get land textures that are out of bounds"); - - assert(x < 2*ESM::Land::LAND_TEXTURE_SIZE && - y < 2*ESM::Land::LAND_TEXTURE_SIZE && - "Trying to get land textures that are out of bounds"); - - if ( x < 0 ) - { - cellX--; - x += ESM::Land::LAND_TEXTURE_SIZE; - } - else if ( x >= ESM::Land::LAND_TEXTURE_SIZE ) - { - cellX++; - x -= ESM::Land::LAND_TEXTURE_SIZE; - } - - if ( y < 0 ) - { - cellY--; - y += ESM::Land::LAND_TEXTURE_SIZE; - } - else if ( y >= ESM::Land::LAND_TEXTURE_SIZE ) - { - cellY++; - y -= ESM::Land::LAND_TEXTURE_SIZE; - } - - - ESM::Land* land = - MWBase::Environment::get().getWorld()->getStore().get().search(cellX, cellY); - if ( land != NULL ) - { - if (!land->isDataLoaded(ESM::Land::DATA_VTEX)) - { - land->loadData(ESM::Land::DATA_VTEX); - } - - return land->mLandData - ->mTextures[y * ESM::Land::LAND_TEXTURE_SIZE + x]; - } - else - { - return 0; - } - } - - //---------------------------------------------------------------------------------------------- - - TexturePtr TerrainManager::getVertexColours(ESM::Land* land, - int cellX, int cellY, - int fromX, int fromY, int size) - { - TextureManager* const texMgr = TextureManager::getSingletonPtr(); - - const std::string colourTextureName = "VtexColours_" + - boost::lexical_cast(cellX) + - "_" + - boost::lexical_cast(cellY) + - "_" + - boost::lexical_cast(fromX) + - "_" + - boost::lexical_cast(fromY); - - TexturePtr tex = texMgr->getByName(colourTextureName); - if ( !tex.isNull() ) - { - return tex; - } - - tex = texMgr->createManual(colourTextureName, - ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - TEX_TYPE_2D, size, size, 0, PF_BYTE_BGR); - - HardwarePixelBufferSharedPtr pixelBuffer = tex->getBuffer(); - - pixelBuffer->lock(HardwareBuffer::HBL_DISCARD); - const PixelBox& pixelBox = pixelBuffer->getCurrentLock(); - - uint8* pDest = static_cast(pixelBox.data); - - if ( land != NULL ) - { - const char* const colours = land->mLandData->mColours; - for ( int y = 0; y < size; y++ ) - { - for ( int x = 0; x < size; x++ ) - { - const size_t colourOffset = (y+fromY)*3*65 + (x+fromX)*3; - - assert( colourOffset < 65*65*3 && - "Colour offset is out of the expected bounds of record" ); - - const unsigned char r = colours[colourOffset + 0]; - const unsigned char g = colours[colourOffset + 1]; - const unsigned char b = colours[colourOffset + 2]; - - //as is the case elsewhere we need to flip the y - const size_t imageOffset = (size - 1 - y)*size*4 + x*4; - pDest[imageOffset + 0] = b; - pDest[imageOffset + 1] = g; - pDest[imageOffset + 2] = r; - } - } - } - else - { - for ( int y = 0; y < size; y++ ) - { - for ( int x = 0; x < size; x++ ) - { - for ( int k = 0; k < 3; k++ ) - { - *pDest++ = 0; - } - } - } - } - - pixelBuffer->unlock(); - - return tex; - } - -} diff --git a/apps/openmw/mwrender/terrain.hpp b/apps/openmw/mwrender/terrain.hpp deleted file mode 100644 index 45c56390e..000000000 --- a/apps/openmw/mwrender/terrain.hpp +++ /dev/null @@ -1,128 +0,0 @@ -#ifndef _GAME_RENDER_TERRAIN_H -#define _GAME_RENDER_TERRAIN_H - -#include -#include - -#include - -#include "terrainmaterial.hpp" - -namespace Ogre{ - class SceneManager; - class TerrainGroup; - class TerrainGlobalOptions; - class Terrain; -} - -namespace MWWorld -{ - class CellStore; -} - -namespace MWRender{ - - class RenderingManager; - - /** - * Implements the Morrowind terrain using the Ogre Terrain Component - * - * Each terrain cell is split into four blocks as this leads to an increase - * in performance and means we don't hit splat limits quite as much - */ - class TerrainManager{ - public: - TerrainManager(Ogre::SceneManager* mgr, RenderingManager* rend); - virtual ~TerrainManager(); - - void setDiffuse(const Ogre::ColourValue& diffuse); - void setAmbient(const Ogre::ColourValue& ambient); - - void cellAdded(MWWorld::CellStore* store); - void cellRemoved(MWWorld::CellStore* store); - - float getTerrainHeightAt (Ogre::Vector3 worldPos); - - private: - Ogre::TerrainGlobalOptions* mTerrainGlobals; - Ogre::TerrainGroup mTerrainGroup; - - RenderingManager* mRendering; - - TerrainMaterial::Profile* mActiveProfile; - - /** - * The length in verticies of a single terrain block. - */ - static const int mLandSize = (ESM::Land::LAND_SIZE - 1)/2 + 1; - - /** - * The length in game units of a single terrain block. - */ - static const int mWorldSize = ESM::Land::REAL_SIZE/2; - - /** - * Setups up the list of textures for part of a cell, using indexes as - * an output to create a mapping of MW LtexIndex to the relevant terrain - * layer - * - * @param terrainData the terrain data to setup the textures for - * @param cellX the coord of the cell - * @param cellY the coord of the cell - * @param fromX the ltex index in the current cell to start making the texture from - * @param fromY the ltex index in the current cell to start making the texture from - * @param size the size (number of splats) to get - * @param indexes a mapping of ltex index to the terrain texture layer that - * can be used by initTerrainBlendMaps - */ - void initTerrainTextures(Ogre::Terrain::ImportData* terrainData, - int cellX, int cellY, - int fromX, int fromY, int size, - std::map& indexes, size_t plugin); - - /** - * Creates the blend (splatting maps) for the given terrain from the ltex data. - * - * @param terrain the terrain object for the current cell - * @param cellX the coord of the cell - * @param cellY the coord of the cell - * @param fromX the ltex index in the current cell to start making the texture from - * @param fromY the ltex index in the current cell to start making the texture from - * @param size the size (number of splats) to get - * @param indexes the mapping of ltex to blend map produced by initTerrainTextures - */ - void initTerrainBlendMaps(Ogre::Terrain* terrain, - int cellX, int cellY, - int fromX, int fromY, int size, - const std::map& indexes); - - /** - * Gets a LTEX index at the given point, assuming the current cell - * starts at (0,0). This supports getting values from the surrounding - * cells so negative x, y is acceptable - * - * @param cellX the coord of the cell - * @param cellY the coord of the cell - * @param x, y the splat position of the ltex index to get relative to the - * first splat of the current cell - */ - int getLtexIndexAt(int cellX, int cellY, int x, int y); - - /** - * Due to the fact that Ogre terrain doesn't support vertex colours - * we have to generate them manually - * - * @param cellX the coord of the cell - * @param cellY the coord of the cell - * @param fromX the *vertex* index in the current cell to start making texture from - * @param fromY the *vertex* index in the current cell to start making the texture from - * @param size the size (number of vertexes) to get - */ - Ogre::TexturePtr getVertexColours(ESM::Land* land, - int cellX, int cellY, - int fromX, int fromY, int size); - }; - -} - -#endif // _GAME_RENDER_TERRAIN_H diff --git a/apps/openmw/mwrender/terrainmaterial.cpp b/apps/openmw/mwrender/terrainmaterial.cpp deleted file mode 100644 index 892dab7cf..000000000 --- a/apps/openmw/mwrender/terrainmaterial.cpp +++ /dev/null @@ -1,246 +0,0 @@ -#include "terrainmaterial.hpp" - -#include - -#include - -#include - -namespace -{ - Ogre::String getComponent (int num) - { - if (num == 0) - return "x"; - else if (num == 1) - return "y"; - else if (num == 2) - return "z"; - else - return "w"; - } -} - - -namespace MWRender -{ - - TerrainMaterial::TerrainMaterial() - { - mLayerDecl.samplers.push_back(Ogre::TerrainLayerSampler("albedo_specular", Ogre::PF_BYTE_RGBA)); - //mLayerDecl.samplers.push_back(Ogre::TerrainLayerSampler("normal_height", Ogre::PF_BYTE_RGBA)); - - mLayerDecl.elements.push_back( - Ogre::TerrainLayerSamplerElement(0, Ogre::TLSS_ALBEDO, 0, 3)); - //mLayerDecl.elements.push_back( - // Ogre::TerrainLayerSamplerElement(0, Ogre::TLSS_SPECULAR, 3, 1)); - //mLayerDecl.elements.push_back( - // Ogre::TerrainLayerSamplerElement(1, Ogre::TLSS_NORMAL, 0, 3)); - //mLayerDecl.elements.push_back( - // Ogre::TerrainLayerSamplerElement(1, Ogre::TLSS_HEIGHT, 3, 1)); - - - mProfiles.push_back(OGRE_NEW Profile(this, "SM2", "Profile for rendering on Shader Model 2 capable cards")); - setActiveProfile("SM2"); - } - - // ----------------------------------------------------------------------------------------------------------------------- - - TerrainMaterial::Profile::Profile(Ogre::TerrainMaterialGenerator* parent, const Ogre::String& name, const Ogre::String& desc) - : Ogre::TerrainMaterialGenerator::Profile(parent, name, desc) - , mGlobalColourMap(false) - , mMaterial(0) - { - } - - TerrainMaterial::Profile::~Profile() - { - if (mMaterial) - sh::Factory::getInstance().destroyMaterialInstance(mMaterial->getName()); - } - - Ogre::MaterialPtr TerrainMaterial::Profile::generate(const Ogre::Terrain* terrain) - { - const Ogre::String& matName = terrain->getMaterialName(); - - sh::Factory::getInstance().destroyMaterialInstance (matName); - - Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().getByName(matName); - if (!mat.isNull()) - Ogre::MaterialManager::getSingleton().remove(matName); - - mMaterial = sh::Factory::getInstance().createMaterialInstance (matName); - mMaterial->setProperty ("allow_fixed_function", sh::makeProperty(new sh::BooleanValue(false))); - - int numPasses = getRequiredPasses(terrain); - int maxLayersInOnePass = getMaxLayersPerPass(terrain); - - for (int pass=0; passcreatePass (); - - p->setProperty ("vertex_program", sh::makeProperty(new sh::StringValue("terrain_vertex"))); - p->setProperty ("fragment_program", sh::makeProperty(new sh::StringValue("terrain_fragment"))); - if (pass != 0) - { - p->setProperty ("scene_blend", sh::makeProperty(new sh::StringValue("alpha_blend"))); - // Only write if depth is equal to the depth value written by the previous pass. - p->setProperty ("depth_func", sh::makeProperty(new sh::StringValue("equal"))); - } - - p->mShaderProperties.setProperty ("colour_map", sh::makeProperty(new sh::BooleanValue(mGlobalColourMap))); - p->mShaderProperties.setProperty ("is_first_pass", sh::makeProperty(new sh::BooleanValue(pass == 0))); - - // global colour map - sh::MaterialInstanceTextureUnit* colourMap = p->createTextureUnit ("colourMap"); - colourMap->setProperty ("texture_alias", sh::makeProperty (new sh::StringValue(mMaterial->getName() + "_colourMap"))); - colourMap->setProperty ("tex_address_mode", sh::makeProperty (new sh::StringValue("clamp"))); - - // global normal map - sh::MaterialInstanceTextureUnit* normalMap = p->createTextureUnit ("normalMap"); - normalMap->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(terrain->getTerrainNormalMap ()->getName()))); - normalMap->setProperty ("tex_address_mode", sh::makeProperty (new sh::StringValue("clamp"))); - - Ogre::uint numLayersInThisPass = std::min(maxLayersInOnePass, terrain->getLayerCount()-layerOffset); - - // HACK: Terrain::getLayerBlendTextureIndex should be const, but it is not. - // Remove this once ogre got fixed. - Ogre::Terrain* nonconstTerrain = const_cast(terrain); - - // a blend map might be shared between two passes - // so we can't just use terrain->getBlendTextureCount() - Ogre::uint numBlendTextures=0; - std::vector blendTextures; - for (unsigned int layer=blendmapOffset; layergetBlendTextureName(nonconstTerrain->getLayerBlendTextureIndex( - static_cast(layerOffset+layer)).first); - if (std::find(blendTextures.begin(), blendTextures.end(), blendTextureName) == blendTextures.end()) - { - blendTextures.push_back(blendTextureName); - ++numBlendTextures; - } - } - - p->mShaderProperties.setProperty ("num_layers", sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(numLayersInThisPass)))); - p->mShaderProperties.setProperty ("num_blendmaps", sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(numBlendTextures)))); - - // blend maps - // the index of the first blend map used in this pass - int blendmapStart; - if (terrain->getLayerCount() == 1) // special case. if there's only one layer, we don't need blend maps at all - blendmapStart = 0; - else - blendmapStart = nonconstTerrain->getLayerBlendTextureIndex(static_cast(layerOffset+blendmapOffset)).first; - for (Ogre::uint i = 0; i < numBlendTextures; ++i) - { - sh::MaterialInstanceTextureUnit* blendTex = p->createTextureUnit ("blendMap" + Ogre::StringConverter::toString(i)); - blendTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(terrain->getBlendTextureName(blendmapStart+i)))); - blendTex->setProperty ("tex_address_mode", sh::makeProperty (new sh::StringValue("clamp"))); - } - - // layer maps - for (Ogre::uint i = 0; i < numLayersInThisPass; ++i) - { - sh::MaterialInstanceTextureUnit* diffuseTex = p->createTextureUnit ("diffuseMap" + Ogre::StringConverter::toString(i)); - diffuseTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(terrain->getLayerTextureName(layerOffset+i, 0)))); - - if (i+layerOffset > 0) - { - int blendTextureIndex = nonconstTerrain->getLayerBlendTextureIndex(static_cast(layerOffset+i)).first; - int blendTextureComponent = nonconstTerrain->getLayerBlendTextureIndex(static_cast(layerOffset+i)).second; - p->mShaderProperties.setProperty ("blendmap_component_" + Ogre::StringConverter::toString(i), - sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(blendTextureIndex-blendmapStart) + "." + getComponent(blendTextureComponent)))); - } - else - { - // just to make it shut up about blendmap_component_0 not existing in the first pass. - // it might be retrieved, but will never survive the preprocessing step. - p->mShaderProperties.setProperty ("blendmap_component_" + Ogre::StringConverter::toString(i), - sh::makeProperty (new sh::StringValue(""))); - } - } - - // shadow - for (Ogre::uint i = 0; i < 3; ++i) - { - sh::MaterialInstanceTextureUnit* shadowTex = p->createTextureUnit ("shadowMap" + Ogre::StringConverter::toString(i)); - shadowTex->setProperty ("content_type", sh::makeProperty (new sh::StringValue("shadow"))); - } - - p->mShaderProperties.setProperty ("shadowtexture_offset", sh::makeProperty (new sh::StringValue( - Ogre::StringConverter::toString(numBlendTextures + numLayersInThisPass + 2)))); - - // make sure the pass index is fed to the permutation handler, because blendmap components may be different - p->mShaderProperties.setProperty ("pass_index", sh::makeProperty(new sh::IntValue(pass))); - } - - return Ogre::MaterialManager::getSingleton().getByName(matName); - } - - void TerrainMaterial::Profile::setGlobalColourMapEnabled (bool enabled) - { - mGlobalColourMap = enabled; - mParent->_markChanged(); - } - - void TerrainMaterial::Profile::setGlobalColourMap (Ogre::Terrain* terrain, const std::string& name) - { - sh::Factory::getInstance ().setTextureAlias (terrain->getMaterialName () + "_colourMap", name); - } - - Ogre::MaterialPtr TerrainMaterial::Profile::generateForCompositeMap(const Ogre::Terrain* terrain) - { - throw std::runtime_error ("composite map not supported"); - } - - Ogre::uint8 TerrainMaterial::Profile::getMaxLayers(const Ogre::Terrain* terrain) const - { - return 255; - } - - int TerrainMaterial::Profile::getMaxLayersPerPass (const Ogre::Terrain* terrain) - { - // count the texture units free - Ogre::uint8 freeTextureUnits = 16; - // normalmap - --freeTextureUnits; - // colourmap - --freeTextureUnits; - // shadow - --freeTextureUnits; - --freeTextureUnits; - --freeTextureUnits; - - // each layer needs 1.25 units (1xdiffusespec, 0.25xblend) - return static_cast(freeTextureUnits / (1.25f)); - } - - int TerrainMaterial::Profile::getRequiredPasses (const Ogre::Terrain* terrain) - { - int maxLayersPerPass = getMaxLayersPerPass(terrain); - assert(terrain->getLayerCount()); - assert(maxLayersPerPass); - return std::ceil(static_cast(terrain->getLayerCount()) / maxLayersPerPass); - } - - void TerrainMaterial::Profile::updateParams(const Ogre::MaterialPtr& mat, const Ogre::Terrain* terrain) - { - } - - void TerrainMaterial::Profile::updateParamsForCompositeMap(const Ogre::MaterialPtr& mat, const Ogre::Terrain* terrain) - { - } - - void TerrainMaterial::Profile::requestOptions(Ogre::Terrain* terrain) - { - terrain->_setMorphRequired(true); - terrain->_setNormalMapRequired(true); // global normal map - terrain->_setLightMapRequired(false); - terrain->_setCompositeMapRequired(false); - } - -} diff --git a/apps/openmw/mwrender/terrainmaterial.hpp b/apps/openmw/mwrender/terrainmaterial.hpp deleted file mode 100644 index c90499bae..000000000 --- a/apps/openmw/mwrender/terrainmaterial.hpp +++ /dev/null @@ -1,88 +0,0 @@ -/* ------------------------------------------------------------------------------ -This source file is part of OGRE -(Object-oriented Graphics Rendering Engine) -For the latest info, see http://www.ogre3d.org/ - -Copyright (c) 2000-2011 Torus Knot Software Ltd - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. ------------------------------------------------------------------------------ -*/ - -#ifndef MWRENDER_TERRAINMATERIAL_H -#define MWRENDER_TERRAINMATERIAL_H - -#include "OgreTerrainPrerequisites.h" -#include "OgreTerrainMaterialGenerator.h" -#include "OgreGpuProgramParams.h" - -namespace sh -{ - class MaterialInstance; -} - -namespace MWRender -{ - - class TerrainMaterial : public Ogre::TerrainMaterialGenerator - { - public: - - class Profile : public Ogre::TerrainMaterialGenerator::Profile - { - public: - Profile(Ogre::TerrainMaterialGenerator* parent, const Ogre::String& name, const Ogre::String& desc); - virtual ~Profile(); - - virtual bool isVertexCompressionSupported() const { return false; } - - virtual Ogre::MaterialPtr generate(const Ogre::Terrain* terrain); - - virtual Ogre::MaterialPtr generateForCompositeMap(const Ogre::Terrain* terrain); - - virtual Ogre::uint8 getMaxLayers(const Ogre::Terrain* terrain) const; - - virtual void updateParams(const Ogre::MaterialPtr& mat, const Ogre::Terrain* terrain); - - virtual void updateParamsForCompositeMap(const Ogre::MaterialPtr& mat, const Ogre::Terrain* terrain); - - virtual void requestOptions(Ogre::Terrain* terrain); - - void setGlobalColourMapEnabled(bool enabled); - void setGlobalColourMap (Ogre::Terrain* terrain, const std::string& name); - virtual void setLightmapEnabled(bool) {} - - private: - sh::MaterialInstance* mMaterial; - - int getRequiredPasses (const Ogre::Terrain* terrain); - int getMaxLayersPerPass (const Ogre::Terrain* terrain); - - bool mGlobalColourMap; - - }; - - TerrainMaterial(); - }; - -} - - -#endif diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp new file mode 100644 index 000000000..318627fc7 --- /dev/null +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -0,0 +1,56 @@ +#include "terrainstorage.hpp" + +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" +#include "../mwworld/esmstore.hpp" + +namespace MWRender +{ + + Ogre::AxisAlignedBox TerrainStorage::getBounds() + { + int minX = 0, minY = 0, maxX = 0, maxY = 0; + + const MWWorld::ESMStore &esmStore = + MWBase::Environment::get().getWorld()->getStore(); + + MWWorld::Store::iterator it = esmStore.get().extBegin(); + for (; it != esmStore.get().extEnd(); ++it) + { + if (it->getGridX() < minX) + minX = it->getGridX(); + if (it->getGridX() > maxX) + maxX = it->getGridX(); + if (it->getGridY() < minY) + minY = it->getGridY(); + if (it->getGridY() > maxY) + maxY = it->getGridY(); + } + + // since grid coords are at cell origin, we need to add 1 cell + maxX += 1; + maxY += 1; + + return Ogre::AxisAlignedBox(minX, minY, 0, maxX, maxY, 0); + } + + ESM::Land* TerrainStorage::getLand(int cellX, int cellY) + { + const MWWorld::ESMStore &esmStore = + MWBase::Environment::get().getWorld()->getStore(); + ESM::Land* land = esmStore.get().search(cellX, cellY); + // Load the data we are definitely going to need + int mask = ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX; + if (land && !land->isDataLoaded(mask)) + land->loadData(mask); + return land; + } + + const ESM::LandTexture* TerrainStorage::getLandTexture(int index, short plugin) + { + const MWWorld::ESMStore &esmStore = + MWBase::Environment::get().getWorld()->getStore(); + return esmStore.get().find(index, plugin); + } + +} diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp new file mode 100644 index 000000000..ebf5e26ab --- /dev/null +++ b/apps/openmw/mwrender/terrainstorage.hpp @@ -0,0 +1,22 @@ +#ifndef MWRENDER_TERRAINSTORAGE_H +#define MWRENDER_TERRAINSTORAGE_H + +#include + +namespace MWRender +{ + + class TerrainStorage : public Terrain::Storage + { + private: + virtual ESM::Land* getLand (int cellX, int cellY); + virtual const ESM::LandTexture* getLandTexture(int index, short plugin); + public: + virtual Ogre::AxisAlignedBox getBounds(); + ///< Get bounds in cell units + }; + +} + + +#endif diff --git a/apps/openmw/mwrender/videoplayer.cpp b/apps/openmw/mwrender/videoplayer.cpp index 8c557ee99..ee2b80f73 100644 --- a/apps/openmw/mwrender/videoplayer.cpp +++ b/apps/openmw/mwrender/videoplayer.cpp @@ -1037,7 +1037,6 @@ public: VideoPlayer::VideoPlayer(Ogre::SceneManager* sceneMgr, Ogre::RenderWindow* window) : mState(NULL) , mSceneMgr(sceneMgr) - , mVideoMaterial(NULL) , mRectangle(NULL) , mNode(NULL) , mAllowSkipping(false) diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 772eaf623..082551f37 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -202,7 +202,10 @@ Water::Water (Ogre::Camera *camera, RenderingManager* rend) : mWaterPlane = Plane(Vector3::UNIT_Z, 0); - MeshManager::getSingleton().createPlane("water", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, mWaterPlane, CELL_SIZE*5, CELL_SIZE * 5, 10, 10, true, 1, 3,3, Vector3::UNIT_Y); + int waterScale = 300; + + MeshManager::getSingleton().createPlane("water", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, mWaterPlane, + CELL_SIZE*5*waterScale, CELL_SIZE*5*waterScale, 10, 10, true, 1, 3*waterScale,3*waterScale, Vector3::UNIT_Y); mWater = mSceneMgr->createEntity("water"); mWater->setVisibilityFlags(RV_Water); diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index b77d61aa5..fac44c08f 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -352,7 +352,6 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) { - MWWorld::Ptr ptr = R()(runtime); std::string actorID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index 08b499175..ff3b60ca6 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -351,5 +351,6 @@ op 0x200021b: SetWerewolfAcrobatics op 0x200021c: SetWerewolfAcrobaticsExplicit op 0x200021d: ShowVars op 0x200021e: ShowVarsExplicit +op 0x200021f: ToggleGodMode -opcodes 0x200021f-0x3ffffff unused +opcodes 0x2000220-0x3ffffff unused diff --git a/apps/openmw/mwscript/guiextensions.cpp b/apps/openmw/mwscript/guiextensions.cpp index 4bb10dad5..6c89a0d1c 100644 --- a/apps/openmw/mwscript/guiextensions.cpp +++ b/apps/openmw/mwscript/guiextensions.cpp @@ -67,11 +67,6 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { - InterpreterContext& context = - static_cast (runtime.getContext()); - - MWWorld::Ptr ptr = context.getReference(); - runtime.push (MWBase::Environment::get().getWindowManager()->readPressedButton()); } }; diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 77dc1deb0..c53cafa17 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -102,8 +102,6 @@ namespace MWScript InterpreterContext& context = static_cast (runtime.getContext()); - MWWorld::Ptr ptr = context.getReference(); - context.executeActivation(); } }; @@ -719,6 +717,20 @@ namespace MWScript } }; + class OpToggleGodMode : public Interpreter::Opcode0 + { + public: + virtual void execute (Interpreter::Runtime& runtime) + { + InterpreterContext& context = static_cast (runtime.getContext()); + + bool enabled = MWBase::Environment::get().getWorld()->toggleGodMode(); + + // context.report (enabled ? "God Mode -> On" : "God Mode -> Off"); + context.report("Unimplemented"); + } + }; + void installOpcodes (Interpreter::Interpreter& interpreter) { @@ -777,6 +789,7 @@ namespace MWScript interpreter.installSegment5 (Compiler::Misc::opcodeEnableTeleporting, new OpEnableTeleporting); interpreter.installSegment5 (Compiler::Misc::opcodeShowVars, new OpShowVars); interpreter.installSegment5 (Compiler::Misc::opcodeShowVarsExplicit, new OpShowVars); + interpreter.installSegment5 (Compiler::Misc::opcodeToggleGodMode, new OpToggleGodMode); } } } diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index eca9d279b..603515ff4 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -1081,7 +1081,6 @@ namespace MWScript } }; - void installOpcodes (Interpreter::Interpreter& interpreter) { for (int i=0; i); interpreter.installSegment5 (Compiler::Stats::opcodeUndoWerewolfExplicit, new OpSetWerewolf); interpreter.installSegment5 (Compiler::Stats::opcodeSetWerewolfAcrobatics, new OpSetWerewolfAcrobatics); - interpreter.installSegment5 (Compiler::Stats::opcodeSetWerewolfAcrobaticsExplicit, new OpSetWerewolfAcrobatics); + interpreter.installSegment5 (Compiler::Stats::opcodeSetWerewolfAcrobaticsExplicit, new OpSetWerewolfAcrobatics); } } } diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 03e2ce623..372be8393 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -4,11 +4,10 @@ #include #include -#include "../mwworld/esmstore.hpp" - #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwworld/player.hpp" #include "sound_output.hpp" @@ -103,6 +102,7 @@ namespace MWSound SoundManager::~SoundManager() { + mUnderwaterSound.reset(); mActiveSounds.clear(); mMusic.reset(); mOutput.reset(); @@ -474,27 +474,32 @@ namespace MWSound void SoundManager::updateRegionSound(float duration) { - MWWorld::Ptr::CellStore *current = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getCell(); + static float sTimeToNextEnvSound = 0.0f; static int total = 0; static std::string regionName = ""; - static float timePassed = 0.0; + static float sTimePassed = 0.0; + MWBase::World *world = MWBase::Environment::get().getWorld(); + const MWWorld::Ptr player = world->getPlayer().getPlayer(); + const ESM::Cell *cell = player.getCell()->mCell; - //If the region has changed - timePassed += duration; - if(!current->mCell->isExterior() || timePassed < 10) + sTimePassed += duration; + if(!cell->isExterior() || sTimePassed < sTimeToNextEnvSound) return; - timePassed = 0; - if(regionName != current->mCell->mRegion) + float a = std::rand() / (double)RAND_MAX; + // NOTE: We should use the "Minimum Time Between Environmental Sounds" and + // "Maximum Time Between Environmental Sounds" fallback settings here. + sTimeToNextEnvSound = 5.0f*a + 15.0f*(1.0f-a); + sTimePassed = 0; + + if(regionName != cell->mRegion) { - regionName = current->mCell->mRegion; + regionName = cell->mRegion; total = 0; } - const ESM::Region *regn = - MWBase::Environment::get().getWorld()->getStore().get().search(regionName); - - if (regn == NULL) + const ESM::Region *regn = world->getStore().get().search(regionName); + if(regn == NULL) return; std::vector::const_iterator soundIter; @@ -550,15 +555,13 @@ namespace MWSound { env = Env_Underwater; //play underwater sound - //HACK: this sound is always played underwater, so set volume and pitch higher (it's then lowered) - //Currently not possible to play looping sound with no environment - if(!getSoundPlaying(MWWorld::Ptr(), "Underwater")) - playSound("Underwater", 1.11, 1.42 ,Play_TypeSfx, Play_Loop ); + if(!(mUnderwaterSound && mUnderwaterSound->isPlaying())) + mUnderwaterSound = playSound("Underwater", 1.0f, 1.0f, Play_TypeSfx, Play_LoopNoEnv); } - else + else if(mUnderwaterSound) { - //no need to check if it's playing, stop sound does nothing in that case - stopSound("Underwater"); + mUnderwaterSound->stop(); + mUnderwaterSound.reset(); } mOutput->updateListener( diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index 2450ba5c3..f62e62d50 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -44,6 +44,8 @@ namespace MWSound typedef std::map SoundMap; SoundMap mActiveSounds; + MWBase::SoundPtr mUnderwaterSound; + Ogre::Vector3 mListenerPos; Ogre::Vector3 mListenerDir; Ogre::Vector3 mListenerUp; diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 8bb3d3c8d..0c145ab60 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -43,7 +43,8 @@ namespace MWWorld // Skip this when reference was deleted. // TODO: Support respawning references, in this case, we need to track it somehow. if (ref.mDeleted) { - mList.erase(iter); + if (iter != mList.end()) + mList.erase(iter); return; } diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 9d111a525..c6768f5fd 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -80,7 +80,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::end() bool MWWorld::ContainerStore::stacks(const Ptr& ptr1, const Ptr& ptr2) { /// \todo add current enchantment charge here when it is implemented - if ( ptr1.getCellRef().mRefID == ptr2.getCellRef().mRefID + if ( Misc::StringUtils::ciEqual(ptr1.getCellRef().mRefID, ptr2.getCellRef().mRefID) && MWWorld::Class::get(ptr1).getScript(ptr1) == "" // item with a script never stacks && MWWorld::Class::get(ptr1).getEnchantment(ptr1) == "" // item with enchantment never stacks (we could revisit this later, but for now it makes selecting items in the spell window much easier) && ptr1.getCellRef().mOwner == ptr2.getCellRef().mOwner diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 5a4f1db54..7703f2d23 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -5,6 +5,8 @@ #include +#include + namespace MWWorld { @@ -21,8 +23,10 @@ static bool isCacheableRecord(int id) return false; } -void ESMStore::load(ESM::ESMReader &esm) +void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) { + listener->setProgressRange(1000); + std::set missing; ESM::Dialogue *dialogue = 0; @@ -109,6 +113,7 @@ void ESMStore::load(ESM::ESMReader &esm) mIds[id] = n.val; } } + listener->setProgress(esm.getFileOffset() / (float)esm.getFileSize() * 1000); } /* This information isn't needed on screen. But keep the code around diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index d86faf766..ebb086cee 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -6,6 +6,11 @@ #include #include "store.hpp" +namespace Loading +{ + class Listener; +} + namespace MWWorld { class ESMStore @@ -158,7 +163,7 @@ namespace MWWorld mNpcs.insert(mPlayerTemplate); } - void load(ESM::ESMReader &esm); + void load(ESM::ESMReader &esm, Loading::Listener* listener); template const Store &get() const { diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index 371783bc5..433fe0892 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -29,109 +29,84 @@ namespace MWWorld { static const float sMaxSlope = 60.0f; - static const float sStepSize = 30.0f; + static const float sStepSize = 32.0f; // Arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared. - static const int sMaxIterations = 50; + static const int sMaxIterations = 8; class MovementSolver { private: - static bool stepMove(Ogre::Vector3& position, const Ogre::Quaternion& orient, const Ogre::Vector3 &velocity, float remainingTime, - const Ogre::Vector3 &halfExtents, bool isInterior, + static float getSlope(const Ogre::Vector3 &normal) + { + return normal.angleBetween(Ogre::Vector3(0.0f,0.0f,1.0f)).valueDegrees(); + } + + static bool stepMove(btCollisionObject *colobj, Ogre::Vector3 &position, + const Ogre::Vector3 &velocity, float &remainingTime, OEngine::Physic::PhysicEngine *engine) { - traceResults trace; // no initialization needed + OEngine::Physic::ActorTracer tracer, stepper; - newtrace(&trace, orient, position, position+Ogre::Vector3(0.0f,0.0f,sStepSize), - halfExtents, isInterior, engine); - if(trace.fraction == 0.0f) + stepper.doTrace(colobj, position, position+Ogre::Vector3(0.0f,0.0f,sStepSize), engine); + if(stepper.mFraction < std::numeric_limits::epsilon()) return false; - newtrace(&trace, orient, trace.endpos, trace.endpos + velocity*remainingTime, - halfExtents, isInterior, engine); - if(trace.fraction == 0.0f || (trace.fraction != 1.0f && getSlope(trace.planenormal) > sMaxSlope)) + tracer.doTrace(colobj, stepper.mEndPos, stepper.mEndPos + velocity*remainingTime, engine); + if(tracer.mFraction < std::numeric_limits::epsilon()) return false; - newtrace(&trace, orient, trace.endpos, trace.endpos-Ogre::Vector3(0.0f,0.0f,sStepSize), halfExtents, isInterior, engine); - if(getSlope(trace.planenormal) <= sMaxSlope) + stepper.doTrace(colobj, tracer.mEndPos, tracer.mEndPos-Ogre::Vector3(0.0f,0.0f,sStepSize), engine); + if(stepper.mFraction < 1.0f && getSlope(stepper.mPlaneNormal) <= sMaxSlope) { // only step down onto semi-horizontal surfaces. don't step down onto the side of a house or a wall. - position = trace.endpos; + position = stepper.mEndPos; + remainingTime *= (1.0f-tracer.mFraction); return true; } return false; } - static void clipVelocity(Ogre::Vector3& inout, const Ogre::Vector3& normal, float overbounce=1.0f) - { - //Math stuff. Basically just project the velocity vector onto the plane represented by the normal. - //More specifically, it projects velocity onto the normal, takes that result, multiplies it by overbounce and then subtracts it from velocity. - float backoff = inout.dotProduct(normal); - if(backoff < 0.0f) - backoff *= overbounce; - else - backoff /= overbounce; - - inout -= normal*backoff; - } - static void projectVelocity(Ogre::Vector3& velocity, const Ogre::Vector3& direction) + ///Project a vector u on another vector v + static inline Ogre::Vector3 project(const Ogre::Vector3 u, const Ogre::Vector3 &v) { - Ogre::Vector3 normalizedDirection(direction); - normalizedDirection.normalise(); - - // no divide by normalizedDirection.length necessary because it's normalized - velocity = normalizedDirection * velocity.dotProduct(normalizedDirection); + return v * u.dotProduct(v); } - static float getSlope(const Ogre::Vector3 &normal) + ///Helper for computing the character sliding + static inline Ogre::Vector3 slide(Ogre::Vector3 direction, const Ogre::Vector3 &planeNormal) { - return normal.angleBetween(Ogre::Vector3(0.0f,0.0f,1.0f)).valueDegrees(); + return direction - project(direction, planeNormal); } + public: static Ogre::Vector3 traceDown(const MWWorld::Ptr &ptr, OEngine::Physic::PhysicEngine *engine) { const ESM::Position &refpos = ptr.getRefData().getPosition(); Ogre::Vector3 position(refpos.pos); - bool hit=false; - bool isInterior = !ptr.getCell()->isExterior(); - OEngine::Physic::PhysicActor *physicActor = engine->getCharacter(ptr.getRefData().getHandle()); if (!physicActor) return position; - bool wasCollisionMode = physicActor->getCollisionMode(); - - physicActor->enableCollisions(false); - - Ogre::Vector3 halfExtents = physicActor->getHalfExtents();// + Vector3(1,1,1); - halfExtents.z = 1; // we trace the feet only, so we use a very thin box - - Ogre::Vector3 newPosition = position; - - traceResults trace; //no initialization needed - - int maxHeight = 200.f; - newtrace(&trace, Ogre::Quaternion::IDENTITY, newPosition, newPosition-Ogre::Vector3(0,0,maxHeight), halfExtents, isInterior, engine); - if(trace.fraction < 1.0f) - hit = true; - newPosition = trace.endpos; + const int maxHeight = 200.f; + OEngine::Physic::ActorTracer tracer; + tracer.findGround(physicActor->getCollisionBody(), position, position-Ogre::Vector3(0,0,maxHeight), engine); + if(tracer.mFraction >= 1.0f) + { + physicActor->setOnGround(false); + return position; + } - physicActor->setOnGround(hit && getSlope(trace.planenormal) <= sMaxSlope); - if (wasCollisionMode) - physicActor->enableCollisions(true); + physicActor->setOnGround(getSlope(tracer.mPlaneNormal) <= sMaxSlope); - if (hit) - return newPosition+Ogre::Vector3(0,0,4); - else - return position; + return tracer.mEndPos; } static Ogre::Vector3 move(const MWWorld::Ptr &ptr, const Ogre::Vector3 &movement, float time, - bool gravity, OEngine::Physic::PhysicEngine *engine) + bool isFlying, float waterlevel, OEngine::Physic::PhysicEngine *engine) { const ESM::Position &refpos = ptr.getRefData().getPosition(); Ogre::Vector3 position(refpos.pos); @@ -141,107 +116,130 @@ namespace MWWorld if(!physicActor || !physicActor->getCollisionMode()) { // FIXME: This works, but it's inconcsistent with how the rotations are applied elsewhere. Why? - return position + (Ogre::Quaternion(Ogre::Radian( -refpos.rot[2]), Ogre::Vector3::UNIT_Z)* - Ogre::Quaternion(Ogre::Radian( -refpos.rot[1]), Ogre::Vector3::UNIT_Y)* - Ogre::Quaternion(Ogre::Radian( refpos.rot[0]), Ogre::Vector3::UNIT_X)) * - movement; + return position + (Ogre::Quaternion(Ogre::Radian(-refpos.rot[2]), Ogre::Vector3::UNIT_Z)* + Ogre::Quaternion(Ogre::Radian(-refpos.rot[1]), Ogre::Vector3::UNIT_Y)* + Ogre::Quaternion(Ogre::Radian( refpos.rot[0]), Ogre::Vector3::UNIT_X)) * + movement * time; } - traceResults trace; //no initialization needed - bool onground = false; - float remainingTime = time; - bool isInterior = !ptr.getCell()->isExterior(); - Ogre::Vector3 halfExtents = physicActor->getHalfExtents();// + Vector3(1,1,1); - bool wasCollisionMode = physicActor->getCollisionMode(); - physicActor->enableCollisions(false); - Ogre::Quaternion orient = Ogre::Quaternion(Ogre::Radian(ptr.getRefData().getPosition().rot[2]), Ogre::Vector3::UNIT_Z); + btCollisionObject *colobj = physicActor->getCollisionBody(); + Ogre::Vector3 halfExtents = physicActor->getHalfExtents(); + position.z += halfExtents.z; + + waterlevel -= halfExtents.z * 0.5; + + OEngine::Physic::ActorTracer tracer; + bool wasOnGround = false; + bool isOnGround = false; + Ogre::Vector3 inertia(0.0f); Ogre::Vector3 velocity; - if(!gravity) + if(position.z < waterlevel || isFlying) { - velocity = (Ogre::Quaternion(Ogre::Radian( -refpos.rot[2]), Ogre::Vector3::UNIT_Z)* - Ogre::Quaternion(Ogre::Radian( -refpos.rot[1]), Ogre::Vector3::UNIT_Y)* - Ogre::Quaternion(Ogre::Radian( refpos.rot[0]), Ogre::Vector3::UNIT_X)) * - movement / time; + velocity = (Ogre::Quaternion(Ogre::Radian(-refpos.rot[2]), Ogre::Vector3::UNIT_Z)* + Ogre::Quaternion(Ogre::Radian(-refpos.rot[1]), Ogre::Vector3::UNIT_Y)* + Ogre::Quaternion(Ogre::Radian( refpos.rot[0]), Ogre::Vector3::UNIT_X)) * + movement; } else { + velocity = Ogre::Quaternion(Ogre::Radian(-refpos.rot[2]), Ogre::Vector3::UNIT_Z) * movement; + if(!physicActor->getOnGround()) + { + // If falling, add part of the incoming velocity with the current inertia + velocity = velocity*time + physicActor->getInertialForce(); + } + inertia = velocity; + if(!(movement.z > 0.0f)) { - newtrace(&trace, orient, position, position-Ogre::Vector3(0,0,4), halfExtents, isInterior, engine); - if(trace.fraction < 1.0f && getSlope(trace.planenormal) <= sMaxSlope) - onground = true; + wasOnGround = physicActor->getOnGround(); + tracer.doTrace(colobj, position, position-Ogre::Vector3(0,0,2), engine); + if(tracer.mFraction < 1.0f && getSlope(tracer.mPlaneNormal) <= sMaxSlope) + isOnGround = true; } - velocity = Ogre::Quaternion(Ogre::Radian(-refpos.rot[2]), Ogre::Vector3::UNIT_Z)*movement / time; - velocity.z += physicActor->getVerticalForce(); } - Ogre::Vector3 clippedVelocity(velocity); - if(onground) + if(isOnGround) { - // if we're on the ground, force velocity to track it - clippedVelocity.z = velocity.z = std::max(0.0f, velocity.z); - clipVelocity(clippedVelocity, trace.planenormal); + // if we're on the ground, don't try to fall + velocity.z = std::max(0.0f, velocity.z); } - const Ogre::Vector3 up(0.0f, 0.0f, 1.0f); Ogre::Vector3 newPosition = position; - int iterations = 0; - do { + float remainingTime = time; + for(int iterations = 0;iterations < sMaxIterations && remainingTime > 0.01f;++iterations) + { + Ogre::Vector3 nextpos = newPosition + velocity*remainingTime; + + if(newPosition.z < waterlevel && !isFlying && + nextpos.z > waterlevel && newPosition.z <= waterlevel) + { + const Ogre::Vector3 down(0,0,-1); + Ogre::Real movelen = velocity.normalise(); + Ogre::Vector3 reflectdir = velocity.reflect(down); + reflectdir.normalise(); + velocity = slide(reflectdir, down)*movelen; + continue; + } + // trace to where character would go if there were no obstructions - newtrace(&trace, orient, newPosition, newPosition+clippedVelocity*remainingTime, halfExtents, isInterior, engine); - newPosition = trace.endpos; - remainingTime = remainingTime * (1.0f-trace.fraction); + tracer.doTrace(colobj, newPosition, nextpos, engine); // check for obstructions - if(trace.fraction < 1.0f) + if(tracer.mFraction >= 1.0f) { - //std::cout<<"angle: "< 0.0f); + // We hit something. Try to step up onto it. + if(stepMove(colobj, newPosition, velocity, remainingTime, engine)) + isOnGround = !(newPosition.z < waterlevel || isFlying); // Only on the ground if there's gravity + else + { + // Can't move this way, try to find another spot along the plane + Ogre::Real movelen = velocity.normalise(); + Ogre::Vector3 reflectdir = velocity.reflect(tracer.mPlaneNormal); + reflectdir.normalise(); + velocity = slide(reflectdir, tracer.mPlaneNormal)*movelen; + + // Do not allow sliding upward if there is gravity. Stepping will have taken + // care of that. + if(!(newPosition.z < waterlevel || isFlying)) + velocity.z = std::min(velocity.z, 0.0f); + } + } - if(onground) + if(isOnGround || wasOnGround) { - newtrace(&trace, orient, newPosition, newPosition-Ogre::Vector3(0,0,sStepSize+4.0f), halfExtents, isInterior, engine); - if(trace.fraction < 1.0f && getSlope(trace.planenormal) <= sMaxSlope) - newPosition.z = trace.endpos.z + 2.0f; + tracer.doTrace(colobj, newPosition, newPosition-Ogre::Vector3(0,0,sStepSize+2.0f), engine); + if(tracer.mFraction < 1.0f && getSlope(tracer.mPlaneNormal) <= sMaxSlope) + { + newPosition.z = tracer.mEndPos.z + 1.0f; + isOnGround = true; + } else - onground = false; + isOnGround = false; + } + + if(isOnGround || newPosition.z < waterlevel || isFlying) + physicActor->setInertialForce(Ogre::Vector3(0.0f)); + else + { + inertia.z += time*-627.2f; + physicActor->setInertialForce(inertia); } - physicActor->setOnGround(onground); - physicActor->setVerticalForce(!onground ? clippedVelocity.z - time*627.2f : 0.0f); - if (wasCollisionMode) - physicActor->enableCollisions(true); + physicActor->setOnGround(isOnGround); + + newPosition.z -= halfExtents.z; return newPosition; } }; PhysicsSystem::PhysicsSystem(OEngine::Render::OgreRenderer &_rend) : - mRender(_rend), mEngine(0) + mRender(_rend), mEngine(0), mTimeAccum(0.0f) { // Create physics. shapeLoader is deleted by the physic engine NifBullet::ManualBulletShapeLoader* shapeLoader = new NifBullet::ManualBulletShapeLoader(); @@ -258,7 +256,7 @@ namespace MWWorld return mEngine; } - std::pair PhysicsSystem::getFacedHandle (MWWorld::World& world, float queryDistance) + std::pair PhysicsSystem::getFacedHandle(float queryDistance) { Ray ray = mRender.getCamera()->getCameraToViewportRay(0.5, 0.5); @@ -268,8 +266,7 @@ namespace MWWorld btVector3 dir(dir_.x, dir_.y, dir_.z); btVector3 dest = origin + dir * queryDistance; - std::pair result; - /*auto*/ result = mEngine->rayTest(origin, dest); + std::pair result = mEngine->rayTest(origin, dest); result.second *= queryDistance; return std::make_pair (result.second, result.first); @@ -311,52 +308,35 @@ namespace MWWorld return results; } - std::pair PhysicsSystem::getFacedHandle(const Ogre::Vector3 &origin_, const Ogre::Quaternion &orient_, float queryDistance) + std::pair PhysicsSystem::getHitContact(const std::string &name, + const Ogre::Vector3 &origin, + const Ogre::Quaternion &orient, + float queryDistance) { - btVector3 origin(origin_.x, origin_.y, origin_.z); - - std::pair result = mEngine->sphereTest(queryDistance,origin); - if(result.first == "") return std::make_pair("",0); - btVector3 a = result.second - origin; - Ogre::Vector3 a_ = Ogre::Vector3(a.x(),a.y(),a.z()); - a_ = orient_.Inverse()*a_; - Ogre::Vector2 a_xy = Ogre::Vector2(a_.x,a_.y); - Ogre::Vector2 a_yz = Ogre::Vector2(a_xy.length(),a_.z); - float axy = a_xy.angleBetween(Ogre::Vector2::UNIT_Y).valueDegrees(); - float az = a_yz.angleBetween(Ogre::Vector2::UNIT_X).valueDegrees(); - - float fCombatAngleXY = MWBase::Environment::get().getWorld()->getStore().get().find("fCombatAngleXY")->getFloat(); - float fCombatAngleZ = MWBase::Environment::get().getWorld()->getStore().get().find("fCombatAngleZ")->getFloat(); - if(abs(axy) < fCombatAngleXY && abs(az) < fCombatAngleZ) - return std::make_pair (result.first,result.second.length()); - else - return std::make_pair("",0); + const MWWorld::Store &store = MWBase::Environment::get().getWorld()->getStore().get(); + + btConeShape shape(Ogre::Degree(store.find("fCombatAngleXY")->getFloat()/2.0f).valueRadians(), + queryDistance); + shape.setLocalScaling(btVector3(1, 1, Ogre::Degree(store.find("fCombatAngleZ")->getFloat()/2.0f).valueRadians() / + shape.getRadius())); + + // The shape origin is its center, so we have to move it forward by half the length. The + // real origin will be provided to getFilteredContact to find the closest. + Ogre::Vector3 center = origin + (orient * Ogre::Vector3(0.0f, queryDistance*0.5f, 0.0f)); + + btCollisionObject object; + object.setCollisionShape(&shape); + object.setWorldTransform(btTransform(btQuaternion(orient.x, orient.y, orient.z, orient.w), + btVector3(center.x, center.y, center.z))); + + std::pair result = mEngine->getFilteredContact( + name, btVector3(origin.x, origin.y, origin.z), &object); + if(!result.first) + return std::make_pair(std::string(), Ogre::Vector3(&result.second[0])); + return std::make_pair(result.first->mName, Ogre::Vector3(&result.second[0])); } - void PhysicsSystem::setCurrentWater(bool hasWater, int waterHeight) - { - // TODO: store and use - } - - btVector3 PhysicsSystem::getRayPoint(float extent) - { - //get a ray pointing to the center of the viewport - Ray centerRay = mRender.getCamera()->getCameraToViewportRay( - mRender.getViewport()->getWidth()/2, - mRender.getViewport()->getHeight()/2); - btVector3 result(centerRay.getPoint(extent).x,centerRay.getPoint(extent).y,centerRay.getPoint(extent).z); - return result; - } - - btVector3 PhysicsSystem::getRayPoint(float extent, float mouseX, float mouseY) - { - //get a ray pointing to the center of the viewport - Ray centerRay = mRender.getCamera()->getCameraToViewportRay(mouseX, mouseY); - btVector3 result(centerRay.getPoint(extent).x,centerRay.getPoint(extent).y,centerRay.getPoint(extent).z); - return result; - } - bool PhysicsSystem::castRay(const Vector3& from, const Vector3& to, bool raycastingObjectOnly,bool ignoreHeightMap) { btVector3 _from, _to; @@ -410,11 +390,6 @@ namespace MWWorld return mEngine->getCollisions(ptr.getRefData().getBaseNode()->getName()); } - Ogre::Vector3 PhysicsSystem::move(const MWWorld::Ptr &ptr, const Ogre::Vector3 &movement, float time, bool gravity) - { - return MovementSolver::move(ptr, movement, time, gravity, mEngine); - } - Ogre::Vector3 PhysicsSystem::traceDown(const MWWorld::Ptr &ptr) { return MovementSolver::traceDown(ptr, mEngine); @@ -454,8 +429,6 @@ namespace MWWorld void PhysicsSystem::removeObject (const std::string& handle) { - //TODO:check if actor??? - mEngine->removeCharacter(handle); mEngine->removeRigidBody(handle); mEngine->deleteRigidBody(handle); @@ -567,4 +540,49 @@ namespace MWWorld return true; } + + + void PhysicsSystem::queueObjectMovement(const Ptr &ptr, const Ogre::Vector3 &movement) + { + PtrVelocityList::iterator iter = mMovementQueue.begin(); + for(;iter != mMovementQueue.end();iter++) + { + if(iter->first == ptr) + { + iter->second = movement; + return; + } + } + + mMovementQueue.push_back(std::make_pair(ptr, movement)); + } + + const PtrVelocityList& PhysicsSystem::applyQueuedMovement(float dt) + { + mMovementResults.clear(); + + mTimeAccum += dt; + if(mTimeAccum >= 1.0f/60.0f) + { + const MWBase::World *world = MWBase::Environment::get().getWorld(); + PtrVelocityList::iterator iter = mMovementQueue.begin(); + for(;iter != mMovementQueue.end();iter++) + { + float waterlevel = -std::numeric_limits::max(); + const ESM::Cell *cell = iter->first.getCell()->mCell; + if(cell->hasWater()) + waterlevel = cell->mWater; + + Ogre::Vector3 newpos = MovementSolver::move(iter->first, iter->second, mTimeAccum, + world->isFlying(iter->first), + waterlevel, mEngine); + mMovementResults.push_back(std::make_pair(iter->first, newpos)); + } + + mTimeAccum = 0.0f; + } + mMovementQueue.clear(); + + return mMovementResults; + } } diff --git a/apps/openmw/mwworld/physicssystem.hpp b/apps/openmw/mwworld/physicssystem.hpp index 2b8cb00af..3dcd088f5 100644 --- a/apps/openmw/mwworld/physicssystem.hpp +++ b/apps/openmw/mwworld/physicssystem.hpp @@ -5,6 +5,8 @@ #include +#include "ptr.hpp" + namespace OEngine { @@ -21,7 +23,8 @@ namespace OEngine namespace MWWorld { class World; - class Ptr; + + typedef std::vector > PtrVelocityList; class PhysicsSystem { @@ -49,22 +52,18 @@ namespace MWWorld void scaleObject (const MWWorld::Ptr& ptr); bool toggleCollisionMode(); - - Ogre::Vector3 move(const MWWorld::Ptr &ptr, const Ogre::Vector3 &movement, float time, bool gravity); + std::vector getCollisions(const MWWorld::Ptr &ptr); ///< get handles this object collides with Ogre::Vector3 traceDown(const MWWorld::Ptr &ptr); - std::pair getFacedHandle (MWWorld::World& world, float queryDistance); - std::pair getFacedHandle(const Ogre::Vector3 &origin, - const Ogre::Quaternion &orientation, - float queryDistance); + std::pair getFacedHandle(float queryDistance); + std::pair getHitContact(const std::string &name, + const Ogre::Vector3 &origin, + const Ogre::Quaternion &orientation, + float queryDistance); std::vector < std::pair > getFacedHandles (float queryDistance); std::vector < std::pair > getFacedHandles (float mouseX, float mouseY, float queryDistance); - btVector3 getRayPoint(float extent); - btVector3 getRayPoint(float extent, float mouseX, float mouseY); - - // cast ray, return true if it hit something. if raycasringObjectOnlt is set to false, it ignores NPCs and objects with no collisions. bool castRay(const Ogre::Vector3& from, const Ogre::Vector3& to, bool raycastingObjectOnly = true,bool ignoreHeightMap = false); @@ -76,16 +75,25 @@ namespace MWWorld OEngine::Physic::PhysicEngine* getEngine(); - void setCurrentWater(bool hasWater, int waterHeight); - bool getObjectAABB(const MWWorld::Ptr &ptr, Ogre::Vector3 &min, Ogre::Vector3 &max); + /// Queues velocity movement for a Ptr. If a Ptr is already queued, its velocity will + /// be overwritten. Valid until the next call to applyQueuedMovement. + void queueObjectMovement(const Ptr &ptr, const Ogre::Vector3 &velocity); + + const PtrVelocityList& applyQueuedMovement(float dt); + private: OEngine::Render::OgreRenderer &mRender; OEngine::Physic::PhysicEngine* mEngine; std::map handleToMesh; + PtrVelocityList mMovementQueue; + PtrVelocityList mMovementResults; + + float mTimeAccum; + PhysicsSystem (const PhysicsSystem&); PhysicsSystem& operator= (const PhysicsSystem&); }; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 5fa1400b1..0c98ca504 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -1,5 +1,7 @@ #include "scene.hpp" +#include + #include #include @@ -23,13 +25,12 @@ namespace template void insertCellRefList(MWRender::RenderingManager& rendering, - T& cellRefList, MWWorld::CellStore &cell, MWWorld::PhysicsSystem& physics, bool rescale) + T& cellRefList, MWWorld::CellStore &cell, MWWorld::PhysicsSystem& physics, bool rescale, Loading::Listener* loadingListener) { if (!cellRefList.mList.empty()) { const MWWorld::Class& class_ = MWWorld::Class::get (MWWorld::Ptr (&*cellRefList.mList.begin(), &cell)); - int current = 0; for (typename T::List::iterator it = cellRefList.mList.begin(); it != cellRefList.mList.end(); it++) { @@ -41,8 +42,6 @@ namespace it->mRef.mScale = 2; } - ++current; - if (it->mData.getCount() && it->mData.isEnabled()) { MWWorld::Ptr ptr (&*it, &cell); @@ -66,6 +65,8 @@ namespace std::cerr << error + e.what() << std::endl; } } + + loadingListener->increaseProgress(1); } } } @@ -115,7 +116,7 @@ namespace MWWorld mActiveCells.erase(*iter); } - void Scene::loadCell (Ptr::CellStore *cell) + void Scene::loadCell (Ptr::CellStore *cell, Loading::Listener* loadingListener) { // register local scripts MWBase::Environment::get().getWorld()->getLocalScripts().addCell (cell); @@ -148,7 +149,7 @@ namespace MWWorld // ... then references. This is important for adjustPosition to work correctly. /// \todo rescale depending on the state of a new GMST - insertCell (*cell, true); + insertCell (*cell, true, loadingListener); mRendering.cellAdded (cell); @@ -160,9 +161,6 @@ namespace MWWorld void Scene::playerCellChange(MWWorld::CellStore *cell, const ESM::Position& pos, bool adjustPlayerPos) { - bool hasWater = cell->mCell->mData.mFlags & ESM::Cell::HasWater; - mPhysics->setCurrentWater(hasWater, cell->mCell->mWater); - MWBase::World *world = MWBase::Environment::get().getWorld(); world->getPlayer().setCell(cell); @@ -201,17 +199,15 @@ namespace MWWorld void Scene::changeCell (int X, int Y, const ESM::Position& position, bool adjustPlayerPos) { Nif::NIFFile::CacheLock cachelock; - const MWWorld::Store &gmst = - MWBase::Environment::get().getWorld()->getStore().get(); - mRendering.preCellChange(mCurrentCell); + Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); + Loading::ScopedLoad load(loadingListener); // remove active MWBase::Environment::get().getMechanicsManager()->remove(MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); - std::string loadingExteriorText; - - loadingExteriorText = gmst.find ("sLoadingMessage3")->getString(); + std::string loadingExteriorText = "#{sLoadingMessage3}"; + loadingListener->setLabel(loadingExteriorText); CellStoreCollection::iterator active = mActiveCells.begin(); @@ -233,7 +229,6 @@ namespace MWWorld ++numUnload; } - int current = 0; active = mActiveCells.begin(); while (active!=mActiveCells.end()) { @@ -248,11 +243,10 @@ namespace MWWorld } } unloadCell (active++); - ++current; } - int numLoad = 0; - // get the number of cells to load + int refsToLoad = 0; + // get the number of refs to load for (int x=X-1; x<=X+1; ++x) for (int y=Y-1; y<=Y+1; ++y) { @@ -270,11 +264,12 @@ namespace MWWorld } if (iter==mActiveCells.end()) - ++numLoad; + refsToLoad += countRefs(*MWBase::Environment::get().getWorld()->getExterior(x, y)); } + loadingListener->setProgressRange(refsToLoad); + // Load cells - current = 0; for (int x=X-1; x<=X+1; ++x) for (int y=Y-1; y<=Y+1; ++y) { @@ -295,11 +290,7 @@ namespace MWWorld { CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(x, y); - //Loading Exterior loading text - MWBase::Environment::get().getWindowManager ()->setLoadingProgress (loadingExteriorText, 0, current, numLoad); - - loadCell (cell); - ++current; + loadCell (cell, loadingListener); } } @@ -331,7 +322,7 @@ namespace MWWorld mCellChanged = true; - MWBase::Environment::get().getWindowManager ()->loadingDone (); + loadingListener->removeWallpaper(); } //We need the ogre renderer and a scene node. @@ -357,11 +348,14 @@ namespace MWWorld void Scene::changeToInteriorCell (const std::string& cellName, const ESM::Position& position) { MWBase::Environment::get().getWorld ()->getFader ()->fadeOut(0.5); - const MWWorld::Store &gmst = - MWBase::Environment::get().getWorld()->getStore().get(); - std::string loadingInteriorText; - loadingInteriorText = gmst.find ("sLoadingMessage2")->getString(); + mRendering.enableTerrain(false); + + Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); + Loading::ScopedLoad load(loadingListener); + + std::string loadingInteriorText = "#{sLoadingMessage2}"; + loadingListener->setLabel(loadingInteriorText); CellStore *cell = MWBase::Environment::get().getWorld()->getInterior(cellName); bool loadcell = (mCurrentCell == NULL); @@ -405,13 +399,15 @@ namespace MWWorld ++current; } + int refsToLoad = countRefs(*cell); + loadingListener->setProgressRange(refsToLoad); + // Load cell. std::cout << "cellName: " << cell->mCell->mName << std::endl; //Loading Interior loading text - MWBase::Environment::get().getWindowManager ()->setLoadingProgress (loadingInteriorText, 0, 0, 1); - loadCell (cell); + loadCell (cell, loadingListener); mCurrentCell = cell; @@ -428,7 +424,7 @@ namespace MWWorld mCellChanged = true; MWBase::Environment::get().getWorld ()->getFader ()->fadeIn(0.5); - MWBase::Environment::get().getWindowManager ()->loadingDone (); + loadingListener->removeWallpaper(); } void Scene::changeToExteriorCell (const ESM::Position& position) @@ -438,6 +434,8 @@ namespace MWWorld MWBase::Environment::get().getWorld()->positionToIndex (position.pos[0], position.pos[1], x, y); + mRendering.enableTerrain(true); + changeCell (x, y, position, true); } @@ -451,30 +449,54 @@ namespace MWWorld mCellChanged = false; } - void Scene::insertCell (Ptr::CellStore &cell, bool rescale) + int Scene::countRefs (const Ptr::CellStore& cell) + { + return cell.mActivators.mList.size() + + cell.mPotions.mList.size() + + cell.mAppas.mList.size() + + cell.mArmors.mList.size() + + cell.mBooks.mList.size() + + cell.mClothes.mList.size() + + cell.mContainers.mList.size() + + cell.mDoors.mList.size() + + cell.mIngreds.mList.size() + + cell.mCreatureLists.mList.size() + + cell.mItemLists.mList.size() + + cell.mLights.mList.size() + + cell.mLockpicks.mList.size() + + cell.mMiscItems.mList.size() + + cell.mProbes.mList.size() + + cell.mRepairs.mList.size() + + cell.mStatics.mList.size() + + cell.mWeapons.mList.size() + + cell.mCreatures.mList.size() + + cell.mNpcs.mList.size(); + } + + void Scene::insertCell (Ptr::CellStore &cell, bool rescale, Loading::Listener* loadingListener) { // Loop through all references in the cell - insertCellRefList(mRendering, cell.mActivators, cell, *mPhysics, rescale); - insertCellRefList(mRendering, cell.mPotions, cell, *mPhysics, rescale); - insertCellRefList(mRendering, cell.mAppas, cell, *mPhysics, rescale); - insertCellRefList(mRendering, cell.mArmors, cell, *mPhysics, rescale); - insertCellRefList(mRendering, cell.mBooks, cell, *mPhysics, rescale); - insertCellRefList(mRendering, cell.mClothes, cell, *mPhysics, rescale); - insertCellRefList(mRendering, cell.mContainers, cell, *mPhysics, rescale); - insertCellRefList(mRendering, cell.mDoors, cell, *mPhysics, rescale); - insertCellRefList(mRendering, cell.mIngreds, cell, *mPhysics, rescale); - insertCellRefList(mRendering, cell.mCreatureLists, cell, *mPhysics, rescale); - insertCellRefList(mRendering, cell.mItemLists, cell, *mPhysics, rescale); - insertCellRefList(mRendering, cell.mLights, cell, *mPhysics, rescale); - insertCellRefList(mRendering, cell.mLockpicks, cell, *mPhysics, rescale); - insertCellRefList(mRendering, cell.mMiscItems, cell, *mPhysics, rescale); - insertCellRefList(mRendering, cell.mProbes, cell, *mPhysics, rescale); - insertCellRefList(mRendering, cell.mRepairs, cell, *mPhysics, rescale); - insertCellRefList(mRendering, cell.mStatics, cell, *mPhysics, rescale); - insertCellRefList(mRendering, cell.mWeapons, cell, *mPhysics, rescale); + insertCellRefList(mRendering, cell.mActivators, cell, *mPhysics, rescale, loadingListener); + insertCellRefList(mRendering, cell.mPotions, cell, *mPhysics, rescale, loadingListener); + insertCellRefList(mRendering, cell.mAppas, cell, *mPhysics, rescale, loadingListener); + insertCellRefList(mRendering, cell.mArmors, cell, *mPhysics, rescale, loadingListener); + insertCellRefList(mRendering, cell.mBooks, cell, *mPhysics, rescale, loadingListener); + insertCellRefList(mRendering, cell.mClothes, cell, *mPhysics, rescale, loadingListener); + insertCellRefList(mRendering, cell.mContainers, cell, *mPhysics, rescale, loadingListener); + insertCellRefList(mRendering, cell.mDoors, cell, *mPhysics, rescale, loadingListener); + insertCellRefList(mRendering, cell.mIngreds, cell, *mPhysics, rescale, loadingListener); + insertCellRefList(mRendering, cell.mCreatureLists, cell, *mPhysics, rescale, loadingListener); + insertCellRefList(mRendering, cell.mItemLists, cell, *mPhysics, rescale, loadingListener); + insertCellRefList(mRendering, cell.mLights, cell, *mPhysics, rescale, loadingListener); + insertCellRefList(mRendering, cell.mLockpicks, cell, *mPhysics, rescale, loadingListener); + insertCellRefList(mRendering, cell.mMiscItems, cell, *mPhysics, rescale, loadingListener); + insertCellRefList(mRendering, cell.mProbes, cell, *mPhysics, rescale, loadingListener); + insertCellRefList(mRendering, cell.mRepairs, cell, *mPhysics, rescale, loadingListener); + insertCellRefList(mRendering, cell.mStatics, cell, *mPhysics, rescale, loadingListener); + insertCellRefList(mRendering, cell.mWeapons, cell, *mPhysics, rescale, loadingListener); // Load NPCs and creatures _after_ everything else (important for adjustPosition to work correctly) - insertCellRefList(mRendering, cell.mCreatures, cell, *mPhysics, rescale); - insertCellRefList(mRendering, cell.mNpcs, cell, *mPhysics, rescale); + insertCellRefList(mRendering, cell.mCreatures, cell, *mPhysics, rescale, loadingListener); + insertCellRefList(mRendering, cell.mNpcs, cell, *mPhysics, rescale, loadingListener); } void Scene::addObjectToScene (const Ptr& ptr) diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index 3dfbd1045..e3edad352 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -56,7 +56,9 @@ namespace MWWorld void playerCellChange (CellStore *cell, const ESM::Position& position, bool adjustPlayerPos = true); - void insertCell (Ptr::CellStore &cell, bool rescale); + void insertCell (Ptr::CellStore &cell, bool rescale, Loading::Listener* loadingListener); + + int countRefs (const Ptr::CellStore& cell); public: @@ -66,7 +68,7 @@ namespace MWWorld void unloadCell (CellStoreCollection::iterator iter); - void loadCell (CellStore *cell); + void loadCell (CellStore *cell, Loading::Listener* loadingListener); void changeCell (int X, int Y, const ESM::Position& position, bool adjustPlayerPos); ///< Move from exterior to interior or from interior cell to a different diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 2c10e3edc..2ee23dbd6 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -466,6 +466,18 @@ namespace MWWorld ESM::Land *ptr = new ESM::Land(); ptr->load(esm); + // Same area defined in multiple plugins? -> last plugin wins + // Can't use search() because we aren't sorted yet - is there any other way to speed this up? + for (std::vector::iterator it = mStatic.begin(); it != mStatic.end(); ++it) + { + if ((*it)->mX == ptr->mX && (*it)->mY == ptr->mY) + { + delete *it; + mStatic.erase(it); + break; + } + } + mStatic.push_back(ptr); } diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 587b7847b..6f5dbe23f 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -167,6 +167,11 @@ WeatherManager::WeatherManager(MWRender::RenderingManager* rendering,MWWorld::Fa setFallbackWeather(blizzard,"blizzard"); } +WeatherManager::~WeatherManager() +{ + stopSounds(true); +} + void WeatherManager::setWeather(const String& weather, bool instant) { if (weather == mCurrentWeather && mNextWeather == "") diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index a2a07cec7..80cbe0418 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -120,6 +120,7 @@ namespace MWWorld { public: WeatherManager(MWRender::RenderingManager*,MWWorld::Fallback* fallback); + ~WeatherManager(); /** * Change the weather in the specified region diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index bd234c0b6..a372e5288 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1,5 +1,7 @@ #include "worldimp.hpp" +#include + #include #include @@ -182,11 +184,14 @@ namespace MWWorld int idx = 0; // NOTE: We might need to reserve one more for the running game / save. mEsm.resize(master.size() + plugins.size()); + Loading::Listener* listener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); + listener->loadingOn(); for (std::vector::size_type i = 0; i < master.size(); i++, idx++) { boost::filesystem::path masterPath (fileCollections.getCollection (".esm").getPath (master[i])); std::cout << "Loading ESM " << masterPath.string() << "\n"; + listener->setLabel(masterPath.filename().string()); // This parses the ESM file ESM::ESMReader lEsm; @@ -195,7 +200,7 @@ namespace MWWorld lEsm.setGlobalReaderList(&mEsm); lEsm.open (masterPath.string()); mEsm[idx] = lEsm; - mStore.load (mEsm[idx]); + mStore.load (mEsm[idx], listener); } for (std::vector::size_type i = 0; i < plugins.size(); i++, idx++) @@ -203,6 +208,7 @@ namespace MWWorld boost::filesystem::path pluginPath (fileCollections.getCollection (".esp").getPath (plugins[i])); std::cout << "Loading ESP " << pluginPath.string() << "\n"; + listener->setLabel(pluginPath.filename().string()); // This parses the ESP file ESM::ESMReader lEsm; @@ -211,8 +217,9 @@ namespace MWWorld lEsm.setGlobalReaderList(&mEsm); lEsm.open (pluginPath.string()); mEsm[idx] = lEsm; - mStore.load (mEsm[idx]); + mStore.load (mEsm[idx], listener); } + listener->loadingOff(); // insert records that may not be present in all versions of MW if (mEsm[0].getFormat() == 0) @@ -238,12 +245,13 @@ namespace MWWorld // Rebuild player setupPlayer(); MWWorld::Ptr player = mPlayer->getPlayer(); - renderPlayer(); - mRendering->resetCamera(); // removes NpcStats, ContainerStore etc player.getRefData().setCustomData(NULL); + renderPlayer(); + mRendering->resetCamera(); + // make sure to do this so that local scripts from items that were in the players inventory are removed mLocalScripts.clear(); @@ -758,7 +766,7 @@ namespace MWWorld std::pair result; if (!mRendering->occlusionQuerySupported()) - result = mPhysics->getFacedHandle (*this, getMaxActivationDistance ()); + result = mPhysics->getFacedHandle (getMaxActivationDistance ()); else result = std::make_pair (mFacedDistance, mFacedHandle); @@ -781,7 +789,7 @@ namespace MWWorld return object; } - MWWorld::Ptr World::getFacedObject(const MWWorld::Ptr &ptr, float distance) + std::pair World::getHitContact(const MWWorld::Ptr &ptr, float distance) { const ESM::Position &posdata = ptr.getRefData().getPosition(); Ogre::Vector3 pos(posdata.pos); @@ -796,11 +804,12 @@ namespace MWWorld pos += node->_getDerivedPosition(); } - std::pair result = mPhysics->getFacedHandle(pos, rot, distance); + std::pair result = mPhysics->getHitContact(ptr.getRefData().getHandle(), + pos, rot, distance); if(result.first.empty()) - return MWWorld::Ptr(); + return std::make_pair(MWWorld::Ptr(), Ogre::Vector3(0.0f)); - return searchPtrViaHandle(result.first); + return std::make_pair(searchPtrViaHandle(result.first), result.second); } void World::deleteObject (const Ptr& ptr) @@ -1072,18 +1081,16 @@ namespace MWWorld { const int cellSize = 8192; - cellX = static_cast (x/cellSize); - - if (x<0) - --cellX; - - cellY = static_cast (y/cellSize); + cellX = std::floor(x/cellSize); + cellY = std::floor(y/cellSize); + } - if (y<0) - --cellY; + void World::queueMovement(const Ptr &ptr, const Vector3 &velocity) + { + mPhysics->queueObjectMovement(ptr, velocity); } - void World::doPhysics(const PtrMovementList &actors, float duration) + void World::doPhysics(float duration) { /* No duration? Shouldn't be any movement, then. */ if(duration <= 0.0f) @@ -1091,8 +1098,9 @@ namespace MWWorld processDoors(duration); - PtrMovementList::const_iterator player(actors.end()); - for(PtrMovementList::const_iterator iter(actors.begin());iter != actors.end();iter++) + const PtrVelocityList &results = mPhysics->applyQueuedMovement(duration); + PtrVelocityList::const_iterator player(results.end()); + for(PtrVelocityList::const_iterator iter(results.begin());iter != results.end();iter++) { if(iter->first.getRefData().getHandle() == "player") { @@ -1100,23 +1108,12 @@ namespace MWWorld player = iter; continue; } - - rotateObjectImp(iter->first, Ogre::Vector3(iter->second.mRotation), true); - - Ogre::Vector3 vec = mPhysics->move(iter->first, Ogre::Vector3(iter->second.mPosition), duration, - !isSwimming(iter->first) && !isFlying(iter->first)); - moveObjectImp(iter->first, vec.x, vec.y, vec.z); - } - if(player != actors.end()) - { - rotateObjectImp(player->first, Ogre::Vector3(player->second.mRotation), true); - - Ogre::Vector3 vec = mPhysics->move(player->first, Ogre::Vector3(player->second.mPosition), duration, - !isSwimming(player->first) && !isFlying(player->first)); - moveObjectImp(player->first, vec.x, vec.y, vec.z); + moveObjectImp(iter->first, iter->second.x, iter->second.y, iter->second.z); } + if(player != results.end()) + moveObjectImp(player->first, player->second.x, player->second.y, player->second.z); - mPhysEngine->stepSimulation (duration); + mPhysEngine->stepSimulation(duration); } bool World::castRay (float x1, float y1, float z1, float x2, float y2, float z2) @@ -1163,7 +1160,7 @@ namespace MWWorld bool World::toggleCollisionMode() { - return mPhysics->toggleCollisionMode();; + return mPhysics->toggleCollisionMode(); } bool World::toggleRenderMode (RenderMode mode) @@ -1261,6 +1258,8 @@ namespace MWWorld mWorldScene->update (duration, paused); + doPhysics (duration); + performUpdateSceneQueries (); updateWindowManager (); @@ -1567,9 +1566,19 @@ namespace MWWorld bool World::isFlying(const MWWorld::Ptr &ptr) const { - const MWWorld::Class &cls = MWWorld::Class::get(ptr); - if(cls.isActor() && cls.getCreatureStats(ptr).getMagicEffects().get(MWMechanics::EffectKey(ESM::MagicEffect::Levitate)).mMagnitude > 0) + if(!ptr.getClass().isActor()) + return false; + + const MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr); + if(stats.getMagicEffects().get(MWMechanics::EffectKey(ESM::MagicEffect::Levitate)).mMagnitude > 0) return true; + + // TODO: Check if flying creature + + const OEngine::Physic::PhysicActor *actor = mPhysEngine->getCharacter(ptr.getRefData().getHandle()); + if(!actor || !actor->getCollisionMode()) + return true; + return false; } @@ -1680,9 +1689,9 @@ namespace MWWorld mRendering->stopVideo(); } - void World::frameStarted (float dt) + void World::frameStarted (float dt, bool paused) { - mRendering->frameStarted(dt); + mRendering->frameStarted(dt, paused); } void World::activateDoor(const MWWorld::Ptr& door) @@ -1939,4 +1948,9 @@ namespace MWWorld stats.getSkill(ESM::Skill::Acrobatics).setModified(gmst.find("fWerewolfAcrobatics")->getFloat(), 0); } + bool World::toggleGodMode() + { + return false; + } + } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index b2f6418a3..30ffcda40 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -106,6 +106,9 @@ namespace MWWorld void processDoors(float duration); ///< Run physics simulation and modify \a world accordingly. + void doPhysics(float duration); + ///< Run physics simulation and modify \a world accordingly. + void ensureNeededRecords(); int mPlayIntro; @@ -249,9 +252,10 @@ namespace MWWorld virtual MWWorld::Ptr getFacedObject(); ///< Return pointer to the object the player is looking at, if it is within activation range - /// Returns a pointer to the object the provided object is facing (if within the - /// specified distance). This will attempt to use the "Bip01 Head" node as a basis. - virtual MWWorld::Ptr getFacedObject(const MWWorld::Ptr &ptr, float distance); + /// Returns a pointer to the object the provided object would hit (if within the + /// specified distance), and the point where the hit occurs. This will attempt to + /// use the "Head" node as a basis. + virtual std::pair getHitContact(const MWWorld::Ptr &ptr, float distance); virtual void deleteObject (const Ptr& ptr); @@ -276,8 +280,9 @@ namespace MWWorld virtual void positionToIndex (float x, float y, int &cellX, int &cellY) const; ///< Convert position to cell numbers - virtual void doPhysics(const PtrMovementList &actors, float duration); - ///< Run physics simulation and modify \a world accordingly. + virtual void queueMovement(const Ptr &ptr, const Ogre::Vector3 &velocity); + ///< Queues movement for \a ptr (in local space), to be applied in the next call to + /// doPhysics. virtual bool castRay (float x1, float y1, float z1, float x2, float y2, float z2); ///< cast a Ray and return true if there is an object in the ray path. @@ -415,7 +420,7 @@ namespace MWWorld /// \todo this does not belong here virtual void playVideo(const std::string& name, bool allowSkipping); virtual void stopVideo(); - virtual void frameStarted (float dt); + virtual void frameStarted (float dt, bool paused); /// Find center of exterior cell above land surface /// \return false if exterior with given name not exists, true otherwise @@ -434,6 +439,8 @@ namespace MWWorld virtual void setWerewolf(const MWWorld::Ptr& actor, bool werewolf); virtual void applyWerewolfAcrobatics(const MWWorld::Ptr& actor); + + virtual bool toggleGodMode(); }; } diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index cdf8bc74b..682010340 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -11,14 +11,15 @@ if (GTEST_FOUND AND GMOCK_FOUND) include_directories(${GMOCK_INCLUDE_DIRS}) file(GLOB UNITTEST_SRC_FILES - components/misc/*.cpp + components/misc/test_*.cpp + components/file_finder/test_*.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) add_executable(openmw_test_suite openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) - target_link_libraries(openmw_test_suite ${GMOCK_BOTH_LIBRARIES} ${GTEST_BOTH_LIBRARIES}) + target_link_libraries(openmw_test_suite ${GMOCK_BOTH_LIBRARIES} ${GTEST_BOTH_LIBRARIES} components) # Fix for not visible pthreads functions for linker with glibc 2.15 if (UNIX AND NOT APPLE) target_link_libraries(openmw_test_suite ${CMAKE_THREAD_LIBS_INIT}) diff --git a/apps/openmw_test_suite/components/file_finder/test_filefinder.cpp b/apps/openmw_test_suite/components/file_finder/test_filefinder.cpp new file mode 100644 index 000000000..2d151988b --- /dev/null +++ b/apps/openmw_test_suite/components/file_finder/test_filefinder.cpp @@ -0,0 +1,66 @@ +#include +#include +#include "components/file_finder/file_finder.hpp" + +struct FileFinderTest : public ::testing::Test +{ + protected: + FileFinderTest() + : mTestDir("./filefinder_test_dir/") + , mTestFile("test.txt") + , mTestFileUppercase("TEST.TXT") + , mTestFileNotFound("foobarbaz.txt") + { + } + + virtual void SetUp() + { + boost::filesystem::create_directory(boost::filesystem::path(mTestDir)); + + std::ofstream ofs(std::string(mTestDir + mTestFile).c_str(), std::ofstream::out); + ofs << std::endl; + ofs.close(); + } + + virtual void TearDown() + { + boost::filesystem::remove_all(boost::filesystem::path(mTestDir)); + } + + std::string mTestDir; + std::string mTestFile; + std::string mTestFileUppercase; + std::string mTestFileNotFound; +}; + +TEST_F(FileFinderTest, FileFinder_has_file) +{ + FileFinder::FileFinder fileFinder(mTestDir); + ASSERT_TRUE(fileFinder.has(mTestFile)); + ASSERT_TRUE(fileFinder.has(mTestFileUppercase)); + ASSERT_TRUE(fileFinder.lookup(mTestFile) == std::string(mTestDir + mTestFile)); + ASSERT_TRUE(fileFinder.lookup(mTestFileUppercase) == std::string(mTestDir + mTestFile)); +} + +TEST_F(FileFinderTest, FileFinder_does_not_have_file) +{ + FileFinder::FileFinder fileFinder(mTestDir); + ASSERT_FALSE(fileFinder.has(mTestFileNotFound)); + ASSERT_TRUE(fileFinder.lookup(mTestFileNotFound).empty()); +} + +TEST_F(FileFinderTest, FileFinderStrict_has_file) +{ + FileFinder::FileFinderStrict fileFinder(mTestDir); + ASSERT_TRUE(fileFinder.has(mTestFile)); + ASSERT_FALSE(fileFinder.has(mTestFileUppercase)); + ASSERT_TRUE(fileFinder.lookup(mTestFile) == std::string(mTestDir + mTestFile)); + ASSERT_FALSE(fileFinder.lookup(mTestFileUppercase) == std::string(mTestDir + mTestFile)); +} + +TEST_F(FileFinderTest, FileFinderStrict_does_not_have_file) +{ + FileFinder::FileFinderStrict fileFinder(mTestDir); + ASSERT_FALSE(fileFinder.has(mTestFileNotFound)); + ASSERT_TRUE(fileFinder.lookup(mTestFileNotFound).empty()); +} diff --git a/apps/openmw_test_suite/components/file_finder/test_search.cpp b/apps/openmw_test_suite/components/file_finder/test_search.cpp new file mode 100644 index 000000000..63745b625 --- /dev/null +++ b/apps/openmw_test_suite/components/file_finder/test_search.cpp @@ -0,0 +1,74 @@ +#include +#include +#include + +#include "components/file_finder/search.hpp" + +struct SearchTest : public ::testing::Test +{ + protected: + SearchTest() + : mTestDir("./search_test_dir/") + { + } + + virtual void SetUp() + { + boost::filesystem::create_directory(boost::filesystem::path(mTestDir)); + + std::ofstream ofs(std::string(mTestDir + "test2.txt").c_str(), std::ofstream::out); + ofs << std::endl; + ofs.close(); + } + + virtual void TearDown() + { + boost::filesystem::remove_all(boost::filesystem::path(mTestDir)); + } + + std::string mTestDir; +}; + +TEST_F(SearchTest, file_not_found) +{ + struct Result : public FileFinder::ReturnPath + { + Result(const boost::filesystem::path& expectedPath) + : mExpectedPath(expectedPath) + { + } + + void add(const boost::filesystem::path& p) + { + ASSERT_FALSE(p == mExpectedPath); + } + + private: + boost::filesystem::path mExpectedPath; + + } r(boost::filesystem::path(mTestDir + "test.txt")); + + FileFinder::find(mTestDir, r, false); +} + +TEST_F(SearchTest, file_found) +{ + struct Result : public FileFinder::ReturnPath + { + Result(const boost::filesystem::path& expectedPath) + : mExpectedPath(expectedPath) + { + } + + void add(const boost::filesystem::path& p) + { + ASSERT_TRUE(p == mExpectedPath); + } + + private: + boost::filesystem::path mExpectedPath; + + } r(boost::filesystem::path(mTestDir + "test2.txt")); + + FileFinder::find(mTestDir, r, false); +} diff --git a/apps/openmw_test_suite/components/misc/test_slicearray.cpp b/apps/openmw_test_suite/components/misc/test_slicearray.cpp new file mode 100644 index 000000000..ab63e56c4 --- /dev/null +++ b/apps/openmw_test_suite/components/misc/test_slicearray.cpp @@ -0,0 +1,33 @@ +#include +#include "components/misc/slice_array.hpp" + +struct SliceArrayTest : public ::testing::Test +{ + protected: + virtual void SetUp() + { + } + + virtual void TearDown() + { + } +}; + +TEST_F(SliceArrayTest, hello_string) +{ + Misc::SString s("hello"); + ASSERT_EQ(sizeof("hello") - 1, s.length); + ASSERT_FALSE(s=="hel"); + ASSERT_FALSE(s=="hell"); + ASSERT_TRUE(s=="hello"); +} + +TEST_F(SliceArrayTest, othello_string_with_offset_2_and_size_4) +{ + Misc::SString s("othello" + 2, 4); + ASSERT_EQ(sizeof("hell") - 1, s.length); + ASSERT_FALSE(s=="hel"); + ASSERT_TRUE(s=="hell"); + ASSERT_FALSE(s=="hello"); +} + diff --git a/apps/openmw_test_suite/components/misc/test_stringops.cpp b/apps/openmw_test_suite/components/misc/test_stringops.cpp index b12584196..44587c445 100644 --- a/apps/openmw_test_suite/components/misc/test_stringops.cpp +++ b/apps/openmw_test_suite/components/misc/test_stringops.cpp @@ -1,7 +1,79 @@ #include +#include "components/misc/stringops.hpp" -TEST(simple_test, dummy) +struct StringOpsTest : public ::testing::Test { - EXPECT_EQ(true, true); + protected: + virtual void SetUp() + { + } + + virtual void TearDown() + { + } +}; + +TEST_F(StringOpsTest, begins_matching) +{ + ASSERT_TRUE(Misc::begins("abc", "a")); + ASSERT_TRUE(Misc::begins("abc", "ab")); + ASSERT_TRUE(Misc::begins("abc", "abc")); + ASSERT_TRUE(Misc::begins("abcd", "abc")); +} + +TEST_F(StringOpsTest, begins_not_matching) +{ + ASSERT_FALSE(Misc::begins("abc", "b")); + ASSERT_FALSE(Misc::begins("abc", "bc")); + ASSERT_FALSE(Misc::begins("abc", "bcd")); + ASSERT_FALSE(Misc::begins("abc", "abcd")); +} + +TEST_F(StringOpsTest, ibegins_matching) +{ + ASSERT_TRUE(Misc::ibegins("Abc", "a")); + ASSERT_TRUE(Misc::ibegins("aBc", "ab")); + ASSERT_TRUE(Misc::ibegins("abC", "abc")); + ASSERT_TRUE(Misc::ibegins("abcD", "abc")); +} + +TEST_F(StringOpsTest, ibegins_not_matching) +{ + ASSERT_FALSE(Misc::ibegins("abc", "b")); + ASSERT_FALSE(Misc::ibegins("abc", "bc")); + ASSERT_FALSE(Misc::ibegins("abc", "bcd")); + ASSERT_FALSE(Misc::ibegins("abc", "abcd")); +} + +TEST_F(StringOpsTest, ends_matching) +{ + ASSERT_TRUE(Misc::ends("abc", "c")); + ASSERT_TRUE(Misc::ends("abc", "bc")); + ASSERT_TRUE(Misc::ends("abc", "abc")); + ASSERT_TRUE(Misc::ends("abcd", "abcd")); +} + +TEST_F(StringOpsTest, ends_not_matching) +{ + ASSERT_FALSE(Misc::ends("abc", "b")); + ASSERT_FALSE(Misc::ends("abc", "ab")); + ASSERT_FALSE(Misc::ends("abc", "bcd")); + ASSERT_FALSE(Misc::ends("abc", "abcd")); +} + +TEST_F(StringOpsTest, iends_matching) +{ + ASSERT_TRUE(Misc::iends("Abc", "c")); + ASSERT_TRUE(Misc::iends("aBc", "bc")); + ASSERT_TRUE(Misc::iends("abC", "abc")); + ASSERT_TRUE(Misc::iends("abcD", "abcd")); +} + +TEST_F(StringOpsTest, iends_not_matching) +{ + ASSERT_FALSE(Misc::iends("abc", "b")); + ASSERT_FALSE(Misc::iends("abc", "ab")); + ASSERT_FALSE(Misc::iends("abc", "bcd")); + ASSERT_FALSE(Misc::iends("abc", "abcd")); } diff --git a/apps/openmw_test_suite/openmw_test_suite.cpp b/apps/openmw_test_suite/openmw_test_suite.cpp index 0a09b3028..81476325e 100644 --- a/apps/openmw_test_suite/openmw_test_suite.cpp +++ b/apps/openmw_test_suite/openmw_test_suite.cpp @@ -5,8 +5,8 @@ int main(int argc, char** argv) { // The following line causes Google Mock to throw an exception on failure, // which will be interpreted by your testing framework as a test failure. - ::testing::GTEST_FLAG(throw_on_failure) = true; + ::testing::GTEST_FLAG(throw_on_failure) = false; ::testing::InitGoogleMock(&argc, argv); return RUN_ALL_TESTS(); -} \ No newline at end of file +} diff --git a/cmake/FindLIBUNSHIELD.cmake b/cmake/FindLIBUNSHIELD.cmake new file mode 100644 index 000000000..4f4e98a1c --- /dev/null +++ b/cmake/FindLIBUNSHIELD.cmake @@ -0,0 +1,48 @@ +# Locate LIBUNSHIELD +# This module defines +# LIBUNSHIELD_LIBRARY +# LIBUNSHIELD_FOUND, if false, do not try to link to LibUnshield +# LIBUNSHIELD_INCLUDE_DIR, where to find the headers +# +# Created by Tom Mason (wheybags) for OpenMW (http://openmw.com), based on FindMPG123.cmake +# +# Ripped off from other sources. In fact, this file is so generic (I +# just did a search and replace on another file) that I wonder why the +# CMake guys haven't wrapped this entire thing in a single +# function. Do we really need to repeat this stuff for every single +# library when they all work the same? + +FIND_PATH(LIBUNSHIELD_INCLUDE_DIR libunshield.h + HINTS + PATHS + ~/Library/Frameworks + /Library/Frameworks + /usr/local + /usr + /sw # Fink + /opt/local # DarwinPorts + /opt/csw # Blastwave + /opt + /usr/include +) + +FIND_LIBRARY(LIBUNSHIELD_LIBRARY + unshield + HINTS +# PATH_SUFFIXES lib64 lib libs64 libs libs/Win32 libs/Win64 + PATHS + ~/Library/Frameworks + /Library/Frameworks + /usr/local + /usr + /sw + /opt/local + /opt/csw + /opt + /usr/lib +) + +IF(LIBUNSHIELD_LIBRARY AND LIBUNSHIELD_INCLUDE_DIR) + SET(LIBUNSHIELD_FOUND "YES") +ENDIF(LIBUNSHIELD_LIBRARY AND LIBUNSHIELD_INCLUDE_DIR) + diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index b79fa027e..34ef43ab5 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -65,6 +65,14 @@ add_component_dir (interpreter add_component_dir (translation translation ) + +add_component_dir (terrain + quadtreenode chunk world storage material + ) + +add_component_dir (loadinglistener + loadinglistener + ) set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/datafilespage.ui ) diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 9e0c36825..415f8d168 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -258,6 +258,8 @@ namespace Compiler extensions.registerInstruction ("enableteleporting", "", opcodeEnableTeleporting); extensions.registerInstruction ("showvars", "", opcodeShowVars, opcodeShowVarsExplicit); extensions.registerInstruction ("sv", "", opcodeShowVars, opcodeShowVarsExplicit); + extensions.registerInstruction("tgm", "", opcodeToggleGodMode); + extensions.registerInstruction("togglegodmode", "", opcodeToggleGodMode); } } diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index c4e2c1bc6..5eb54208a 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -221,6 +221,7 @@ namespace Compiler const int opcodeEnableTeleporting = 0x2000216; const int opcodeShowVars = 0x200021d; const int opcodeShowVarsExplicit = 0x200021e; + const int opcodeToggleGodMode = 0x200021f; } namespace Sky diff --git a/components/esm/esmreader.hpp b/components/esm/esmreader.hpp index ff10a202c..edc724cd2 100644 --- a/components/esm/esmreader.hpp +++ b/components/esm/esmreader.hpp @@ -70,6 +70,11 @@ public: void openRaw(const std::string &file); + /// Get the file size. Make sure that the file has been opened! + size_t getFileSize() { return mEsm->size(); } + /// Get the current position in the file. Make sure that the file has been opened! + size_t getFileOffset() { return mEsm->tell(); } + // This is a quick hack for multiple esm/esp files. Each plugin introduces its own // terrain palette, but ESMReader does not pass a reference to the correct plugin // to the individual load() methods. This hack allows to pass this reference diff --git a/components/esm/filter.cpp b/components/esm/filter.cpp index 205332f6b..7d4851a5f 100644 --- a/components/esm/filter.cpp +++ b/components/esm/filter.cpp @@ -7,14 +7,17 @@ void ESM::Filter::load (ESMReader& esm) { mFilter = esm.getHNString ("FILT"); + mDescription = esm.getHNString ("DESC"); } void ESM::Filter::save (ESMWriter& esm) { esm.writeHNCString ("FILT", mFilter); + esm.writeHNCString ("DESC", mDescription); } void ESM::Filter::blank() { mFilter.clear(); + mDescription.clear(); } diff --git a/components/esm/filter.hpp b/components/esm/filter.hpp index 2dde92fb0..0fd564361 100644 --- a/components/esm/filter.hpp +++ b/components/esm/filter.hpp @@ -12,6 +12,8 @@ namespace ESM { std::string mId; + std::string mDescription; + std::string mFilter; void load (ESMReader& esm); diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp index dbd1fed6f..d8d0c1291 100644 --- a/components/esm/loadcell.cpp +++ b/components/esm/loadcell.cpp @@ -153,6 +153,14 @@ bool Cell::getNextRef(ESMReader &esm, CellRef &ref) // That should be it, I haven't seen any other fields yet. } + // NAM0 sometimes appears here, sometimes further on + ref.mNam0 = 0; + if (esm.isNextSub("NAM0")) + { + esm.getHT(ref.mNam0); + //esm.getHNOT(NAM0, "NAM0"); + } + esm.getHNT(ref.mRefnum, "FRMR"); ref.mRefID = esm.getHNString("NAME"); @@ -243,7 +251,6 @@ bool Cell::getNextRef(ESMReader &esm, CellRef &ref) // Update: Well, maybe not completely useless. This might actually be // number_of_references + number_of_references_moved_here_Across_boundaries, // and could be helpful for collecting these weird moved references. - ref.mNam0 = 0; if (esm.isNextSub("NAM0")) { esm.getHT(ref.mNam0); diff --git a/components/esm/loadcell.hpp b/components/esm/loadcell.hpp index eda8a5418..51288b291 100644 --- a/components/esm/loadcell.hpp +++ b/components/esm/loadcell.hpp @@ -119,6 +119,11 @@ struct Cell return mData.mY; } + bool hasWater() const + { + return (mData.mFlags&HasWater); + } + // Restore the given reader to the stored position. Will try to open // the file matching the stored file name. If you want to read from // somewhere other than the file system, you need to pre-open the diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp index c1cce5e7e..9c1fd1f5c 100644 --- a/components/esm/loadland.hpp +++ b/components/esm/loadland.hpp @@ -70,7 +70,7 @@ struct Land }; #pragma pack(pop) - typedef uint8_t VNML[LAND_NUM_VERTS * 3]; + typedef signed char VNML[LAND_NUM_VERTS * 3]; struct LandData { diff --git a/components/esm/variant.hpp b/components/esm/variant.hpp index 8c5f3b3d4..2bba60a15 100644 --- a/components/esm/variant.hpp +++ b/components/esm/variant.hpp @@ -11,7 +11,7 @@ namespace ESM enum VarType { - VT_Unknown, + VT_Unknown = 0, VT_None, VT_Short, // stored as a float, kinda VT_Int, diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index 56e55a98d..75c877dc5 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -56,7 +56,7 @@ void ConfigurationManager::readConfiguration(boost::program_options::variables_m } -void ConfigurationManager::processPaths(Files::PathContainer& dataDirs) +void ConfigurationManager::processPaths(Files::PathContainer& dataDirs, bool create) { std::string path; for (Files::PathContainer::iterator it = dataDirs.begin(); it != dataDirs.end(); ++it) @@ -94,6 +94,18 @@ void ConfigurationManager::processPaths(Files::PathContainer& dataDirs) if (!boost::filesystem::is_directory(*it)) { + if (create) + { + try + { + boost::filesystem::create_directories (*it); + } + catch (...) {} + + if (boost::filesystem::is_directory(*it)) + continue; + } + (*it).clear(); } } diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp index 765f1cebf..4df871664 100644 --- a/components/files/configurationmanager.hpp +++ b/components/files/configurationmanager.hpp @@ -30,7 +30,9 @@ struct ConfigurationManager void readConfiguration(boost::program_options::variables_map& variables, boost::program_options::options_description& description); - void processPaths(Files::PathContainer& dataDirs); + + void processPaths(Files::PathContainer& dataDirs, bool create = false); + ///< \param create Try creating the directory, if it does not exist. /**< Fixed paths */ const boost::filesystem::path& getGlobalPath() const; diff --git a/components/loadinglistener/loadinglistener.hpp b/components/loadinglistener/loadinglistener.hpp new file mode 100644 index 000000000..483d52491 --- /dev/null +++ b/components/loadinglistener/loadinglistener.hpp @@ -0,0 +1,35 @@ +#ifndef COMPONENTS_LOADINGLISTENER_H +#define COMPONENTS_LOADINGLISTENER_H + +namespace Loading +{ + class Listener + { + public: + virtual void setLabel (const std::string& label) = 0; + + // Use ScopedLoad instead of using these directly + virtual void loadingOn() = 0; + virtual void loadingOff() = 0; + + /// Indicate that some progress has been made, without specifying how much + virtual void indicateProgress () = 0; + + virtual void setProgressRange (size_t range) = 0; + virtual void setProgress (size_t value) = 0; + virtual void increaseProgress (size_t increase) = 0; + + /// Indicate the scene is now ready to be shown + virtual void removeWallpaper() = 0; + }; + + // Used for stopping a loading sequence when the object goes out of scope + struct ScopedLoad + { + ScopedLoad(Listener* l) : mListener(l) { mListener->loadingOn(); } + ~ScopedLoad() { mListener->loadingOff(); } + Listener* mListener; + }; +} + +#endif diff --git a/components/terrain/chunk.cpp b/components/terrain/chunk.cpp new file mode 100644 index 000000000..ce2118cdb --- /dev/null +++ b/components/terrain/chunk.cpp @@ -0,0 +1,169 @@ +#include "chunk.hpp" + +#include +#include + +#include "quadtreenode.hpp" +#include "world.hpp" +#include "storage.hpp" + +namespace Terrain +{ + + Chunk::Chunk(QuadTreeNode* node, short lodLevel) + : mNode(node) + , mVertexLod(lodLevel) + , mAdditionalLod(0) + { + mVertexData = OGRE_NEW Ogre::VertexData; + mVertexData->vertexStart = 0; + + // Set the total number of vertices + size_t numVertsOneSide = mNode->getSize() * (ESM::Land::LAND_SIZE-1); + numVertsOneSide /= 1 << lodLevel; + numVertsOneSide += 1; + assert((int)numVertsOneSide == ESM::Land::LAND_SIZE); + mVertexData->vertexCount = numVertsOneSide * numVertsOneSide; + + // Set up the vertex declaration, which specifies the info for each vertex (normals, colors, UVs, etc) + Ogre::VertexDeclaration* vertexDecl = mVertexData->vertexDeclaration; + + Ogre::HardwareBufferManager* mgr = Ogre::HardwareBufferManager::getSingletonPtr(); + size_t nextBuffer = 0; + + // Positions + vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_FLOAT3, Ogre::VES_POSITION); + mVertexBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3), + mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC); + // Normals + vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_FLOAT3, Ogre::VES_NORMAL); + mNormalBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3), + mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC); + + // UV texture coordinates + vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_FLOAT2, + Ogre::VES_TEXTURE_COORDINATES, 0); + Ogre::HardwareVertexBufferSharedPtr uvBuf = mNode->getTerrain()->getVertexBuffer(numVertsOneSide); + + // Colours + vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_COLOUR, Ogre::VES_DIFFUSE); + mColourBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_COLOUR), + mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC); + + mNode->getTerrain()->getStorage()->fillVertexBuffers(lodLevel, mNode->getSize(), mNode->getCenter(), + mVertexBuffer, mNormalBuffer, mColourBuffer); + + mVertexData->vertexBufferBinding->setBinding(0, mVertexBuffer); + mVertexData->vertexBufferBinding->setBinding(1, mNormalBuffer); + mVertexData->vertexBufferBinding->setBinding(2, uvBuf); + mVertexData->vertexBufferBinding->setBinding(3, mColourBuffer); + + mIndexData = OGRE_NEW Ogre::IndexData(); + mIndexData->indexStart = 0; + } + + + + void Chunk::updateIndexBuffer() + { + // Fetch a suitable index buffer (which may be shared) + size_t ourLod = mVertexLod + mAdditionalLod; + + int flags = 0; + + for (int i=0; i<4; ++i) + { + QuadTreeNode* neighbour = mNode->getNeighbour((Direction)i); + + // If the neighbour isn't currently rendering itself, + // go up until we find one. NOTE: We don't need to go down, + // because in that case neighbour's detail would be higher than + // our detail and the neighbour would handle stitching by itself. + while (neighbour && !neighbour->hasChunk()) + neighbour = neighbour->getParent(); + + size_t lod = 0; + if (neighbour) + lod = neighbour->getActualLodLevel(); + + if (lod <= ourLod) // We only need to worry about neighbours less detailed than we are - + lod = 0; // neighbours with more detail will do the stitching themselves + + // Use 4 bits for each LOD delta + if (lod > 0) + { + assert (lod - ourLod < (1 << 4)); + flags |= int(lod - ourLod) << (4*i); + } + } + + flags |= ((int)mAdditionalLod) << (4*4); + + size_t numIndices; + mIndexBuffer = mNode->getTerrain()->getIndexBuffer(flags, numIndices); + mIndexData->indexCount = numIndices; + mIndexData->indexBuffer = mIndexBuffer; + } + + Chunk::~Chunk() + { + OGRE_DELETE mVertexData; + OGRE_DELETE mIndexData; + } + + void Chunk::setMaterial(const Ogre::MaterialPtr &material) + { + mMaterial = material; + } + + const Ogre::AxisAlignedBox& Chunk::getBoundingBox(void) const + { + return mNode->getBoundingBox(); + } + + Ogre::Real Chunk::getBoundingRadius(void) const + { + return mNode->getBoundingBox().getHalfSize().length(); + } + + void Chunk::_updateRenderQueue(Ogre::RenderQueue* queue) + { + queue->addRenderable(this, mRenderQueueID); + } + + void Chunk::visitRenderables(Ogre::Renderable::Visitor* visitor, + bool debugRenderables) + { + visitor->visit(this, 0, false); + } + + const Ogre::MaterialPtr& Chunk::getMaterial(void) const + { + return mMaterial; + } + + void Chunk::getRenderOperation(Ogre::RenderOperation& op) + { + assert (!mIndexBuffer.isNull() && "Trying to render, but no index buffer set!"); + op.useIndexes = true; + op.operationType = Ogre::RenderOperation::OT_TRIANGLE_LIST; + op.vertexData = mVertexData; + op.indexData = mIndexData; + } + + void Chunk::getWorldTransforms(Ogre::Matrix4* xform) const + { + *xform = getParentSceneNode()->_getFullTransform(); + } + + Ogre::Real Chunk::getSquaredViewDepth(const Ogre::Camera* cam) const + { + return getParentSceneNode()->getSquaredViewDepth(cam); + } + + const Ogre::LightList& Chunk::getLights(void) const + { + return queryLights(); + } + +} diff --git a/components/terrain/chunk.hpp b/components/terrain/chunk.hpp new file mode 100644 index 000000000..d74c65ba6 --- /dev/null +++ b/components/terrain/chunk.hpp @@ -0,0 +1,63 @@ +#ifndef COMPONENTS_TERRAIN_TERRAINBATCH_H +#define COMPONENTS_TERRAIN_TERRAINBATCH_H + +#include +#include + +namespace Terrain +{ + + class QuadTreeNode; + + /** + * @brief Renders a chunk of terrain, either using alpha splatting or a composite map. + */ + class Chunk : public Ogre::Renderable, public Ogre::MovableObject + { + public: + /// @param lodLevel LOD level for the vertex buffer. + Chunk (QuadTreeNode* node, short lodLevel); + virtual ~Chunk(); + + void setMaterial (const Ogre::MaterialPtr& material); + + /// Set additional LOD applied on top of vertex LOD. \n + /// This is achieved by changing the index buffer to omit vertices. + void setAdditionalLod (size_t lod) { mAdditionalLod = lod; } + size_t getAdditionalLod() { return mAdditionalLod; } + + void updateIndexBuffer(); + + // Inherited from MovableObject + virtual const Ogre::String& getMovableType(void) const { static Ogre::String t = "MW_TERRAIN"; return t; } + virtual const Ogre::AxisAlignedBox& getBoundingBox(void) const; + virtual Ogre::Real getBoundingRadius(void) const; + virtual void _updateRenderQueue(Ogre::RenderQueue* queue); + virtual void visitRenderables(Renderable::Visitor* visitor, + bool debugRenderables = false); + + // Inherited from Renderable + virtual const Ogre::MaterialPtr& getMaterial(void) const; + virtual void getRenderOperation(Ogre::RenderOperation& op); + virtual void getWorldTransforms(Ogre::Matrix4* xform) const; + virtual Ogre::Real getSquaredViewDepth(const Ogre::Camera* cam) const; + virtual const Ogre::LightList& getLights(void) const; + + private: + QuadTreeNode* mNode; + Ogre::MaterialPtr mMaterial; + + size_t mVertexLod; + size_t mAdditionalLod; + + Ogre::VertexData* mVertexData; + Ogre::IndexData* mIndexData; + Ogre::HardwareVertexBufferSharedPtr mVertexBuffer; + Ogre::HardwareVertexBufferSharedPtr mNormalBuffer; + Ogre::HardwareVertexBufferSharedPtr mColourBuffer; + Ogre::HardwareIndexBufferSharedPtr mIndexBuffer; + }; + +} + +#endif diff --git a/components/terrain/esm_land_factory.cpp b/components/terrain/esm_land_factory.cpp deleted file mode 100644 index 5cab7ed5d..000000000 --- a/components/terrain/esm_land_factory.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include "esm_land_factory.hpp" - -// The first one already includes the others implicitly, but it -// doesn't hurt to be explicit. -#include "../esm_store/store.hpp" -#include "../esm/esmreader.hpp" -#include "../esm/loadland.hpp" - -using namespace Terrain; - -bool ESMLandFactory::has(int x, int y) -{ - return store.landscapes.has(x,y); -} diff --git a/components/terrain/esm_land_factory.hpp b/components/terrain/esm_land_factory.hpp deleted file mode 100644 index bb1f9a8c6..000000000 --- a/components/terrain/esm_land_factory.hpp +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef TERRAIN_ESM_LAND_FACTORY_H -#define TERRAIN_ESM_LAND_FACTORY_H - -#include "land_factory.hpp" - -namespace ESMS -{ - struct ESMStore; -} - -namespace ESM -{ - class ESMReader; -} - -namespace Terrain -{ - /* - Land factory that loads data from ESM files. - */ - class ESMLandFactory - { - ESMS::ESMStore &store; - ESM::ESMReader &reader; - - public: - // Initialize the land factory. Note that refrences to the given - // store and reader are stored in the class, so the given objects - // must be valid for a long as you plan to use this factory. - ESMLandFactory(ESMS::ESMStore &st, ESM::ESMReader &rd) - : store(st), reader(rd) {} - - // True if this factory has any data for the given grid cell. - bool has(int x, int y); - }; -} -#endif diff --git a/components/terrain/heightmap.hpp b/components/terrain/heightmap.hpp deleted file mode 100644 index e395b541e..000000000 --- a/components/terrain/heightmap.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef TERRAIN_HEIGHTMAP_H -#define TERRAIN_HEIGHTMAP_H - -/* - Generic interface for a structure holding heightmap data. - - A HeightMap returns information about landscape data in the form of - a regular grid of float heights. - */ - -namespace Terrain -{ - struct HeightMap - { - // Get height from grid position, counted from 0 to getNumX/Y(). - virtual float getHeight(int x, int y) = 0; - - // Get heigth from vertex index, assumed to be y*getNumX() + x. - virtual float getHeight(int index) = 0; - - virtual float getMinX() = 0; - virtual float getMaxX() = 0; - virtual float getMinY() = 0; - virtual float getMaxY() = 0; - - virtual int getNumX() = 0; - virtual int getNumY() = 0; - - // True if the given coordinate is within the grid - bool isWithin(float x, float y) - { - return - x >= getMinX() && x < getMaxX() && - y >= getMinY() && y < getMaxY(); - } - }; -} -#endif diff --git a/components/terrain/heightmapbuf.hpp b/components/terrain/heightmapbuf.hpp deleted file mode 100644 index d147e6015..000000000 --- a/components/terrain/heightmapbuf.hpp +++ /dev/null @@ -1,72 +0,0 @@ -#ifndef TERRAIN_HEIGHTMAPBUF_H -#define TERRAIN_HEIGHTMAPBUF_H - -/* - A HeightMap implementation that stores heigths in a buffer. - */ - -#include "heightmap.hpp" -#include "land_factory.hpp" -#include -#include - -namespace Terrain -{ - class HeightMapBuffer : public HeightMap - { - std::vector buf; - - float beginX, sizeX, endX; - float beginY, sizeY, endY; - - int numX, numY; - - public: - void load(LandDataPtr data, const LandInfo &info) - { - // We don't support other kinds of grid data yet. - assert(info.grid == LGT_Quadratic); - assert(info.data == LDT_Float); - - // Set up internal data - beginX = info.xoffset; - sizeX = info.xsize; - endX = beginX+sizeX; - numX = info.numx; - - beginY = info.yoffset; - sizeY = info.ysize; - endY = beginY+sizeY; - numY = info.numy; - - // Prepare the buffer and load it - buf.resize(numX*numY); - - data.read(&buf[0], buf.size()*sizeof(float)); - } - - // Functions inherited from HeightMap: - - float getHeight(int x, int y) - { - assert(x>=0 && x=0 && y= 0 && index < buf.size()); - return buf[index]; - } - - float getMinX() { return beginX; } - float getMaxX() { return endX; } - float getMinY() { return beginY; } - float getMaxY() { return endY; } - - int getNumX() { return numX; } - int getNumY() { return numY; } - }; -} -#endif diff --git a/components/terrain/land_factory.hpp b/components/terrain/land_factory.hpp deleted file mode 100644 index c4d160443..000000000 --- a/components/terrain/land_factory.hpp +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef TERRAIN_LAND_FACTORY_H -#define TERRAIN_LAND_FACTORY_H - -namespace Terrain -{ - enum LandInfoGridType - { - LGT_Quadratic - }; - - enum LandInfoDataType - { - LDT_Float - }; - - struct LandInfo - { - // Type information - LandInfoGridType grid; - LandInfoDataType data; - - // Landscape size and number of vertices. Note that xsize and - // ysize may be negative, signaling a flipped landscape in that - // direction. - float xsize, ysize; - int numx, numy; - - // World offset along the same x/y axes. Whether these are set or - // used depends on the client implementation. - float xoffset, yoffset; - }; - - /* - Factory class that provides streams to land data cells. Each - "cell" has a unique integer coordinate in the plane. - */ - struct LandFactory - { - // True if this factory has any data for the given grid cell. - virtual bool has(int x, int y) = 0; - }; -} -#endif diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp new file mode 100644 index 000000000..ebf6046ff --- /dev/null +++ b/components/terrain/material.cpp @@ -0,0 +1,300 @@ +#include "material.hpp" + +#include +#include +#include + +#include + +namespace +{ + +int getBlendmapIndexForLayer (int layerIndex) +{ + return std::floor((layerIndex-1)/4.f); +} + +std::string getBlendmapComponentForLayer (int layerIndex) +{ + int n = (layerIndex-1)%4; + if (n == 0) + return "x"; + if (n == 1) + return "y"; + if (n == 2) + return "z"; + else + return "w"; +} + +} + +namespace Terrain +{ + + MaterialGenerator::MaterialGenerator(bool shaders) + : mShaders(shaders) + , mShadows(false) + , mSplitShadows(false) + { + + } + + Ogre::MaterialPtr MaterialGenerator::generate(Ogre::MaterialPtr mat) + { + return create(mat, false, false); + } + + Ogre::MaterialPtr MaterialGenerator::generateForCompositeMapRTT(Ogre::MaterialPtr mat) + { + return create(mat, true, false); + } + + Ogre::MaterialPtr MaterialGenerator::generateForCompositeMap(Ogre::MaterialPtr mat) + { + return create(mat, false, true); + } + + Ogre::MaterialPtr MaterialGenerator::create(Ogre::MaterialPtr mat, bool renderCompositeMap, bool displayCompositeMap) + { + assert(!renderCompositeMap || !displayCompositeMap); + if (!mat.isNull()) + { + sh::Factory::getInstance().destroyMaterialInstance(mat->getName()); + Ogre::MaterialManager::getSingleton().remove(mat->getName()); + } + + static int count = 0; + std::stringstream name; + name << "terrain/mat" << count++; + + if (!mShaders) + { + mat = Ogre::MaterialManager::getSingleton().create(name.str(), + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); + Ogre::Technique* technique = mat->getTechnique(0); + technique->removeAllPasses(); + + if (displayCompositeMap) + { + Ogre::Pass* pass = technique->createPass(); + pass->setVertexColourTracking(Ogre::TVC_AMBIENT|Ogre::TVC_DIFFUSE); + pass->createTextureUnitState(mCompositeMap)->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP); + } + else + { + assert(mLayerList.size() == mBlendmapList.size()+1); + std::vector::iterator blend = mBlendmapList.begin(); + for (std::vector::iterator layer = mLayerList.begin(); layer != mLayerList.end(); ++layer) + { + Ogre::Pass* pass = technique->createPass(); + pass->setLightingEnabled(false); + pass->setVertexColourTracking(Ogre::TVC_NONE); + // TODO: How to handle fog? + pass->setFog(true, Ogre::FOG_NONE); + + bool first = (layer == mLayerList.begin()); + + Ogre::TextureUnitState* tus; + + if (!first) + { + pass->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA); + pass->setDepthFunction(Ogre::CMPF_EQUAL); + + tus = pass->createTextureUnitState((*blend)->getName()); + tus->setAlphaOperation(Ogre::LBX_BLEND_TEXTURE_ALPHA, + Ogre::LBS_TEXTURE, + Ogre::LBS_TEXTURE); + tus->setColourOperationEx(Ogre::LBX_BLEND_DIFFUSE_ALPHA, + Ogre::LBS_TEXTURE, + Ogre::LBS_TEXTURE); + tus->setIsAlpha(true); + tus->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP); + + float scale = (16/(16.f+1.f)); + tus->setTextureScale(1.f/scale,1.f/scale); + } + + // Add the actual layer texture on top of the alpha map. + tus = pass->createTextureUnitState("textures\\" + *layer); + if (!first) + tus->setColourOperationEx(Ogre::LBX_BLEND_DIFFUSE_ALPHA, + Ogre::LBS_TEXTURE, + Ogre::LBS_CURRENT); + + tus->setTextureScale(1/16.f,1/16.f); + + if (!first) + ++blend; + } + + if (!renderCompositeMap) + { + Ogre::Pass* lightingPass = technique->createPass(); + lightingPass->setSceneBlending(Ogre::SBT_MODULATE); + lightingPass->setVertexColourTracking(Ogre::TVC_AMBIENT|Ogre::TVC_DIFFUSE); + lightingPass->setFog(true, Ogre::FOG_NONE); + } + } + + return mat; + } + else + { + sh::MaterialInstance* material = sh::Factory::getInstance().createMaterialInstance (name.str()); + material->setProperty ("allow_fixed_function", sh::makeProperty(new sh::BooleanValue(false))); + + if (displayCompositeMap) + { + sh::MaterialInstancePass* p = material->createPass (); + + p->setProperty ("vertex_program", sh::makeProperty(new sh::StringValue("terrain_vertex"))); + p->setProperty ("fragment_program", sh::makeProperty(new sh::StringValue("terrain_fragment"))); + p->mShaderProperties.setProperty ("is_first_pass", sh::makeProperty(new sh::BooleanValue(true))); + p->mShaderProperties.setProperty ("render_composite_map", sh::makeProperty(new sh::BooleanValue(false))); + p->mShaderProperties.setProperty ("display_composite_map", sh::makeProperty(new sh::BooleanValue(true))); + p->mShaderProperties.setProperty ("num_layers", sh::makeProperty (new sh::StringValue("0"))); + p->mShaderProperties.setProperty ("num_blendmaps", sh::makeProperty (new sh::StringValue("0"))); + + sh::MaterialInstanceTextureUnit* tex = p->createTextureUnit ("compositeMap"); + tex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(mCompositeMap))); + tex->setProperty ("tex_address_mode", sh::makeProperty (new sh::StringValue("clamp"))); + + // shadow. TODO: repeated, put in function + if (mShadows) + { + for (Ogre::uint i = 0; i < (mSplitShadows ? 3 : 1); ++i) + { + sh::MaterialInstanceTextureUnit* shadowTex = p->createTextureUnit ("shadowMap" + Ogre::StringConverter::toString(i)); + shadowTex->setProperty ("content_type", sh::makeProperty (new sh::StringValue("shadow"))); + } + } + p->mShaderProperties.setProperty ("shadowtexture_offset", sh::makeProperty (new sh::StringValue( + Ogre::StringConverter::toString(1)))); + + p->mShaderProperties.setProperty ("pass_index", sh::makeProperty(new sh::IntValue(0))); + } + else + { + + bool shadows = mShadows && !renderCompositeMap; + + int layerOffset = 0; + while (layerOffset < (int)mLayerList.size()) + { + int blendmapOffset = (layerOffset == 0) ? 1 : 0; // the first layer of the first pass is the base layer and does not need a blend map + + // Check how many layers we can fit in this pass + int numLayersInThisPass = 0; + int numBlendTextures = 0; + std::vector blendTextures; + int remainingTextureUnits = OGRE_MAX_TEXTURE_LAYERS; + if (shadows) + remainingTextureUnits -= (mSplitShadows ? 3 : 1); + while (remainingTextureUnits && layerOffset + numLayersInThisPass < (int)mLayerList.size()) + { + int layerIndex = numLayersInThisPass + layerOffset; + + int neededTextureUnits=0; + int neededBlendTextures=0; + + if (layerIndex != 0) + { + std::string blendTextureName = mBlendmapList[getBlendmapIndexForLayer(layerIndex)]->getName(); + if (std::find(blendTextures.begin(), blendTextures.end(), blendTextureName) == blendTextures.end()) + { + blendTextures.push_back(blendTextureName); + ++neededBlendTextures; + ++neededTextureUnits; // blend texture + } + } + ++neededTextureUnits; // layer texture + if (neededTextureUnits <= remainingTextureUnits) + { + // We can fit another! + remainingTextureUnits -= neededTextureUnits; + numBlendTextures += neededBlendTextures; + ++numLayersInThisPass; + } + else + break; // We're full + } + + + sh::MaterialInstancePass* p = material->createPass (); + + p->setProperty ("vertex_program", sh::makeProperty(new sh::StringValue("terrain_vertex"))); + p->setProperty ("fragment_program", sh::makeProperty(new sh::StringValue("terrain_fragment"))); + if (layerOffset != 0) + { + p->setProperty ("scene_blend", sh::makeProperty(new sh::StringValue("alpha_blend"))); + // Only write if depth is equal to the depth value written by the previous pass. + p->setProperty ("depth_func", sh::makeProperty(new sh::StringValue("equal"))); + } + + p->mShaderProperties.setProperty ("render_composite_map", sh::makeProperty(new sh::BooleanValue(renderCompositeMap))); + p->mShaderProperties.setProperty ("display_composite_map", sh::makeProperty(new sh::BooleanValue(displayCompositeMap))); + + p->mShaderProperties.setProperty ("num_layers", sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(numLayersInThisPass)))); + p->mShaderProperties.setProperty ("num_blendmaps", sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(numBlendTextures)))); + + // blend maps + // the index of the first blend map used in this pass + int blendmapStart; + if (mLayerList.size() == 1) // special case. if there's only one layer, we don't need blend maps at all + blendmapStart = 0; + else + blendmapStart = getBlendmapIndexForLayer(layerOffset+blendmapOffset); + for (int i = 0; i < numBlendTextures; ++i) + { + sh::MaterialInstanceTextureUnit* blendTex = p->createTextureUnit ("blendMap" + Ogre::StringConverter::toString(i)); + blendTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(mBlendmapList[blendmapStart+i]->getName()))); + blendTex->setProperty ("tex_address_mode", sh::makeProperty (new sh::StringValue("clamp"))); + } + + // layer maps + for (int i = 0; i < numLayersInThisPass; ++i) + { + sh::MaterialInstanceTextureUnit* diffuseTex = p->createTextureUnit ("diffuseMap" + Ogre::StringConverter::toString(i)); + diffuseTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue("textures\\"+mLayerList[layerOffset+i]))); + + if (i+layerOffset > 0) + { + int blendTextureIndex = getBlendmapIndexForLayer(layerOffset+i); + std::string blendTextureComponent = getBlendmapComponentForLayer(layerOffset+i); + p->mShaderProperties.setProperty ("blendmap_component_" + Ogre::StringConverter::toString(i), + sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(blendTextureIndex-blendmapStart) + "." + blendTextureComponent))); + } + else + { + // just to make it shut up about blendmap_component_0 not existing in the first pass. + // it might be retrieved, but will never survive the preprocessing step. + p->mShaderProperties.setProperty ("blendmap_component_" + Ogre::StringConverter::toString(i), + sh::makeProperty (new sh::StringValue(""))); + } + } + + // shadow + if (shadows) + { + for (Ogre::uint i = 0; i < (mSplitShadows ? 3 : 1); ++i) + { + sh::MaterialInstanceTextureUnit* shadowTex = p->createTextureUnit ("shadowMap" + Ogre::StringConverter::toString(i)); + shadowTex->setProperty ("content_type", sh::makeProperty (new sh::StringValue("shadow"))); + } + } + p->mShaderProperties.setProperty ("shadowtexture_offset", sh::makeProperty (new sh::StringValue( + Ogre::StringConverter::toString(numBlendTextures + numLayersInThisPass)))); + + // Make sure the pass index is fed to the permutation handler, because blendmap components may be different + p->mShaderProperties.setProperty ("pass_index", sh::makeProperty(new sh::IntValue(layerOffset))); + + layerOffset += numLayersInThisPass; + } + } + } + return Ogre::MaterialManager::getSingleton().getByName(name.str()); + } + +} diff --git a/components/terrain/material.hpp b/components/terrain/material.hpp new file mode 100644 index 000000000..330ed3d14 --- /dev/null +++ b/components/terrain/material.hpp @@ -0,0 +1,56 @@ +#ifndef COMPONENTS_TERRAIN_MATERIAL_H +#define COMPONENTS_TERRAIN_MATERIAL_H + +#include + +namespace Terrain +{ + + class MaterialGenerator + { + public: + /// @param layerList layer textures + /// @param blendmapList blend textures + /// @param shaders Whether to use shaders. With a shader, blendmap packing can be used (4 channels instead of one), + /// so if this parameter is true, then the supplied blend maps are expected to be packed. + MaterialGenerator (bool shaders); + + void setLayerList (const std::vector& layerList) { mLayerList = layerList; } + bool hasLayers() { return mLayerList.size(); } + void setBlendmapList (const std::vector& blendmapList) { mBlendmapList = blendmapList; } + const std::vector& getBlendmapList() { return mBlendmapList; } + void setCompositeMap (const std::string& name) { mCompositeMap = name; } + + void enableShadows(bool shadows) { mShadows = shadows; } + void enableSplitShadows(bool splitShadows) { mSplitShadows = splitShadows; } + + /// Creates a material suitable for displaying a chunk of terrain using alpha-blending. + /// @param mat Material that will be replaced by the generated material. May be empty as well, in which case + /// a new material is created. + Ogre::MaterialPtr generate (Ogre::MaterialPtr mat); + + /// Creates a material suitable for displaying a chunk of terrain using a ready-made composite map. + /// @param mat Material that will be replaced by the generated material. May be empty as well, in which case + /// a new material is created. + Ogre::MaterialPtr generateForCompositeMap (Ogre::MaterialPtr mat); + + /// Creates a material suitable for rendering composite maps, i.e. for "baking" several layer textures + /// into one. The main difference compared to a normal material is that no shading is applied at this point. + /// @param mat Material that will be replaced by the generated material. May be empty as well, in which case + /// a new material is created. + Ogre::MaterialPtr generateForCompositeMapRTT (Ogre::MaterialPtr mat); + + private: + Ogre::MaterialPtr create (Ogre::MaterialPtr mat, bool renderCompositeMap, bool displayCompositeMap); + + std::vector mLayerList; + std::vector mBlendmapList; + std::string mCompositeMap; + bool mShaders; + bool mShadows; + bool mSplitShadows; + }; + +} + +#endif diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp new file mode 100644 index 000000000..ef2c61013 --- /dev/null +++ b/components/terrain/quadtreenode.cpp @@ -0,0 +1,512 @@ +#include "quadtreenode.hpp" + +#include +#include + +#include "world.hpp" +#include "chunk.hpp" +#include "storage.hpp" + +#include "material.hpp" + +using namespace Terrain; + +namespace +{ + int Log2( int n ) + { + assert(n > 0); + int targetlevel = 0; + while (n >>= 1) ++targetlevel; + return targetlevel; + } + + // Utility functions for neighbour finding algorithm + ChildDirection reflect(ChildDirection dir, Direction dir2) + { + assert(dir != Root); + + const int lookupTable[4][4] = + { + // NW NE SW SE + { SW, SE, NW, NE }, // N + { NE, NW, SE, SW }, // E + { SW, SE, NW, NE }, // S + { NE, NW, SE, SW } // W + }; + return (ChildDirection)lookupTable[dir2][dir]; + } + + bool adjacent(ChildDirection dir, Direction dir2) + { + assert(dir != Root); + const bool lookupTable[4][4] = + { + // NW NE SW SE + { true, true, false, false }, // N + { false, true, false, true }, // E + { false, false, true, true }, // S + { true, false, true, false } // W + }; + return lookupTable[dir2][dir]; + } + + // Algorithm described by Hanan Samet - 'Neighbour Finding in Quadtrees' + // http://www.cs.umd.edu/~hjs/pubs/SametPRIP81.pdf + QuadTreeNode* searchNeighbourRecursive (QuadTreeNode* currentNode, Direction dir) + { + if (!currentNode->getParent()) + return NULL; // Arrived at root node, the root node does not have neighbours + + QuadTreeNode* nextNode; + if (adjacent(currentNode->getDirection(), dir)) + nextNode = searchNeighbourRecursive(currentNode->getParent(), dir); + else + nextNode = currentNode->getParent(); + + if (nextNode && nextNode->hasChildren()) + return nextNode->getChild(reflect(currentNode->getDirection(), dir)); + else + return NULL; + } + + + // Ogre::AxisAlignedBox::distance is broken in 1.8. + Ogre::Real distance(const Ogre::AxisAlignedBox& box, const Ogre::Vector3& v) + { + + if (box.contains(v)) + return 0; + else + { + Ogre::Vector3 maxDist(0,0,0); + const Ogre::Vector3& minimum = box.getMinimum(); + const Ogre::Vector3& maximum = box.getMaximum(); + + if (v.x < minimum.x) + maxDist.x = minimum.x - v.x; + else if (v.x > maximum.x) + maxDist.x = v.x - maximum.x; + + if (v.y < minimum.y) + maxDist.y = minimum.y - v.y; + else if (v.y > maximum.y) + maxDist.y = v.y - maximum.y; + + if (v.z < minimum.z) + maxDist.z = minimum.z - v.z; + else if (v.z > maximum.z) + maxDist.z = v.z - maximum.z; + + return maxDist.length(); + } + } + + // Create a 2D quad + void makeQuad(Ogre::SceneManager* sceneMgr, float left, float top, float right, float bottom, Ogre::MaterialPtr material) + { + Ogre::ManualObject* manual = sceneMgr->createManualObject(); + + // Use identity view/projection matrices to get a 2d quad + manual->setUseIdentityProjection(true); + manual->setUseIdentityView(true); + + manual->begin(material->getName()); + + float normLeft = left*2-1; + float normTop = top*2-1; + float normRight = right*2-1; + float normBottom = bottom*2-1; + + manual->position(normLeft, normTop, 0.0); + manual->textureCoord(0, 1); + manual->position(normRight, normTop, 0.0); + manual->textureCoord(1, 1); + manual->position(normRight, normBottom, 0.0); + manual->textureCoord(1, 0); + manual->position(normLeft, normBottom, 0.0); + manual->textureCoord(0, 0); + + manual->quad(0,1,2,3); + + manual->end(); + + Ogre::AxisAlignedBox aabInf; + aabInf.setInfinite(); + manual->setBoundingBox(aabInf); + + sceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(manual); + } +} + +QuadTreeNode::QuadTreeNode(World* terrain, ChildDirection dir, float size, const Ogre::Vector2 ¢er, QuadTreeNode* parent) + : mSize(size) + , mCenter(center) + , mParent(parent) + , mDirection(dir) + , mIsDummy(false) + , mSceneNode(NULL) + , mTerrain(terrain) + , mChunk(NULL) + , mMaterialGenerator(NULL) + , mBounds(Ogre::AxisAlignedBox::BOX_NULL) + , mWorldBounds(Ogre::AxisAlignedBox::BOX_NULL) +{ + mBounds.setNull(); + for (int i=0; i<4; ++i) + mChildren[i] = NULL; + for (int i=0; i<4; ++i) + mNeighbours[i] = NULL; + + if (mDirection == Root) + mSceneNode = mTerrain->getRootSceneNode(); + else + mSceneNode = mTerrain->getSceneManager()->createSceneNode(); + Ogre::Vector2 pos (0,0); + if (mParent) + pos = mParent->getCenter(); + pos = mCenter - pos; + mSceneNode->setPosition(Ogre::Vector3(pos.x*8192, pos.y*8192, 0)); + + mLodLevel = Log2(mSize); + + mMaterialGenerator = new MaterialGenerator(mTerrain->getShadersEnabled()); +} + +void QuadTreeNode::createChild(ChildDirection id, float size, const Ogre::Vector2 ¢er) +{ + mChildren[id] = new QuadTreeNode(mTerrain, id, size, center, this); +} + +QuadTreeNode::~QuadTreeNode() +{ + for (int i=0; i<4; ++i) + delete mChildren[i]; + delete mChunk; + delete mMaterialGenerator; +} + +QuadTreeNode* QuadTreeNode::getNeighbour(Direction dir) +{ + return mNeighbours[static_cast(dir)]; +} + +void QuadTreeNode::initNeighbours() +{ + for (int i=0; i<4; ++i) + mNeighbours[i] = searchNeighbourRecursive(this, (Direction)i); + + if (hasChildren()) + for (int i=0; i<4; ++i) + mChildren[i]->initNeighbours(); +} + +void QuadTreeNode::initAabb() +{ + if (hasChildren()) + { + for (int i=0; i<4; ++i) + { + mChildren[i]->initAabb(); + mBounds.merge(mChildren[i]->getBoundingBox()); + } + mBounds = Ogre::AxisAlignedBox (Ogre::Vector3(-mSize/2*8192, -mSize/2*8192, mBounds.getMinimum().z), + Ogre::Vector3(mSize/2*8192, mSize/2*8192, mBounds.getMaximum().z)); + } + mWorldBounds = Ogre::AxisAlignedBox(mBounds.getMinimum() + Ogre::Vector3(mCenter.x*8192, mCenter.y*8192, 0), + mBounds.getMaximum() + Ogre::Vector3(mCenter.x*8192, mCenter.y*8192, 0)); +} + +void QuadTreeNode::setBoundingBox(const Ogre::AxisAlignedBox &box) +{ + mBounds = box; +} + +const Ogre::AxisAlignedBox& QuadTreeNode::getBoundingBox() +{ + return mBounds; +} + +void QuadTreeNode::update(const Ogre::Vector3 &cameraPos, Loading::Listener* loadingListener) +{ + const Ogre::AxisAlignedBox& bounds = getBoundingBox(); + if (bounds.isNull()) + return; + + float dist = distance(mWorldBounds, cameraPos); + + bool distantLand = mTerrain->getDistantLandEnabled(); + + // Make sure our scene node is attached + if (!mSceneNode->isInSceneGraph()) + { + mParent->getSceneNode()->addChild(mSceneNode); + } + + /// \todo implement error metrics or some other means of not using arbitrary values + /// (general quality needs to be user configurable as well) + size_t wantedLod = 0; + if (dist > 8192*1) + wantedLod = 1; + if (dist > 8192*2) + wantedLod = 2; + if (dist > 8192*5) + wantedLod = 3; + if (dist > 8192*12) + wantedLod = 4; + if (dist > 8192*32) + wantedLod = 5; + if (dist > 8192*64) + wantedLod = 6; + + bool hadChunk = hasChunk(); + + if (loadingListener) + loadingListener->indicateProgress(); + + if (!distantLand && dist > 8192*2) + { + if (mIsActive) + { + destroyChunks(true); + mIsActive = false; + } + return; + } + + mIsActive = true; + + if (mSize <= mTerrain->getMaxBatchSize() && mLodLevel <= wantedLod) + { + // Wanted LOD is small enough to render this node in one chunk + if (!mChunk) + { + mChunk = new Chunk(this, mLodLevel); + mChunk->setVisibilityFlags(mTerrain->getVisiblityFlags()); + mChunk->setCastShadows(true); + mSceneNode->attachObject(mChunk); + + mMaterialGenerator->enableShadows(mTerrain->getShadowsEnabled()); + mMaterialGenerator->enableSplitShadows(mTerrain->getSplitShadowsEnabled()); + + if (mSize == 1) + { + ensureLayerInfo(); + mChunk->setMaterial(mMaterialGenerator->generate(mChunk->getMaterial())); + } + else + { + ensureCompositeMap(); + mMaterialGenerator->setCompositeMap(mCompositeMap->getName()); + mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap(mChunk->getMaterial())); + } + } + + // Additional (index buffer) LOD is currently disabled. + // This is due to a problem with the LOD selection when a node splits. + // After splitting, the distance is measured from the children's bounding boxes, which are possibly + // further away than the original node's bounding box, possibly causing a child to switch to a *lower* LOD + // than the original node. + // In short, we'd sometimes get a switch to a lesser detail when actually moving closer. + // This wouldn't be so bad, but unfortunately it also breaks LOD edge connections if a neighbour + // node hasn't split yet, and has a higher LOD than our node's child: + // ----- ----- ------------ + // | LOD | LOD | | + // | 1 | 1 | | + // |-----|-----| LOD 0 | + // | LOD | LOD | | + // | 0 | 0 | | + // ----- ----- ------------ + // To prevent this, nodes of the same size need to always select the same LOD, which is basically what we're + // doing here. + // But this "solution" does increase triangle overhead, so eventually we need to find a more clever way. + //mChunk->setAdditionalLod(wantedLod - mLodLevel); + + mChunk->setVisible(true); + + if (!hadChunk && hasChildren()) + { + // Make sure child scene nodes are detached + mSceneNode->removeAllChildren(); + + // If distant land is enabled, keep the chunks around in case we need them again, + // otherwise, prefer low memory usage + if (!distantLand) + for (int i=0; i<4; ++i) + mChildren[i]->destroyChunks(true); + } + } + else + { + // Wanted LOD is too detailed to be rendered in one chunk, + // so split it up by delegating to child nodes + if (hadChunk) + { + // If distant land is enabled, keep the chunks around in case we need them again, + // otherwise, prefer low memory usage + if (!distantLand) + destroyChunks(false); + else if (mChunk) + mChunk->setVisible(false); + } + assert(hasChildren() && "Leaf node's LOD needs to be 0"); + for (int i=0; i<4; ++i) + mChildren[i]->update(cameraPos, loadingListener); + } +} + +void QuadTreeNode::destroyChunks(bool children) +{ + if (mChunk) + { + Ogre::MaterialManager::getSingleton().remove(mChunk->getMaterial()->getName()); + mSceneNode->detachObject(mChunk); + + delete mChunk; + mChunk = NULL; + // destroy blendmaps + if (mMaterialGenerator) + { + const std::vector& list = mMaterialGenerator->getBlendmapList(); + for (std::vector::const_iterator it = list.begin(); it != list.end(); ++it) + Ogre::TextureManager::getSingleton().remove((*it)->getName()); + mMaterialGenerator->setBlendmapList(std::vector()); + mMaterialGenerator->setLayerList(std::vector()); + mMaterialGenerator->setCompositeMap(""); + } + + if (!mCompositeMap.isNull()) + { + Ogre::TextureManager::getSingleton().remove(mCompositeMap->getName()); + mCompositeMap.setNull(); + } + } + else if (children && hasChildren()) + for (int i=0; i<4; ++i) + mChildren[i]->destroyChunks(true); +} + +void QuadTreeNode::updateIndexBuffers() +{ + if (hasChunk()) + mChunk->updateIndexBuffer(); + else if (hasChildren()) + { + for (int i=0; i<4; ++i) + mChildren[i]->updateIndexBuffers(); + } +} + +bool QuadTreeNode::hasChunk() +{ + return mSceneNode->isInSceneGraph() && mChunk && mChunk->getVisible(); +} + +size_t QuadTreeNode::getActualLodLevel() +{ + assert(hasChunk() && "Can't get actual LOD level if this node has no render chunk"); + return mLodLevel + mChunk->getAdditionalLod(); +} + +void QuadTreeNode::ensureLayerInfo() +{ + if (mMaterialGenerator->hasLayers()) + return; + + std::vector blendmaps; + std::vector layerList; + mTerrain->getStorage()->getBlendmaps(mSize, mCenter, mTerrain->getShadersEnabled(), blendmaps, layerList); + + mMaterialGenerator->setLayerList(layerList); + mMaterialGenerator->setBlendmapList(blendmaps); +} + +void QuadTreeNode::prepareForCompositeMap(Ogre::TRect area) +{ + Ogre::SceneManager* sceneMgr = mTerrain->getCompositeMapSceneManager(); + + if (mIsDummy) + { + // TODO - why is this completely black? + // TODO - store this default material somewhere instead of creating one for each empty cell + MaterialGenerator matGen(mTerrain->getShadersEnabled()); + std::vector layer; + layer.push_back("_land_default.dds"); + matGen.setLayerList(layer); + makeQuad(sceneMgr, area.left, area.top, area.right, area.bottom, matGen.generateForCompositeMapRTT(Ogre::MaterialPtr())); + return; + } + if (mSize > 1) + { + assert(hasChildren()); + + // 0,0 -------- 1,0 + // | | | + // |-----|------| + // | | | + // 0,1 -------- 1,1 + + float halfW = area.width()/2.f; + float halfH = area.height()/2.f; + mChildren[NW]->prepareForCompositeMap(Ogre::TRect(area.left, area.top, area.right-halfW, area.bottom-halfH)); + mChildren[NE]->prepareForCompositeMap(Ogre::TRect(area.left+halfW, area.top, area.right, area.bottom-halfH)); + mChildren[SW]->prepareForCompositeMap(Ogre::TRect(area.left, area.top+halfH, area.right-halfW, area.bottom)); + mChildren[SE]->prepareForCompositeMap(Ogre::TRect(area.left+halfW, area.top+halfH, area.right, area.bottom)); + } + else + { + ensureLayerInfo(); + + Ogre::MaterialPtr material = mMaterialGenerator->generateForCompositeMapRTT(Ogre::MaterialPtr()); + makeQuad(sceneMgr, area.left, area.top, area.right, area.bottom, material); + } +} + +void QuadTreeNode::ensureCompositeMap() +{ + if (!mCompositeMap.isNull()) + return; + + static int i=0; + std::stringstream name; + name << "terrain/comp" << i++; + + const int size = 128; + mCompositeMap = Ogre::TextureManager::getSingleton().createManual( + name.str(), Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + Ogre::TEX_TYPE_2D, size, size, Ogre::MIP_DEFAULT, Ogre::PF_A8B8G8R8); + + // Create quads for each cell + prepareForCompositeMap(Ogre::TRect(0,0,1,1)); + + mTerrain->renderCompositeMap(mCompositeMap); + + mTerrain->clearCompositeMapSceneManager(); + +} + +void QuadTreeNode::applyMaterials() +{ + if (mChunk) + { + mMaterialGenerator->enableShadows(mTerrain->getShadowsEnabled()); + mMaterialGenerator->enableSplitShadows(mTerrain->getSplitShadowsEnabled()); + if (mSize <= 1) + mChunk->setMaterial(mMaterialGenerator->generate(Ogre::MaterialPtr())); + else + mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap(Ogre::MaterialPtr())); + } + if (hasChildren()) + for (int i=0; i<4; ++i) + mChildren[i]->applyMaterials(); +} + +void QuadTreeNode::setVisible(bool visible) +{ + if (!visible && mChunk) + mChunk->setVisible(false); + + if (hasChildren()) + for (int i=0; i<4; ++i) + mChildren[i]->setVisible(visible); +} diff --git a/components/terrain/quadtreenode.hpp b/components/terrain/quadtreenode.hpp new file mode 100644 index 000000000..9f6fb5d24 --- /dev/null +++ b/components/terrain/quadtreenode.hpp @@ -0,0 +1,161 @@ +#ifndef COMPONENTS_TERRAIN_QUADTREENODE_H +#define COMPONENTS_TERRAIN_QUADTREENODE_H + +#include +#include +#include + +#include + +namespace Ogre +{ + class Rectangle2D; +} + +namespace Terrain +{ + class World; + class Chunk; + class MaterialGenerator; + + enum Direction + { + North = 0, + East = 1, + South = 2, + West = 3 + }; + + enum ChildDirection + { + NW = 0, + NE = 1, + SW = 2, + SE = 3, + Root + }; + + /** + * @brief A node in the quad tree for our terrain. Depending on LOD, + * a node can either choose to render itself in one batch (merging its children), + * or delegate the render process to its children, rendering each child in at least one batch. + */ + class QuadTreeNode + { + public: + /// @param terrain + /// @param dir relative to parent, or Root if we are the root node + /// @param size size (in *cell* units!) + /// @param center center (in *cell* units!) + /// @param parent parent node + QuadTreeNode (World* terrain, ChildDirection dir, float size, const Ogre::Vector2& center, QuadTreeNode* parent); + ~QuadTreeNode(); + + void setVisible(bool visible); + + /// Rebuild all materials + void applyMaterials(); + + /// Initialize neighbours - do this after the quadtree is built + void initNeighbours(); + /// Initialize bounding boxes of non-leafs by merging children bounding boxes. + /// Do this after the quadtree is built - note that leaf bounding boxes + /// need to be set first via setBoundingBox! + void initAabb(); + + /// @note takes ownership of \a child + void createChild (ChildDirection id, float size, const Ogre::Vector2& center); + + /// Mark this node as a dummy node. This can happen if the terrain size isn't a power of two. + /// For the QuadTree to work, we need to round the size up to a power of two, which means we'll + /// end up with empty nodes that don't actually render anything. + void markAsDummy() { mIsDummy = true; } + bool isDummy() { return mIsDummy; } + + QuadTreeNode* getParent() { return mParent; } + + Ogre::SceneNode* getSceneNode() { return mSceneNode; } + + int getSize() { return mSize; } + Ogre::Vector2 getCenter() { return mCenter; } + + bool hasChildren() { return mChildren[0] != 0; } + QuadTreeNode* getChild(ChildDirection dir) { return mChildren[dir]; } + + /// Get neighbour node in this direction + QuadTreeNode* getNeighbour (Direction dir); + + /// Returns our direction relative to the parent node, or Root if we are the root node. + ChildDirection getDirection() { return mDirection; } + + /// Set bounding box in local coordinates. Should be done at load time for leaf nodes. + /// Other nodes can merge AABB of child nodes. + void setBoundingBox (const Ogre::AxisAlignedBox& box); + + /// Get bounding box in local coordinates + const Ogre::AxisAlignedBox& getBoundingBox(); + + World* getTerrain() { return mTerrain; } + + /// Adjust LODs for the given camera position, possibly splitting up chunks or merging them. + void update (const Ogre::Vector3& cameraPos, Loading::Listener* loadingListener); + + /// Adjust index buffers of chunks to stitch together chunks of different LOD, so that cracks are avoided. + /// Call after QuadTreeNode::update! + void updateIndexBuffers(); + + /// Destroy chunks rendered by this node *and* its children (if param is true) + void destroyChunks(bool children); + + /// Get the effective LOD level if this node was rendered in one chunk + /// with ESM::Land::LAND_SIZE^2 vertices + size_t getNativeLodLevel() { return mLodLevel; } + + /// Get the effective current LOD level used by the chunk rendering this node + size_t getActualLodLevel(); + + /// Is this node currently configured to render itself? + bool hasChunk(); + + /// Add a textured quad to a specific 2d area in the composite map scenemanager. + /// Only nodes with size <= 1 can be rendered with alpha blending, so larger nodes will simply + /// call this method on their children. + /// @param area area in image space to put the quad + /// @param quads collect quads here so they can be deleted later + void prepareForCompositeMap(Ogre::TRect area); + + private: + // Stored here for convenience in case we need layer list again + MaterialGenerator* mMaterialGenerator; + + /// Is this node (or any of its child nodes) currently configured to render itself? + /// (only relevant when distant land is disabled, otherwise whole terrain is always rendered) + bool mIsActive; + + bool mIsDummy; + float mSize; + size_t mLodLevel; // LOD if we were to render this node in one chunk + Ogre::AxisAlignedBox mBounds; + Ogre::AxisAlignedBox mWorldBounds; + ChildDirection mDirection; + Ogre::Vector2 mCenter; + + Ogre::SceneNode* mSceneNode; + + QuadTreeNode* mParent; + QuadTreeNode* mChildren[4]; + QuadTreeNode* mNeighbours[4]; + + Chunk* mChunk; + + World* mTerrain; + + Ogre::TexturePtr mCompositeMap; + + void ensureLayerInfo(); + void ensureCompositeMap(); + }; + +} + +#endif diff --git a/components/terrain/storage.cpp b/components/terrain/storage.cpp new file mode 100644 index 000000000..f00677e97 --- /dev/null +++ b/components/terrain/storage.cpp @@ -0,0 +1,470 @@ +#include "storage.hpp" + +#include +#include +#include +#include +#include + +#include + +namespace Terrain +{ + + struct VertexElement + { + Ogre::Vector3 pos; + Ogre::Vector3 normal; + Ogre::ColourValue colour; + }; + + bool Storage::getMinMaxHeights(float size, const Ogre::Vector2 ¢er, float &min, float &max) + { + assert (size <= 1 && "Storage::getMinMaxHeights, chunk size should be <= 1 cell"); + + /// \todo investigate if min/max heights should be stored at load time in ESM::Land instead + + Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f); + + assert(origin.x == (int) origin.x); + assert(origin.y == (int) origin.y); + + int cellX = origin.x; + int cellY = origin.y; + + const ESM::Land* land = getLand(cellX, cellY); + if (!land) + return false; + + min = std::numeric_limits().max(); + max = -std::numeric_limits().max(); + for (int row=0; rowmLandData->mHeights[col*ESM::Land::LAND_SIZE+row]; + if (h > max) + max = h; + if (h < min) + min = h; + } + } + return true; + } + + void Storage::fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row) + { + while (col >= ESM::Land::LAND_SIZE-1) + { + ++cellY; + col -= ESM::Land::LAND_SIZE-1; + } + while (row >= ESM::Land::LAND_SIZE-1) + { + ++cellX; + row -= ESM::Land::LAND_SIZE-1; + } + while (col < 0) + { + --cellY; + col += ESM::Land::LAND_SIZE-1; + } + while (row < 0) + { + --cellX; + row += ESM::Land::LAND_SIZE-1; + } + ESM::Land* land = getLand(cellX, cellY); + if (land && land->mHasData) + { + normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; + normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; + normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; + normal.normalise(); + } + else + normal = Ogre::Vector3(0,0,1); + } + + void Storage::averageNormal(Ogre::Vector3 &normal, int cellX, int cellY, int col, int row) + { + Ogre::Vector3 n1,n2,n3,n4; + fixNormal(n1, cellX, cellY, col+1, row); + fixNormal(n2, cellX, cellY, col-1, row); + fixNormal(n3, cellX, cellY, col, row+1); + fixNormal(n4, cellX, cellY, col, row-1); + normal = (n1+n2+n3+n4); + normal.normalise(); + } + + void Storage::fixColour (Ogre::ColourValue& color, int cellX, int cellY, int col, int row) + { + if (col == ESM::Land::LAND_SIZE-1) + { + ++cellY; + col = 0; + } + if (row == ESM::Land::LAND_SIZE-1) + { + ++cellX; + row = 0; + } + ESM::Land* land = getLand(cellX, cellY); + if (land && land->mLandData->mUsingColours) + { + color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f; + color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f; + color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f; + } + else + { + color.r = 1; + color.g = 1; + color.b = 1; + } + + } + + void Storage::fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, + Ogre::HardwareVertexBufferSharedPtr vertexBuffer, + Ogre::HardwareVertexBufferSharedPtr normalBuffer, + Ogre::HardwareVertexBufferSharedPtr colourBuffer) + { + // LOD level n means every 2^n-th vertex is kept + size_t increment = 1 << lodLevel; + + Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f); + assert(origin.x == (int) origin.x); + assert(origin.y == (int) origin.y); + + int startX = origin.x; + int startY = origin.y; + + size_t numVerts = size*(ESM::Land::LAND_SIZE-1)/increment + 1; + + std::vector colors; + colors.resize(numVerts*numVerts*4); + std::vector positions; + positions.resize(numVerts*numVerts*3); + std::vector normals; + normals.resize(numVerts*numVerts*3); + + Ogre::Vector3 normal; + Ogre::ColourValue color; + + float vertY; + float vertX; + + float vertY_ = 0; // of current cell corner + for (int cellY = startY; cellY < startY + std::ceil(size); ++cellY) + { + float vertX_ = 0; // of current cell corner + for (int cellX = startX; cellX < startX + std::ceil(size); ++cellX) + { + ESM::Land* land = getLand(cellX, cellY); + if (land && !land->mHasData) + land = NULL; + bool hasColors = land && land->mLandData->mUsingColours; + + int rowStart = 0; + int colStart = 0; + // Skip the first row / column unless we're at a chunk edge, + // since this row / column is already contained in a previous cell + if (colStart == 0 && vertY_ != 0) + colStart += increment; + if (rowStart == 0 && vertX_ != 0) + rowStart += increment; + + vertY = vertY_; + for (int col=colStart; colmLandData->mHeights[col*ESM::Land::LAND_SIZE+row]; + else + positions[vertX*numVerts*3 + vertY*3 + 2] = -2048; + + if (land) + { + normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; + normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; + normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; + normal.normalise(); + } + else + normal = Ogre::Vector3(0,0,1); + + // Normals apparently don't connect seamlessly between cells + if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1) + fixNormal(normal, cellX, cellY, col, row); + + // some corner normals appear to be complete garbage (z < 0) + if ((row == 0 || row == ESM::Land::LAND_SIZE-1) && (col == 0 || col == ESM::Land::LAND_SIZE-1)) + averageNormal(normal, cellX, cellY, col, row); + + assert(normal.z > 0); + + normals[vertX*numVerts*3 + vertY*3] = normal.x; + normals[vertX*numVerts*3 + vertY*3 + 1] = normal.y; + normals[vertX*numVerts*3 + vertY*3 + 2] = normal.z; + + if (hasColors) + { + color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f; + color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f; + color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f; + } + else + { + color.r = 1; + color.g = 1; + color.b = 1; + } + + // Unlike normals, colors mostly connect seamlessly between cells, but not always... + if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1) + fixColour(color, cellX, cellY, col, row); + + color.a = 1; + Ogre::uint32 rsColor; + Ogre::Root::getSingleton().getRenderSystem()->convertColourValue(color, &rsColor); + memcpy(&colors[vertX*numVerts*4 + vertY*4], &rsColor, sizeof(Ogre::uint32)); + + ++vertX; + } + ++vertY; + } + vertX_ = vertX; + } + vertY_ = vertY; + + assert(vertX_ == numVerts); // Ensure we covered whole area + } + assert(vertY_ == numVerts); // Ensure we covered whole area + + vertexBuffer->writeData(0, vertexBuffer->getSizeInBytes(), &positions[0], true); + normalBuffer->writeData(0, normalBuffer->getSizeInBytes(), &normals[0], true); + colourBuffer->writeData(0, colourBuffer->getSizeInBytes(), &colors[0], true); + } + + Storage::UniqueTextureId Storage::getVtexIndexAt(int cellX, int cellY, + int x, int y) + { + // For the first/last row/column, we need to get the texture from the neighbour cell + // to get consistent blending at the borders + --x; + if (x < 0) + { + --cellX; + x += ESM::Land::LAND_TEXTURE_SIZE; + } + if (y >= ESM::Land::LAND_TEXTURE_SIZE) // Y appears to be wrapped from the other side because why the hell not? + { + ++cellY; + y -= ESM::Land::LAND_TEXTURE_SIZE; + } + + assert(xisDataLoaded(ESM::Land::DATA_VTEX)) + land->loadData(ESM::Land::DATA_VTEX); + + int tex = land->mLandData->mTextures[y * ESM::Land::LAND_TEXTURE_SIZE + x]; + if (tex == 0) + return std::make_pair(0,0); // vtex 0 is always the base texture, regardless of plugin + return std::make_pair(tex, land->mPlugin); + } + else + return std::make_pair(0,0); + } + + std::string Storage::getTextureName(UniqueTextureId id) + { + if (id.first == 0) + return "_land_default.dds"; // Not sure if the default texture floatly is hardcoded? + + // NB: All vtex ids are +1 compared to the ltex ids + const ESM::LandTexture* ltex = getLandTexture(id.first-1, id.second); + + std::string texture = ltex->mTexture; + //TODO this is needed due to MWs messed up texture handling + texture = texture.substr(0, texture.rfind(".")) + ".dds"; + + return texture; + } + + void Storage::getBlendmaps(float chunkSize, const Ogre::Vector2 &chunkCenter, + bool pack, std::vector &blendmaps, std::vector &layerList) + { + // TODO - blending isn't completely right yet; the blending radius appears to be + // different at a cell transition (2 vertices, not 4), so we may need to create a larger blendmap + // and interpolate the rest of the cell by hand? :/ + + Ogre::Vector2 origin = chunkCenter - Ogre::Vector2(chunkSize/2.f, chunkSize/2.f); + int cellX = origin.x; + int cellY = origin.y; + + // Save the used texture indices so we know the total number of textures + // and number of required blend maps + std::set textureIndices; + // Due to the way the blending works, the base layer will always shine through in between + // blend transitions (eg halfway between two texels, both blend values will be 0.5, so 25% of base layer visible). + // To get a consistent look, we need to make sure to use the same base layer in all cells. + // So we're always adding _land_default.dds as the base layer here, even if it's not referenced in this cell. + textureIndices.insert(std::make_pair(0,0)); + + for (int y=0; y textureIndicesMap; + for (std::set::iterator it = textureIndices.begin(); it != textureIndices.end(); ++it) + { + int size = textureIndicesMap.size(); + textureIndicesMap[*it] = size; + layerList.push_back(getTextureName(*it)); + } + + int numTextures = textureIndices.size(); + // numTextures-1 since the base layer doesn't need blending + int numBlendmaps = pack ? std::ceil((numTextures-1) / 4.f) : (numTextures-1); + + int channels = pack ? 4 : 1; + + // Second iteration - create and fill in the blend maps + const int blendmapSize = ESM::Land::LAND_TEXTURE_SIZE+1; + std::vector data; + data.resize(blendmapSize * blendmapSize * channels, 0); + + for (int i=0; isecond; + int blendIndex = (pack ? std::floor((layerIndex-1)/4.f) : layerIndex-1); + int channel = pack ? std::max(0, (layerIndex-1) % 4) : 0; + + if (blendIndex == i) + data[y*blendmapSize*channels + x*channels + channel] = 255; + else + data[y*blendmapSize*channels + x*channels + channel] = 0; + } + } + + // All done, upload to GPU + Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(&data[0], data.size())); + map->loadRawData(stream, blendmapSize, blendmapSize, format); + blendmaps.push_back(map); + } + } + + float Storage::getHeightAt(const Ogre::Vector3 &worldPos) + { + int cellX = std::floor(worldPos.x / 8192.f); + int cellY = std::floor(worldPos.y / 8192.f); + + ESM::Land* land = getLand(cellX, cellY); + if (!land) + return -2048; + + // Mostly lifted from Ogre::Terrain::getHeightAtTerrainPosition + + // Normalized position in the cell + float nX = (worldPos.x - (cellX * 8192))/8192.f; + float nY = (worldPos.y - (cellY * 8192))/8192.f; + + // get left / bottom points (rounded down) + float factor = ESM::Land::LAND_SIZE - 1.0f; + float invFactor = 1.0f / factor; + + int startX = static_cast(nX * factor); + int startY = static_cast(nY * factor); + int endX = startX + 1; + int endY = startY + 1; + + assert(endX < ESM::Land::LAND_SIZE); + assert(endY < ESM::Land::LAND_SIZE); + + // now get points in terrain space (effectively rounding them to boundaries) + float startXTS = startX * invFactor; + float startYTS = startY * invFactor; + float endXTS = endX * invFactor; + float endYTS = endY * invFactor; + + // get parametric from start coord to next point + float xParam = (nX - startXTS) * factor; + float yParam = (nY - startYTS) * factor; + + /* For even / odd tri strip rows, triangles are this shape: + even odd + 3---2 3---2 + | / | | \ | + 0---1 0---1 + */ + + // Build all 4 positions in normalized cell space, using point-sampled height + Ogre::Vector3 v0 (startXTS, startYTS, getVertexHeight(land, startX, startY) / 8192.f); + Ogre::Vector3 v1 (endXTS, startYTS, getVertexHeight(land, endX, startY) / 8192.f); + Ogre::Vector3 v2 (endXTS, endYTS, getVertexHeight(land, endX, endY) / 8192.f); + Ogre::Vector3 v3 (startXTS, endYTS, getVertexHeight(land, startX, endY) / 8192.f); + // define this plane in terrain space + Ogre::Plane plane; + // (At the moment, all rows have the same triangle alignment) + if (true) + { + // odd row + bool secondTri = ((1.0 - yParam) > xParam); + if (secondTri) + plane.redefine(v0, v1, v3); + else + plane.redefine(v1, v2, v3); + } + else + { + // even row + bool secondTri = (yParam > xParam); + if (secondTri) + plane.redefine(v0, v2, v3); + else + plane.redefine(v0, v1, v2); + } + + // Solve plane equation for z + return (-plane.normal.x * nX + -plane.normal.y * nY + - plane.d) / plane.normal.z * 8192; + + } + + float Storage::getVertexHeight(const ESM::Land *land, int x, int y) + { + assert(x < ESM::Land::LAND_SIZE); + assert(y < ESM::Land::LAND_SIZE); + return land->mLandData->mHeights[y * ESM::Land::LAND_SIZE + x]; + } + + +} diff --git a/components/terrain/storage.hpp b/components/terrain/storage.hpp new file mode 100644 index 000000000..b82f6bbb6 --- /dev/null +++ b/components/terrain/storage.hpp @@ -0,0 +1,84 @@ +#ifndef COMPONENTS_TERRAIN_STORAGE_H +#define COMPONENTS_TERRAIN_STORAGE_H + +#include +#include + +#include + +#include + +namespace Terrain +{ + + /// We keep storage of terrain data abstract here since we need different implementations for game and editor + class Storage + { + public: + virtual ~Storage() {} + private: + virtual ESM::Land* getLand (int cellX, int cellY) = 0; + virtual const ESM::LandTexture* getLandTexture(int index, short plugin) = 0; + + public: + /// Get bounds of the whole terrain in cell units + virtual Ogre::AxisAlignedBox getBounds() = 0; + + /// Get the minimum and maximum heights of a terrain chunk. + /// @note Should only be called for chunks <= 1 cell, i.e. leafs of the quad tree. + /// Larger chunks can simply merge AABB of children. + /// @param size size of the chunk in cell units + /// @param center center of the chunk in cell units + /// @param min min height will be stored here + /// @param max max height will be stored here + /// @return true if there was data available for this terrain chunk + bool getMinMaxHeights (float size, const Ogre::Vector2& center, float& min, float& max); + + /// Fill vertex buffers for a terrain chunk. + /// @param lodLevel LOD level, 0 = most detailed + /// @param size size of the terrain chunk in cell units + /// @param center center of the chunk in cell units + /// @param vertexBuffer buffer to write vertices + /// @param normalBuffer buffer to write vertex normals + /// @param colourBuffer buffer to write vertex colours + void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, + Ogre::HardwareVertexBufferSharedPtr vertexBuffer, + Ogre::HardwareVertexBufferSharedPtr normalBuffer, + Ogre::HardwareVertexBufferSharedPtr colourBuffer); + + /// Create textures holding layer blend values for a terrain chunk. + /// @note The terrain chunk shouldn't be larger than one cell since otherwise we might + /// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used. + /// @param chunkSize size of the terrain chunk in cell units + /// @param chunkCenter center of the chunk in cell units + /// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) - + /// otherwise, each texture contains blend values for one layer only. Shader-based rendering + /// can utilize packing, FFP can't. + /// @param blendmaps created blendmaps will be written here + /// @param layerList names of the layer textures used will be written here + void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack, + std::vector& blendmaps, + std::vector& layerList); + + float getHeightAt (const Ogre::Vector3& worldPos); + + private: + void fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row); + void fixColour (Ogre::ColourValue& colour, int cellX, int cellY, int col, int row); + void averageNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row); + + float getVertexHeight (const ESM::Land* land, int x, int y); + + // Since plugins can define new texture palettes, we need to know the plugin index too + // in order to retrieve the correct texture name. + // pair + typedef std::pair UniqueTextureId; + + UniqueTextureId getVtexIndexAt(int cellX, int cellY, + int x, int y); + std::string getTextureName (UniqueTextureId id); + }; + +} + +#endif diff --git a/components/terrain/tests/.gitignore b/components/terrain/tests/.gitignore deleted file mode 100644 index 814490404..000000000 --- a/components/terrain/tests/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*_test diff --git a/components/terrain/tests/Makefile b/components/terrain/tests/Makefile deleted file mode 100644 index c886f392f..000000000 --- a/components/terrain/tests/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -GCC=g++ - -all: triangle_test esm_test - -LIB_INC=-I../../../libs/ - -triangle_test: triangle_test.cpp - $(GCC) $^ -o $@ - -esm_test: esm_test.cpp - $(GCC) $^ -o $@ $(LIB_INC) - -clean: - rm *_test diff --git a/components/terrain/tests/esm_test.cpp b/components/terrain/tests/esm_test.cpp deleted file mode 100644 index 509aa8aa9..000000000 --- a/components/terrain/tests/esm_test.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include -using namespace std; - -#include "../esm_land_factory.hpp" - -int main() -{ - cout << "under development\n"; - return 0; -} diff --git a/components/terrain/tests/output/esm_test.out b/components/terrain/tests/output/esm_test.out deleted file mode 100644 index c6fec4b4d..000000000 --- a/components/terrain/tests/output/esm_test.out +++ /dev/null @@ -1 +0,0 @@ -under development diff --git a/components/terrain/tests/output/triangle_test.out b/components/terrain/tests/output/triangle_test.out deleted file mode 100644 index 001215043..000000000 --- a/components/terrain/tests/output/triangle_test.out +++ /dev/null @@ -1,55 +0,0 @@ -Cell types: -\ / \ / -/ \ / \ -\ / \ / -/ \ / \ - -Full index list: -0 -6 -5 -0 -1 -6 -1 -2 -6 -6 -2 -7 -2 -8 -7 -2 -3 -8 -3 -4 -8 -8 -4 -9 -5 -6 -10 -10 -6 -11 -6 -12 -11 -6 -7 -12 -7 -8 -12 -12 -8 -13 -8 -14 -13 -8 -9 -14 diff --git a/components/terrain/tests/test.sh b/components/terrain/tests/test.sh deleted file mode 100755 index 2d07708ad..000000000 --- a/components/terrain/tests/test.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -make || exit - -mkdir -p output - -PROGS=*_test - -for a in $PROGS; do - if [ -f "output/$a.out" ]; then - echo "Running $a:" - ./$a | diff output/$a.out - - else - echo "Creating $a.out" - ./$a > "output/$a.out" - git add "output/$a.out" - fi -done diff --git a/components/terrain/tests/triangle_test.cpp b/components/terrain/tests/triangle_test.cpp deleted file mode 100644 index 464bc8709..000000000 --- a/components/terrain/tests/triangle_test.cpp +++ /dev/null @@ -1,93 +0,0 @@ -#include -using namespace std; - -#include "../triangulator.hpp" - -const int X = 4; -const int Y = 4; - -typedef Terrain::Triangulator Triangles4x4; - -int main() -{ - Triangles4x4 t; - - cout << "Cell types:\n"; - for(int y=0;y= 0 && trinum < TriNum); - trinum *= 3; - - p1 = array[trinum++]; - p2 = array[trinum++]; - p3 = array[trinum]; - } - - /* - Get height interpolation weights for a given grid square. The - input is the grid square number (x,y) and the relative position - within that square (xrel,yrel = [0.0..1.0].) The weights are - returned as three vertex index + weight factor pairs. - - A more user-friendly version for HeightMap structs is given - below. - * / - void getWeights(int x, int y, float xrel, float yrel, - Index &p1, float w1, - Index &p2, float w2, - Index &p3, float w3) - { - // Find cell index - int index = y*SizeX + x; - - // First triangle in cell - index *= 2; - - // The rest depends on how the cell is triangulated - if(cellType(x,y)) - { - } - else - { - // Cell is divided as \ from 0,0 to 1,1 - if(xrel < yrel) - { - // Bottom left triangle. - - // Order is (0,0),(1,1),(0,1). - getTriangle(index, p1,p2,p3); - - - } - else - { - // Top right triangle - - // Order is (0,0),(1,0),(1,1). - getTriangle(index+1, p1,p2,p3); - } - } - } - - */ diff --git a/components/terrain/triangulator.hpp b/components/terrain/triangulator.hpp deleted file mode 100644 index c5c0e699b..000000000 --- a/components/terrain/triangulator.hpp +++ /dev/null @@ -1,104 +0,0 @@ -#ifndef TERRAIN_TRIANGULATOR_H -#define TERRAIN_TRIANGULATOR_H - -/* - The triangulator is a simple math helper class, used for dividing a - regular square grid into alternating set of triangles. It divides a - grid like this: - - +----+----+ - | | | - | | | - +----+----+ - | | | - | | | - +----+----+ - - into this: - - +----+----+ - | \ 2|3 / | - |1 \ | / 4| - +----+----+ - |5 / | \ 8| - | / 6|7 \ | - +----+----+ - - Since the triangulation information is typically the same for all - terrains of the same size, once instance can usually be shared. -*/ - -#include - -namespace Terrain -{ - // Index number type, number of grid cells (not vertices) in X and Y - // directions. - template - class Triangulator - { - // Number of triangles - static const int TriNum = SizeX * SizeY * 2; - - // 3 indices per triangle - Index array[TriNum * 3]; - - public: - // Get raw triangle data pointer. Typically used for creating - // meshes. - const Index *getData() { return array; } - - // Return whether a given cell is divided as / (true) or \ - // (false). - static bool cellType(int x, int y) - { - assert(x >= 0 && x < SizeX); - assert(y >= 0 && y < SizeY); - - bool even = (x & 1) == 1; - if((y & 1) == 1) even = !even; - return even; - } - - // Constructor sets up the index buffer - Triangulator() - { - int index = 0; - for ( int y = 0; y < SizeX; y++ ) - for ( int x = 0; x < SizeY; x++ ) - { - // Get vertex indices - Index line1 = y*(SizeX+1) + x; - Index line2 = line1 + SizeX+1; - - if(cellType(x,y)) - { - // Top left - array[index++] = line1; - array[index++] = line1 + 1; - array[index++] = line2; - - // Bottom right - array[index++] = line2; - array[index++] = line1 + 1; - array[index++] = line2 + 1; - } - else - { - // Bottom left - array[index++] = line1; - array[index++] = line2 + 1; - array[index++] = line2; - - // Top right - array[index++] = line1; - array[index++] = line1 + 1; - array[index++] = line2 + 1; - } - } - assert(index == TriNum*3); - } - }; -} // Namespace - -#endif diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp new file mode 100644 index 000000000..711ebbc8f --- /dev/null +++ b/components/terrain/world.cpp @@ -0,0 +1,410 @@ +#include "world.hpp" + +#include +#include +#include +#include +#include + +#include +#include + +#include "storage.hpp" +#include "quadtreenode.hpp" + +namespace +{ + + bool isPowerOfTwo(int x) + { + return ( (x > 0) && ((x & (x - 1)) == 0) ); + } + + int nextPowerOfTwo (int v) + { + if (isPowerOfTwo(v)) return v; + int depth=0; + while(v) + { + v >>= 1; + depth++; + } + return 1 << depth; + } + + Terrain::QuadTreeNode* findNode (const Ogre::Vector2& center, Terrain::QuadTreeNode* node) + { + if (center == node->getCenter()) + return node; + + if (center.x > node->getCenter().x && center.y > node->getCenter().y) + return findNode(center, node->getChild(Terrain::NE)); + else if (center.x > node->getCenter().x && center.y < node->getCenter().y) + return findNode(center, node->getChild(Terrain::SE)); + else if (center.x < node->getCenter().x && center.y > node->getCenter().y) + return findNode(center, node->getChild(Terrain::NW)); + else //if (center.x < node->getCenter().x && center.y < node->getCenter().y) + return findNode(center, node->getChild(Terrain::SW)); + } + +} + +namespace Terrain +{ + + World::World(Loading::Listener* loadingListener, Ogre::SceneManager* sceneMgr, + Storage* storage, int visibilityFlags, bool distantLand, bool shaders) + : mStorage(storage) + , mMinBatchSize(1) + , mMaxBatchSize(64) + , mSceneMgr(sceneMgr) + , mVisibilityFlags(visibilityFlags) + , mDistantLand(distantLand) + , mShaders(shaders) + , mVisible(true) + , mLoadingListener(loadingListener) + { + loadingListener->setLabel("Creating terrain"); + loadingListener->indicateProgress(); + + mCompositeMapSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC); + + Ogre::Camera* compositeMapCam = mCompositeMapSceneMgr->createCamera("a"); + mCompositeMapRenderTexture = Ogre::TextureManager::getSingleton().createManual( + "terrain/comp/rt", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + Ogre::TEX_TYPE_2D, 128, 128, 0, Ogre::PF_A8B8G8R8, Ogre::TU_RENDERTARGET); + mCompositeMapRenderTarget = mCompositeMapRenderTexture->getBuffer()->getRenderTarget(); + mCompositeMapRenderTarget->setAutoUpdated(false); + mCompositeMapRenderTarget->addViewport(compositeMapCam); + + mBounds = storage->getBounds(); + + int origSizeX = mBounds.getSize().x; + int origSizeY = mBounds.getSize().y; + + // Dividing a quad tree only works well for powers of two, so round up to the nearest one + int size = nextPowerOfTwo(std::max(origSizeX, origSizeY)); + + // Adjust the center according to the new size + Ogre::Vector3 center = mBounds.getCenter() + Ogre::Vector3((size-origSizeX)/2.f, (size-origSizeY)/2.f, 0); + + mRootSceneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(); + + mRootNode = new QuadTreeNode(this, Root, size, Ogre::Vector2(center.x, center.y), NULL); + buildQuadTree(mRootNode); + loadingListener->indicateProgress(); + mRootNode->initAabb(); + loadingListener->indicateProgress(); + mRootNode->initNeighbours(); + loadingListener->indicateProgress(); + } + + World::~World() + { + delete mRootNode; + delete mStorage; + } + + void World::buildQuadTree(QuadTreeNode *node) + { + float halfSize = node->getSize()/2.f; + + if (node->getSize() <= mMinBatchSize) + { + // We arrived at a leaf + float minZ,maxZ; + Ogre::Vector2 center = node->getCenter(); + if (mStorage->getMinMaxHeights(node->getSize(), center, minZ, maxZ)) + node->setBoundingBox(Ogre::AxisAlignedBox(Ogre::Vector3(-halfSize*8192, -halfSize*8192, minZ), + Ogre::Vector3(halfSize*8192, halfSize*8192, maxZ))); + else + node->markAsDummy(); // no data available for this node, skip it + return; + } + + if (node->getCenter().x - halfSize > mBounds.getMaximum().x + || node->getCenter().x + halfSize < mBounds.getMinimum().x + || node->getCenter().y - halfSize > mBounds.getMaximum().y + || node->getCenter().y + halfSize < mBounds.getMinimum().y ) + // Out of bounds of the actual terrain - this will happen because + // we rounded the size up to the next power of two + { + node->markAsDummy(); + return; + } + + // Not a leaf, create its children + node->createChild(SW, halfSize, node->getCenter() - halfSize/2.f); + node->createChild(SE, halfSize, node->getCenter() + Ogre::Vector2(halfSize/2.f, -halfSize/2.f)); + node->createChild(NW, halfSize, node->getCenter() + Ogre::Vector2(-halfSize/2.f, halfSize/2.f)); + node->createChild(NE, halfSize, node->getCenter() + halfSize/2.f); + buildQuadTree(node->getChild(SW)); + buildQuadTree(node->getChild(SE)); + buildQuadTree(node->getChild(NW)); + buildQuadTree(node->getChild(NE)); + + // if all children are dummy, we are also dummy + for (int i=0; i<4; ++i) + { + if (!node->getChild((ChildDirection)i)->isDummy()) + return; + } + node->markAsDummy(); + } + + void World::update(const Ogre::Vector3& cameraPos) + { + if (!mVisible) + return; + mRootNode->update(cameraPos, mLoadingListener); + mRootNode->updateIndexBuffers(); + } + + Ogre::AxisAlignedBox World::getWorldBoundingBox (const Ogre::Vector2& center) + { + if (center.x > mBounds.getMaximum().x + || center.x < mBounds.getMinimum().x + || center.y > mBounds.getMaximum().y + || center.y < mBounds.getMinimum().y) + return Ogre::AxisAlignedBox::BOX_NULL; + QuadTreeNode* node = findNode(center, mRootNode); + Ogre::AxisAlignedBox box = node->getBoundingBox(); + box.setExtents(box.getMinimum() + Ogre::Vector3(center.x, center.y, 0) * 8192, + box.getMaximum() + Ogre::Vector3(center.x, center.y, 0) * 8192); + return box; + } + + Ogre::HardwareVertexBufferSharedPtr World::getVertexBuffer(int numVertsOneSide) + { + if (mUvBufferMap.find(numVertsOneSide) != mUvBufferMap.end()) + { + return mUvBufferMap[numVertsOneSide]; + } + + int vertexCount = numVertsOneSide * numVertsOneSide; + + std::vector uvs; + uvs.reserve(vertexCount*2); + + for (int col = 0; col < numVertsOneSide; ++col) + { + for (int row = 0; row < numVertsOneSide; ++row) + { + uvs.push_back(col / static_cast(numVertsOneSide-1)); // U + uvs.push_back(row / static_cast(numVertsOneSide-1)); // V + } + } + + Ogre::HardwareBufferManager* mgr = Ogre::HardwareBufferManager::getSingletonPtr(); + Ogre::HardwareVertexBufferSharedPtr buffer = mgr->createVertexBuffer( + Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT2), + vertexCount, Ogre::HardwareBuffer::HBU_STATIC); + + buffer->writeData(0, buffer->getSizeInBytes(), &uvs[0], true); + + mUvBufferMap[numVertsOneSide] = buffer; + return buffer; + } + + Ogre::HardwareIndexBufferSharedPtr World::getIndexBuffer(int flags, size_t& numIndices) + { + if (mIndexBufferMap.find(flags) != mIndexBufferMap.end()) + { + numIndices = mIndexBufferMap[flags]->getNumIndexes(); + return mIndexBufferMap[flags]; + } + + // LOD level n means every 2^n-th vertex is kept + size_t lodLevel = (flags >> (4*4)); + + size_t lodDeltas[4]; + for (int i=0; i<4; ++i) + lodDeltas[i] = (flags >> (4*i)) & (0xf); + + bool anyDeltas = (lodDeltas[North] || lodDeltas[South] || lodDeltas[West] || lodDeltas[East]); + + size_t increment = 1 << lodLevel; + assert((int)increment < ESM::Land::LAND_SIZE); + std::vector indices; + indices.reserve((ESM::Land::LAND_SIZE-1)*(ESM::Land::LAND_SIZE-1)*2*3 / increment); + + size_t rowStart = 0, colStart = 0, rowEnd = ESM::Land::LAND_SIZE-1, colEnd = ESM::Land::LAND_SIZE-1; + // If any edge needs stitching we'll skip all edges at this point, + // mainly because stitching one edge would have an effect on corners and on the adjacent edges + if (anyDeltas) + { + colStart += increment; + colEnd -= increment; + rowEnd -= increment; + rowStart += increment; + } + for (size_t row = rowStart; row < rowEnd; row += increment) + { + for (size_t col = colStart; col < colEnd; col += increment) + { + indices.push_back(ESM::Land::LAND_SIZE*col+row); + indices.push_back(ESM::Land::LAND_SIZE*(col+increment)+row+increment); + indices.push_back(ESM::Land::LAND_SIZE*col+row+increment); + + indices.push_back(ESM::Land::LAND_SIZE*col+row); + indices.push_back(ESM::Land::LAND_SIZE*(col+increment)+row); + indices.push_back(ESM::Land::LAND_SIZE*(col+increment)+row+increment); + } + } + + size_t innerStep = increment; + if (anyDeltas) + { + // Now configure LOD transitions at the edges - this is pretty tedious, + // and some very long and boring code, but it works great + + // South + size_t row = 0; + size_t outerStep = 1 << (lodDeltas[South] + lodLevel); + for (size_t col = 0; col < ESM::Land::LAND_SIZE-1; col += outerStep) + { + indices.push_back(ESM::Land::LAND_SIZE*col+row); + indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row); + // Make sure not to touch the right edge + if (col+outerStep == ESM::Land::LAND_SIZE-1) + indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep-innerStep)+row+innerStep); + else + indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row+innerStep); + + for (size_t i = 0; i < outerStep; i += innerStep) + { + // Make sure not to touch the left or right edges + if (col+i == 0 || col+i == ESM::Land::LAND_SIZE-1-innerStep) + continue; + indices.push_back(ESM::Land::LAND_SIZE*(col)+row); + indices.push_back(ESM::Land::LAND_SIZE*(col+i+innerStep)+row+innerStep); + indices.push_back(ESM::Land::LAND_SIZE*(col+i)+row+innerStep); + } + } + + // North + row = ESM::Land::LAND_SIZE-1; + outerStep = 1 << (lodDeltas[North] + lodLevel); + for (size_t col = 0; col < ESM::Land::LAND_SIZE-1; col += outerStep) + { + indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row); + indices.push_back(ESM::Land::LAND_SIZE*col+row); + // Make sure not to touch the left edge + if (col == 0) + indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row-innerStep); + else + indices.push_back(ESM::Land::LAND_SIZE*col+row-innerStep); + + for (size_t i = 0; i < outerStep; i += innerStep) + { + // Make sure not to touch the left or right edges + if (col+i == 0 || col+i == ESM::Land::LAND_SIZE-1-innerStep) + continue; + indices.push_back(ESM::Land::LAND_SIZE*(col+i)+row-innerStep); + indices.push_back(ESM::Land::LAND_SIZE*(col+i+innerStep)+row-innerStep); + indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row); + } + } + + // West + size_t col = 0; + outerStep = 1 << (lodDeltas[West] + lodLevel); + for (size_t row = 0; row < ESM::Land::LAND_SIZE-1; row += outerStep) + { + indices.push_back(ESM::Land::LAND_SIZE*col+row+outerStep); + indices.push_back(ESM::Land::LAND_SIZE*col+row); + // Make sure not to touch the top edge + if (row+outerStep == ESM::Land::LAND_SIZE-1) + indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+outerStep-innerStep); + else + indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+outerStep); + + for (size_t i = 0; i < outerStep; i += innerStep) + { + // Make sure not to touch the top or bottom edges + if (row+i == 0 || row+i == ESM::Land::LAND_SIZE-1-innerStep) + continue; + indices.push_back(ESM::Land::LAND_SIZE*col+row); + indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+i); + indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+i+innerStep); + } + } + + // East + col = ESM::Land::LAND_SIZE-1; + outerStep = 1 << (lodDeltas[East] + lodLevel); + for (size_t row = 0; row < ESM::Land::LAND_SIZE-1; row += outerStep) + { + indices.push_back(ESM::Land::LAND_SIZE*col+row); + indices.push_back(ESM::Land::LAND_SIZE*col+row+outerStep); + // Make sure not to touch the bottom edge + if (row == 0) + indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row+innerStep); + else + indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row); + + for (size_t i = 0; i < outerStep; i += innerStep) + { + // Make sure not to touch the top or bottom edges + if (row+i == 0 || row+i == ESM::Land::LAND_SIZE-1-innerStep) + continue; + indices.push_back(ESM::Land::LAND_SIZE*col+row+outerStep); + indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row+i+innerStep); + indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row+i); + } + } + } + + + + numIndices = indices.size(); + + Ogre::HardwareBufferManager* mgr = Ogre::HardwareBufferManager::getSingletonPtr(); + Ogre::HardwareIndexBufferSharedPtr buffer = mgr->createIndexBuffer(Ogre::HardwareIndexBuffer::IT_16BIT, + numIndices, Ogre::HardwareBuffer::HBU_STATIC); + buffer->writeData(0, buffer->getSizeInBytes(), &indices[0], true); + mIndexBufferMap[flags] = buffer; + return buffer; + } + + void World::renderCompositeMap(Ogre::TexturePtr target) + { + mCompositeMapRenderTarget->update(); + target->getBuffer()->blit(mCompositeMapRenderTexture->getBuffer()); + } + + void World::clearCompositeMapSceneManager() + { + mCompositeMapSceneMgr->destroyAllManualObjects(); + mCompositeMapSceneMgr->clearScene(); + } + + float World::getHeightAt(const Ogre::Vector3 &worldPos) + { + return mStorage->getHeightAt(worldPos); + } + + void World::applyMaterials(bool shadows, bool splitShadows) + { + mShadows = shadows; + mSplitShadows = splitShadows; + mRootNode->applyMaterials(); + } + + void World::setVisible(bool visible) + { + if (visible && !mVisible) + mSceneMgr->getRootSceneNode()->addChild(mRootSceneNode); + else if (!visible && mVisible) + mSceneMgr->getRootSceneNode()->removeChild(mRootSceneNode); + + mVisible = visible; + } + + bool World::getVisible() + { + return mVisible; + } + + +} diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp new file mode 100644 index 000000000..b8c1b0a7d --- /dev/null +++ b/components/terrain/world.hpp @@ -0,0 +1,154 @@ +#ifndef COMPONENTS_TERRAIN_H +#define COMPONENTS_TERRAIN_H + +#include +#include +#include +#include + +namespace Loading +{ + class Listener; +} + +namespace Ogre +{ + class Camera; +} + +namespace Terrain +{ + + class QuadTreeNode; + class Storage; + + /** + * @brief A quadtree-based terrain implementation suitable for large data sets. \n + * Near cells are rendered with alpha splatting, distant cells are merged + * together in batches and have their layers pre-rendered onto a composite map. \n + * Cracks at LOD transitions are avoided using stitching. + * @note Multiple cameras are not supported yet + */ + class World + { + public: + /// @note takes ownership of \a storage + /// @param loadingListener Listener to update with progress + /// @param sceneMgr scene manager to use + /// @param storage Storage instance to get terrain data from (heights, normals, colors, textures..) + /// @param visbilityFlags visibility flags for the created meshes + /// @param distantLand Whether to draw all of the terrain, or only a 3x3 grid around the camera. + /// This is a temporary option until it can be streamlined. + /// @param shaders Whether to use splatting shader, or multi-pass fixed function splatting. Shader is usually + /// faster so this is just here for compatibility. + World(Loading::Listener* loadingListener, Ogre::SceneManager* sceneMgr, + Storage* storage, int visiblityFlags, bool distantLand, bool shaders); + ~World(); + + void setLoadingListener(Loading::Listener* loadingListener) { mLoadingListener = loadingListener; } + + bool getDistantLandEnabled() { return mDistantLand; } + bool getShadersEnabled() { return mShaders; } + bool getShadowsEnabled() { return mShadows; } + bool getSplitShadowsEnabled() { return mSplitShadows; } + + float getHeightAt (const Ogre::Vector3& worldPos); + + /// Update chunk LODs according to this camera position + /// @note Calling this method might lead to composite textures being rendered, so it is best + /// not to call it when render commands are still queued, since that would cause a flush. + void update (const Ogre::Vector3& cameraPos); + + /// Get the world bounding box of a chunk of terrain centered at \a center + Ogre::AxisAlignedBox getWorldBoundingBox (const Ogre::Vector2& center); + + Ogre::SceneManager* getSceneManager() { return mSceneMgr; } + + Ogre::SceneNode* getRootSceneNode() { return mRootSceneNode; } + + Storage* getStorage() { return mStorage; } + + /// Show or hide the whole terrain + /// @note this setting will be invalidated once you call Terrain::update, so do not call it while the terrain should be hidden + void setVisible(bool visible); + bool getVisible(); + + /// Recreate materials used by terrain chunks. This should be called whenever settings of + /// the material factory are changed. (Relying on the factory to update those materials is not + /// enough, since turning a feature on/off can change the number of texture units available for layer/blend + /// textures, and to properly respond to this we may need to change the structure of the material, such as + /// adding or removing passes. This can only be achieved by a full rebuild.) + void applyMaterials(bool shadows, bool splitShadows); + + int getVisiblityFlags() { return mVisibilityFlags; } + + int getMaxBatchSize() { return mMaxBatchSize; } + + void enableSplattingShader(bool enabled); + + private: + bool mDistantLand; + bool mShaders; + bool mShadows; + bool mSplitShadows; + bool mVisible; + + Loading::Listener* mLoadingListener; + + QuadTreeNode* mRootNode; + Ogre::SceneNode* mRootSceneNode; + Storage* mStorage; + + int mVisibilityFlags; + + Ogre::SceneManager* mSceneMgr; + Ogre::SceneManager* mCompositeMapSceneMgr; + + /// Bounds in cell units + Ogre::AxisAlignedBox mBounds; + + /// Minimum size of a terrain batch along one side (in cell units) + float mMinBatchSize; + /// Maximum size of a terrain batch along one side (in cell units) + float mMaxBatchSize; + + void buildQuadTree(QuadTreeNode* node); + + public: + // ----INTERNAL---- + + enum IndexBufferFlags + { + IBF_North = 1 << 0, + IBF_East = 1 << 1, + IBF_South = 1 << 2, + IBF_West = 1 << 3 + }; + + /// @param flags first 4*4 bits are LOD deltas on each edge, respectively (4 bits each) + /// next 4 bits are LOD level of the index buffer (LOD 0 = don't omit any vertices) + /// @param numIndices number of indices that were used will be written here + Ogre::HardwareIndexBufferSharedPtr getIndexBuffer (int flags, size_t& numIndices); + + Ogre::HardwareVertexBufferSharedPtr getVertexBuffer (int numVertsOneSide); + + Ogre::SceneManager* getCompositeMapSceneManager() { return mCompositeMapSceneMgr; } + + // Delete all quads + void clearCompositeMapSceneManager(); + void renderCompositeMap (Ogre::TexturePtr target); + + private: + // Index buffers are shared across terrain batches where possible. There is one index buffer for each + // combination of LOD deltas and index buffer LOD we may need. + std::map mIndexBufferMap; + + std::map mUvBufferMap; + + Ogre::RenderTarget* mCompositeMapRenderTarget; + Ogre::TexturePtr mCompositeMapRenderTexture; + }; + +} + +#endif diff --git a/credits.txt b/credits.txt index dd583a0ec..9a84c5327 100644 --- a/credits.txt +++ b/credits.txt @@ -43,6 +43,7 @@ lazydev Leon Saunders (emoose) Lukasz Gromanowski (lgro) Manuel Edelmann (vorenon) +Marc Bouvier (CramitDeFrog) Marcin Hulist (Gohan) Mark Siewert (mark76) Mateusz KoÅ‚aczek (PL_kolek) diff --git a/extern/oics/CMakeLists.txt b/extern/oics/CMakeLists.txt index 7c14387a4..5c1edbf62 100644 --- a/extern/oics/CMakeLists.txt +++ b/extern/oics/CMakeLists.txt @@ -9,12 +9,23 @@ set(OICS_SOURCE_FILES ICSInputControlSystem_keyboard.cpp ICSInputControlSystem_mouse.cpp ICSInputControlSystem_joystick.cpp +) + +set(TINYXML_SOURCE_FILES tinyxml.cpp tinyxmlparser.cpp tinyxmlerror.cpp - tinystr.cpp + tinystr.cpp ) -add_library(${OICS_LIBRARY} STATIC ${OICS_SOURCE_FILES}) +if(USE_SYSTEM_TINYXML) + add_library(${OICS_LIBRARY} STATIC ${OICS_SOURCE_FILES}) + target_link_libraries(${OICS_LIBRARY} ${TINYXML_LIBRARIES}) +else() + add_library(${OICS_LIBRARY} STATIC + ${OICS_SOURCE_FILES} + ${TINYXML_SOURCE_FILES}) +endif() +# Does this do anything? link_directories(${CMAKE_CURRENT_BINARY_DIR}) diff --git a/extern/sdl4ogre/cursormanager.hpp b/extern/sdl4ogre/cursormanager.hpp index 1f52eca73..f45c5cdc2 100644 --- a/extern/sdl4ogre/cursormanager.hpp +++ b/extern/sdl4ogre/cursormanager.hpp @@ -4,10 +4,8 @@ #include #include -namespace Ogre -{ - class TexturePtr; -} +#include +#include namespace SFO { diff --git a/extern/sdl4ogre/sdlinputwrapper.cpp b/extern/sdl4ogre/sdlinputwrapper.cpp index 5602946ff..931d6aca3 100644 --- a/extern/sdl4ogre/sdlinputwrapper.cpp +++ b/extern/sdl4ogre/sdlinputwrapper.cpp @@ -35,9 +35,20 @@ namespace SFO mSDLWindow = NULL; } - void InputWrapper::capture() + void InputWrapper::capture(bool windowEventsOnly) { + SDL_PumpEvents(); + SDL_Event evt; + + if (windowEventsOnly) + { + // During loading, just handle window events, and keep others for later + while (SDL_PeepEvents(&evt, 1, SDL_GETEVENT, SDL_WINDOWEVENT, SDL_WINDOWEVENT)) + handleWindowEvent(evt); + return; + } + while(SDL_PollEvent(&evt)) { switch(evt.type) diff --git a/extern/sdl4ogre/sdlinputwrapper.hpp b/extern/sdl4ogre/sdlinputwrapper.hpp index 66bf60c7c..1bd8947a0 100644 --- a/extern/sdl4ogre/sdlinputwrapper.hpp +++ b/extern/sdl4ogre/sdlinputwrapper.hpp @@ -24,7 +24,7 @@ namespace SFO void setWindowEventCallback(WindowListener* listen) { mWindowListener = listen; } void setJoyEventCallback(JoyListener* listen) { mJoyListener = listen; } - void capture(); + void capture(bool windowEventsOnly); bool isModifierHeld(SDL_Keymod mod); bool isKeyDown(SDL_Scancode key); diff --git a/files/materials/objects.shader b/files/materials/objects.shader index 0c869d2cb..36f92bfd9 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -80,7 +80,6 @@ #endif #if VERTEX_LIGHTING - shUniform(float, lightCount) @shAutoConstant(lightCount, light_count) shUniform(float4, lightPosition[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightPosition, light_position_view_space_array, @shGlobalSettingString(num_lights)) shUniform(float4, lightDiffuse[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightDiffuse, light_diffuse_colour_array, @shGlobalSettingString(num_lights)) shUniform(float4, lightAttenuation[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightAttenuation, light_attenuation_array, @shGlobalSettingString(num_lights)) diff --git a/files/materials/terrain.shader b/files/materials/terrain.shader index 133022957..861841a84 100644 --- a/files/materials/terrain.shader +++ b/files/materials/terrain.shader @@ -2,7 +2,7 @@ #define IS_FIRST_PASS (@shPropertyString(pass_index) == 0) -#define FOG @shGlobalSettingBool(fog) +#define FOG (@shGlobalSettingBool(fog) && !@shPropertyBool(render_composite_map)) #define SHADOWS_PSSM @shGlobalSettingBool(shadows_pssm) #define SHADOWS @shGlobalSettingBool(shadows) @@ -11,8 +11,6 @@ #include "shadows.h" #endif -#define COLOUR_MAP @shPropertyBool(colour_map) - #define NUM_LAYERS @shPropertyString(num_layers) #if FOG || SHADOWS_PSSM @@ -23,9 +21,12 @@ #define VIEWPROJ_FIX @shGlobalSettingBool(viewproj_fix) -#if !IS_FIRST_PASS -// This is not the first pass. -#endif +#define RENDERCMP @shPropertyBool(render_composite_map) + +#define LIGHTING !RENDERCMP + +#define COMPOSITE_MAP @shPropertyBool(display_composite_map) + #if NEED_DEPTH @shAllocatePassthrough(1, depth) @@ -35,6 +36,10 @@ @shAllocatePassthrough(3, worldPos) +#if LIGHTING +@shAllocatePassthrough(3, lightResult) +@shAllocatePassthrough(3, directionalResult) + #if SHADOWS @shAllocatePassthrough(4, lightSpacePos0) #endif @@ -43,6 +48,7 @@ @shAllocatePassthrough(4, lightSpacePos@shIterator) @shEndForeach #endif +#endif #ifdef SH_VERTEX_SHADER @@ -55,11 +61,19 @@ #if VIEWPROJ_FIX shUniform(float4, vpRow2Fix) @shSharedParameter(vpRow2Fix, vpRow2Fix) #endif - - shUniform(float2, lodMorph) @shAutoConstant(lodMorph, custom, 1001) shVertexInput(float2, uv0) shVertexInput(float2, uv1) // lodDelta, lodThreshold + +#if LIGHTING + shNormalInput(float4) + shColourInput(float4) + + shUniform(float, lightCount) @shAutoConstant(lightCount, light_count) + shUniform(float4, lightPosition[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightPosition, light_position_object_space_array, @shGlobalSettingString(num_lights)) + shUniform(float4, lightDiffuse[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightDiffuse, light_diffuse_colour_array, @shGlobalSettingString(num_lights)) + shUniform(float4, lightAttenuation[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightAttenuation, light_attenuation_array, @shGlobalSettingString(num_lights)) + shUniform(float4, lightAmbient) @shAutoConstant(lightAmbient, ambient_light_colour) #if SHADOWS shUniform(float4x4, texViewProjMatrix0) @shAutoConstant(texViewProjMatrix0, texture_viewproj_matrix) @@ -71,31 +85,15 @@ @shEndForeach #endif +#endif + @shPassthroughVertexOutputs SH_START_PROGRAM { - - float4 worldPos = shMatrixMult(worldMatrix, shInputPosition); - // determine whether to apply the LOD morph to this vertex - // we store the deltas against all vertices so we only want to apply - // the morph to the ones which would disappear. The target LOD which is - // being morphed to is stored in lodMorph.y, and the LOD at which - // the vertex should be morphed is stored in uv.w. If we subtract - // the former from the latter, and arrange to only morph if the - // result is negative (it will only be -1 in fact, since after that - // the vertex will never be indexed), we will achieve our aim. - // sign(vertexLOD - targetLOD) == -1 is to morph - float toMorph = -min(0, sign(uv1.y - lodMorph.y)); - - // morph - // this assumes XY terrain alignment - worldPos.z += uv1.x * toMorph * lodMorph.x; - - shOutputPosition = shMatrixMult(viewProjMatrix, worldPos); #if NEED_DEPTH @@ -124,6 +122,8 @@ @shPassthroughAssign(worldPos, worldPos.xyz); +#if LIGHTING + #if SHADOWS float4 lightSpacePos = shMatrixMult(texViewProjMatrix0, shMatrixMult(worldMatrix, shInputPosition)); @shPassthroughAssign(lightSpacePos0, lightSpacePos); @@ -138,6 +138,34 @@ @shEndForeach #endif + + // Lighting + float3 lightDir; + float d; + float3 lightResult = float3(0,0,0); + float3 directionalResult = float3(0,0,0); + @shForeach(@shGlobalSettingString(num_lights)) + lightDir = lightPosition[@shIterator].xyz - (shInputPosition.xyz * lightPosition[@shIterator].w); + d = length(lightDir); + lightDir = normalize(lightDir); + + + lightResult.xyz += lightDiffuse[@shIterator].xyz + * shSaturate(1.0 / ((lightAttenuation[@shIterator].y) + (lightAttenuation[@shIterator].z * d) + (lightAttenuation[@shIterator].w * d * d))) + * max(dot(normal.xyz, lightDir), 0); + +#if @shIterator == 0 + directionalResult = lightResult.xyz; +#endif + @shEndForeach + lightResult.xyz += lightAmbient.xyz; + lightResult.xyz *= colour.xyz; + directionalResult.xyz *= colour.xyz; + + @shPassthroughAssign(lightResult, lightResult); + @shPassthroughAssign(directionalResult, directionalResult); + +#endif } #else @@ -151,12 +179,9 @@ SH_BEGIN_PROGRAM -#if COLOUR_MAP - shSampler2D(colourMap) -#endif - - shSampler2D(normalMap) // global normal map - +#if COMPOSITE_MAP + shSampler2D(compositeMap) +#else @shForeach(@shPropertyString(num_blendmaps)) shSampler2D(blendMap@shIterator) @@ -165,6 +190,8 @@ @shForeach(@shPropertyString(num_layers)) shSampler2D(diffuseMap@shIterator) @shEndForeach + +#endif #if FOG shUniform(float3, fogColour) @shAutoConstant(fogColour, fog_colour) @@ -173,13 +200,7 @@ @shPassthroughFragmentInputs - shUniform(float4, lightAmbient) @shAutoConstant(lightAmbient, ambient_light_colour) - @shForeach(@shGlobalSettingString(terrain_num_lights)) - shUniform(float4, lightPosObjSpace@shIterator) @shAutoConstant(lightPosObjSpace@shIterator, light_position, @shIterator) - shUniform(float4, lightAttenuation@shIterator) @shAutoConstant(lightAttenuation@shIterator, light_attenuation, @shIterator) - shUniform(float4, lightDiffuse@shIterator) @shAutoConstant(lightDiffuse@shIterator, light_diffuse_colour, @shIterator) - @shEndForeach - +#if LIGHTING #if SHADOWS shSampler2D(shadowMap0) shUniform(float2, invShadowmapSize0) @shAutoConstant(invShadowmapSize0, inverse_texture_size, @shPropertyString(shadowtexture_offset)) @@ -195,6 +216,7 @@ #if SHADOWS || SHADOWS_PSSM shUniform(float4, shadowFar_fadeStart) @shSharedParameter(shadowFar_fadeStart) #endif +#endif #if (UNDERWATER) || (FOG) shUniform(float4x4, worldMatrix) @shAutoConstant(worldMatrix, world_matrix) @@ -215,9 +237,6 @@ float2 UV = @shPassthroughReceive(UV); float3 worldPos = @shPassthroughReceive(worldPos); - - float3 normal = shSample(normalMap, UV).rgb * 2 - 1; - normal = normalize(normal); #if UNDERWATER @@ -230,17 +249,26 @@ float previousAlpha = 1.f; #endif + +shOutputColour(0) = float4(1,1,1,1); + +#if COMPOSITE_MAP + shOutputColour(0).xyz = shSample(compositeMap, UV).xyz; +#else + // Layer calculations -// rescale UV to directly map vertices to texel centers +// rescale UV to directly map edge vertices to texel centers - this is +// important to get correct blending at cell transitions // TODO: parameterize texel size -float2 blendUV = (UV - 0.5) * (8.0 / (8.0+1.0)) + 0.5; +float2 blendUV = (UV - 0.5) * (16.0 / (16.0+1.0)) + 0.5; @shForeach(@shPropertyString(num_blendmaps)) - float4 blendValues@shIterator = shSample(blendMap@shIterator, blendUV); + float4 blendValues@shIterator = shSaturate(shSample(blendMap@shIterator, blendUV)); @shEndForeach + float3 albedo = float3(0,0,0); - float2 layerUV = UV * 8; + float2 layerUV = UV * 16; @shForeach(@shPropertyString(num_layers)) @@ -262,25 +290,14 @@ float2 blendUV = (UV - 0.5) * (8.0 / (8.0+1.0)) + 0.5; #endif @shEndForeach - shOutputColour(0) = float4(1,1,1,1); - + shOutputColour(0).rgb *= albedo; -#if COLOUR_MAP - // Since we're emulating vertex colors here, - // rescale UV to directly map vertices to texel centers. TODO: parameterize texel size - const float colourmapSize = 33.f; - float2 colourUV = (UV - 0.5) * (colourmapSize / (colourmapSize+1.f)) + 0.5; - shOutputColour(0).rgb *= shSample(colourMap, colourUV).rgb; #endif - shOutputColour(0).rgb *= albedo; - - - - - - +#if LIGHTING // Lighting + float3 lightResult = @shPassthroughReceive(lightResult); + float3 directionalResult = @shPassthroughReceive(directionalResult); // shadows only for the first (directional) light #if SHADOWS @@ -305,40 +322,9 @@ float2 blendUV = (UV - 0.5) * (8.0 / (8.0+1.0)) + 0.5; float shadow = 1.0; #endif - - - float3 lightDir; - float3 diffuse = float3(0,0,0); - float d; - - @shForeach(@shGlobalSettingString(terrain_num_lights)) - - lightDir = lightPosObjSpace@shIterator.xyz - (worldPos.xyz * lightPosObjSpace@shIterator.w); - d = length(lightDir); - - - lightDir = normalize(lightDir); - -#if @shIterator == 0 - - #if (SHADOWS || SHADOWS_PSSM) - diffuse += lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0) * shadow; - - #else - diffuse += lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0); - - #endif - -#else - diffuse += lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0); + shOutputColour(0).xyz *= (lightResult - directionalResult * (1.0-shadow)); #endif - @shEndForeach - - shOutputColour(0).xyz *= (lightAmbient.xyz + diffuse); - - - #if FOG float fogValue = shSaturate((depth - fogParams.y) * fogParams.w); @@ -357,7 +343,6 @@ float2 blendUV = (UV - 0.5) * (8.0 / (8.0+1.0)) + 0.5; #else shOutputColour(0).a = 1.f-previousAlpha; #endif - } #endif diff --git a/files/materials/water.shader b/files/materials/water.shader index a6d0a47e6..87e90a291 100644 --- a/files/materials/water.shader +++ b/files/materials/water.shader @@ -3,23 +3,28 @@ #define SIMPLE_WATER @shGlobalSettingBool(simple_water) - #if SIMPLE_WATER // --------------------------------------- SIMPLE WATER --------------------------------------------------- +#define FOG @shGlobalSettingBool(fog) + #ifdef SH_VERTEX_SHADER SH_BEGIN_PROGRAM shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix) shVertexInput(float2, uv0) shOutput(float2, UV) - shOutput(float, depth) +#if FOG + shOutput(float, depth) +#endif SH_START_PROGRAM { shOutputPosition = shMatrixMult(wvp, shInputPosition); UV = uv0; +#if FOG depth = shOutputPosition.z; +#endif } #else @@ -38,8 +43,10 @@ shOutputColour(0).xyz = shSample(animatedTexture, UV * 15).xyz * float3(1.0, 1.0, 1.0); shOutputColour(0).w = 0.7; +#if FOG float fogValue = shSaturate((depth - fogParams.y) * fogParams.w); shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, fogColor, fogValue); +#endif } #endif diff --git a/files/mygui/core_layouteditor.xml b/files/mygui/core_layouteditor.xml index db917b6ef..007b5e638 100644 --- a/files/mygui/core_layouteditor.xml +++ b/files/mygui/core_layouteditor.xml @@ -1,25 +1,25 @@ - - + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + diff --git a/files/mygui/openmw_alchemy_window.layout b/files/mygui/openmw_alchemy_window.layout index 2241813cf..4b15ac1bd 100644 --- a/files/mygui/openmw_alchemy_window.layout +++ b/files/mygui/openmw_alchemy_window.layout @@ -2,7 +2,7 @@ - + diff --git a/files/mygui/openmw_button.skin.xml b/files/mygui/openmw_button.skin.xml index 4f78f7125..e152a9112 100644 --- a/files/mygui/openmw_button.skin.xml +++ b/files/mygui/openmw_button.skin.xml @@ -62,7 +62,7 @@ - + diff --git a/files/mygui/openmw_chargen_birth.layout b/files/mygui/openmw_chargen_birth.layout index 75f741271..b368a6407 100644 --- a/files/mygui/openmw_chargen_birth.layout +++ b/files/mygui/openmw_chargen_birth.layout @@ -1,20 +1,20 @@ - + + - + - - + + - - + - + @@ -25,5 +25,6 @@ + diff --git a/files/mygui/openmw_chargen_class.layout b/files/mygui/openmw_chargen_class.layout index 6d159fe61..3c0348b66 100644 --- a/files/mygui/openmw_chargen_class.layout +++ b/files/mygui/openmw_chargen_class.layout @@ -1,66 +1,71 @@ - + + - + - - + + - + - - - - - - - - - - + + + + + + + - - - - - - - - - - + + + - - - - - - - - - - + + + + + + + + - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -71,5 +76,6 @@ + diff --git a/files/mygui/openmw_chargen_class_description.layout b/files/mygui/openmw_chargen_class_description.layout index 7050f1e3e..eaf754697 100644 --- a/files/mygui/openmw_chargen_class_description.layout +++ b/files/mygui/openmw_chargen_class_description.layout @@ -1,22 +1,22 @@ - + + - - - - - - + + + + - + + diff --git a/files/mygui/openmw_chargen_create_class.layout b/files/mygui/openmw_chargen_create_class.layout index 9677f438f..92382640b 100644 --- a/files/mygui/openmw_chargen_create_class.layout +++ b/files/mygui/openmw_chargen_create_class.layout @@ -1,14 +1,17 @@ - + + - - + + - + + + - + @@ -18,6 +21,7 @@ + @@ -30,35 +34,38 @@ - - + + + - - - - - + + + + + + - - - - - + + + + + + - + @@ -72,5 +79,6 @@ + diff --git a/files/mygui/openmw_chargen_generate_class_result.layout b/files/mygui/openmw_chargen_generate_class_result.layout index 9ee3e0701..f7178042f 100644 --- a/files/mygui/openmw_chargen_generate_class_result.layout +++ b/files/mygui/openmw_chargen_generate_class_result.layout @@ -1,26 +1,26 @@ - + - - + + - + - + - + diff --git a/files/mygui/openmw_chargen_race.layout b/files/mygui/openmw_chargen_race.layout index 0f63ba700..1290795ed 100644 --- a/files/mygui/openmw_chargen_race.layout +++ b/files/mygui/openmw_chargen_race.layout @@ -9,7 +9,7 @@ - + @@ -22,7 +22,7 @@ - + @@ -34,7 +34,7 @@ - + @@ -46,7 +46,7 @@ - + @@ -65,17 +65,17 @@ - + - + - + diff --git a/files/mygui/openmw_chargen_review.layout b/files/mygui/openmw_chargen_review.layout index 84b105bd0..5d18f4bff 100644 --- a/files/mygui/openmw_chargen_review.layout +++ b/files/mygui/openmw_chargen_review.layout @@ -1,10 +1,10 @@ - + - - + + @@ -17,27 +17,27 @@ - - - - + + + + - - - + + + - + - + @@ -46,57 +46,57 @@ - - + + - + - + - + - + - + - + - + @@ -105,13 +105,13 @@ - - - + + + - - + + diff --git a/files/mygui/openmw_chargen_select_attribute.layout b/files/mygui/openmw_chargen_select_attribute.layout index b847bcd3b..f0f72bb0f 100644 --- a/files/mygui/openmw_chargen_select_attribute.layout +++ b/files/mygui/openmw_chargen_select_attribute.layout @@ -10,14 +10,14 @@ - - - - - - - - + + + + + + + + diff --git a/files/mygui/openmw_chargen_select_skill.layout b/files/mygui/openmw_chargen_select_skill.layout index 90ae477e5..dc1798995 100644 --- a/files/mygui/openmw_chargen_select_skill.layout +++ b/files/mygui/openmw_chargen_select_skill.layout @@ -14,45 +14,45 @@ - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + diff --git a/files/mygui/openmw_confirmation_dialog.layout b/files/mygui/openmw_confirmation_dialog.layout index 024b8ab3b..47e1fd2b8 100644 --- a/files/mygui/openmw_confirmation_dialog.layout +++ b/files/mygui/openmw_confirmation_dialog.layout @@ -1,19 +1,19 @@ - + - - - + + + - + - + diff --git a/files/mygui/openmw_console.layout b/files/mygui/openmw_console.layout index a65e8b4f4..bfda40c68 100644 --- a/files/mygui/openmw_console.layout +++ b/files/mygui/openmw_console.layout @@ -2,7 +2,7 @@ - + @@ -12,7 +12,7 @@ - + diff --git a/files/mygui/openmw_console.skin.xml b/files/mygui/openmw_console.skin.xml index 3537bd662..219cce39a 100644 --- a/files/mygui/openmw_console.skin.xml +++ b/files/mygui/openmw_console.skin.xml @@ -1,30 +1,33 @@ - - - - - - - - - + - + + + + + - + + + - - - - + + + + + + + + + diff --git a/files/mygui/openmw_dialogue_window.layout b/files/mygui/openmw_dialogue_window.layout index 9d54f03d0..78daa0705 100644 --- a/files/mygui/openmw_dialogue_window.layout +++ b/files/mygui/openmw_dialogue_window.layout @@ -15,11 +15,11 @@ - + - - + + diff --git a/files/mygui/openmw_dialogue_window_skin.xml b/files/mygui/openmw_dialogue_window_skin.xml index d4b76daa7..4f68a90fa 100644 --- a/files/mygui/openmw_dialogue_window_skin.xml +++ b/files/mygui/openmw_dialogue_window_skin.xml @@ -8,11 +8,11 @@ - - - - - + + + + + diff --git a/files/mygui/openmw_edit.skin.xml b/files/mygui/openmw_edit.skin.xml index 76988a5be..b10854b19 100644 --- a/files/mygui/openmw_edit.skin.xml +++ b/files/mygui/openmw_edit.skin.xml @@ -21,12 +21,10 @@ + + - - - - - + @@ -39,9 +37,9 @@ - + - + diff --git a/files/mygui/openmw_hud_energybar.skin.xml b/files/mygui/openmw_hud_energybar.skin.xml index f64cb6ca0..f10908d7b 100644 --- a/files/mygui/openmw_hud_energybar.skin.xml +++ b/files/mygui/openmw_hud_energybar.skin.xml @@ -43,32 +43,32 @@ - - + + - - + + - - + + - - + + diff --git a/files/mygui/openmw_interactive_messagebox.layout b/files/mygui/openmw_interactive_messagebox.layout index 8d6b6dc34..d3ac1f8b5 100644 --- a/files/mygui/openmw_interactive_messagebox.layout +++ b/files/mygui/openmw_interactive_messagebox.layout @@ -3,16 +3,16 @@ - - - + + + - - + + - + diff --git a/files/mygui/openmw_journal_skin.xml b/files/mygui/openmw_journal_skin.xml index 9fc5e4c7f..ca6d309d7 100644 --- a/files/mygui/openmw_journal_skin.xml +++ b/files/mygui/openmw_journal_skin.xml @@ -2,9 +2,9 @@ - - - + + + diff --git a/files/mygui/openmw_list.skin.xml b/files/mygui/openmw_list.skin.xml index 54143f270..02c11c354 100644 --- a/files/mygui/openmw_list.skin.xml +++ b/files/mygui/openmw_list.skin.xml @@ -5,8 +5,8 @@ - - + + @@ -43,8 +43,8 @@ - - + + @@ -89,17 +89,17 @@ - - + + - + - - + + @@ -114,9 +114,9 @@ - - - + + + @@ -144,9 +144,9 @@ - - - + + + @@ -157,12 +157,12 @@ - - - - + + + + - + @@ -171,7 +171,7 @@ - + diff --git a/files/mygui/openmw_loading_screen.layout b/files/mygui/openmw_loading_screen.layout index 1e4bba5ed..5fd3440f9 100644 --- a/files/mygui/openmw_loading_screen.layout +++ b/files/mygui/openmw_loading_screen.layout @@ -12,8 +12,7 @@ - - + diff --git a/files/mygui/openmw_messagebox.layout b/files/mygui/openmw_messagebox.layout index 3d91a289a..dfdb57648 100644 --- a/files/mygui/openmw_messagebox.layout +++ b/files/mygui/openmw_messagebox.layout @@ -2,17 +2,17 @@ - - - - + + + + - - + + diff --git a/files/mygui/openmw_pointer.xml b/files/mygui/openmw_pointer.xml index cf21037f8..a55a5453c 100644 --- a/files/mygui/openmw_pointer.xml +++ b/files/mygui/openmw_pointer.xml @@ -1,39 +1,39 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/mygui/openmw_progress.skin.xml b/files/mygui/openmw_progress.skin.xml index 635e40fb2..35114ffeb 100644 --- a/files/mygui/openmw_progress.skin.xml +++ b/files/mygui/openmw_progress.skin.xml @@ -17,7 +17,7 @@ - + @@ -25,43 +25,56 @@ - - + + - + - - + + - - + + - - + + - - - + + + + + + + + + + + + + + + + diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index e4fc9f724..ebfaf678a 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -335,12 +335,12 @@ - - + + - + - + diff --git a/files/mygui/openmw_stats_window.layout b/files/mygui/openmw_stats_window.layout index dec962f05..5ae3f96ca 100644 --- a/files/mygui/openmw_stats_window.layout +++ b/files/mygui/openmw_stats_window.layout @@ -219,7 +219,7 @@ - + diff --git a/files/mygui/openmw_text.skin.xml b/files/mygui/openmw_text.skin.xml index 6959c1f5b..6a1dea60b 100644 --- a/files/mygui/openmw_text.skin.xml +++ b/files/mygui/openmw_text.skin.xml @@ -4,46 +4,46 @@ - - - + + + - - - + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - + + + @@ -51,36 +51,36 @@ - - - - + + + + - - - - - + + + + + - - - - - + + + + + - + - - - + + + @@ -93,9 +93,9 @@ - - - + + + @@ -108,9 +108,9 @@ - - - + + + @@ -123,9 +123,9 @@ - - - + + + @@ -138,46 +138,46 @@ - + - - + + - - + + - - + + - - + + - - - + + + - - - + + + - - - + + + diff --git a/files/mygui/openmw_tooltips.layout b/files/mygui/openmw_tooltips.layout index bec6dd87c..624c133f2 100644 --- a/files/mygui/openmw_tooltips.layout +++ b/files/mygui/openmw_tooltips.layout @@ -200,7 +200,7 @@ - + diff --git a/files/mygui/openmw_windows.skin.xml b/files/mygui/openmw_windows.skin.xml index e8861578f..22586716c 100644 --- a/files/mygui/openmw_windows.skin.xml +++ b/files/mygui/openmw_windows.skin.xml @@ -97,26 +97,26 @@ - + - + - + - + - + - + - + - + @@ -167,7 +167,7 @@ - + @@ -185,7 +185,7 @@ - + @@ -196,7 +196,7 @@ - + @@ -207,7 +207,7 @@ - + @@ -218,7 +218,7 @@ - + @@ -230,25 +230,25 @@ - + - + - + - + @@ -256,56 +256,56 @@ - + - + - + - + - + - + - + - + @@ -390,9 +390,9 @@ - - - + + + @@ -415,10 +415,10 @@ ------------------------------------------------------ --> - - - - + + + + @@ -555,10 +555,10 @@ - - - - + + + + @@ -692,10 +692,10 @@ - - - - + + + + @@ -835,9 +835,9 @@ - - - + + + diff --git a/files/opencs/raster/GMST.png b/files/opencs/raster/GMST.png new file mode 100644 index 000000000..f24620288 Binary files /dev/null and b/files/opencs/raster/GMST.png differ diff --git a/files/opencs/raster/Info.png b/files/opencs/raster/Info.png new file mode 100644 index 000000000..d7bdad6cb Binary files /dev/null and b/files/opencs/raster/Info.png differ diff --git a/files/opencs/raster/LandTexture.png b/files/opencs/raster/LandTexture.png new file mode 100644 index 000000000..84f729098 Binary files /dev/null and b/files/opencs/raster/LandTexture.png differ diff --git a/files/opencs/raster/PathGrid.png b/files/opencs/raster/PathGrid.png new file mode 100644 index 000000000..23b6b84d7 Binary files /dev/null and b/files/opencs/raster/PathGrid.png differ diff --git a/files/opencs/raster/activator.png b/files/opencs/raster/activator.png index 0446af22c..32cc6f8a3 100644 Binary files a/files/opencs/raster/activator.png and b/files/opencs/raster/activator.png differ diff --git a/files/opencs/raster/attribute.png b/files/opencs/raster/attribute.png new file mode 100644 index 000000000..4aa5dc02e Binary files /dev/null and b/files/opencs/raster/attribute.png differ diff --git a/files/opencs/raster/birthsign.png b/files/opencs/raster/birthsign.png new file mode 100644 index 000000000..8192d2ebf Binary files /dev/null and b/files/opencs/raster/birthsign.png differ diff --git a/files/opencs/raster/body-part.png b/files/opencs/raster/body-part.png index 333c5d523..823e43712 100644 Binary files a/files/opencs/raster/body-part.png and b/files/opencs/raster/body-part.png differ diff --git a/files/opencs/raster/book.png b/files/opencs/raster/book.png index 3afa9e8aa..9d7669bd7 100644 Binary files a/files/opencs/raster/book.png and b/files/opencs/raster/book.png differ diff --git a/files/opencs/raster/cell.png b/files/opencs/raster/cell.png new file mode 100644 index 000000000..c4f00c1f0 Binary files /dev/null and b/files/opencs/raster/cell.png differ diff --git a/files/opencs/raster/class.png b/files/opencs/raster/class.png new file mode 100644 index 000000000..316380363 Binary files /dev/null and b/files/opencs/raster/class.png differ diff --git a/files/opencs/raster/enchantment.png b/files/opencs/raster/enchantment.png new file mode 100644 index 000000000..c90fb27ce Binary files /dev/null and b/files/opencs/raster/enchantment.png differ diff --git a/files/opencs/raster/faction.png b/files/opencs/raster/faction.png new file mode 100644 index 000000000..8ac1f5200 Binary files /dev/null and b/files/opencs/raster/faction.png differ diff --git a/files/opencs/raster/filter.png b/files/opencs/raster/filter.png new file mode 100644 index 000000000..94a57ecd9 Binary files /dev/null and b/files/opencs/raster/filter.png differ diff --git a/files/opencs/raster/globvar.png b/files/opencs/raster/globvar.png new file mode 100644 index 000000000..646145f0f Binary files /dev/null and b/files/opencs/raster/globvar.png differ diff --git a/files/opencs/raster/ingredient.png b/files/opencs/raster/ingredient.png index 6b36d008d..564a93047 100644 Binary files a/files/opencs/raster/ingredient.png and b/files/opencs/raster/ingredient.png differ diff --git a/files/opencs/raster/land.png b/files/opencs/raster/land.png new file mode 100644 index 000000000..20dd321dd Binary files /dev/null and b/files/opencs/raster/land.png differ diff --git a/files/opencs/raster/landpaint.png b/files/opencs/raster/landpaint.png new file mode 100644 index 000000000..711c0d8f5 Binary files /dev/null and b/files/opencs/raster/landpaint.png differ diff --git a/files/opencs/raster/light.png b/files/opencs/raster/light.png index c606fcd98..2765ef1d3 100644 Binary files a/files/opencs/raster/light.png and b/files/opencs/raster/light.png differ diff --git a/files/opencs/raster/magic-effect.png b/files/opencs/raster/magic-effect.png new file mode 100644 index 000000000..e672ffccb Binary files /dev/null and b/files/opencs/raster/magic-effect.png differ diff --git a/files/opencs/raster/magicrabbit.png b/files/opencs/raster/magicrabbit.png new file mode 100644 index 000000000..d1d7c8270 Binary files /dev/null and b/files/opencs/raster/magicrabbit.png differ diff --git a/files/opencs/raster/map.png b/files/opencs/raster/map.png new file mode 100644 index 000000000..3653797cc Binary files /dev/null and b/files/opencs/raster/map.png differ diff --git a/files/opencs/raster/race.png b/files/opencs/raster/race.png new file mode 100644 index 000000000..94a2de696 Binary files /dev/null and b/files/opencs/raster/race.png differ diff --git a/files/opencs/raster/random.png b/files/opencs/raster/random.png new file mode 100644 index 000000000..2667630f5 Binary files /dev/null and b/files/opencs/raster/random.png differ diff --git a/files/opencs/raster/script.png b/files/opencs/raster/script.png new file mode 100644 index 000000000..297da4021 Binary files /dev/null and b/files/opencs/raster/script.png differ diff --git a/files/opencs/raster/skill.png b/files/opencs/raster/skill.png new file mode 100644 index 000000000..418f4f35c Binary files /dev/null and b/files/opencs/raster/skill.png differ diff --git a/files/opencs/raster/soundgen.png b/files/opencs/raster/soundgen.png index 70ae43a1d..222fc4c7f 100644 Binary files a/files/opencs/raster/soundgen.png and b/files/opencs/raster/soundgen.png differ diff --git a/files/opencs/raster/spell.png b/files/opencs/raster/spell.png new file mode 100644 index 000000000..69c897180 Binary files /dev/null and b/files/opencs/raster/spell.png differ diff --git a/files/opencs/raster/startup/big/configure.png b/files/opencs/raster/startup/big/configure.png new file mode 100644 index 000000000..f0be888a1 Binary files /dev/null and b/files/opencs/raster/startup/big/configure.png differ diff --git a/files/opencs/raster/startup/big/create-addon.png b/files/opencs/raster/startup/big/create-addon.png new file mode 100644 index 000000000..fa059264e Binary files /dev/null and b/files/opencs/raster/startup/big/create-addon.png differ diff --git a/files/opencs/raster/startup/big/edit-content.png b/files/opencs/raster/startup/big/edit-content.png new file mode 100644 index 000000000..dbb2602e5 Binary files /dev/null and b/files/opencs/raster/startup/big/edit-content.png differ diff --git a/files/opencs/raster/startup/big/new-game.png b/files/opencs/raster/startup/big/new-game.png new file mode 100644 index 000000000..5cec44417 Binary files /dev/null and b/files/opencs/raster/startup/big/new-game.png differ diff --git a/files/opencs/raster/startup/small/configure.png b/files/opencs/raster/startup/small/configure.png new file mode 100644 index 000000000..e91b7f773 Binary files /dev/null and b/files/opencs/raster/startup/small/configure.png differ diff --git a/files/opencs/raster/startup/small/create-addon.png b/files/opencs/raster/startup/small/create-addon.png new file mode 100644 index 000000000..64fd138be Binary files /dev/null and b/files/opencs/raster/startup/small/create-addon.png differ diff --git a/files/opencs/raster/startup/small/edit-content.png b/files/opencs/raster/startup/small/edit-content.png new file mode 100644 index 000000000..6297f1169 Binary files /dev/null and b/files/opencs/raster/startup/small/edit-content.png differ diff --git a/files/opencs/raster/startup/small/new-game.png b/files/opencs/raster/startup/small/new-game.png new file mode 100644 index 000000000..0d7d14c55 Binary files /dev/null and b/files/opencs/raster/startup/small/new-game.png differ diff --git a/files/opencs/raster/static.png b/files/opencs/raster/static.png index b53be12d9..aedf2d30e 100644 Binary files a/files/opencs/raster/static.png and b/files/opencs/raster/static.png differ diff --git a/files/opencs/resources.qrc b/files/opencs/resources.qrc index 321413763..56e25b2c1 100644 --- a/files/opencs/resources.qrc +++ b/files/opencs/resources.qrc @@ -25,5 +25,9 @@ repair.png static.png weapon.png + raster/startup/big/create-addon.png + raster/startup/big/new-game.png + raster/startup/big/edit-content.png + raster/startup/small/configure.png diff --git a/files/opencs/scalable/referenceable-record/.directory b/files/opencs/scalable/referenceable-record/.directory index e2d80ed58..98e1b7f92 100644 --- a/files/opencs/scalable/referenceable-record/.directory +++ b/files/opencs/scalable/referenceable-record/.directory @@ -1,5 +1,7 @@ [Dolphin] -PreviewsShown=true -Timestamp=2013,3,21,10,19,49 +GroupedSorting=true +SortFoldersFirst=false +Timestamp=2013,8,25,18,35,16 Version=3 -ViewMode=1 +ViewMode=2 +VisibleRoles=Compact_text,Compact_size diff --git a/files/opencs/scalable/referenceable-record/activator.svg b/files/opencs/scalable/referenceable-record/activator.svg index 0c6db59a7..e917d9332 100644 --- a/files/opencs/scalable/referenceable-record/activator.svg +++ b/files/opencs/scalable/referenceable-record/activator.svg @@ -23,18 +23,18 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="5.6568542" - inkscape:cx="5.7818736" - inkscape:cy="23.778975" + inkscape:zoom="2" + inkscape:cx="-35.165989" + inkscape:cy="-65.142411" inkscape:document-units="px" - inkscape:current-layer="g3891" + inkscape:current-layer="g3910" showgrid="false" showguides="true" inkscape:guide-bbox="true" inkscape:window-width="1280" - inkscape:window-height="994" + inkscape:window-height="1001" inkscape:window-x="0" - inkscape:window-y="30" + inkscape:window-y="23" inkscape:window-maximized="1" inkscape:snap-page="true" inkscape:snap-bbox="true" @@ -45,7 +45,7 @@ inkscape:object-paths="true" inkscape:snap-intersection-paths="false" inkscape:object-nodes="true" - inkscape:snap-global="true" + inkscape:snap-global="false" inkscape:snap-smooth-nodes="true" inkscape:snap-grids="false" inkscape:snap-nodes="true" /> @@ -867,7 +867,7 @@ xlink:href="#linearGradient3942" id="linearGradient3955" gradientUnits="userSpaceOnUse" - gradientTransform="translate(-183.1468,-158.28118)" + gradientTransform="translate(-445.4059,-219.9852)" x1="74.006111" y1="153.75172" x2="344.5" @@ -877,7 +877,7 @@ xlink:href="#linearGradient3880" id="radialGradient3957" gradientUnits="userSpaceOnUse" - gradientTransform="matrix(1,0,0,1.0000234,0,-0.00345228)" + gradientTransform="matrix(0.90336573,0,0,0.90338687,22.936413,14.223841)" cx="237.35278" cy="147.22479" fx="237.35278" @@ -888,7 +888,7 @@ xlink:href="#linearGradient3846" id="radialGradient3959" gradientUnits="userSpaceOnUse" - gradientTransform="matrix(0.43641683,0,0,0.43645606,205.85223,33.676924)" + gradientTransform="matrix(0.40004875,0,0,0.40008471,208.47728,43.13925)" cx="72.191498" cy="260.15875" fx="72.191498" @@ -1003,84 +1003,18 @@ - - - - - - - - - - - - - - - - + transform="matrix(10.683152,0,0,10.683152,-3645.2454,641.29516)"> - - - diff --git a/files/opencs/scalable/referenceable-record/book2.svgz b/files/opencs/scalable/referenceable-record/book2.svgz new file mode 100644 index 000000000..4535a2fce Binary files /dev/null and b/files/opencs/scalable/referenceable-record/book2.svgz differ diff --git a/files/opencs/scalable/referenceable-record/miscellaneous.svg b/files/opencs/scalable/referenceable-record/miscellaneous.svg deleted file mode 100644 index 96522048c..000000000 --- a/files/opencs/scalable/referenceable-record/miscellaneous.svg +++ /dev/null @@ -1,965 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/files/opencs/scalable/startup/configure.svgz b/files/opencs/scalable/startup/configure.svgz new file mode 100644 index 000000000..1275ec53f Binary files /dev/null and b/files/opencs/scalable/startup/configure.svgz differ diff --git a/files/opencs/scalable/startup/create-addon.svgz b/files/opencs/scalable/startup/create-addon.svgz new file mode 100644 index 000000000..75425667b Binary files /dev/null and b/files/opencs/scalable/startup/create-addon.svgz differ diff --git a/files/opencs/scalable/startup/edit-content.svgz b/files/opencs/scalable/startup/edit-content.svgz new file mode 100644 index 000000000..049f1e813 Binary files /dev/null and b/files/opencs/scalable/startup/edit-content.svgz differ diff --git a/files/opencs/scalable/startup/new-game.svgz b/files/opencs/scalable/startup/new-game.svgz new file mode 100644 index 000000000..e81571911 Binary files /dev/null and b/files/opencs/scalable/startup/new-game.svgz differ diff --git a/files/openmw.cfg.local b/files/openmw.cfg.local index dd116e108..d6ca2d554 100644 --- a/files/openmw.cfg.local +++ b/files/openmw.cfg.local @@ -1,2 +1,5 @@ +data="?global?data" +data="?mw?Data Files" data=./data +data-local="?user?data" resources=./resources diff --git a/files/settings-default.cfg b/files/settings-default.cfg index ac56604d1..f191430df 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -80,6 +80,7 @@ texture size = 1024 actor shadows = true misc shadows = true statics shadows = true +terrain shadows = true # Fraction of the total shadow distance after which the shadow starts to fade out fade start = 0.8 @@ -125,8 +126,9 @@ fog start factor = 0.5 fog end factor = 1.0 [Terrain] -# Max. number of lights that affect the terrain. Setting to 1 will only reflect sunlight -num lights = 8 +distant land = false + +shader = true [Water] shader = true diff --git a/libs/openengine/bullet/BulletShapeLoader.cpp b/libs/openengine/bullet/BulletShapeLoader.cpp index 431d2b91b..5528924a9 100644 --- a/libs/openengine/bullet/BulletShapeLoader.cpp +++ b/libs/openengine/bullet/BulletShapeLoader.cpp @@ -104,6 +104,20 @@ BulletShapeManager::~BulletShapeManager() sThis = 0; } +#if (OGRE_VERSION >= ((1 << 16) | (9 << 8) | 0)) +BulletShapePtr BulletShapeManager::getByName(const Ogre::String& name, const Ogre::String& groupName) +{ + return getResourceByName(name, groupName).staticCast(); +} + +BulletShapePtr BulletShapeManager::create (const Ogre::String& name, const Ogre::String& group, + bool isManual, Ogre::ManualResourceLoader* loader, + const Ogre::NameValuePairList* createParams) +{ + return createResource(name,group,isManual,loader,createParams).staticCast(); +} +#endif + BulletShapePtr BulletShapeManager::load(const Ogre::String &name, const Ogre::String &group) { BulletShapePtr textf = getByName(name); diff --git a/libs/openengine/bullet/BulletShapeLoader.h b/libs/openengine/bullet/BulletShapeLoader.h index a6591010a..98cda859d 100644 --- a/libs/openengine/bullet/BulletShapeLoader.h +++ b/libs/openengine/bullet/BulletShapeLoader.h @@ -48,6 +48,8 @@ public: /** * */ + +#if (OGRE_VERSION < ((1 << 16) | (9 << 8) | 0)) class BulletShapePtr : public Ogre::SharedPtr { public: @@ -91,9 +93,9 @@ public: return *this; } }; - - - +#else +typedef Ogre::SharedPtr BulletShapePtr; +#endif /** *Hold any BulletShape that was created by the ManualBulletShapeLoader. @@ -137,6 +139,19 @@ public: BulletShapeManager(); virtual ~BulletShapeManager(); + +#if (OGRE_VERSION >= ((1 << 16) | (9 << 8) | 0)) + /// Get a resource by name + /// @see ResourceManager::getByName + BulletShapePtr getByName(const Ogre::String& name, const Ogre::String& groupName = Ogre::ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME); + + /// Create a new shape + /// @see ResourceManager::createResource + BulletShapePtr create (const Ogre::String& name, const Ogre::String& group, + bool isManual = false, Ogre::ManualResourceLoader* loader = 0, + const Ogre::NameValuePairList* createParams = 0); +#endif + virtual BulletShapePtr load(const Ogre::String &name, const Ogre::String &group); static BulletShapeManager &getSingleton(); diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index ba47b6e64..e33edda18 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -19,7 +19,8 @@ namespace Physic PhysicActor::PhysicActor(const std::string &name, const std::string &mesh, PhysicEngine *engine, const Ogre::Vector3 &position, const Ogre::Quaternion &rotation, float scale) : mName(name), mEngine(engine), mMesh(mesh), mBoxScaledTranslation(0,0,0), mBoxRotationInverse(0,0,0,0) - , mBody(0), mRaycastingBody(0), onGround(false), collisionMode(true), mBoxRotation(0,0,0,0), verticalForce(0.0f) + , mBody(0), mRaycastingBody(0), mOnGround(false), mCollisionMode(true), mBoxRotation(0,0,0,0) + , mForce(0.0f) { mBody = mEngine->createAndAdjustRigidBody(mMesh, mName, scale, position, rotation, &mBoxScaledTranslation, &mBoxRotation); mRaycastingBody = mEngine->createAndAdjustRigidBody(mMesh, mName, scale, position, rotation, &mBoxScaledTranslation, &mBoxRotation, true); @@ -45,9 +46,9 @@ namespace Physic void PhysicActor::enableCollisions(bool collision) { assert(mBody); - if(collision && !collisionMode) enableCollisionBody(); - if(!collision && collisionMode) disableCollisionBody(); - collisionMode = collision; + if(collision && !mCollisionMode) enableCollisionBody(); + if(!collision && mCollisionMode) disableCollisionBody(); + mCollisionMode = collision; } @@ -123,24 +124,14 @@ namespace Physic return Ogre::Vector3(0.0f); } - void PhysicActor::setVerticalForce(float force) + void PhysicActor::setInertialForce(const Ogre::Vector3 &force) { - verticalForce = force; - } - - float PhysicActor::getVerticalForce() const - { - return verticalForce; + mForce = force; } void PhysicActor::setOnGround(bool grounded) { - onGround = grounded; - } - - bool PhysicActor::getOnGround() const - { - return collisionMode && onGround; + mOnGround = grounded; } void PhysicActor::disableCollisionBody() @@ -552,61 +543,63 @@ namespace Physic #endif }; - struct AabbResultCallback : public btBroadphaseAabbCallback { - std::vector hits; - //AabbResultCallback(){} - virtual bool process(const btBroadphaseProxy* proxy) { - RigidBody* collisionObject = static_cast(proxy->m_clientObject); - if(proxy->m_collisionFilterGroup == CollisionType_Actor && (collisionObject->mName != "player")) - this->hits.push_back(collisionObject); - return true; - } - }; - - - std::pair PhysicEngine::sphereTest(float radius,btVector3& pos) + class DeepestNotMeContactTestResultCallback : public btCollisionWorld::ContactResultCallback { - AabbResultCallback callback; - /*btDefaultMotionState* newMotionState = - new btDefaultMotionState(btTransform(btQuaternion(0,0,0,1),pos)); - btCollisionShape * shape = new btSphereShape(radius); - btRigidBody::btRigidBodyConstructionInfo CI = btRigidBody::btRigidBodyConstructionInfo - (0,newMotionState, shape); - RigidBody* body = new RigidBody(CI,"hitDetectionShpere__"); - btTransform tr = body->getWorldTransform(); - tr.setOrigin(pos); - body->setWorldTransform(tr); - dynamicsWorld->addRigidBody(body,CollisionType_Actor,CollisionType_World|CollisionType_World); - body->setWorldTransform(tr);*/ + const std::string &mFilter; + // Store the real origin, since the shape's origin is its center + btVector3 mOrigin; + + public: + const RigidBody *mObject; + btVector3 mContactPoint; + btScalar mLeastDistSqr; - btVector3 aabbMin = pos - radius*btVector3(1.0f, 1.0f, 1.0f); - btVector3 aabbMax = pos + radius*btVector3(1.0f, 1.0f, 1.0f); + DeepestNotMeContactTestResultCallback(const std::string &filter, const btVector3 &origin) + : mFilter(filter), mOrigin(origin), mObject(0), mContactPoint(0,0,0), + mLeastDistSqr(std::numeric_limits::max()) + { } - broadphase->aabbTest(aabbMin,aabbMax,callback); - for(int i=0;i (callback.hits.size()); ++i) +#if defined(BT_COLLISION_OBJECT_WRAPPER_H) + virtual btScalar addSingleResult(btManifoldPoint& cp, + const btCollisionObjectWrapper* col0Wrap,int partId0,int index0, + const btCollisionObjectWrapper* col1Wrap,int partId1,int index1) { - float d = (callback.hits[i]->getWorldTransform().getOrigin()-pos).length(); - if(d(col1Wrap->m_collisionObject); + if(body && body->mName != mFilter) { - std::pair rayResult = this->rayTest(pos,callback.hits[i]->getWorldTransform().getOrigin()); - if(rayResult.second>d || rayResult.first == callback.hits[i]->mName) - return std::make_pair(callback.hits[i]->mName,callback.hits[i]->getWorldTransform().getOrigin()); + btScalar distsqr = mOrigin.distance2(cp.getPositionWorldOnA()); + if(!mObject || distsqr < mLeastDistSqr) + { + mObject = body; + mLeastDistSqr = distsqr; + mContactPoint = cp.getPositionWorldOnA(); + } } + + return 0.f; } - //ContactTestResultCallback callback; - //dynamicsWorld->contactTest(body, callback); - //dynamicsWorld->removeRigidBody(body); - //delete body; - //delete shape; - //if(callback.mResultName.empty()) return std::make_pair(std::string(""),btVector3(0,0,0)); - /*for(int i=0;i(callback.mResultName[i],callback.mResultContact[i]); - */ - return std::make_pair(std::string(""),btVector3(0,0,0)); - } + const RigidBody* body = dynamic_cast(col1); + if(body && body->mName != mFilter) + { + btScalar distsqr = mOrigin.distance2(cp.getPositionWorldOnA()); + if(!mObject || distsqr < mLeastDistSqr) + { + mObject = body; + mLeastDistSqr = distsqr; + mContactPoint = cp.getPositionWorldOnA(); + } + } + + return 0.f; + } +#endif + }; + std::vector PhysicEngine::getCollisions(const std::string& name) { @@ -616,6 +609,17 @@ namespace Physic return callback.mResult; } + + std::pair PhysicEngine::getFilteredContact(const std::string &filter, + const btVector3 &origin, + btCollisionObject *object) + { + DeepestNotMeContactTestResultCallback callback(filter, origin); + dynamicsWorld->contactTest(object, callback); + return std::make_pair(callback.mObject, callback.mContactPoint); + } + + void PhysicEngine::stepSimulation(double deltaT) { // This seems to be needed for character controller objects diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp index 32af0da4e..f28f95ccb 100644 --- a/libs/openengine/bullet/physic.hpp +++ b/libs/openengine/bullet/physic.hpp @@ -66,6 +66,20 @@ namespace Physic std::string mName; }; + /** + *This class is just an extension of normal btRigidBody in order to add extra info. + *When bullet give back a btRigidBody, you can just do a static_cast to RigidBody, + *so one never should use btRigidBody directly! + */ + class RigidBody: public btRigidBody + { + public: + RigidBody(btRigidBody::btRigidBodyConstructionInfo& CI,std::string name); + virtual ~RigidBody(); + std::string mName; + bool mPlaceable; + }; + /** * A physic actor uses a rigid body based on box shapes. * Pmove is used to move the physic actor around the dynamic world. @@ -90,7 +104,7 @@ namespace Physic bool getCollisionMode() const { - return collisionMode; + return mCollisionMode; } @@ -116,60 +130,60 @@ namespace Physic Ogre::Vector3 getHalfExtents() const; /** - * Sets the current amount of vertical force (gravity) affecting this physic actor + * Sets the current amount of inertial force (incl. gravity) affecting this physic actor */ - void setVerticalForce(float force); + void setInertialForce(const Ogre::Vector3 &force); /** - * Gets the current amount of vertical force (gravity) affecting this physic actor + * Gets the current amount of inertial force (incl. gravity) affecting this physic actor */ - float getVerticalForce() const; + const Ogre::Vector3 &getInertialForce() const + { + return mForce; + } void setOnGround(bool grounded); - bool getOnGround() const; + bool getOnGround() const + { + return mCollisionMode && mOnGround; + } + + btCollisionObject *getCollisionBody() const + { + return mBody; + } private: void disableCollisionBody(); void enableCollisionBody(); public: //HACK: in Visual Studio 2010 and presumably above, this structures alignment -// must be 16, but the built in operator new & delete don't properly -// perform this alignment. +// must be 16, but the built in operator new & delete don't properly +// perform this alignment. #if _MSC_VER >= 1600 - void * operator new (size_t Size) { return _aligned_malloc (Size, 16); } - void operator delete (void * Data) { _aligned_free (Data); } + void * operator new (size_t Size) { return _aligned_malloc (Size, 16); } + void operator delete (void * Data) { _aligned_free (Data); } #endif private: - OEngine::Physic::RigidBody* mBody; OEngine::Physic::RigidBody* mRaycastingBody; + Ogre::Vector3 mBoxScaledTranslation; - btQuaternion mBoxRotationInverse; Ogre::Quaternion mBoxRotation; - float verticalForce; - bool onGround; - bool collisionMode; + btQuaternion mBoxRotationInverse; + + Ogre::Vector3 mForce; + bool mOnGround; + bool mCollisionMode; + std::string mMesh; - PhysicEngine* mEngine; std::string mName; + PhysicEngine *mEngine; }; - /** - *This class is just an extension of normal btRigidBody in order to add extra info. - *When bullet give back a btRigidBody, you can just do a static_cast to RigidBody, - *so one never should use btRigidBody directly! - */ - class RigidBody: public btRigidBody - { - public: - RigidBody(btRigidBody::btRigidBodyConstructionInfo& CI,std::string name); - virtual ~RigidBody(); - std::string mName; - bool mPlaceable; - }; struct HeightField { @@ -307,10 +321,14 @@ public: std::pair sphereCast (float radius, btVector3& from, btVector3& to); ///< @return (hit, relative distance) - std::pair sphereTest(float radius,btVector3& pos); - std::vector getCollisions(const std::string& name); + // Get the nearest object that's inside the given object, filtering out objects of the + // provided name + std::pair getFilteredContact(const std::string &filter, + const btVector3 &origin, + btCollisionObject *object); + //event list of non player object std::list NPEventList; diff --git a/libs/openengine/bullet/trace.cpp b/libs/openengine/bullet/trace.cpp index d246417c7..afda52448 100644 --- a/libs/openengine/bullet/trace.cpp +++ b/libs/openengine/bullet/trace.cpp @@ -8,41 +8,123 @@ #include "physic.hpp" -enum traceWorldType + +namespace OEngine +{ +namespace Physic { - collisionWorldTrace = 1, - pickWorldTrace = 2, - bothWorldTrace = collisionWorldTrace | pickWorldTrace + +class ClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback +{ +public: + ClosestNotMeConvexResultCallback(btCollisionObject *me, const btVector3 &up, btScalar minSlopeDot) + : btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)), + mMe(me), mUp(up), mMinSlopeDot(minSlopeDot) + { + } + + virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace) + { + if(convexResult.m_hitCollisionObject == mMe) + return btScalar( 1 ); + + btVector3 hitNormalWorld; + if(normalInWorldSpace) + hitNormalWorld = convexResult.m_hitNormalLocal; + else + { + ///need to transform normal into worldspace + hitNormalWorld = convexResult.m_hitCollisionObject->getWorldTransform().getBasis()*convexResult.m_hitNormalLocal; + } + + // NOTE : m_hitNormalLocal is not always vertical on the ground with a capsule or a box... + + btScalar dotUp = mUp.dot(hitNormalWorld); + if(dotUp < mMinSlopeDot) + return btScalar(1); + + return ClosestConvexResultCallback::addSingleResult(convexResult, normalInWorldSpace); + } + +protected: + btCollisionObject *mMe; + const btVector3 mUp; + const btScalar mMinSlopeDot; }; -void newtrace(traceResults *results, const Ogre::Quaternion& orient, const Ogre::Vector3& start, const Ogre::Vector3& end, const Ogre::Vector3& BBHalfExtents, bool isInterior, OEngine::Physic::PhysicEngine *enginePass) //Traceobj was a Aedra Object + +void ActorTracer::doTrace(btCollisionObject *actor, const Ogre::Vector3 &start, const Ogre::Vector3 &end, const PhysicEngine *enginePass) { - const btVector3 btstart(start.x, start.y, start.z + BBHalfExtents.z); - const btVector3 btend(end.x, end.y, end.z + BBHalfExtents.z); - const btQuaternion btorient (orient.x, orient.y, orient.z, orient.w); + const btVector3 btstart(start.x, start.y, start.z); + const btVector3 btend(end.x, end.y, end.z); - const btBoxShape newshape(btVector3(BBHalfExtents.x, BBHalfExtents.y, BBHalfExtents.z)); - //const btCapsuleShapeZ newshape(BBHalfExtents.x, BBHalfExtents.z * 2 - BBHalfExtents.x * 2); - const btTransform from(btorient, btstart); - const btTransform to(btorient, btend); + const btTransform &trans = actor->getWorldTransform(); + btTransform from(trans); + btTransform to(trans); + from.setOrigin(btstart); + to.setOrigin(btend); - btCollisionWorld::ClosestConvexResultCallback newTraceCallback(btstart, btend); - newTraceCallback.m_collisionFilterMask = OEngine::Physic::CollisionType_World|OEngine::Physic::CollisionType_HeightMap|OEngine::Physic::CollisionType_Actor; + ClosestNotMeConvexResultCallback newTraceCallback(actor, btstart-btend, btScalar(0.0)); + newTraceCallback.m_collisionFilterMask = CollisionType_World | CollisionType_HeightMap | + CollisionType_Actor; - enginePass->dynamicsWorld->convexSweepTest(&newshape, from, to, newTraceCallback); + btCollisionShape *shape = actor->getCollisionShape(); + assert(shape->isConvex()); + enginePass->dynamicsWorld->convexSweepTest(static_cast(shape), + from, to, newTraceCallback); // Copy the hit data over to our trace results struct: if(newTraceCallback.hasHit()) { const btVector3& tracehitnormal = newTraceCallback.m_hitNormalWorld; - results->fraction = newTraceCallback.m_closestHitFraction; - results->planenormal = Ogre::Vector3(tracehitnormal.x(), tracehitnormal.y(), tracehitnormal.z()); - results->endpos = (end-start)*results->fraction + start; + mFraction = newTraceCallback.m_closestHitFraction; + mPlaneNormal = Ogre::Vector3(tracehitnormal.x(), tracehitnormal.y(), tracehitnormal.z()); + mEndPos = (end-start)*mFraction + start; } else { - results->endpos = end; - results->planenormal = Ogre::Vector3(0.0f, 0.0f, 1.0f); - results->fraction = 1.0f; + mEndPos = end; + mPlaneNormal = Ogre::Vector3(0.0f, 0.0f, 1.0f); + mFraction = 1.0f; } } + +void ActorTracer::findGround(btCollisionObject *actor, const Ogre::Vector3 &start, const Ogre::Vector3 &end, const PhysicEngine *enginePass) +{ + const btVector3 btstart(start.x, start.y, start.z+1.0f); + const btVector3 btend(end.x, end.y, end.z+1.0f); + + const btTransform &trans = actor->getWorldTransform(); + btTransform from(trans.getBasis(), btstart); + btTransform to(trans.getBasis(), btend); + + ClosestNotMeConvexResultCallback newTraceCallback(actor, btstart-btend, btScalar(0.0)); + newTraceCallback.m_collisionFilterMask = CollisionType_World | CollisionType_HeightMap | + CollisionType_Actor; + + const btBoxShape *shape = dynamic_cast(actor->getCollisionShape()); + assert(shape); + + btVector3 halfExtents = shape->getHalfExtentsWithMargin(); + halfExtents[2] = 1.0f; + btBoxShape box(halfExtents); + + enginePass->dynamicsWorld->convexSweepTest(&box, from, to, newTraceCallback); + if(newTraceCallback.hasHit()) + { + const btVector3& tracehitnormal = newTraceCallback.m_hitNormalWorld; + mFraction = newTraceCallback.m_closestHitFraction; + mPlaneNormal = Ogre::Vector3(tracehitnormal.x(), tracehitnormal.y(), tracehitnormal.z()); + mEndPos = (end-start)*mFraction + start; + mEndPos[2] -= 1.0f; + } + else + { + mEndPos = end; + mPlaneNormal = Ogre::Vector3(0.0f, 0.0f, 1.0f); + mFraction = 1.0f; + } +} + +} +} diff --git a/libs/openengine/bullet/trace.h b/libs/openengine/bullet/trace.h index cd2547f8c..92795c87f 100644 --- a/libs/openengine/bullet/trace.h +++ b/libs/openengine/bullet/trace.h @@ -4,23 +4,28 @@ #include -namespace OEngine -{ - namespace Physic - { - class PhysicEngine; - } -} +class btCollisionObject; -struct traceResults +namespace OEngine { - Ogre::Vector3 endpos; - Ogre::Vector3 planenormal; +namespace Physic +{ + class PhysicEngine; + + struct ActorTracer + { + Ogre::Vector3 mEndPos; + Ogre::Vector3 mPlaneNormal; - float fraction; -}; + float mFraction; -void newtrace(traceResults *results, const Ogre::Quaternion& orient, const Ogre::Vector3& start, const Ogre::Vector3& end, const Ogre::Vector3& BBHalfExtents, bool isInterior, OEngine::Physic::PhysicEngine* enginePass); + void doTrace(btCollisionObject *actor, const Ogre::Vector3 &start, const Ogre::Vector3 &end, + const PhysicEngine *enginePass); + void findGround(btCollisionObject *actor, const Ogre::Vector3 &start, const Ogre::Vector3 &end, + const PhysicEngine *enginePass); + }; +} +} #endif diff --git a/libs/openengine/ogre/imagerotate.cpp b/libs/openengine/ogre/imagerotate.cpp index 3dd584078..9c32924f1 100644 --- a/libs/openengine/ogre/imagerotate.cpp +++ b/libs/openengine/ogre/imagerotate.cpp @@ -56,7 +56,7 @@ void ImageRotate::rotate(const std::string& sourceImage, const std::string& dest TEX_TYPE_2D, width, height, 0, - PF_FLOAT16_RGBA, + PF_A8B8G8R8, TU_RENDERTARGET); RenderTarget* rtt = destTextureRot->getBuffer()->getRenderTarget(); @@ -75,7 +75,7 @@ void ImageRotate::rotate(const std::string& sourceImage, const std::string& dest TEX_TYPE_2D, width, height, 0, - PF_FLOAT16_RGBA, + PF_A8B8G8R8, Ogre::TU_STATIC); destTexture->getBuffer()->blit(destTextureRot->getBuffer()); diff --git a/libs/openengine/ogre/lights.cpp b/libs/openengine/ogre/lights.cpp index 52aca6a70..348057b84 100644 --- a/libs/openengine/ogre/lights.cpp +++ b/libs/openengine/ogre/lights.cpp @@ -1,20 +1,11 @@ #include "lights.hpp" #include -#include + namespace OEngine { namespace Render { - -LightFunction::LightFunction(LightType type) - : ControllerFunction(true) - , mType(type) - , mPhase(Ogre::Math::RangeRandom(-500.0f, +500.0f)) - , mDirection(1.0f) -{ -} - Ogre::Real LightFunction::pulseAmplitude(Ogre::Real time) { return std::sin(time); @@ -97,13 +88,6 @@ Ogre::Real LightFunction::calculate(Ogre::Real value) return brightness; } - -LightValue::LightValue(Ogre::Light *light, const Ogre::ColourValue &color) - : mTarget(light) - , mColor(color) -{ -} - Ogre::Real LightValue::getValue() const { return 0.0f; diff --git a/libs/openengine/ogre/lights.hpp b/libs/openengine/ogre/lights.hpp index c63f16425..61d09a0e6 100644 --- a/libs/openengine/ogre/lights.hpp +++ b/libs/openengine/ogre/lights.hpp @@ -3,6 +3,7 @@ #include #include +#include /* * Controller classes to handle pulsing and flicker lights @@ -30,7 +31,14 @@ namespace Render { static Ogre::Real flickerFrequency(Ogre::Real phase); public: - LightFunction(LightType type); + // MSVC needs the constructor for a class inheriting a template to be defined in header + LightFunction(LightType type) + : ControllerFunction(true) + , mType(type) + , mPhase(Ogre::Math::RangeRandom(-500.0f, +500.0f)) + , mDirection(1.0f) + { + } virtual Ogre::Real calculate(Ogre::Real value); }; @@ -40,7 +48,12 @@ namespace Render { Ogre::ColourValue mColor; public: - LightValue(Ogre::Light *light, const Ogre::ColourValue &color); + // MSVC needs the constructor for a class inheriting a template to be defined in header + LightValue(Ogre::Light *light, const Ogre::ColourValue &color) + : mTarget(light) + , mColor(color) + { + } virtual Ogre::Real getValue() const; virtual void setValue(Ogre::Real value); diff --git a/libs/openengine/ogre/renderer.cpp b/libs/openengine/ogre/renderer.cpp index 0834a2cd1..912781240 100644 --- a/libs/openengine/ogre/renderer.cpp +++ b/libs/openengine/ogre/renderer.cpp @@ -279,27 +279,23 @@ void OgreRenderer::createWindow(const std::string &title, const WindowSettings& 0, Ogre::PF_A8R8G8B8, Ogre::TU_WRITE_ONLY); -} -void OgreRenderer::createScene(const std::string& camName, float fov, float nearClip) -{ - assert(mRoot); - assert(mWindow); - // Get the SceneManager, in this case a generic one mScene = mRoot->createSceneManager(ST_GENERIC); - // Create the camera - mCamera = mScene->createCamera(camName); - mCamera->setNearClipDistance(nearClip); - mCamera->setFOVy(Degree(fov)); + mFader = new Fader(mScene); + + mCamera = mScene->createCamera("cam"); // Create one viewport, entire window mView = mWindow->addViewport(mCamera); - // Alter the camera aspect ratio to match the viewport mCamera->setAspectRatio(Real(mView->getActualWidth()) / Real(mView->getActualHeight())); +} - mFader = new Fader(mScene); +void OgreRenderer::adjustCamera(float fov, float nearClip) +{ + mCamera->setNearClipDistance(nearClip); + mCamera->setFOVy(Degree(fov)); } void OgreRenderer::adjustViewport() diff --git a/libs/openengine/ogre/renderer.hpp b/libs/openengine/ogre/renderer.hpp index 9e83bf4f6..89edc567d 100644 --- a/libs/openengine/ogre/renderer.hpp +++ b/libs/openengine/ogre/renderer.hpp @@ -157,7 +157,7 @@ namespace OEngine void createWindow(const std::string &title, const WindowSettings& settings); /// Set up the scene manager, camera and viewport - void createScene(const std::string& camName="Camera",// Camera name + void adjustCamera( float fov=55, // Field of view angle float nearClip=5 // Near clip distance ); diff --git a/readme.txt b/readme.txt index d00a959a0..7865f8dba 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ OpenMW: A reimplementation of The Elder Scrolls III: Morrowind OpenMW is an attempt at recreating the engine for the popular role-playing game Morrowind by Bethesda Softworks. You need to own and install the original game for OpenMW to work. -Version: 0.25.0 +Version: 0.26.0 License: GPL (see GPL3.txt for more information) Website: http://www.openmw.org @@ -71,17 +71,61 @@ Allowed options: --new-game [=arg(=1)] (=0) activate char gen/new game mechanics --fs-strict [=arg(=1)] (=0) strict file system handling (no case folding) --encoding arg (=win1252) Character encoding used in OpenMW game messages: - + win1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages - + win1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages - + win1252 - Western European (Latin) alphabet, used by default - + --fallback arg fallback values CHANGELOG +0.26.0 + +Bug #274: Inconsistencies in the terrain +Bug #557: Already-dead NPCs do not equip clothing/items. +Bug #592: Window resizing +Bug #612: [Tamriel Rebuilt] Missing terrain (South of Tel Oren) +Bug #664: Heart of lorkhan acts like a dead body (container) +Bug #767: Wonky ramp physics & water +Bug #780: Swimming out of water +Bug #792: Wrong ground alignment on actors when no clipping +Bug #796: Opening and closing door sound issue +Bug #797: No clipping hinders opening and closing of doors +Bug #799: sliders in enchanting window +Bug #838: Pressing key during startup procedure freezes the game +Bug #839: Combat/magic stances during character creation +Bug #843: [Tribunal] Dark Brotherhood assassin appears without equipment +Bug #844: Resting "until healed" option given even with full stats +Bug #846: Equipped torches are invisible. +Bug #847: Incorrect formula for autocalculated NPC initial health +Bug #850: Shealt weapon sound plays when leaving magic-ready stance +Bug #852: Some boots do not produce footstep sounds +Bug #860: FPS bar misalignment +Bug #861: Unable to print screen +Bug #863: No sneaking and jumping at the same time +Bug #866: Empty variables in [Movies] section of Morrowind.ini gets imported into OpenMW.cfg as blank fallback option and crashes game on start. +Bug #867: Dancing girls in "Suran, Desele's House of Earthly Delights" don't dance. +Bug #868: Idle animations are repeated +Bug #874: Underwater swimming close to the ground is jerky +Bug #875: Animation problem while swimming on the surface and looking up +Bug #876: Always a starting upper case letter in the inventory +Bug #878: Active spell effects don't update the layout properly when ended +Bug #891: Cell 24,-12 (Tamriel Rebuilt) crashes on load +Bug #896: New game sound issue +Feature #49: Melee Combat +Feature #71: Lycanthropy +Feature #393: Initialise MWMechanics::AiSequence from ESM::AIPackageList +Feature #622: Multiple positions for inventory window +Feature #627: Drowning +Feature #786: Allow the 'Activate' key to close the countdialog window +Feature #798: Morrowind installation via Launcher (Linux/Max OS only) +Feature #851: First/Third person transitions with mouse wheel +Task #689: change PhysicActor::enableCollisions +Task #707: Reorganise Compiler + 0.25.0 Bug #411: Launcher crash on OS X < 10.8