diff --git a/CMakeLists.txt b/CMakeLists.txt index 204efce9a..538e6a2fb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,9 +4,6 @@ if (APPLE) set(APP_BUNDLE_NAME "${CMAKE_PROJECT_NAME}.app") set(APP_BUNDLE_DIR "${OpenMW_BINARY_DIR}/${APP_BUNDLE_NAME}") - - # using 10.6 sdk - set(CMAKE_OSX_SYSROOT "/Developer/SDKs/MacOSX10.6.sdk") endif (APPLE) # Macros @@ -146,6 +143,7 @@ endif (USE_MPG123) # Platform specific if (WIN32) + set(Boost_USE_STATIC_LIBS ON) set(PLATFORM_INCLUDE_DIR "platform") add_definitions(-DBOOST_ALL_NO_LIB) else (WIN32) @@ -163,7 +161,7 @@ endif (APPLE) # Dependencies -# Fix for not visible pthreads functions for linker with glibc 2.15 +# Fix for not visible pthreads functions for linker with glibc 2.15 if (UNIX AND NOT APPLE) find_package (Threads) endif() @@ -184,6 +182,7 @@ 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} ${OIS_INCLUDE_DIRS} ${Boost_INCLUDE_DIR} ${PLATFORM_INCLUDE_DIR} ${MYGUI_INCLUDE_DIRS} @@ -203,6 +202,7 @@ if(APPLE) "Plugin_ParticleFX") endif(APPLE) +add_subdirectory( files/) add_subdirectory( files/mygui ) # Specify build paths @@ -258,7 +258,16 @@ endif (APPLE) # Compiler settings if (CMAKE_COMPILER_IS_GNUCC) - add_definitions (-Wall -Wextra -Wno-unused-parameter -Wno-unused-but-set-parameter -Wno-reorder) + add_definitions (-Wall -Wextra -Wno-unused-parameter -Wno-reorder) + + # Silence warnings in OGRE headers. Remove once OGRE got fixed! + add_definitions (-Wno-ignored-qualifiers) + + execute_process(COMMAND ${CMAKE_C_COMPILER} -dumpversion + OUTPUT_VARIABLE GCC_VERSION) + if ("${GCC_VERSION}" VERSION_GREATER 4.6 OR "${GCC_VERSION}" VERSION_EQUAL 4.6) + add_definitions (-Wno-unused-but-set-parameter) + endif("${GCC_VERSION}" VERSION_GREATER 4.6 OR "${GCC_VERSION}" VERSION_EQUAL 4.6) endif (CMAKE_COMPILER_IS_GNUCC) if(DPKG_PROGRAM) @@ -297,7 +306,7 @@ if(DPKG_PROGRAM) Data files from the original game is required to run it.") SET(CPACK_DEBIAN_PACKAGE_NAME "openmw") SET(CPACK_DEBIAN_PACKAGE_VERSION "${VERSION_STRING}") - SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW esmtool;Esmtool omwlauncher;OMWLauncher") + SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW esmtool;Esmtool omwlauncher;OMWLauncher mwiniimporter;MWiniImporter") SET(CPACK_DEBIAN_PACKAGE_DEPENDS "nvidia-cg-toolkit (>= 2.1), libboost-filesystem1.46.1 (>= 1.46.1), libboost-program-options1.46.1 (>= 1.46.1), libboost-system1.46.1 (>= 1.46.1), libboost-thread1.46.1 (>= 1.46.1), libc6 (>= 2.11.2), libfreetype6 (>= 2.2.1), libgcc1 (>= 1:4.1.1), libmpg123-0 (>= 1.12.1), libois-1.3.0 (>= 1.3.0), libopenal1 (>= 1:1.12.854), libsndfile1 (>= 1.0.23), libstdc++6 (>= 4.4.5), libuuid1 (>= 2.17.2), libqtgui4 (>= 4.7.0)") SET(CPACK_DEBIAN_PACKAGE_SECTION "Games") @@ -318,6 +327,7 @@ if(WIN32) FILE(GLOB files "${OpenMW_BINARY_DIR}/Release/*.*") INSTALL(FILES ${files} DESTINATION ".") INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" DESTINATION "." RENAME "openmw.cfg") + INSTALL(FILES "${OpenMW_SOURCE_DIR}/readme.txt" DESTINATION ".") INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION ".") SET(CPACK_GENERATOR "NSIS") @@ -328,6 +338,7 @@ if(WIN32) SET(CPACK_PACKAGE_VERSION_MINOR ${OPENMW_VERSION_MINO}) SET(CPACK_PACKAGE_VERSION_PATCH ${OPENMW_VERSION_RELEASE}) SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW;esmtool;Esmtool;omwlauncher;OpenMW Launcher") + set(CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '\$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Readme.lnk' '\$INSTDIR\\\\readme.txt'") SET(CPACK_PACKAGE_DESCRIPTION_FILE "${OpenMW_SOURCE_DIR}/readme.txt") SET(CPACK_RESOURCE_FILE_LICENSE "${OpenMW_SOURCE_DIR}/GPL3.txt") SET(CPACK_NSIS_EXECUTABLES_DIRECTORY ".") @@ -381,6 +392,11 @@ if (BUILD_LAUNCHER) add_subdirectory( apps/launcher ) endif() +option(BUILD_MWINIIMPORTER "build MWiniImporter inspector" ON) +if (BUILD_MWINIIMPORTER) + add_subdirectory( apps/mwiniimporter ) +endif() + if (WIN32) if (MSVC) if (USE_DEBUG_CONSOLE) @@ -470,6 +486,7 @@ if (APPLE) install(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" RENAME "openmw.cfg" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) install(FILES "${OpenMW_BINARY_DIR}/plugins.cfg" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) + install(FILES "${OpenMW_BINARY_DIR}/launcher.qss" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) set(CPACK_GENERATOR "DragNDrop") set(CPACK_PACKAGE_VERSION ${OPENMW_VERSION}) diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index fd736e011..ccefee1ee 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -95,5 +95,5 @@ else() "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/launcher.qss") configure_file(${CMAKE_SOURCE_DIR}/files/launcher.cfg - "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}launcher.cfg") + "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/launcher.cfg") endif() diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index c96fc2c7b..c15274e74 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -225,7 +225,7 @@ void DataFilesPage::setupDataFiles() msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::Cancel); msgBox.setText(tr("
Could not find the Data Files location

\ - The directory containing the Data Files was not found.

\ + The directory containing the data files was not found.

\ Press \"Browse...\" to specify the location manually.
")); QAbstractButton *dirSelectButton = @@ -1057,16 +1057,8 @@ void DataFilesPage::writeConfig(QString profile) return; } - // Prepare the OpenMW config - QString config = QString::fromStdString((mCfgMgr.getLocalPath() / "openmw.cfg").string()); - QFile file(config); - - if (!file.exists()) { - config = QString::fromStdString((mCfgMgr.getUserPath() / "openmw.cfg").string()); - } - - // Open the config as a QFile - file.setFileName(config); + // Open the OpenMW config as a QFile + QFile file(QString::fromStdString((mCfgMgr.getUserPath() / "openmw.cfg").string())); if (!file.open(QIODevice::ReadWrite | QIODevice::Text)) { // File cannot be opened or created diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index ef9cfa851..8bb618dd6 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -194,6 +194,7 @@ void MainDialog::play() QDir dir(QCoreApplication::applicationDirPath()); QString game = dir.absoluteFilePath("openmw"); QFile file(game); + game = "\"" + game + "\""; #else QString game = "./openmw"; QFile file(game); diff --git a/apps/mwiniimporter/CMakeLists.txt b/apps/mwiniimporter/CMakeLists.txt new file mode 100644 index 000000000..2a8c0f5fe --- /dev/null +++ b/apps/mwiniimporter/CMakeLists.txt @@ -0,0 +1,20 @@ +set(MWINIIMPORT + main.cpp + importer.cpp +) + +set(MWINIIMPORT_HEADER + importer.hpp +) + +source_group(launcher FILES ${MWINIIMPORT} ${MWINIIMPORT_HEADER}) + +add_executable(mwiniimport + ${MWINIIMPORT} +) + +target_link_libraries(mwiniimport + ${Boost_LIBRARIES} + components +) + diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp new file mode 100644 index 000000000..08b05f417 --- /dev/null +++ b/apps/mwiniimporter/importer.cpp @@ -0,0 +1,184 @@ +#include "importer.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +MwIniImporter::MwIniImporter() { + const char *map[][2] = + { + { "fps", "General:Show FPS" }, + { 0, 0 } + }; + + for(int i=0; map[i][0]; i++) { + mMergeMap.insert(std::make_pair(map[i][0], map[i][1])); + } +} + +void MwIniImporter::setVerbose(bool verbose) { + mVerbose = verbose; +} + +std::string MwIniImporter::numberToString(int n) { + std::stringstream str; + str << n; + return str.str(); +} + +MwIniImporter::multistrmap MwIniImporter::loadIniFile(std::string filename) { + std::cout << "load ini file: " << filename << std::endl; + + std::string section(""); + MwIniImporter::multistrmap map; + boost::iostreams::streamfile(filename.c_str()); + + std::string line; + while (std::getline(file, line)) { + + if(line[0] == '[') { + if(line.length() > 2) { + section = line.substr(1, line.length()-3); + } + continue; + } + + int comment_pos = line.find(";"); + if(comment_pos > 0) { + line = line.substr(0,comment_pos); + } + + if(line.empty()) { + continue; + } + + int pos = line.find("="); + if(pos < 1) { + continue; + } + + std::string key(section + ":" + line.substr(0,pos)); + std::string value(line.substr(pos+1)); + + multistrmap::iterator it; + if((it = map.find(key)) == map.end()) { + map.insert( std::make_pair > (key, std::vector() ) ); + } + map[key].push_back(value); + } + + return map; +} + +MwIniImporter::multistrmap MwIniImporter::loadCfgFile(std::string filename) { + std::cout << "load cfg file: " << filename << std::endl; + + MwIniImporter::multistrmap map; + boost::iostreams::streamfile(filename.c_str()); + + std::string line; + while (std::getline(file, line)) { + + // we cant say comment by only looking at first char anymore + int comment_pos = line.find("#"); + if(comment_pos > 0) { + line = line.substr(0,comment_pos); + } + + if(line.empty()) { + continue; + } + + int pos = line.find("="); + if(pos < 1) { + continue; + } + + std::string key(line.substr(0,pos)); + std::string value(line.substr(pos+1)); + + multistrmap::iterator it; + if((it = map.find(key)) == map.end()) { + map.insert( std::make_pair > (key, std::vector() ) ); + } + map[key].push_back(value); + } + + return map; +} + +void MwIniImporter::merge(multistrmap &cfg, multistrmap &ini) { + multistrmap::iterator cfgIt; + multistrmap::iterator iniIt; + for(strmap::iterator it=mMergeMap.begin(); it!=mMergeMap.end(); it++) { + if((iniIt = ini.find(it->second)) != ini.end()) { + cfg.erase(it->first); + if(!this->specialMerge(it->first, it->second, cfg, ini)) { + cfg.insert(std::make_pair >(it->first, iniIt->second)); + } + } + } +} + +bool MwIniImporter::specialMerge(std::string cfgKey, std::string iniKey, multistrmap &cfg, multistrmap &ini) { + return false; +} + +void MwIniImporter::importGameFiles(multistrmap &cfg, multistrmap &ini) { + std::vector esmFiles; + std::vector espFiles; + std::string baseGameFile("Game Files:GameFile"); + std::string gameFile(""); + + multistrmap::iterator it = ini.begin(); + for(int i=0; it != ini.end(); i++) { + gameFile = baseGameFile; + gameFile.append(this->numberToString(i)); + + it = ini.find(gameFile); + if(it == ini.end()) { + break; + } + + for(std::vector::iterator entry = it->second.begin(); entry!=it->second.end(); entry++) { + std::string filetype(entry->substr(entry->length()-4, 3)); + std::transform(filetype.begin(), filetype.end(), filetype.begin(), ::tolower); + + if(filetype.compare("esm") == 0) { + esmFiles.push_back(*entry); + } + else if(filetype.compare("esp") == 0) { + espFiles.push_back(*entry); + } + } + + gameFile = ""; + } + + cfg.erase("master"); + cfg.insert( std::make_pair > ("master", std::vector() ) ); + + for(std::vector::iterator it=esmFiles.begin(); it!=esmFiles.end(); it++) { + cfg["master"].push_back(*it); + } + + cfg.erase("plugin"); + cfg.insert( std::make_pair > ("plugin", std::vector() ) ); + + for(std::vector::iterator it=espFiles.begin(); it!=espFiles.end(); it++) { + cfg["plugin"].push_back(*it); + } +} + +void MwIniImporter::writeToFile(boost::iostreams::stream &out, multistrmap &cfg) { + + for(multistrmap::iterator it=cfg.begin(); it != cfg.end(); it++) { + for(std::vector::iterator entry=it->second.begin(); entry != it->second.end(); entry++) { + out << (it->first) << "=" << (*entry) << std::endl; + } + } +} diff --git a/apps/mwiniimporter/importer.hpp b/apps/mwiniimporter/importer.hpp new file mode 100644 index 000000000..988f10255 --- /dev/null +++ b/apps/mwiniimporter/importer.hpp @@ -0,0 +1,32 @@ +#ifndef MWINIIMPORTER_IMPORTER +#define MWINIIMPORTER_IMPORTER 1 + +#include +#include +#include +#include +#include +#include + +class MwIniImporter { + public: + typedef std::map strmap; + typedef std::map > multistrmap; + + MwIniImporter(); + void setVerbose(bool verbose); + multistrmap loadIniFile(std::string filename); + multistrmap loadCfgFile(std::string filename); + void merge(multistrmap &cfg, multistrmap &ini); + void importGameFiles(multistrmap &cfg, multistrmap &ini); + void writeToFile(boost::iostreams::stream &out, multistrmap &cfg); + + private: + bool specialMerge(std::string cfgKey, std::string iniKey, multistrmap &cfg, multistrmap &ini); + std::string numberToString(int n); + bool mVerbose; + strmap mMergeMap; +}; + + +#endif diff --git a/apps/mwiniimporter/main.cpp b/apps/mwiniimporter/main.cpp new file mode 100644 index 000000000..9a6e61645 --- /dev/null +++ b/apps/mwiniimporter/main.cpp @@ -0,0 +1,79 @@ +#include "importer.hpp" + +#include +#include +#include +#include + +namespace bpo = boost::program_options; + +int main(int argc, char *argv[]) { + + bpo::options_description desc("Syntax: mwiniimporter \nAllowed options"); + desc.add_options() + ("help,h", "produce help message") + ("verbose,v", "verbose output") + ("ini,i", bpo::value(), "morrowind.ini file") + ("cfg,c", bpo::value(), "openmw.cfg file") + ("output,o", bpo::value()->default_value(""), "openmw.cfg file") + ("game-files,g", "import esm and esp files") + ; + + bpo::variables_map vm; + try { + bpo::store(boost::program_options::parse_command_line(argc, argv, desc), vm); + + // parse help before calling notify because we dont want it to throw an error if help is set + if(vm.count("help")) { + std::cout << desc; + return 0; + } + + bpo::notify(vm); + + } + catch(std::exception& e) { + std::cerr << "Error:" << e.what() << std::endl; + return -1; + } + catch(...) { + std::cerr << "Error" << std::endl; + return -2; + } + + std::string iniFile = vm["ini"].as(); + std::string cfgFile = vm["cfg"].as(); + + // if no output is given, write back to cfg file + std::string outputFile(vm["output"].as()); + if(vm["output"].defaulted()) { + outputFile = vm["cfg"].as(); + } + + if(!boost::filesystem::exists(iniFile)) { + std::cerr << "ini file does not exist" << std::endl; + return -3; + } + if(!boost::filesystem::exists(cfgFile)) { + std::cerr << "cfg file does not exist" << std::endl; + return -4; + } + + MwIniImporter importer; + importer.setVerbose(vm.count("verbose")); + boost::iostreams::stream file(outputFile); + + MwIniImporter::multistrmap ini = importer.loadIniFile(iniFile); + MwIniImporter::multistrmap cfg = importer.loadCfgFile(cfgFile); + + importer.merge(cfg, ini); + + if(vm.count("game-files")) { + importer.importGameFiles(cfg, ini); + } + + std::cout << "write to: " << outputFile << std::endl; + importer.writeToFile(file, cfg); + + return 0; +} diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 6dc9382d0..3dabc9ac8 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -15,7 +15,7 @@ source_group(game FILES ${GAME} ${GAME_HEADER}) add_openmw_dir (mwrender renderingmanager debugging sky player animation npcanimation creatureanimation actors objects - renderinginterface localmap + renderinginterface localmap occlusionquery terrain terrainmaterial water ) add_openmw_dir (mwinput @@ -82,6 +82,7 @@ add_definitions(${SOUND_DEFINE}) target_link_libraries(openmw ${OGRE_LIBRARIES} + ${OGRE_Terrain_LIBRARY} ${OGRE_STATIC_PLUGINS} ${OIS_LIBRARIES} ${Boost_LIBRARIES} diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 5e49ae2f7..38050e53b 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -204,13 +204,18 @@ OMW::Engine::~Engine() void OMW::Engine::loadBSA() { const Files::MultiDirCollection& bsa = mFileCollections.getCollection (".bsa"); - std::string dataDirectory; + for (Files::MultiDirCollection::TIter iter(bsa.begin()); iter!=bsa.end(); ++iter) { std::cout << "Adding " << iter->second.string() << std::endl; Bsa::addBSA(iter->second.string()); + } - dataDirectory = iter->second.parent_path().string(); + const Files::PathContainer& dataDirs = mFileCollections.getPaths(); + std::string dataDirectory; + for (Files::PathContainer::const_iterator iter = dataDirs.begin(); iter != dataDirs.end(); ++iter) + { + dataDirectory = iter->string(); std::cout << "Data dir " << dataDirectory << std::endl; Bsa::addDir(dataDirectory, mFSStrict); } @@ -315,7 +320,11 @@ void OMW::Engine::go() // This has to be added BEFORE MyGUI is initialized, as it needs // to find core.xml here. + + //addResourcesDirectory(mResDir); + addResourcesDirectory(mResDir / "mygui"); + addResourcesDirectory(mResDir / "water"); // Create the window mOgre->createWindow("OpenMW"); diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index e95fb572f..d27d0bc71 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -56,7 +56,7 @@ namespace MWClass boost::shared_ptr Apparatus::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const { - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index e1c2734f0..9956a56fb 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -60,7 +60,7 @@ namespace MWClass boost::shared_ptr Armor::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const { - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index 0a81ebafb..76370dc5c 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -58,7 +58,7 @@ namespace MWClass { // TODO implement reading - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 4fe19ada4..2357851d7 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -57,7 +57,7 @@ namespace MWClass boost::shared_ptr Clothing::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const { - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index c58a25c03..29b3331ba 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -85,7 +85,7 @@ namespace MWClass { // TODO check for key std::cout << "Locked container" << std::endl; - environment.mSoundManager->playSound3D (ptr, lockedSound, 1.0, 1.0, false); + environment.mSoundManager->playSound3D (ptr, lockedSound, 1.0, 1.0); return boost::shared_ptr (new MWWorld::NullAction); } else @@ -100,7 +100,7 @@ namespace MWClass { // Trap activation goes here std::cout << "Activated trap: " << ptr.getCellRef().trap << std::endl; - environment.mSoundManager->playSound3D (ptr, trapActivationSound, 1.0, 1.0, false); + environment.mSoundManager->playSound3D (ptr, trapActivationSound, 1.0, 1.0); ptr.getCellRef().trap = ""; return boost::shared_ptr (new MWWorld::NullAction); } diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 5654dff69..9d6c6a78d 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -73,7 +73,7 @@ namespace MWClass // TODO check for key // TODO report failure to player (message, sound?). Look up behaviour of original MW. std::cout << "Locked!" << std::endl; - environment.mSoundManager->playSound3D (ptr, lockedSound, 1.0, 1.0, false); + environment.mSoundManager->playSound3D (ptr, lockedSound, 1.0, 1.0); return boost::shared_ptr (new MWWorld::NullAction); } @@ -81,7 +81,7 @@ namespace MWClass { // Trap activation std::cout << "Activated trap: " << ptr.getCellRef().trap << std::endl; - environment.mSoundManager->playSound3D(ptr, trapActivationSound, 1.0, 1.0, false); + environment.mSoundManager->playSound3D(ptr, trapActivationSound, 1.0, 1.0); ptr.getCellRef().trap = ""; return boost::shared_ptr (new MWWorld::NullAction); } @@ -110,7 +110,7 @@ namespace MWClass // TODO return action for rotating the door // This is a little pointless, but helps with testing - environment.mSoundManager->playSound3D (ptr, openSound, 1.0, 1.0, false); + environment.mSoundManager->playSound3D (ptr, openSound, 1.0, 1.0); return boost::shared_ptr (new MWWorld::NullAction); } } diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index 1a7edf632..cbe153ba3 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -54,7 +54,7 @@ namespace MWClass boost::shared_ptr Ingredient::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const { - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index e2e63a89b..71e477591 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -59,7 +59,7 @@ namespace MWClass if (!ref->base->sound.empty()) { - environment.mSoundManager->playSound3D (ptr, ref->base->sound, 1.0, 1.0, true); + environment.mSoundManager->playSound3D (ptr, ref->base->sound, 1.0, 1.0, MWSound::Play_Loop); } } @@ -83,7 +83,7 @@ namespace MWClass if (!(ref->base->data.flags & ESM::Light::Carry)) return boost::shared_ptr (new MWWorld::NullAction); - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 3dda2f4af..1eef0db8b 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -58,7 +58,7 @@ namespace MWClass boost::shared_ptr Lockpick::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const { - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index 864fc1e38..def1a90a8 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -56,7 +56,7 @@ namespace MWClass boost::shared_ptr Miscellaneous::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const { - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 4ab374590..ed1733e2d 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -56,7 +56,7 @@ namespace MWClass boost::shared_ptr Potion::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const { - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index 4b4d79a73..8013e2e80 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -57,7 +57,7 @@ namespace MWClass boost::shared_ptr Probe::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const { - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index 758bf4079..d49979861 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -56,7 +56,7 @@ namespace MWClass boost::shared_ptr Repair::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const { - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 20db0cf38..e36e9202f 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -57,7 +57,7 @@ namespace MWClass boost::shared_ptr Weapon::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const { - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index d6c4ce4e5..ac6681e27 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -43,9 +43,6 @@ DialogueWindow::DialogueWindow(WindowManager& parWindowManager,MWWorld::Environm // Centre dialog center(); - //WindowManager *wm = environment.mWindowManager; - setText("NpcName", "Name of character"); - //History view getWidget(history, "History"); history->setOverflowToTheLeft(true); @@ -116,7 +113,8 @@ void DialogueWindow::onSelectTopic(MyGUI::ListBox* _sender, size_t _index) void DialogueWindow::startDialogue(std::string npcName) { - setText("NpcName", npcName); + static_cast(mMainWidget)->setCaption(npcName); + adjustWindowCaption(); } void DialogueWindow::setKeywords(std::list keyWords) diff --git a/apps/openmw/mwgui/layouts.cpp b/apps/openmw/mwgui/layouts.cpp index dbd6154b7..de74214ee 100644 --- a/apps/openmw/mwgui/layouts.cpp +++ b/apps/openmw/mwgui/layouts.cpp @@ -182,7 +182,9 @@ void HUD::setPlayerPos(const float x, const float y) } MapWindow::MapWindow() - : Layout("openmw_map_window_layout.xml"), mGlobal(false) + : Layout("openmw_map_window_layout.xml") + , mGlobal(false) + , mVisible(false) { setCoord(500,0,320,300); setText("WorldButton", "World"); @@ -272,6 +274,17 @@ void MapWindow::onWorldButtonClicked(MyGUI::Widget* _sender) mButton->setCaption( mGlobal ? "Local" : "World" ); } +LocalMapBase::LocalMapBase() + : mCurX(0) + , mCurY(0) + , mInterior(false) + , mLocalMap(NULL) + , mPrefix() + , mChanged(true) + , mLayout(NULL) +{ +} + void LocalMapBase::init(MyGUI::ScrollView* widget, OEngine::GUI::Layout* layout) { mLocalMap = widget; diff --git a/apps/openmw/mwgui/layouts.hpp b/apps/openmw/mwgui/layouts.hpp index 8d9a41a22..614479ccc 100644 --- a/apps/openmw/mwgui/layouts.hpp +++ b/apps/openmw/mwgui/layouts.hpp @@ -34,6 +34,7 @@ namespace MWGui class LocalMapBase { public: + LocalMapBase(); void init(MyGUI::ScrollView* widget, OEngine::GUI::Layout* layout); void setCellPrefix(const std::string& prefix); @@ -85,6 +86,7 @@ namespace MWGui { public: MapWindow(); + virtual ~MapWindow(){} void setVisible(bool b); void setPlayerPos(const float x, const float y); diff --git a/apps/openmw/mwgui/window_manager.cpp b/apps/openmw/mwgui/window_manager.cpp index a04e2dcb8..49b6e644d 100644 --- a/apps/openmw/mwgui/window_manager.cpp +++ b/apps/openmw/mwgui/window_manager.cpp @@ -180,71 +180,58 @@ void WindowManager::updateVisible() // Mouse is visible whenever we're not in game mode MyGUI::PointerManager::getInstance().setVisible(isGuiMode()); - // If in game mode, don't show anything. - if(mode == GM_Game) //Use a switch/case structure - { - return; - } - - if(mode == GM_MainMenu) - { - // Enable the main menu - menu->setVisible(true); - return; - } - - if(mode == GM_Console) - { - console->enable(); - return; - } - - //There must be a more elegant solution - if (mode == GM_Name || mode == GM_Race || mode == GM_Class || mode == GM_ClassPick || mode == GM_ClassCreate || mode == GM_Birth || mode == GM_ClassGenerate || mode == GM_Review) - { - mCharGen->spawnDialog(mode); - return; - } - - if(mode == GM_Inventory) - { - // Ah, inventory mode. First, compute the effective set of - // windows to show. This is controlled both by what windows the - // user has opened/closed (the 'shown' variable) and by what - // windows we are allowed to show (the 'allowed' var.) - int eff = shown & allowed; - - // Show the windows we want - map -> setVisible( (eff & GW_Map) != 0 ); - stats -> setVisible( (eff & GW_Stats) != 0 ); - return; - } - - if (mode == GM_Dialogue) - { - dialogueWindow->open(); - return; - } - - if(mode == GM_InterMessageBox) - { - if(!mMessageBoxManager->isInteractiveMessageBox()) { - setGuiMode(GM_Game); + switch(mode) { + case GM_Game: + // If in game mode, don't show anything. + break; + case GM_MainMenu: + menu->setVisible(true); + break; + case GM_Console: + console->enable(); + break; + case GM_Name: + case GM_Race: + case GM_Class: + case GM_ClassPick: + case GM_ClassCreate: + case GM_Birth: + case GM_ClassGenerate: + case GM_Review: + mCharGen->spawnDialog(mode); + break; + case GM_Inventory: + { + // First, compute the effective set of windows to show. + // This is controlled both by what windows the + // user has opened/closed (the 'shown' variable) and by what + // windows we are allowed to show (the 'allowed' var.) + int eff = shown & allowed; + + // Show the windows we want + map -> setVisible( (eff & GW_Map) != 0 ); + stats -> setVisible( (eff & GW_Stats) != 0 ); + break; } - return; - } - - if(mode == GM_Journal) - { - mJournal->setVisible(true); - mJournal->open(); - return; + case GM_Dialogue: + dialogueWindow->open(); + break; + case GM_InterMessageBox: + if(!mMessageBoxManager->isInteractiveMessageBox()) { + setGuiMode(GM_Game); + } + break; + case GM_Journal: + mJournal->setVisible(true); + mJournal->open(); + break; + default: + // Unsupported mode, switch back to game + // Note: The call will eventually end up this method again but + // will stop at the check if mode is GM_Game. + setGuiMode(GM_Game); + break; } - - // Unsupported mode, switch back to game - // Note: The call will eventually end up this method again but - // will stop at the check if(mode == GM_Game) above. - setGuiMode(GM_Game); } void WindowManager::setValue (const std::string& id, const MWMechanics::Stat& value) @@ -371,7 +358,6 @@ void WindowManager::updateSkillArea() void WindowManager::removeDialog(OEngine::GUI::Layout*dialog) { - std::cout << "dialogue a la poubelle"; assert(dialog); if (!dialog) return; diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index f3a8f64d5..fb710443b 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -126,6 +126,11 @@ namespace MWRender{ void Animation::handleShapes(std::vector* allshapes, Ogre::Entity* creaturemodel, Ogre::SkeletonInstance *skel){ shapeNumber = 0; + if (allshapes == NULL || creaturemodel == NULL || skel == NULL) + { + return; + } + std::vector::iterator allshapesiter; for(allshapesiter = allshapes->begin(); allshapesiter != allshapes->end(); allshapesiter++) diff --git a/apps/openmw/mwrender/debugging.cpp b/apps/openmw/mwrender/debugging.cpp index 60b299acd..422185273 100644 --- a/apps/openmw/mwrender/debugging.cpp +++ b/apps/openmw/mwrender/debugging.cpp @@ -27,11 +27,7 @@ bool Debugging::toggleRenderMode (int mode){ switch (mode) { case MWWorld::World::Render_CollisionDebug: - - // TODO use a proper function instead of accessing the member variable - // directly. - eng->setDebugRenderingMode (!eng->isDebugCreated); - return eng->isDebugCreated; + return eng->toggleDebugRendering(); } return false; diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index e4e721227..94ccb6e97 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -88,38 +88,76 @@ void Objects::insertMesh (const MWWorld::Ptr& ptr, const std::string& mesh) NifOgre::NIFLoader::load(mesh); Ogre::Entity *ent = mRenderer.getScene()->createEntity(mesh); +/* + Ogre::Vector3 extents = ent->getBoundingBox().getSize(); + extents *= insert->getScale(); +// float size = std::max(std::max(extents.x, extents.y), extents.z); + + bool small = (size < 250); /// \todo config value + + // do not fade out doors. that will cause holes and look stupid + if (ptr.getTypeName().find("Door") != std::string::npos) + small = false; +*/ + const bool small = false; + + if (mBounds.find(ptr.getCell()) == mBounds.end()) + mBounds[ptr.getCell()] = Ogre::AxisAlignedBox::BOX_NULL; + + Ogre::AxisAlignedBox bounds = ent->getBoundingBox(); + bounds = Ogre::AxisAlignedBox( + insert->_getDerivedPosition() + bounds.getMinimum(), + insert->_getDerivedPosition() + bounds.getMaximum() + ); + + bounds.scale(insert->getScale()); + mBounds[ptr.getCell()].merge(bounds); + if(!mIsStatic) { insert->attachObject(ent); + + ent->setRenderingDistance(small ? 2500 : 0); /// \todo config value } else { Ogre::StaticGeometry* sg = 0; - if(mStaticGeometry.find(ptr.getCell()) == mStaticGeometry.end()) + + if (small) { - uniqueID = uniqueID +1; - sg = mRenderer.getScene()->createStaticGeometry( "sg" + Ogre::StringConverter::toString(uniqueID)); - //Create the scenenode and put it in the map - mStaticGeometry[ptr.getCell()] = sg; - - // This specifies the size of a single batch region. - // If it is set too high: - // - there will be problems choosing the correct lights - // - 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)); - - mBounds[ptr.getCell()] = Ogre::AxisAlignedBox::BOX_NULL; - mBounds[ptr.getCell()].merge(ent->getBoundingBox()); + if( mStaticGeometrySmall.find(ptr.getCell()) == mStaticGeometrySmall.end()) + { + uniqueID = uniqueID +1; + sg = mRenderer.getScene()->createStaticGeometry( "sg" + Ogre::StringConverter::toString(uniqueID)); + mStaticGeometrySmall[ptr.getCell()] = sg; + + sg->setRenderingDistance(2500); /// \todo config value + } + else + sg = mStaticGeometrySmall[ptr.getCell()]; } else { - sg = mStaticGeometry[ptr.getCell()]; + if( mStaticGeometry.find(ptr.getCell()) == mStaticGeometry.end()) + { + + uniqueID = uniqueID +1; + sg = mRenderer.getScene()->createStaticGeometry( "sg" + Ogre::StringConverter::toString(uniqueID)); + mStaticGeometry[ptr.getCell()] = sg; + } + else + sg = mStaticGeometry[ptr.getCell()]; } + // This specifies the size of a single batch region. + // If it is set too high: + // - there will be problems choosing the correct lights + // - 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)); + sg->addEntity(ent,insert->_getDerivedPosition(),insert->_getDerivedOrientation(),insert->_getDerivedScale()); - mBounds[ptr.getCell()].merge(insert->_getDerivedPosition()); mRenderer.getScene()->destroyEntity(ent); } @@ -206,7 +244,14 @@ void Objects::removeCell(MWWorld::Ptr::CellStore* store) mRenderer.getScene()->destroyStaticGeometry (sg); sg = 0; } - + if(mStaticGeometrySmall.find(store) != mStaticGeometrySmall.end()) + { + Ogre::StaticGeometry* sg = mStaticGeometrySmall[store]; + mStaticGeometrySmall.erase(store); + mRenderer.getScene()->destroyStaticGeometry (sg); + sg = 0; + } + if(mBounds.find(store) != mBounds.end()) mBounds.erase(store); } @@ -218,6 +263,11 @@ void Objects::buildStaticGeometry(ESMS::CellStore& cell) Ogre::StaticGeometry* sg = mStaticGeometry[&cell]; sg->build(); } + if(mStaticGeometrySmall.find(&cell) != mStaticGeometrySmall.end()) + { + Ogre::StaticGeometry* sg = mStaticGeometrySmall[&cell]; + sg->build(); + } } Ogre::AxisAlignedBox Objects::getDimensions(MWWorld::Ptr::CellStore* cell) diff --git a/apps/openmw/mwrender/objects.hpp b/apps/openmw/mwrender/objects.hpp index 1ca81331d..265de875b 100644 --- a/apps/openmw/mwrender/objects.hpp +++ b/apps/openmw/mwrender/objects.hpp @@ -14,6 +14,7 @@ class Objects{ OEngine::Render::OgreRenderer &mRenderer; std::map mCellSceneNodes; std::map mStaticGeometry; + std::map mStaticGeometrySmall; std::map mBounds; Ogre::SceneNode* mMwRoot; bool mIsStatic; diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp new file mode 100644 index 000000000..29cfe33fe --- /dev/null +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -0,0 +1,266 @@ +#include "occlusionquery.hpp" + +#include +#include +#include +#include +#include + +using namespace MWRender; +using namespace Ogre; + +OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNode* sunNode) : + mSunTotalAreaQuery(0), mSunVisibleAreaQuery(0), mSingleObjectQuery(0), mActiveQuery(0), + mDoQuery(0), mSunVisibility(0), mQuerySingleObjectStarted(false), mTestResult(false), + mQuerySingleObjectRequested(false), mWasVisible(false), mObjectWasVisible(false), mDoQuery2(false), + mBBNode(0) +{ + mRendering = renderer; + mSunNode = sunNode; + + try { + RenderSystem* renderSystem = Root::getSingleton().getRenderSystem(); + + mSunTotalAreaQuery = renderSystem->createHardwareOcclusionQuery(); + mSunVisibleAreaQuery = renderSystem->createHardwareOcclusionQuery(); + mSingleObjectQuery = renderSystem->createHardwareOcclusionQuery(); + + mSupported = (mSunTotalAreaQuery != 0) && (mSunVisibleAreaQuery != 0) && (mSingleObjectQuery != 0); + } + catch (Ogre::Exception e) + { + mSupported = false; + } + + if (!mSupported) + { + std::cout << "Hardware occlusion queries not supported." << std::endl; + return; + } + + // This means that everything up to RENDER_QUEUE_MAIN can occlude the objects that are tested + const int queue = RENDER_QUEUE_MAIN+1; + + MaterialPtr matBase = MaterialManager::getSingleton().getByName("BaseWhiteNoLighting"); + MaterialPtr matQueryArea = matBase->clone("QueryTotalPixels"); + matQueryArea->setDepthWriteEnabled(false); + matQueryArea->setColourWriteEnabled(false); + matQueryArea->setDepthCheckEnabled(false); // Not occluded by objects + MaterialPtr matQueryVisible = matBase->clone("QueryVisiblePixels"); + matQueryVisible->setDepthWriteEnabled(false); + matQueryVisible->setColourWriteEnabled(false); // Uncomment this to visualize the occlusion query + matQueryVisible->setDepthCheckEnabled(true); // Occluded by objects + matQueryVisible->setCullingMode(CULL_NONE); + matQueryVisible->setManualCullingMode(MANUAL_CULL_NONE); + + if (sunNode) + mBBNode = mSunNode->getParentSceneNode()->createChildSceneNode(); + + mObjectNode = mRendering->getScene()->getRootSceneNode()->createChildSceneNode(); + mBBNodeReal = mRendering->getScene()->getRootSceneNode()->createChildSceneNode(); + + mBBQueryTotal = mRendering->getScene()->createBillboardSet(1); + mBBQueryTotal->setDefaultDimensions(150, 150); + mBBQueryTotal->createBillboard(Vector3::ZERO); + mBBQueryTotal->setMaterialName("QueryTotalPixels"); + mBBQueryTotal->setRenderQueueGroup(queue+1); + mBBNodeReal->attachObject(mBBQueryTotal); + + mBBQueryVisible = mRendering->getScene()->createBillboardSet(1); + mBBQueryVisible->setDefaultDimensions(150, 150); + mBBQueryVisible->createBillboard(Vector3::ZERO); + mBBQueryVisible->setMaterialName("QueryVisiblePixels"); + mBBQueryVisible->setRenderQueueGroup(queue+1); + mBBNodeReal->attachObject(mBBQueryVisible); + + mBBQuerySingleObject = mRendering->getScene()->createBillboardSet(1); + /// \todo ideally this should occupy exactly 1 pixel on the screen + mBBQuerySingleObject->setDefaultDimensions(0.003, 0.003); + mBBQuerySingleObject->createBillboard(Vector3::ZERO); + mBBQuerySingleObject->setMaterialName("QueryVisiblePixels"); + mBBQuerySingleObject->setRenderQueueGroup(queue); + mObjectNode->attachObject(mBBQuerySingleObject); + + mRendering->getScene()->addRenderObjectListener(this); + mRendering->getScene()->addRenderQueueListener(this); + mDoQuery = true; + mDoQuery2 = true; +} + +OcclusionQuery::~OcclusionQuery() +{ + RenderSystem* renderSystem = Root::getSingleton().getRenderSystem(); + if (mSunTotalAreaQuery) renderSystem->destroyHardwareOcclusionQuery(mSunTotalAreaQuery); + if (mSunVisibleAreaQuery) renderSystem->destroyHardwareOcclusionQuery(mSunVisibleAreaQuery); + if (mSingleObjectQuery) renderSystem->destroyHardwareOcclusionQuery(mSingleObjectQuery); +} + +bool OcclusionQuery::supported() +{ + return mSupported; +} + +void OcclusionQuery::notifyRenderSingleObject(Renderable* rend, const Pass* pass, const AutoParamDataSource* source, + const LightList* pLightList, bool suppressRenderStateChanges) +{ + // The following code activates and deactivates the occlusion queries + // so that the queries only include the rendering of their intended targets + + // Close the last occlusion query + // Each occlusion query should only last a single rendering + if (mActiveQuery != NULL) + { + mActiveQuery->endOcclusionQuery(); + mActiveQuery = NULL; + } + + // Open a new occlusion query + if (mDoQuery == true) + { + if (rend == mBBQueryTotal) + { + mActiveQuery = mSunTotalAreaQuery; + mWasVisible = true; + } + else if (rend == mBBQueryVisible) + { + mActiveQuery = mSunVisibleAreaQuery; + } + } + if (mDoQuery == true && rend == mBBQuerySingleObject) + { + mQuerySingleObjectStarted = true; + mQuerySingleObjectRequested = false; + mActiveQuery = mSingleObjectQuery; + mObjectWasVisible = true; + } + + if (mActiveQuery != NULL) + mActiveQuery->beginOcclusionQuery(); +} + +void OcclusionQuery::renderQueueEnded(uint8 queueGroupId, const String& invocation, bool& repeatThisInvocation) +{ + if (mActiveQuery != NULL) + { + mActiveQuery->endOcclusionQuery(); + mActiveQuery = NULL; + } + /** + * for every beginOcclusionQuery(), we want a respective pullOcclusionQuery() and vice versa + * this also means that results can be wrong at other places if we pull, but beginOcclusionQuery() was never called + * this can happen for example if the object that is tested is outside of the view frustum + * to prevent this, check if the queries have been performed after everything has been rendered and if not, start them manually + */ + if (queueGroupId == RENDER_QUEUE_SKIES_LATE) + { + if (mWasVisible == false && mDoQuery) + { + mSunTotalAreaQuery->beginOcclusionQuery(); + mSunTotalAreaQuery->endOcclusionQuery(); + mSunVisibleAreaQuery->beginOcclusionQuery(); + mSunVisibleAreaQuery->endOcclusionQuery(); + } + if (mObjectWasVisible == false && mDoQuery) + { + mSingleObjectQuery->beginOcclusionQuery(); + mSingleObjectQuery->endOcclusionQuery(); + mQuerySingleObjectStarted = true; + mQuerySingleObjectRequested = false; + } + } +} + +void OcclusionQuery::update(float duration) +{ + if (!mSupported) return; + + mWasVisible = false; + mObjectWasVisible = false; + + // Adjust the position of the sun billboards according to camera viewing distance + // we need to do this to make sure that _everything_ can occlude the sun + float dist = mRendering->getCamera()->getFarClipDistance(); + if (dist==0) dist = 10000000; + dist -= 1000; // bias + dist /= 1000.f; + if (mBBNode) + { + mBBNode->setPosition(mSunNode->getPosition() * dist); + mBBNode->setScale(dist, dist, dist); + mBBNodeReal->setPosition(mBBNode->_getDerivedPosition()); + mBBNodeReal->setScale(mBBNode->getScale()); + } + + // Stop occlusion queries until we get their information + // (may not happen on the same frame they are requested in) + mDoQuery = false; + mDoQuery2 = false; + + if (!mSunTotalAreaQuery->isStillOutstanding() + && !mSunVisibleAreaQuery->isStillOutstanding() + && !mSingleObjectQuery->isStillOutstanding()) + { + unsigned int totalPixels; + unsigned int visiblePixels; + + mSunTotalAreaQuery->pullOcclusionQuery(&totalPixels); + mSunVisibleAreaQuery->pullOcclusionQuery(&visiblePixels); + + if (totalPixels == 0) + { + // probably outside of the view frustum + mSunVisibility = 0; + } + else + { + mSunVisibility = float(visiblePixels) / float(totalPixels); + if (mSunVisibility > 1) mSunVisibility = 1; + } + + unsigned int result; + + mSingleObjectQuery->pullOcclusionQuery(&result); + + mTestResult = (result != 0); + + mQuerySingleObjectStarted = false; + mQuerySingleObjectRequested = false; + + mDoQuery = true; + } +} + +void OcclusionQuery::occlusionTest(const Ogre::Vector3& position, Ogre::SceneNode* object) +{ + assert( !occlusionTestPending() + && "Occlusion test still pending"); + + mBBQuerySingleObject->setVisible(true); + + mObjectNode->setPosition(position); + // scale proportional to camera distance, in order to always give the billboard the same size in screen-space + mObjectNode->setScale( Vector3(1,1,1)*(position - mRendering->getCamera()->getRealPosition()).length() ); + + mQuerySingleObjectRequested = true; +} + +bool OcclusionQuery::occlusionTestPending() +{ + return (mQuerySingleObjectRequested || mQuerySingleObjectStarted); +} + +void OcclusionQuery::setSunNode(Ogre::SceneNode* node) +{ + mSunNode = node; + if (!mBBNode) + mBBNode = node->getParentSceneNode()->createChildSceneNode(); +} + +bool OcclusionQuery::getTestResult() +{ + assert( !occlusionTestPending() + && "Occlusion test still pending"); + + return mTestResult; +} diff --git a/apps/openmw/mwrender/occlusionquery.hpp b/apps/openmw/mwrender/occlusionquery.hpp new file mode 100644 index 000000000..b655c8e46 --- /dev/null +++ b/apps/openmw/mwrender/occlusionquery.hpp @@ -0,0 +1,97 @@ +#ifndef _GAME_OCCLUSION_QUERY_H +#define _GAME_OCCLUSION_QUERY_H + +#include +#include + +namespace Ogre +{ + class HardwareOcclusionQuery; + class Entity; + class SceneNode; +} + +#include + +namespace MWRender +{ + /// + /// \brief Implements hardware occlusion queries on the GPU + /// + class OcclusionQuery : public Ogre::RenderObjectListener, public Ogre::RenderQueueListener + { + public: + OcclusionQuery(OEngine::Render::OgreRenderer*, Ogre::SceneNode* sunNode); + ~OcclusionQuery(); + + /** + * @return true if occlusion queries are supported on the user's hardware + */ + bool supported(); + + /** + * per-frame update + */ + void update(float duration); + + /** + * request occlusion test for a billboard at the given position, omitting an entity + * @param position of the billboard in ogre coordinates + * @param object to exclude from the occluders + */ + void occlusionTest(const Ogre::Vector3& position, Ogre::SceneNode* object); + + /** + * @return true if a request is still outstanding + */ + bool occlusionTestPending(); + + /** + * @return true if the object tested in the last request was occluded + */ + bool getTestResult(); + + float getSunVisibility() const {return mSunVisibility;}; + + void setSunNode(Ogre::SceneNode* node); + + private: + Ogre::HardwareOcclusionQuery* mSunTotalAreaQuery; + Ogre::HardwareOcclusionQuery* mSunVisibleAreaQuery; + Ogre::HardwareOcclusionQuery* mSingleObjectQuery; + Ogre::HardwareOcclusionQuery* mActiveQuery; + + Ogre::BillboardSet* mBBQueryVisible; + Ogre::BillboardSet* mBBQueryTotal; + Ogre::BillboardSet* mBBQuerySingleObject; + + Ogre::SceneNode* mSunNode; + Ogre::SceneNode* mBBNode; + Ogre::SceneNode* mBBNodeReal; + float mSunVisibility; + + Ogre::SceneNode* mObjectNode; + + bool mWasVisible; + bool mObjectWasVisible; + + bool mTestResult; + + bool mSupported; + bool mDoQuery; + bool mDoQuery2; + + bool mQuerySingleObjectRequested; + bool mQuerySingleObjectStarted; + + OEngine::Render::OgreRenderer* mRendering; + + protected: + virtual void notifyRenderSingleObject(Ogre::Renderable* rend, const Ogre::Pass* pass, const Ogre::AutoParamDataSource* source, + const Ogre::LightList* pLightList, bool suppressRenderStateChanges); + + virtual void renderQueueEnded(Ogre::uint8 queueGroupId, const Ogre::String& invocation, bool& repeatThisInvocation); + }; +} + +#endif diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index e2aea19c6..bbddd325a 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -23,6 +23,12 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const :mRendering(_rend), mObjects(mRendering), mActors(mRendering, environment), mAmbientMode(0), mDebugging(engine) { mRendering.createScene("PlayerCam", 55, 5); + mTerrainManager = new TerrainManager(mRendering.getScene(), + environment); + + //The fog type must be set before any terrain objects are created as if the + //fog type is set to FOG_NONE then the initially created terrain won't have any fog + configureFog(1, ColourValue(1,1,1)); // Set default mipmap level (NB some APIs ignore this) TextureManager::getSingleton().setDefaultNumMipmaps(5); @@ -40,9 +46,6 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const mMwRoot->pitch(Degree(-90)); mObjects.setMwRoot(mMwRoot); mActors.setMwRoot(mMwRoot); - - //used to obtain ingame information of ogre objects (which are faced or selected) - mRaySceneQuery = mRendering.getScene()->createRayQuery(Ray()); Ogre::SceneNode *playerNode = mMwRoot->createChildSceneNode ("player"); playerNode->pitch(Degree(90)); @@ -53,6 +56,10 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const //mSkyManager = 0; mSkyManager = new SkyManager(mMwRoot, mRendering.getCamera(), &environment); + mOcclusionQuery = new OcclusionQuery(&mRendering, mSkyManager->getSunNode()); + + mWater = 0; + mPlayer = new MWRender::Player (mRendering.getCamera(), playerNode); mSun = 0; @@ -64,7 +71,9 @@ RenderingManager::~RenderingManager () //TODO: destroy mSun? delete mPlayer; delete mSkyManager; + delete mTerrainManager; delete mLocalMap; + delete mOcclusionQuery; } MWRender::SkyManager* RenderingManager::getSkyManager() @@ -88,14 +97,33 @@ OEngine::Render::Fader* RenderingManager::getFader() return mRendering.getFader(); } -void RenderingManager::removeCell (MWWorld::Ptr::CellStore *store){ +void RenderingManager::removeCell (MWWorld::Ptr::CellStore *store) +{ mObjects.removeCell(store); mActors.removeCell(store); + if (store->cell->isExterior()) + mTerrainManager->cellRemoved(store); +} + +void RenderingManager::removeWater () +{ + if(mWater){ + delete mWater; + mWater = 0; + } +} + +void RenderingManager::toggleWater() +{ + if (mWater) + mWater->toggle(); } void RenderingManager::cellAdded (MWWorld::Ptr::CellStore *store) { mObjects.buildStaticGeometry (*store); + if (store->cell->isExterior()) + mTerrainManager->cellAdded(store); } void RenderingManager::addObject (const MWWorld::Ptr& ptr){ @@ -136,18 +164,45 @@ void RenderingManager::moveObjectToCell (const MWWorld::Ptr& ptr, const Ogre::Ve void RenderingManager::update (float duration){ mActors.update (duration); - + + mOcclusionQuery->update(duration); + mSkyManager->update(duration); - + + mSkyManager->setGlare(mOcclusionQuery->getSunVisibility()); + mRendering.update(duration); mLocalMap->updatePlayer( mRendering.getCamera()->getRealPosition(), mRendering.getCamera()->getRealDirection() ); + + checkUnderwater(); +} +void RenderingManager::waterAdded (MWWorld::Ptr::CellStore *store){ + if(store->cell->data.flags & store->cell->HasWater){ + if(mWater == 0) + mWater = new MWRender::Water(mRendering.getCamera(), store->cell); + else + mWater->changeCell(store->cell); + //else + + } + else + removeWater(); + +} + +void RenderingManager::setWaterHeight(const float height) +{ + if (mWater) + mWater->setHeight(height); } void RenderingManager::skyEnable () { if(mSkyManager) mSkyManager->enable(); + + mOcclusionQuery->setSunNode(mSkyManager->getSunNode()); } void RenderingManager::skyDisable () @@ -236,17 +291,17 @@ void RenderingManager::setAmbientMode() { case 0: - mRendering.getScene()->setAmbientLight(mAmbientColor); + setAmbientColour(mAmbientColor); break; case 1: - mRendering.getScene()->setAmbientLight(0.7f*mAmbientColor + 0.3f*ColourValue(1,1,1)); + setAmbientColour(0.7f*mAmbientColor + 0.3f*ColourValue(1,1,1)); break; case 2: - mRendering.getScene()->setAmbientLight(ColourValue(1,1,1)); + setAmbientColour(ColourValue(1,1,1)); break; } } @@ -286,6 +341,11 @@ void RenderingManager::toggleLight() setAmbientMode(); } +void RenderingManager::checkUnderwater(){ + if(mWater){ + mWater->checkUnderwater( mRendering.getCamera()->getRealPosition().y ); + } +} void RenderingManager::playAnimationGroup (const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number) @@ -301,11 +361,13 @@ void RenderingManager::skipAnimation (const MWWorld::Ptr& ptr) void RenderingManager::setSunColour(const Ogre::ColourValue& colour) { mSun->setDiffuseColour(colour); + mTerrainManager->setDiffuse(colour); } void RenderingManager::setAmbientColour(const Ogre::ColourValue& colour) { mRendering.getScene()->setAmbientLight(colour); + mTerrainManager->setAmbient(colour); } void RenderingManager::sunEnable() diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 78a1d2fdb..01decf57c 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -3,6 +3,7 @@ #include "sky.hpp" +#include "terrain.hpp" #include "debugging.hpp" #include "../mwworld/class.hpp" @@ -24,7 +25,9 @@ #include "objects.hpp" #include "actors.hpp" #include "player.hpp" +#include "water.hpp" #include "localmap.hpp" +#include "occlusionquery.hpp" namespace Ogre { @@ -59,6 +62,8 @@ class RenderingManager: private RenderingInterface { RenderingManager(OEngine::Render::OgreRenderer& _rend, const boost::filesystem::path& resDir, OEngine::Physic::PhysicEngine* engine, MWWorld::Environment& environment); virtual ~RenderingManager(); + + virtual MWRender::Player& getPlayer(); /// \todo move this to private again as soon as /// MWWorld::Player has been rewritten to not need access /// to internal details of the rendering system anymore @@ -67,7 +72,7 @@ class RenderingManager: private RenderingInterface { void toggleLight(); bool toggleRenderMode(int mode); - + OEngine::Render::Fader* getFader(); void removeCell (MWWorld::Ptr::CellStore *store); @@ -75,6 +80,9 @@ class RenderingManager: private RenderingInterface { /// \todo this function should be removed later. Instead the rendering subsystems should track /// when rebatching is needed and update automatically at the end of each frame. void cellAdded (MWWorld::Ptr::CellStore *store); + void waterAdded(MWWorld::Ptr::CellStore *store); + + void removeWater(); void preCellChange (MWWorld::Ptr::CellStore* store); ///< this event is fired immediately before changing cell @@ -86,17 +94,24 @@ class RenderingManager: private RenderingInterface { void scaleObject (const MWWorld::Ptr& ptr, const Ogre::Vector3& scale); void rotateObject (const MWWorld::Ptr& ptr, const::Ogre::Quaternion& orientation); + void checkUnderwater(); + void setWaterHeight(const float height); + void toggleWater(); + /// \param store Cell the object was in previously (\a ptr has already been updated to the new cell). void moveObjectToCell (const MWWorld::Ptr& ptr, const Ogre::Vector3& position, MWWorld::Ptr::CellStore *store); void update (float duration); - + void setAmbientColour(const Ogre::ColourValue& colour); void setSunColour(const Ogre::ColourValue& colour); void setSunDirection(const Ogre::Vector3& direction); void sunEnable(); void sunDisable(); - + + bool occlusionQuerySupported() { return mOcclusionQuery->supported(); }; + OcclusionQuery* getOcclusionQuery() { return mOcclusionQuery; }; + void setGlare(bool glare); void skyEnable (); void skyDisable (); @@ -109,13 +124,13 @@ class RenderingManager: private RenderingInterface { void requestMap (MWWorld::Ptr::CellStore* cell); ///< request the local map for a cell - + /// configure fog according to cell void configureFog(ESMS::CellStore &mCell); - + /// configure fog manually void configureFog(const float density, const Ogre::ColourValue& colour); - + void playAnimationGroup (const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number = 1); ///< Run animation for a MW-reference. Calls to this function for references that are currently not @@ -131,9 +146,15 @@ class RenderingManager: private RenderingInterface { private: void setAmbientMode(); - + SkyManager* mSkyManager; - + + OcclusionQuery* mOcclusionQuery; + + TerrainManager* mTerrainManager; + + MWRender::Water *mWater; + OEngine::Render::OgreRenderer &mRendering; MWRender::Objects mObjects; @@ -149,7 +170,6 @@ class RenderingManager: private RenderingInterface { /// that the OGRE coordinate system matches that used internally in /// Morrowind. Ogre::SceneNode *mMwRoot; - Ogre::RaySceneQuery *mRaySceneQuery; OEngine::Physic::PhysicEngine* mPhysicsEngine; diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index a41bc21e0..2fdf9b2cd 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -12,6 +12,7 @@ #include "../mwworld/environment.hpp" #include "../mwworld/world.hpp" +#include "occlusionquery.hpp" using namespace MWRender; using namespace Ogre; @@ -30,7 +31,7 @@ BillboardObject::BillboardObject() void BillboardObject::setVisible(const bool visible) { - mNode->setVisible(visible); + mBBSet->setVisible(visible); } void BillboardObject::setSize(const float size) @@ -88,7 +89,7 @@ void BillboardObject::init(const String& textureName, /// \todo These billboards are not 100% correct, might want to revisit them later mBBSet = sceneMgr->createBillboardSet("SkyBillboardSet"+StringConverter::toString(bodyCount), 1); mBBSet->setDefaultDimensions(550.f*initialSize, 550.f*initialSize); - mBBSet->setRenderQueueGroup(RENDER_QUEUE_SKIES_EARLY+2); + mBBSet->setRenderQueueGroup(RENDER_QUEUE_MAIN+2); mBBSet->setBillboardType(BBT_PERPENDICULAR_COMMON); mBBSet->setCommonDirection( -position.normalisedCopy() ); mNode = rootNode->createChildSceneNode(); @@ -319,19 +320,22 @@ SkyManager::SkyManager (SceneNode* pMwRoot, Camera* pCamera, MWWorld::Environmen , mThunderTextureUnit(NULL) , mRemainingTransitionTime(0.0f) , mGlareFade(0.0f) + , mGlare(0.0f) , mEnabled(true) - , mGlareEnabled(true) , mSunEnabled(true) , mMasserEnabled(true) , mSecundaEnabled(true) + , mCreated(false) { - mViewport = pCamera->getViewport(); mSceneMgr = pMwRoot->getCreator(); mRootNode = pCamera->getParentSceneNode()->createChildSceneNode(); mRootNode->pitch(Degree(-90)); // convert MW to ogre coordinates mRootNode->setInheritOrientation(false); +} +void SkyManager::create() +{ /// \todo preload all the textures and meshes that are used for sky rendering // Create overlay used for thunderstorm @@ -532,7 +536,7 @@ SkyManager::SkyManager (SceneNode* pMwRoot, Camera* pCamera, MWWorld::Environmen " uniform float4 emissive \n" ") \n" "{ \n" - " uv += float2(1,0) * time * speed * 0.003; \n" // Scroll in x direction + " uv += float2(0,1) * time * speed * 0.003; \n" // Scroll in y direction " float4 tex = lerp(tex2D(texture, uv), tex2D(secondTexture, uv), transitionFactor); \n" " oColor = color * float4(emissive.xyz,1) * tex * float4(1,1,1,opacity); \n" "}"; @@ -561,6 +565,8 @@ SkyManager::SkyManager (SceneNode* pMwRoot, Camera* pCamera, MWWorld::Environmen mCloudMaterial->getTechnique(0)->getPass(0)->setSceneBlending(SBT_TRANSPARENT_ALPHA); mCloudMaterial->getTechnique(0)->getPass(0)->createTextureUnitState(""); + + mCreated = true; } SkyManager::~SkyManager() @@ -573,11 +579,13 @@ SkyManager::~SkyManager() int SkyManager::getMasserPhase() const { + if (!mCreated) return 0; return mMasser->getPhaseInt(); } int SkyManager::getSecundaPhase() const { + if (!mCreated) return 0; return mSecunda->getPhaseInt(); } @@ -592,10 +600,23 @@ void SkyManager::update(float duration) mMasser->setPhase( static_cast( (int) ((mDay % 32)/4.f)) ); mSecunda->setPhase ( static_cast( (int) ((mDay % 32)/4.f)) ); - // increase the strength of the sun glare effect depending - // on how directly the player is looking at the sun + if (mSunEnabled) { + // take 1/5 sec for fading the glare effect from invisible to full + if (mGlareFade > mGlare) + { + mGlareFade -= duration*5; + if (mGlareFade < mGlare) mGlareFade = mGlare; + } + else if (mGlareFade < mGlare) + { + mGlareFade += duration*5; + if (mGlareFade > mGlare) mGlareFade = mGlare; + } + + // increase the strength of the sun glare effect depending + // on how directly the player is looking at the sun Vector3 sun = mSunGlare->getPosition(); sun = Vector3(sun.x, sun.z, -sun.y); Vector3 cam = mViewport->getCamera()->getRealDirection(); @@ -603,21 +624,10 @@ void SkyManager::update(float duration) float val = 1- (angle.valueDegrees() / 180.f); val = (val*val*val*val)*2; - if (mGlareEnabled) - { - mGlareFade += duration*3; - if (mGlareFade > 1) mGlareFade = 1; - } - else - { - mGlareFade -= duration*3; - if (mGlareFade < 0.3) mGlareFade = 0; - } - - mSunGlare->setSize(val * (mGlareFade)); + mSunGlare->setSize(val * mGlareFade); } - mSunGlare->setVisible(mGlareFade>0 && mSunEnabled); + mSunGlare->setVisible(mSunEnabled); mSun->setVisible(mSunEnabled); mMasser->setVisible(mMasserEnabled); mSecunda->setVisible(mSecundaEnabled); @@ -628,6 +638,9 @@ void SkyManager::update(float duration) void SkyManager::enable() { + if (!mCreated) + create(); + mRootNode->setVisible(true); mEnabled = true; } @@ -651,6 +664,7 @@ void SkyManager::setCloudsOpacity(float opacity) void SkyManager::setWeather(const MWWorld::WeatherResult& weather) { + if (!mCreated) return; if (mClouds != weather.mCloudTexture) { mCloudMaterial->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName("textures\\"+weather.mCloudTexture); @@ -719,15 +733,15 @@ void SkyManager::setWeather(const MWWorld::WeatherResult& weather) else strength = 1.f; - mSunGlare->setVisibility(weather.mGlareView * strength); - mSun->setVisibility(strength); + mSunGlare->setVisibility(weather.mGlareView * mGlareFade * strength); + mSun->setVisibility(mGlareFade >= 0.5 ? weather.mGlareView * mGlareFade * strength : 0); mAtmosphereNight->setVisible(weather.mNight && mEnabled); } -void SkyManager::setGlare(bool glare) +void SkyManager::setGlare(const float glare) { - mGlareEnabled = glare; + mGlare = glare; } Vector3 SkyManager::getRealSunPos() @@ -747,17 +761,20 @@ void SkyManager::sunDisable() void SkyManager::setSunDirection(const Vector3& direction) { + if (!mCreated) return; mSun->setPosition(direction); mSunGlare->setPosition(direction); } void SkyManager::setMasserDirection(const Vector3& direction) { + if (!mCreated) return; mMasser->setPosition(direction); } void SkyManager::setSecundaDirection(const Vector3& direction) { + if (!mCreated) return; mSecunda->setPosition(direction); } @@ -783,6 +800,7 @@ void SkyManager::secundaDisable() void SkyManager::setThunder(const float factor) { + if (!mCreated) return; if (factor > 0.f) { mThunderOverlay->show(); @@ -812,3 +830,9 @@ void SkyManager::setDate(int day, int month) mDay = day; mMonth = month; } + +Ogre::SceneNode* SkyManager::getSunNode() +{ + if (!mCreated) return 0; + return mSun->getNode(); +} diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index bf52afd8d..baf5933cb 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -109,61 +109,68 @@ namespace MWRender public: SkyManager(Ogre::SceneNode* pMwRoot, Ogre::Camera* pCamera, MWWorld::Environment* env); ~SkyManager(); - + void update(float duration); - + + void create(); + ///< no need to call this, automatically done on first enable() + void enable(); - + void disable(); - + void setHour (double hour); ///< will be called even when sky is disabled. - + void setDate (int day, int month); ///< will be called even when sky is disabled. - + int getMasserPhase() const; ///< 0 new moon, 1 waxing or waning cresecent, 2 waxing or waning half, /// 3 waxing or waning gibbous, 4 full moon - + int getSecundaPhase() const; ///< 0 new moon, 1 waxing or waning cresecent, 2 waxing or waning half, /// 3 waxing or waning gibbous, 4 full moon - + void setMoonColour (bool red); ///< change Secunda colour to red - + void setCloudsOpacity(float opacity); ///< change opacity of the clouds - + void setWeather(const MWWorld::WeatherResult& weather); - + + Ogre::SceneNode* getSunNode(); + void sunEnable(); - + void sunDisable(); - + void setSunDirection(const Ogre::Vector3& direction); - + void setMasserDirection(const Ogre::Vector3& direction); - + void setSecundaDirection(const Ogre::Vector3& direction); - + void setMasserFade(const float fade); - + void setSecundaFade(const float fade); - + void masserEnable(); void masserDisable(); void secundaEnable(); void secundaDisable(); - + void setThunder(const float factor); - - void setGlare(bool glare); + + void setGlare(const float glare); Ogre::Vector3 getRealSunPos(); - + private: + bool mCreated; + MWWorld::Environment* mEnvironment; float mHour; int mDay; @@ -203,12 +210,12 @@ namespace MWRender float mRemainingTransitionTime; - float mGlareFade; + float mGlare; // target + float mGlareFade; // actual void ModVertexAlpha(Ogre::Entity* ent, unsigned int meshType); bool mEnabled; - bool mGlareEnabled; bool mSunEnabled; bool mMasserEnabled; bool mSecundaEnabled; diff --git a/apps/openmw/mwrender/terrain.cpp b/apps/openmw/mwrender/terrain.cpp new file mode 100644 index 000000000..887721565 --- /dev/null +++ b/apps/openmw/mwrender/terrain.cpp @@ -0,0 +1,510 @@ +#include +#include +#include + +#include "../mwworld/world.hpp" + +#include "terrainmaterial.hpp" +#include "terrain.hpp" + + +using namespace Ogre; + +namespace MWRender +{ + + //---------------------------------------------------------------------------------------------- + + TerrainManager::TerrainManager(Ogre::SceneManager* mgr, const MWWorld::Environment& evn) : + mEnvironment(evn), mTerrainGroup(TerrainGroup(mgr, Terrain::ALIGN_X_Z, mLandSize, mWorldSize)) + { + + TerrainMaterialGeneratorPtr matGen; + TerrainMaterialGeneratorB* matGenP = new TerrainMaterialGeneratorB(); + matGen.bind(matGenP); + mTerrainGlobals.setDefaultMaterialGenerator(matGen); + + TerrainMaterialGenerator::Profile* const activeProfile = + mTerrainGlobals.getDefaultMaterialGenerator() + ->getActiveProfile(); + mActiveProfile = static_cast(activeProfile); + + //The pixel error should be as high as possible without it being noticed + //as it governs how fast mesh quality decreases. + mTerrainGlobals.setMaxPixelError(8); + + mTerrainGlobals.setLayerBlendMapSize(32); + mTerrainGlobals.setDefaultGlobalColourMapSize(65); + + //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); + + mActiveProfile->setLightmapEnabled(false); + mActiveProfile->setLayerSpecularMappingEnabled(false); + mActiveProfile->setLayerNormalMappingEnabled(false); + mActiveProfile->setLayerParallaxMappingEnabled(false); + mActiveProfile->setReceiveDynamicShadowsEnabled(false); + + //composite maps lead to a drastic reduction in loading time so are + //disabled + mActiveProfile->setCompositeMapEnabled(false); + + mTerrainGroup.setOrigin(Vector3(mWorldSize/2, + 0, + -mWorldSize/2)); + + Terrain::ImportData& importSettings = mTerrainGroup.getDefaultImportSettings(); + + importSettings.inputBias = 0; + importSettings.terrainSize = mLandSize; + importSettings.worldSize = mWorldSize; + importSettings.minBatchSize = 9; + importSettings.maxBatchSize = mLandSize; + + importSettings.deleteInputData = true; + } + + //---------------------------------------------------------------------------------------------- + + TerrainManager::~TerrainManager() + { + } + + //---------------------------------------------------------------------------------------------- + + 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->cell->getGridX(); + const int cellY = store->cell->getGridY(); + + ESM::Land* land = mEnvironment.mWorld->getStore().lands.search(cellX, cellY); + if ( land != NULL ) + { + land->loadData(); + } + + //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); + + if ( land != NULL ) + { + //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->landData->heights[yOffset + xOffset], + mLandSize*sizeof(float)); + } + } + else + { + memset(terrainData.inputFloat, 0, mLandSize*mLandSize*sizeof(float)); + } + + std::map indexes; + initTerrainTextures(&terrainData, cellX, cellY, + x * numTextures, y * numTextures, + numTextures, indexes); + + 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); + + if ( land && land->landData->usingColours ) + { + // disable or enable global colour map (depends on available vertex colours) + mActiveProfile->setGlobalColourMapEnabled(true); + TexturePtr vertex = getVertexColours(land, + cellX, cellY, + x*(mLandSize-1), + y*(mLandSize-1), + mLandSize); + + //this is a hack to get around the fact that Ogre seems to + //corrupt the global colour map leading to rendering errors + MaterialPtr mat = terrain->getMaterial(); + mat->getTechnique(0)->getPass(0)->getTextureUnitState(1)->setTextureName( vertex->getName() ); + //mat = terrain->_getCompositeMapMaterial(); + //mat->getTechnique(0)->getPass(0)->getTextureUnitState(1)->setTextureName( vertex->getName() ); + } + else + { + mActiveProfile->setGlobalColourMapEnabled(false); + } + } + } + } + + mTerrainGroup.freeTemporaryResources(); + } + + //---------------------------------------------------------------------------------------------- + + void TerrainManager::cellRemoved(MWWorld::Ptr::CellStore *store) + { + for ( int x = 0; x < 2; x++ ) + { + for ( int y = 0; y < 2; y++ ) + { + mTerrainGroup.unloadTerrain(store->cell->getGridX() * 2 + x, + store->cell->getGridY() * 2 + y); + } + } + } + + //---------------------------------------------------------------------------------------------- + + void TerrainManager::initTerrainTextures(Terrain::ImportData* terrainData, + int cellX, int cellY, + int fromX, int fromY, int size, + std::map& indexes) + { + 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 + std::set ltexIndexes; + for ( int y = fromY - 1; y < fromY + size + 1; y++ ) + { + for ( int x = fromX - 1; x < fromX + size + 1; x++ ) + { + ltexIndexes.insert(getLtexIndexAt(cellX, cellY, x, y)); + } + } + + //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 ) + { + const 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 + + assert( (int)mEnvironment.mWorld->getStore().landTexts.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 = mEnvironment.mWorld->getStore().landTexts.search(ltexIndex-1)->texture; + //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(); + const int splatSize = blendMapSize / size; + + //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 - 1; texY < fromY + size + 1; texY++ ) + { + for ( int texX = fromX - 1; texX < fromX + size + 1; texX++ ) + { + 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; + const int relY = texY - fromY; + + const int layerIndex = indexes.find(ltexIndex)->second; + + float* const pBlend = terrain->getLayerBlendMap(layerIndex) + ->getBlendPointer(); + + for ( int y = -1; y < splatSize + 1; y++ ) + { + for ( int x = -1; x < splatSize + 1; x++ ) + { + + //Note: Y is reversed + const int splatY = blendMapSize - 1 - relY * splatSize - y; + const int splatX = relX * splatSize + x; + + if ( splatX >= 0 && splatX < blendMapSize && + splatY >= 0 && splatY < blendMapSize ) + { + const int index = (splatY)*blendMapSize + splatX; + + if ( y >= 0 && y < splatSize && + x >= 0 && x < splatSize ) + { + pBlend[index] = 1; + } + else + { + //this provides a transition shading but also + //rounds off the corners slightly + pBlend[index] = std::min(1.0f, pBlend[index] + 0.5f); + } + } + + } + } + } + } + + 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 = mEnvironment.mWorld->getStore().lands.search(cellX, cellY); + if ( land != NULL ) + { + land->loadData(); + return land->landData + ->textures[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->landData->colours; + 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 new file mode 100644 index 000000000..29a4ba36b --- /dev/null +++ b/apps/openmw/mwrender/terrain.hpp @@ -0,0 +1,117 @@ +#ifndef _GAME_RENDER_TERRAIN_H +#define _GAME_RENDER_TERRAIN_H + +#include +#include +#include "terrainmaterial.hpp" + +#include "../mwworld/ptr.hpp" + +namespace Ogre{ + class SceneManager; + class TerrainGroup; + class TerrainGlobalOptions; + class Terrain; +} + +namespace MWRender{ + + /** + * 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, const MWWorld::Environment& env); + virtual ~TerrainManager(); + + void setDiffuse(const Ogre::ColourValue& diffuse); + void setAmbient(const Ogre::ColourValue& ambient); + + void cellAdded(MWWorld::Ptr::CellStore* store); + void cellRemoved(MWWorld::Ptr::CellStore* store); + private: + Ogre::TerrainGlobalOptions mTerrainGlobals; + Ogre::TerrainGroup mTerrainGroup; + + const MWWorld::Environment& mEnvironment; + + Ogre::TerrainMaterialGeneratorB::SM2Profile* 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); + + /** + * 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 new file mode 100644 index 000000000..67ebf45af --- /dev/null +++ b/apps/openmw/mwrender/terrainmaterial.cpp @@ -0,0 +1,1741 @@ +/* +----------------------------------------------------------------------------- +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. +----------------------------------------------------------------------------- +*/ +#include "terrainmaterial.hpp" +#include "OgreTerrain.h" +#include "OgreMaterialManager.h" +#include "OgreTechnique.h" +#include "OgrePass.h" +#include "OgreTextureUnitState.h" +#include "OgreGpuProgramManager.h" +#include "OgreHighLevelGpuProgramManager.h" +#include "OgreHardwarePixelBuffer.h" +#include "OgreShadowCameraSetupPSSM.h" + +#define POINTLIGHTS + +namespace Ogre +{ + //--------------------------------------------------------------------- + TerrainMaterialGeneratorB::TerrainMaterialGeneratorB() + { + // define the layers + // We expect terrain textures to have no alpha, so we use the alpha channel + // in the albedo texture to store specular reflection + // similarly we double-up the normal and height (for parallax) + mLayerDecl.samplers.push_back(TerrainLayerSampler("albedo_specular", PF_BYTE_RGBA)); + //mLayerDecl.samplers.push_back(TerrainLayerSampler("normal_height", PF_BYTE_RGBA)); + + mLayerDecl.elements.push_back( + TerrainLayerSamplerElement(0, TLSS_ALBEDO, 0, 3)); + //mLayerDecl.elements.push_back( + // TerrainLayerSamplerElement(0, TLSS_SPECULAR, 3, 1)); + //mLayerDecl.elements.push_back( + // TerrainLayerSamplerElement(1, TLSS_NORMAL, 0, 3)); + //mLayerDecl.elements.push_back( + // TerrainLayerSamplerElement(1, TLSS_HEIGHT, 3, 1)); + + + mProfiles.push_back(OGRE_NEW SM2Profile(this, "SM2", "Profile for rendering on Shader Model 2 capable cards")); + // TODO - check hardware capabilities & use fallbacks if required (more profiles needed) + setActiveProfile("SM2"); + + } + //--------------------------------------------------------------------- + TerrainMaterialGeneratorB::~TerrainMaterialGeneratorB() + { + + } + //--------------------------------------------------------------------- + //--------------------------------------------------------------------- + TerrainMaterialGeneratorB::SM2Profile::SM2Profile(TerrainMaterialGenerator* parent, const String& name, const String& desc) + : Profile(parent, name, desc) + , mShaderGen(0) + , mLayerNormalMappingEnabled(true) + , mLayerParallaxMappingEnabled(true) + , mLayerSpecularMappingEnabled(true) + , mGlobalColourMapEnabled(true) + , mLightmapEnabled(true) + , mCompositeMapEnabled(true) + , mReceiveDynamicShadows(true) + , mPSSM(0) + , mDepthShadows(false) + , mLowLodShadows(false) + { + + } + //--------------------------------------------------------------------- + TerrainMaterialGeneratorB::SM2Profile::~SM2Profile() + { + OGRE_DELETE mShaderGen; + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::requestOptions(Terrain* terrain) + { + terrain->_setMorphRequired(true); + terrain->_setNormalMapRequired(true); + terrain->_setLightMapRequired(mLightmapEnabled, true); + terrain->_setCompositeMapRequired(mCompositeMapEnabled); + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::setLayerNormalMappingEnabled(bool enabled) + { + if (enabled != mLayerNormalMappingEnabled) + { + mLayerNormalMappingEnabled = enabled; + mParent->_markChanged(); + } + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::setLayerParallaxMappingEnabled(bool enabled) + { + if (enabled != mLayerParallaxMappingEnabled) + { + mLayerParallaxMappingEnabled = enabled; + mParent->_markChanged(); + } + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::setLayerSpecularMappingEnabled(bool enabled) + { + if (enabled != mLayerSpecularMappingEnabled) + { + mLayerSpecularMappingEnabled = enabled; + mParent->_markChanged(); + } + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::setGlobalColourMapEnabled(bool enabled) + { + if (enabled != mGlobalColourMapEnabled) + { + mGlobalColourMapEnabled = enabled; + //mParent->_markChanged(); + } + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::setLightmapEnabled(bool enabled) + { + if (enabled != mLightmapEnabled) + { + mLightmapEnabled = enabled; + mParent->_markChanged(); + } + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::setCompositeMapEnabled(bool enabled) + { + if (enabled != mCompositeMapEnabled) + { + mCompositeMapEnabled = enabled; + mParent->_markChanged(); + } + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::setReceiveDynamicShadowsEnabled(bool enabled) + { + if (enabled != mReceiveDynamicShadows) + { + mReceiveDynamicShadows = enabled; + mParent->_markChanged(); + } + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::setReceiveDynamicShadowsPSSM(PSSMShadowCameraSetup* pssmSettings) + { + if (pssmSettings != mPSSM) + { + mPSSM = pssmSettings; + mParent->_markChanged(); + } + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::setReceiveDynamicShadowsDepth(bool enabled) + { + if (enabled != mDepthShadows) + { + mDepthShadows = enabled; + mParent->_markChanged(); + } + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::setReceiveDynamicShadowsLowLod(bool enabled) + { + if (enabled != mLowLodShadows) + { + mLowLodShadows = enabled; + mParent->_markChanged(); + } + } + //--------------------------------------------------------------------- + uint8 TerrainMaterialGeneratorB::SM2Profile::getMaxLayers(const Terrain* terrain) const + { + // count the texture units free + uint8 freeTextureUnits = 16; + // lightmap + if (mLightmapEnabled) + --freeTextureUnits; + // normalmap + --freeTextureUnits; + // colourmap + //if (terrain->getGlobalColourMapEnabled()) + --freeTextureUnits; + if (isShadowingEnabled(HIGH_LOD, terrain)) + { + uint numShadowTextures = 1; + if (getReceiveDynamicShadowsPSSM()) + { + numShadowTextures = getReceiveDynamicShadowsPSSM()->getSplitCount(); + } + freeTextureUnits -= numShadowTextures; + } + + // each layer needs 2.25 units (1xdiffusespec, (1xnormalheight), 0.25xblend) + return static_cast(freeTextureUnits / (1.25f + (mLayerNormalMappingEnabled||mLayerParallaxMappingEnabled))); + + + } + int TerrainMaterialGeneratorB::SM2Profile::getNumberOfLightsSupported() const + { + #ifndef POINTLIGHTS + return 1; + #else + // number of supported lights depends on the number of available constant registers, + // which in turn depends on the shader profile used + if (GpuProgramManager::getSingleton().isSyntaxSupported("ps_3_0") + || GpuProgramManager::getSingleton().isSyntaxSupported("ps_4_0") + || GpuProgramManager::getSingleton().isSyntaxSupported("fp40") + ) + return 32; + else + return 8; + #endif + } + //--------------------------------------------------------------------- + MaterialPtr TerrainMaterialGeneratorB::SM2Profile::generate(const Terrain* terrain) + { + // re-use old material if exists + MaterialPtr mat = terrain->_getMaterial(); + if (mat.isNull()) + { + MaterialManager& matMgr = MaterialManager::getSingleton(); + + // it's important that the names are deterministic for a given terrain, so + // use the terrain pointer as an ID + const String& matName = terrain->getMaterialName(); + mat = matMgr.getByName(matName); + if (mat.isNull()) + { + mat = matMgr.create(matName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); + } + } + // clear everything + mat->removeAllTechniques(); + + // Automatically disable normal & parallax mapping if card cannot handle it + // We do this rather than having a specific technique for it since it's simpler + GpuProgramManager& gmgr = GpuProgramManager::getSingleton(); + if (!gmgr.isSyntaxSupported("ps_3_0") && !gmgr.isSyntaxSupported("ps_2_x") + && !gmgr.isSyntaxSupported("fp40") && !gmgr.isSyntaxSupported("arbfp1")) + { + setLayerNormalMappingEnabled(false); + setLayerParallaxMappingEnabled(false); + } + + addTechnique(mat, terrain, HIGH_LOD); + + // LOD + if(mCompositeMapEnabled) + { + addTechnique(mat, terrain, LOW_LOD); + Material::LodValueList lodValues; + lodValues.push_back(TerrainGlobalOptions::getSingleton().getCompositeMapDistance()); + mat->setLodLevels(lodValues); + Technique* lowLodTechnique = mat->getTechnique(1); + lowLodTechnique->setLodIndex(1); + } + + updateParams(mat, terrain); + + return mat; + + } + //--------------------------------------------------------------------- + MaterialPtr TerrainMaterialGeneratorB::SM2Profile::generateForCompositeMap(const Terrain* terrain) + { + // re-use old material if exists + MaterialPtr mat = terrain->_getCompositeMapMaterial(); + if (mat.isNull()) + { + MaterialManager& matMgr = MaterialManager::getSingleton(); + + // it's important that the names are deterministic for a given terrain, so + // use the terrain pointer as an ID + const String& matName = terrain->getMaterialName() + "/comp"; + mat = matMgr.getByName(matName); + if (mat.isNull()) + { + mat = matMgr.create(matName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); + } + } + // clear everything + mat->removeAllTechniques(); + + addTechnique(mat, terrain, RENDER_COMPOSITE_MAP); + + updateParamsForCompositeMap(mat, terrain); + + return mat; + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::addTechnique( + const MaterialPtr& mat, const Terrain* terrain, TechniqueType tt) + { + + Technique* tech = mat->createTechnique(); + + // Only supporting one pass + Pass* pass = tech->createPass(); + + GpuProgramManager& gmgr = GpuProgramManager::getSingleton(); + HighLevelGpuProgramManager& hmgr = HighLevelGpuProgramManager::getSingleton(); + if (!mShaderGen) + { + bool check2x = mLayerNormalMappingEnabled || mLayerParallaxMappingEnabled; + if (hmgr.isLanguageSupported("cg")) + mShaderGen = OGRE_NEW ShaderHelperCg(); + else if (hmgr.isLanguageSupported("hlsl") && + ((check2x && gmgr.isSyntaxSupported("ps_2_x")) || + (!check2x && gmgr.isSyntaxSupported("ps_2_0")))) + mShaderGen = OGRE_NEW ShaderHelperHLSL(); + else if (hmgr.isLanguageSupported("glsl")) + mShaderGen = OGRE_NEW ShaderHelperGLSL(); + else + { + // todo + } + + // check SM3 features + mSM3Available = GpuProgramManager::getSingleton().isSyntaxSupported("ps_3_0"); + + } + HighLevelGpuProgramPtr vprog = mShaderGen->generateVertexProgram(this, terrain, tt); + HighLevelGpuProgramPtr fprog = mShaderGen->generateFragmentProgram(this, terrain, tt); + + pass->setVertexProgram(vprog->getName()); + pass->setFragmentProgram(fprog->getName()); + + if (tt == HIGH_LOD || tt == RENDER_COMPOSITE_MAP) + { + // global normal map + TextureUnitState* tu = pass->createTextureUnitState(); + tu->setTextureName(terrain->getTerrainNormalMap()->getName()); + tu->setTextureAddressingMode(TextureUnitState::TAM_CLAMP); + + // global colour map + if (isGlobalColourMapEnabled()) + { + tu = pass->createTextureUnitState(""); + tu->setTextureAddressingMode(TextureUnitState::TAM_CLAMP); + } + + // light map + if (isLightmapEnabled()) + { + tu = pass->createTextureUnitState(terrain->getLightmap()->getName()); + tu->setTextureAddressingMode(TextureUnitState::TAM_CLAMP); + } + + // blend maps + uint maxLayers = getMaxLayers(terrain); + uint numBlendTextures = std::min(terrain->getBlendTextureCount(maxLayers), terrain->getBlendTextureCount()); + uint numLayers = std::min(maxLayers, static_cast(terrain->getLayerCount())); + for (uint i = 0; i < numBlendTextures; ++i) + { + tu = pass->createTextureUnitState(terrain->getBlendTextureName(i)); + tu->setTextureAddressingMode(TextureUnitState::TAM_CLAMP); + } + + // layer textures + for (uint i = 0; i < numLayers; ++i) + { + // diffuse / specular + tu = pass->createTextureUnitState(terrain->getLayerTextureName(i, 0)); + + // normal / height + if (mLayerNormalMappingEnabled || mLayerParallaxMappingEnabled) + tu = pass->createTextureUnitState(terrain->getLayerTextureName(i, 1)); + } + + } + else + { + // LOW_LOD textures + // composite map + TextureUnitState* tu = pass->createTextureUnitState(); + tu->setTextureName(terrain->getCompositeMap()->getName()); + tu->setTextureAddressingMode(TextureUnitState::TAM_CLAMP); + + // That's it! + + } + + // Add shadow textures (always at the end) + if (isShadowingEnabled(tt, terrain)) + { + uint numTextures = 1; + if (getReceiveDynamicShadowsPSSM()) + { + numTextures = getReceiveDynamicShadowsPSSM()->getSplitCount(); + } + for (uint i = 0; i < numTextures; ++i) + { + TextureUnitState* tu = pass->createTextureUnitState(); + tu->setContentType(TextureUnitState::CONTENT_SHADOW); + tu->setTextureAddressingMode(TextureUnitState::TAM_BORDER); + tu->setTextureBorderColour(ColourValue::White); + } + } + + } + //--------------------------------------------------------------------- + bool TerrainMaterialGeneratorB::SM2Profile::isShadowingEnabled(TechniqueType tt, const Terrain* terrain) const + { + return getReceiveDynamicShadowsEnabled() && tt != RENDER_COMPOSITE_MAP && + (tt != LOW_LOD || mLowLodShadows) && + terrain->getSceneManager()->isShadowTechniqueTextureBased(); + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::updateParams(const MaterialPtr& mat, const Terrain* terrain) + { + mShaderGen->updateParams(this, mat, terrain, false); + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::updateParamsForCompositeMap(const MaterialPtr& mat, const Terrain* terrain) + { + mShaderGen->updateParams(this, mat, terrain, true); + } + //--------------------------------------------------------------------- + //--------------------------------------------------------------------- + HighLevelGpuProgramPtr + TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::generateVertexProgram( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) + { + HighLevelGpuProgramPtr ret = createVertexProgram(prof, terrain, tt); + + StringUtil::StrStreamType sourceStr; + generateVertexProgramSource(prof, terrain, tt, sourceStr); + ret->setSource(sourceStr.str()); + ret->load(); + defaultVpParams(prof, terrain, tt, ret); +#if OGRE_DEBUG_MODE + LogManager::getSingleton().stream(LML_TRIVIAL) << "*** Terrain Vertex Program: " + << ret->getName() << " ***\n" << ret->getSource() << "\n*** ***"; +#endif + + return ret; + + } + //--------------------------------------------------------------------- + HighLevelGpuProgramPtr + TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::generateFragmentProgram( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) + { + HighLevelGpuProgramPtr ret = createFragmentProgram(prof, terrain, tt); + + StringUtil::StrStreamType sourceStr; + generateFragmentProgramSource(prof, terrain, tt, sourceStr); + ret->setSource(sourceStr.str()); + ret->load(); + defaultFpParams(prof, terrain, tt, ret); + +#if OGRE_DEBUG_MODE + LogManager::getSingleton().stream(LML_TRIVIAL) << "*** Terrain Fragment Program: " + << ret->getName() << " ***\n" << ret->getSource() << "\n*** ***"; +#endif + + return ret; + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::generateVertexProgramSource( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) + { + generateVpHeader(prof, terrain, tt, outStream); + + if (tt != LOW_LOD) + { + uint maxLayers = prof->getMaxLayers(terrain); + uint numLayers = std::min(maxLayers, static_cast(terrain->getLayerCount())); + + for (uint i = 0; i < numLayers; ++i) + generateVpLayer(prof, terrain, tt, i, outStream); + } + + generateVpFooter(prof, terrain, tt, outStream); + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::generateFragmentProgramSource( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) + { + generateFpHeader(prof, terrain, tt, outStream); + + if (tt != LOW_LOD) + { + uint maxLayers = prof->getMaxLayers(terrain); + uint numLayers = std::min(maxLayers, static_cast(terrain->getLayerCount())); + + for (uint i = 0; i < numLayers; ++i) + generateFpLayer(prof, terrain, tt, i, outStream); + } + + generateFpFooter(prof, terrain, tt, outStream); + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::defaultVpParams( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, const HighLevelGpuProgramPtr& prog) + { + GpuProgramParametersSharedPtr params = prog->getDefaultParameters(); + params->setIgnoreMissingParams(true); + params->setNamedAutoConstant("worldMatrix", GpuProgramParameters::ACT_WORLD_MATRIX); + params->setNamedAutoConstant("viewProjMatrix", GpuProgramParameters::ACT_VIEWPROJ_MATRIX); + params->setNamedAutoConstant("lodMorph", GpuProgramParameters::ACT_CUSTOM, + Terrain::LOD_MORPH_CUSTOM_PARAM); + params->setNamedAutoConstant("fogParams", GpuProgramParameters::ACT_FOG_PARAMS); + + if (prof->isShadowingEnabled(tt, terrain)) + { + uint numTextures = 1; + if (prof->getReceiveDynamicShadowsPSSM()) + { + numTextures = prof->getReceiveDynamicShadowsPSSM()->getSplitCount(); + } + for (uint i = 0; i < numTextures; ++i) + { + params->setNamedAutoConstant("texViewProjMatrix" + StringConverter::toString(i), + GpuProgramParameters::ACT_TEXTURE_VIEWPROJ_MATRIX, i); + if (prof->getReceiveDynamicShadowsDepth()) + { + params->setNamedAutoConstant("depthRange" + StringConverter::toString(i), + GpuProgramParameters::ACT_SHADOW_SCENE_DEPTH_RANGE, i); + } + } + } + + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::defaultFpParams( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, const HighLevelGpuProgramPtr& prog) + { + GpuProgramParametersSharedPtr params = prog->getDefaultParameters(); + params->setIgnoreMissingParams(true); + + params->setNamedAutoConstant("ambient", GpuProgramParameters::ACT_AMBIENT_LIGHT_COLOUR); + + for (int i=0; igetNumberOfLightsSupported(); ++i) + { + params->setNamedAutoConstant("lightPosObjSpace"+StringConverter::toString(i), GpuProgramParameters::ACT_LIGHT_POSITION_OBJECT_SPACE, i); + params->setNamedAutoConstant("lightDiffuseColour"+StringConverter::toString(i), GpuProgramParameters::ACT_LIGHT_DIFFUSE_COLOUR, i); + params->setNamedAutoConstant("lightAttenuation"+StringConverter::toString(i), GpuProgramParameters::ACT_LIGHT_ATTENUATION, i); + //params->setNamedAutoConstant("lightSpecularColour"+StringConverter::toString(i), GpuProgramParameters::ACT_LIGHT_SPECULAR_COLOUR, i); + } + + params->setNamedAutoConstant("eyePosObjSpace", GpuProgramParameters::ACT_CAMERA_POSITION_OBJECT_SPACE); + params->setNamedAutoConstant("fogColour", GpuProgramParameters::ACT_FOG_COLOUR); + + if (prof->isShadowingEnabled(tt, terrain)) + { + uint numTextures = 1; + if (prof->getReceiveDynamicShadowsPSSM()) + { + PSSMShadowCameraSetup* pssm = prof->getReceiveDynamicShadowsPSSM(); + numTextures = pssm->getSplitCount(); + Vector4 splitPoints; + const PSSMShadowCameraSetup::SplitPointList& splitPointList = pssm->getSplitPoints(); + // Populate from split point 1, not 0, since split 0 isn't useful (usually 0) + for (uint i = 1; i < numTextures; ++i) + { + splitPoints[i-1] = splitPointList[i]; + } + params->setNamedConstant("pssmSplitPoints", splitPoints); + } + + if (prof->getReceiveDynamicShadowsDepth()) + { + size_t samplerOffset = (tt == HIGH_LOD) ? mShadowSamplerStartHi : mShadowSamplerStartLo; + for (uint i = 0; i < numTextures; ++i) + { + params->setNamedAutoConstant("inverseShadowmapSize" + StringConverter::toString(i), + GpuProgramParameters::ACT_INVERSE_TEXTURE_SIZE, i + samplerOffset); + } + } + } + + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::updateParams( + const SM2Profile* prof, const MaterialPtr& mat, const Terrain* terrain, bool compositeMap) + { + Pass* p = mat->getTechnique(0)->getPass(0); + if (compositeMap) + { + updateVpParams(prof, terrain, RENDER_COMPOSITE_MAP, p->getVertexProgramParameters()); + updateFpParams(prof, terrain, RENDER_COMPOSITE_MAP, p->getFragmentProgramParameters()); + } + else + { + // high lod + updateVpParams(prof, terrain, HIGH_LOD, p->getVertexProgramParameters()); + updateFpParams(prof, terrain, HIGH_LOD, p->getFragmentProgramParameters()); + + if(prof->isCompositeMapEnabled()) + { + // low lod + p = mat->getTechnique(1)->getPass(0); + updateVpParams(prof, terrain, LOW_LOD, p->getVertexProgramParameters()); + updateFpParams(prof, terrain, LOW_LOD, p->getFragmentProgramParameters()); + } + } + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::updateVpParams( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, const GpuProgramParametersSharedPtr& params) + { + params->setIgnoreMissingParams(true); + uint maxLayers = prof->getMaxLayers(terrain); + uint numLayers = std::min(maxLayers, static_cast(terrain->getLayerCount())); + uint numUVMul = numLayers / 4; + if (numLayers % 4) + ++numUVMul; + for (uint i = 0; i < numUVMul; ++i) + { + Vector4 uvMul( + terrain->getLayerUVMultiplier(i * 4), + terrain->getLayerUVMultiplier(i * 4 + 1), + terrain->getLayerUVMultiplier(i * 4 + 2), + terrain->getLayerUVMultiplier(i * 4 + 3) + ); + params->setNamedConstant("uvMul" + StringConverter::toString(i), uvMul); + } + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::updateFpParams( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, const GpuProgramParametersSharedPtr& params) + { + params->setIgnoreMissingParams(true); + // TODO - parameterise this? + Vector4 scaleBiasSpecular(0.03, -0.04, 32, 1); + params->setNamedConstant("scaleBiasSpecular", scaleBiasSpecular); + + } + //--------------------------------------------------------------------- + String TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::getChannel(uint idx) + { + uint rem = idx % 4; + switch(rem) + { + case 0: + default: + return "r"; + case 1: + return "g"; + case 2: + return "b"; + case 3: + return "a"; + }; + } + //--------------------------------------------------------------------- + String TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::getVertexProgramName( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) + { + String progName = terrain->getMaterialName() + "/sm2/vp"; + + switch(tt) + { + case HIGH_LOD: + progName += "/hlod"; + break; + case LOW_LOD: + progName += "/llod"; + break; + case RENDER_COMPOSITE_MAP: + progName += "/comp"; + break; + } + + return progName; + + } + //--------------------------------------------------------------------- + String TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::getFragmentProgramName( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) + { + + String progName = terrain->getMaterialName() + "/sm2/fp"; + + switch(tt) + { + case HIGH_LOD: + progName += "/hlod"; + break; + case LOW_LOD: + progName += "/llod"; + break; + case RENDER_COMPOSITE_MAP: + progName += "/comp"; + break; + } + + return progName; + } + //--------------------------------------------------------------------- + //--------------------------------------------------------------------- + HighLevelGpuProgramPtr + TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::createVertexProgram( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) + { + HighLevelGpuProgramManager& mgr = HighLevelGpuProgramManager::getSingleton(); + String progName = getVertexProgramName(prof, terrain, tt); + HighLevelGpuProgramPtr ret = mgr.getByName(progName); + if (ret.isNull()) + { + ret = mgr.createProgram(progName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + "cg", GPT_VERTEX_PROGRAM); + } + else + { + ret->unload(); + } + + ret->setParameter("profiles", "vs_3_0 vs_2_0 arbvp1"); + ret->setParameter("entry_point", "main_vp"); + + return ret; + + } + //--------------------------------------------------------------------- + HighLevelGpuProgramPtr + TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::createFragmentProgram( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) + { + HighLevelGpuProgramManager& mgr = HighLevelGpuProgramManager::getSingleton(); + String progName = getFragmentProgramName(prof, terrain, tt); + + HighLevelGpuProgramPtr ret = mgr.getByName(progName); + if (ret.isNull()) + { + ret = mgr.createProgram(progName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + "cg", GPT_FRAGMENT_PROGRAM); + } + else + { + ret->unload(); + } + + if(prof->isLayerNormalMappingEnabled() || prof->isLayerParallaxMappingEnabled()) + ret->setParameter("profiles", "ps_3_0 ps_2_x fp40 arbfp1"); + //else + //ret->setParameter("profiles", "ps_3_0 ps_2_0 fp30 arbfp1"); + else // fp30 doesn't work (black terrain) + ret->setParameter("profiles", "ps_3_0 ps_2_x fp40 arbfp1"); + ret->setParameter("entry_point", "main_fp"); + + return ret; + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::generateVpHeader( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) + { + outStream << + "void main_vp(\n" + "float4 pos : POSITION,\n" + "float2 uv : TEXCOORD0,\n"; + if (tt != RENDER_COMPOSITE_MAP) + outStream << "float2 delta : TEXCOORD1,\n"; // lodDelta, lodThreshold + + outStream << + "uniform float4x4 worldMatrix,\n" + "uniform float4x4 viewProjMatrix,\n" + "uniform float2 lodMorph,\n"; // morph amount, morph LOD target + + // uv multipliers + uint maxLayers = prof->getMaxLayers(terrain); + uint numLayers = std::min(maxLayers, static_cast(terrain->getLayerCount())); + uint numUVMultipliers = (numLayers / 4); + if (numLayers % 4) + ++numUVMultipliers; + for (uint i = 0; i < numUVMultipliers; ++i) + outStream << "uniform float4 uvMul" << i << ", \n"; + + outStream << + "out float4 oPos : POSITION,\n" + "out float4 oPosObj : TEXCOORD0 \n"; + + uint texCoordSet = 1; + outStream << + ", out float4 oUVMisc : TEXCOORD" << texCoordSet++ <<" // xy = uv, z = camDepth\n"; + + // layer UV's premultiplied, packed as xy/zw + uint numUVSets = numLayers / 2; + if (numLayers % 2) + ++numUVSets; + if (tt != LOW_LOD) + { + for (uint i = 0; i < numUVSets; ++i) + { + outStream << + ", out float4 oUV" << i << " : TEXCOORD" << texCoordSet++ << "\n"; + } + } + + if (prof->getParent()->getDebugLevel() && tt != RENDER_COMPOSITE_MAP) + { + outStream << ", out float2 lodInfo : TEXCOORD" << texCoordSet++ << "\n"; + } + + bool fog = terrain->getSceneManager()->getFogMode() != FOG_NONE && tt != RENDER_COMPOSITE_MAP; + if (fog) + { + outStream << + ", uniform float4 fogParams\n" + ", out float fogVal : COLOR\n"; + } + + if (prof->isShadowingEnabled(tt, terrain)) + { + texCoordSet = generateVpDynamicShadowsParams(texCoordSet, prof, terrain, tt, outStream); + } + + // check we haven't exceeded texture coordinates + if (texCoordSet > 8) + { + OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, + "Requested options require too many texture coordinate sets! Try reducing the number of layers.", + __FUNCTION__); + } + + outStream << + ")\n" + "{\n" + " float4 worldPos = mul(worldMatrix, pos);\n" + " oPosObj = pos;\n"; + + if (tt != RENDER_COMPOSITE_MAP) + { + // 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 + outStream << + " float toMorph = -min(0, sign(delta.y - lodMorph.y));\n"; + // this will either be 1 (morph) or 0 (don't morph) + if (prof->getParent()->getDebugLevel()) + { + // x == LOD level (-1 since value is target level, we want to display actual) + outStream << "lodInfo.x = (lodMorph.y - 1) / " << terrain->getNumLodLevels() << ";\n"; + // y == LOD morph + outStream << "lodInfo.y = toMorph * lodMorph.x;\n"; + } + + // morph + switch (terrain->getAlignment()) + { + case Terrain::ALIGN_X_Y: + outStream << " worldPos.z += delta.x * toMorph * lodMorph.x;\n"; + break; + case Terrain::ALIGN_X_Z: + outStream << " worldPos.y += delta.x * toMorph * lodMorph.x;\n"; + break; + case Terrain::ALIGN_Y_Z: + outStream << " worldPos.x += delta.x * toMorph * lodMorph.x;\n"; + break; + }; + } + + + // generate UVs + if (tt != LOW_LOD) + { + for (uint i = 0; i < numUVSets; ++i) + { + uint layer = i * 2; + uint uvMulIdx = layer / 4; + + outStream << + " oUV" << i << ".xy = " << " uv.xy * uvMul" << uvMulIdx << "." << getChannel(layer) << ";\n"; + outStream << + " oUV" << i << ".zw = " << " uv.xy * uvMul" << uvMulIdx << "." << getChannel(layer+1) << ";\n"; + + } + + } + + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::generateFpHeader( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) + { + + // Main header + outStream << + // helpers + "float4 expand(float4 v)\n" + "{ \n" + " return v * 2 - 1;\n" + "}\n\n\n"; + + if (prof->isShadowingEnabled(tt, terrain)) + generateFpDynamicShadowsHelpers(prof, terrain, tt, outStream); + + + outStream << + "float4 main_fp(\n" + "float4 position : TEXCOORD0,\n"; + + uint texCoordSet = 1; + outStream << + "float4 uvMisc : TEXCOORD" << texCoordSet++ << ",\n"; + + // UV's premultiplied, packed as xy/zw + uint maxLayers = prof->getMaxLayers(terrain); + uint numBlendTextures = std::min(terrain->getBlendTextureCount(maxLayers), terrain->getBlendTextureCount()); + uint numLayers = std::min(maxLayers, static_cast(terrain->getLayerCount())); + uint numUVSets = numLayers / 2; + if (numLayers % 2) + ++numUVSets; + if (tt != LOW_LOD) + { + for (uint i = 0; i < numUVSets; ++i) + { + outStream << + "float4 layerUV" << i << " : TEXCOORD" << texCoordSet++ << ", \n"; + } + + } + if (prof->getParent()->getDebugLevel() && tt != RENDER_COMPOSITE_MAP) + { + outStream << "float2 lodInfo : TEXCOORD" << texCoordSet++ << ", \n"; + } + + bool fog = terrain->getSceneManager()->getFogMode() != FOG_NONE && tt != RENDER_COMPOSITE_MAP; + if (fog) + { + outStream << + "uniform float3 fogColour, \n" + "float fogVal : COLOR,\n"; + } + + uint currentSamplerIdx = 0; + + outStream << + // Only 1 light supported in this version + // deferred shading profile / generator later, ok? :) + "uniform float3 ambient,\n"; + + + for (int i=0; igetNumberOfLightsSupported(); ++i) + { + outStream << + "uniform float4 lightPosObjSpace"<isGlobalColourMapEnabled()) + { + outStream << ", uniform sampler2D globalColourMap : register(s" + << currentSamplerIdx++ << ")\n"; + } + if (prof->isLightmapEnabled()) + { + outStream << ", uniform sampler2D lightMap : register(s" + << currentSamplerIdx++ << ")\n"; + } + // Blend textures - sampler definitions + for (uint i = 0; i < numBlendTextures; ++i) + { + outStream << ", uniform sampler2D blendTex" << i + << " : register(s" << currentSamplerIdx++ << ")\n"; + } + + // Layer textures - sampler definitions & UV multipliers + for (uint i = 0; i < numLayers; ++i) + { + outStream << ", uniform sampler2D difftex" << i + << " : register(s" << currentSamplerIdx++ << ")\n"; + + if (prof->mLayerNormalMappingEnabled || prof->mLayerParallaxMappingEnabled) + outStream << ", uniform sampler2D normtex" << i + << " : register(s" << currentSamplerIdx++ << ")\n"; + } + } + + if (prof->isShadowingEnabled(tt, terrain)) + { + generateFpDynamicShadowsParams(&texCoordSet, ¤tSamplerIdx, prof, terrain, tt, outStream); + } + + // check we haven't exceeded samplers + if (currentSamplerIdx > 16) + { + OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, + "Requested options require too many texture samplers! Try reducing the number of layers.", + __FUNCTION__); + } + + outStream << + ") : COLOR\n" + "{\n" + " float4 outputCol;\n" + " float shadow = 1.0;\n" + " float2 uv = uvMisc.xy;\n" + // base colour + " outputCol = float4(0,0,0,1);\n"; + + if (tt != LOW_LOD) + { + outStream << + // global normal + " float3 normal = expand(tex2D(globalNormal, uv)).rgb;\n"; + + // not needed anymore apparently + //" normal = float3(normal.x, normal.z, -normal.y); \n"; // convert Ogre to MW coordinate system + + } + + for (int i=0; igetNumberOfLightsSupported(); ++i) + outStream << + " float3 lightDir"<isLayerNormalMappingEnabled()) + { + // derive the tangent space basis + // we do this in the pixel shader because we don't have per-vertex normals + // because of the LOD, we use a normal map + // tangent is always +x or -z in object space depending on alignment + switch(terrain->getAlignment()) + { + case Terrain::ALIGN_X_Y: + case Terrain::ALIGN_X_Z: + outStream << " float3 tangent = float3(1, 0, 0);\n"; + break; + case Terrain::ALIGN_Y_Z: + outStream << " float3 tangent = float3(0, 0, -1);\n"; + break; + }; + + outStream << " float3 binormal = normalize(cross(tangent, normal));\n"; + // note, now we need to re-cross to derive tangent again because it wasn't orthonormal + outStream << " tangent = normalize(cross(normal, binormal));\n"; + // derive final matrix + outStream << " float3x3 TBN = float3x3(tangent, binormal, normal);\n"; + + // set up lighting result placeholders for interpolation + outStream << " float4 litRes, litResLayer;\n"; + outStream << " float3 TSlightDir, TSeyeDir, TShalfAngle, TSnormal;\n"; + if (prof->isLayerParallaxMappingEnabled()) + outStream << " float displacement;\n"; + // move + outStream << " TSlightDir = normalize(mul(TBN, lightDir));\n"; + outStream << " TSeyeDir = normalize(mul(TBN, eyeDir));\n"; + + } + else + { + #ifdef POINTLIGHTS + outStream << "float d; \n" + "float attn; \n"; + #endif + + outStream << + " eyeDir = normalize(eyeDir); \n"; + + // simple per-pixel lighting with no normal mapping + for (int i=0; igetNumberOfLightsSupported(); ++i) + { + outStream << " float3 halfAngle"<_isSM3Available()) + outStream << " if (" << blendWeightStr << " > 0.0003)\n { \n"; + + + // generate UV + outStream << " float2 uv" << layer << " = layerUV" << uvIdx << uvChannels << ";\n"; + + // calculate lighting here if normal mapping + if (prof->isLayerNormalMappingEnabled()) + { + if (prof->isLayerParallaxMappingEnabled() && tt != RENDER_COMPOSITE_MAP) + { + // modify UV - note we have to sample an extra time + outStream << " displacement = tex2D(normtex" << layer << ", uv" << layer << ").a\n" + " * scaleBiasSpecular.x + scaleBiasSpecular.y;\n"; + outStream << " uv" << layer << " += TSeyeDir.xy * displacement;\n"; + } + + // access TS normal map + outStream << " TSnormal = expand(tex2D(normtex" << layer << ", uv" << layer << ")).rgb;\n"; + outStream << " TShalfAngle = normalize(TSlightDir + TSeyeDir);\n"; + outStream << " litResLayer = lit(dot(TSlightDir, TSnormal), dot(TShalfAngle, TSnormal), scaleBiasSpecular.z);\n"; + if (!layer) + outStream << " litRes = litResLayer;\n"; + else + outStream << " litRes = lerp(litRes, litResLayer, " << blendWeightStr << ");\n"; + + } + + // sample diffuse texture + outStream << " float4 diffuseSpecTex" << layer + << " = tex2D(difftex" << layer << ", uv" << layer << ");\n"; + + // apply to common + if (!layer) + { + outStream << " diffuse = diffuseSpecTex0.rgb;\n"; + if (prof->isLayerSpecularMappingEnabled()) + outStream << " specular = diffuseSpecTex0.a;\n"; + } + else + { + outStream << " diffuse = lerp(diffuse, diffuseSpecTex" << layer + << ".rgb, " << blendWeightStr << ");\n"; + if (prof->isLayerSpecularMappingEnabled()) + outStream << " specular = lerp(specular, diffuseSpecTex" << layer + << ".a, " << blendWeightStr << ");\n"; + + } + + // End early-out + // Disable - causing some issues even when trying to force the use of texldd + + // comment by scrawl: + // on a NVIDIA card in opengl mode, didn't produce any problems, + // while increasing FPS from 170 to 185 (!!!) in the same area + // so let's try this out - if something does cause problems for + // someone else (with a different card / renderer) we can just + // add a vendor-specific check here + if (layer && prof->_isSM3Available()) + outStream << " } // early-out blend value\n"; + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::generateVpFooter( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) + { + + outStream << + " oPos = mul(viewProjMatrix, worldPos);\n" + " oUVMisc.xy = uv.xy;\n"; + + bool fog = terrain->getSceneManager()->getFogMode() != FOG_NONE && tt != RENDER_COMPOSITE_MAP; + if (fog) + { + if (terrain->getSceneManager()->getFogMode() == FOG_LINEAR) + { + outStream << + " fogVal = saturate((oPos.z - fogParams.y) * fogParams.w);\n"; + } + else + { + outStream << + " fogVal = saturate(1 / (exp(oPos.z * fogParams.x)));\n"; + } + } + + if (prof->isShadowingEnabled(tt, terrain)) + generateVpDynamicShadows(prof, terrain, tt, outStream); + + outStream << + "}\n"; + + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::generateFpFooter( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) + { + + if (tt == LOW_LOD) + { + if (prof->isShadowingEnabled(tt, terrain)) + { + generateFpDynamicShadows(prof, terrain, tt, outStream); + outStream << + " outputCol.rgb = diffuse * rtshadow;\n"; + } + else + { + outStream << + " outputCol.rgb = diffuse;\n"; + } + } + else + { + if (prof->isGlobalColourMapEnabled()) + { + // sample colour map and apply to diffuse + outStream << " diffuse *= tex2D(globalColourMap, uv).rgb;\n"; + } + if (prof->isLightmapEnabled()) + { + // sample lightmap + outStream << " shadow = tex2D(lightMap, uv).r;\n"; + } + + if (prof->isShadowingEnabled(tt, terrain)) + { + generateFpDynamicShadows(prof, terrain, tt, outStream); + } + + outStream << " outputCol.rgb += ambient * diffuse; \n"; + + // diffuse lighting + for (int i=0; igetNumberOfLightsSupported(); ++i) + outStream << " outputCol.rgb += litRes"<isLayerSpecularMappingEnabled()) + outStream << " specular = 0.0;\n"; + + if (tt == RENDER_COMPOSITE_MAP) + { + // Lighting embedded in alpha + outStream << + " outputCol.a = shadow;\n"; + + } + else + { + // Apply specular + //outStream << " outputCol.rgb += litRes.z * lightSpecularColour * specular * shadow;\n"; + + if (prof->getParent()->getDebugLevel()) + { + outStream << " outputCol.rg += lodInfo.xy;\n"; + } + } + } + + bool fog = terrain->getSceneManager()->getFogMode() != FOG_NONE && tt != RENDER_COMPOSITE_MAP; + if (fog) + { + outStream << " outputCol.rgb = lerp(outputCol.rgb, fogColour, fogVal);\n"; + } + + // Final return + outStream << " return outputCol;\n" + << "}\n"; + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::generateFpDynamicShadowsHelpers( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) + { + // TODO make filtering configurable + outStream << + "// Simple PCF \n" + "// Number of samples in one dimension (square for total samples) \n" + "#define NUM_SHADOW_SAMPLES_1D 2.0 \n" + "#define SHADOW_FILTER_SCALE 1 \n" + + "#define SHADOW_SAMPLES NUM_SHADOW_SAMPLES_1D*NUM_SHADOW_SAMPLES_1D \n" + + "float4 offsetSample(float4 uv, float2 offset, float invMapSize) \n" + "{ \n" + " return float4(uv.xy + offset * invMapSize * uv.w, uv.z, uv.w); \n" + "} \n"; + + if (prof->getReceiveDynamicShadowsDepth()) + { + outStream << + "float calcDepthShadow(sampler2D shadowMap, float4 uv, float invShadowMapSize) \n" + "{ \n" + " // 4-sample PCF \n" + + " float shadow = 0.0; \n" + " float offset = (NUM_SHADOW_SAMPLES_1D/2 - 0.5) * SHADOW_FILTER_SCALE; \n" + " for (float y = -offset; y <= offset; y += SHADOW_FILTER_SCALE) \n" + " for (float x = -offset; x <= offset; x += SHADOW_FILTER_SCALE) \n" + " { \n" + " float4 newUV = offsetSample(uv, float2(x, y), invShadowMapSize);\n" + " // manually project and assign derivatives \n" + " // to avoid gradient issues inside loops \n" + " newUV = newUV / newUV.w; \n" + " float depth = tex2D(shadowMap, newUV.xy, 1, 1).x; \n" + " if (depth >= 1 || depth >= uv.z)\n" + " shadow += 1.0;\n" + " } \n" + + " shadow /= SHADOW_SAMPLES; \n" + + " return shadow; \n" + "} \n"; + } + else + { + outStream << + "float calcSimpleShadow(sampler2D shadowMap, float4 shadowMapPos) \n" + "{ \n" + " return tex2Dproj(shadowMap, shadowMapPos).x; \n" + "} \n"; + + } + + if (prof->getReceiveDynamicShadowsPSSM()) + { + uint numTextures = prof->getReceiveDynamicShadowsPSSM()->getSplitCount(); + + + if (prof->getReceiveDynamicShadowsDepth()) + { + outStream << + "float calcPSSMDepthShadow("; + } + else + { + outStream << + "float calcPSSMSimpleShadow("; + } + + outStream << "\n "; + for (uint i = 0; i < numTextures; ++i) + outStream << "sampler2D shadowMap" << i << ", "; + outStream << "\n "; + for (uint i = 0; i < numTextures; ++i) + outStream << "float4 lsPos" << i << ", "; + if (prof->getReceiveDynamicShadowsDepth()) + { + outStream << "\n "; + for (uint i = 0; i < numTextures; ++i) + outStream << "float invShadowmapSize" << i << ", "; + } + outStream << "\n" + " float4 pssmSplitPoints, float camDepth) \n" + "{ \n" + " float shadow; \n" + " // calculate shadow \n"; + + for (uint i = 0; i < numTextures; ++i) + { + if (!i) + outStream << " if (camDepth <= pssmSplitPoints." << ShaderHelper::getChannel(i) << ") \n"; + else if (i < numTextures - 1) + outStream << " else if (camDepth <= pssmSplitPoints." << ShaderHelper::getChannel(i) << ") \n"; + else + outStream << " else \n"; + + outStream << + " { \n"; + if (prof->getReceiveDynamicShadowsDepth()) + { + outStream << + " shadow = calcDepthShadow(shadowMap" << i << ", lsPos" << i << ", invShadowmapSize" << i << "); \n"; + } + else + { + outStream << + " shadow = calcSimpleShadow(shadowMap" << i << ", lsPos" << i << "); \n"; + } + outStream << + " } \n"; + + } + + outStream << + " return shadow; \n" + "} \n\n\n"; + } + + + } + //--------------------------------------------------------------------- + uint TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::generateVpDynamicShadowsParams( + uint texCoord, const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) + { + // out semantics & params + uint numTextures = 1; + if (prof->getReceiveDynamicShadowsPSSM()) + { + numTextures = prof->getReceiveDynamicShadowsPSSM()->getSplitCount(); + } + for (uint i = 0; i < numTextures; ++i) + { + outStream << + ", out float4 oLightSpacePos" << i << " : TEXCOORD" << texCoord++ << " \n" << + ", uniform float4x4 texViewProjMatrix" << i << " \n"; + if (prof->getReceiveDynamicShadowsDepth()) + { + outStream << + ", uniform float4 depthRange" << i << " // x = min, y = max, z = range, w = 1/range \n"; + } + } + + return texCoord; + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::generateVpDynamicShadows( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) + { + uint numTextures = 1; + if (prof->getReceiveDynamicShadowsPSSM()) + { + numTextures = prof->getReceiveDynamicShadowsPSSM()->getSplitCount(); + } + + // Calculate the position of vertex in light space + for (uint i = 0; i < numTextures; ++i) + { + outStream << + " oLightSpacePos" << i << " = mul(texViewProjMatrix" << i << ", worldPos); \n"; + if (prof->getReceiveDynamicShadowsDepth()) + { + // make linear + outStream << + "oLightSpacePos" << i << ".z = (oLightSpacePos" << i << ".z - depthRange" << i << ".x) * depthRange" << i << ".w;\n"; + + } + } + + + if (prof->getReceiveDynamicShadowsPSSM()) + { + outStream << + " // pass cam depth\n" + " oUVMisc.z = oPos.z;\n"; + } + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::generateFpDynamicShadowsParams( + uint* texCoord, uint* sampler, const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) + { + if (tt == HIGH_LOD) + mShadowSamplerStartHi = *sampler; + else if (tt == LOW_LOD) + mShadowSamplerStartLo = *sampler; + + // in semantics & params + uint numTextures = 1; + if (prof->getReceiveDynamicShadowsPSSM()) + { + numTextures = prof->getReceiveDynamicShadowsPSSM()->getSplitCount(); + outStream << + ", uniform float4 pssmSplitPoints \n"; + } + for (uint i = 0; i < numTextures; ++i) + { + outStream << + ", float4 lightSpacePos" << i << " : TEXCOORD" << *texCoord << " \n" << + ", uniform sampler2D shadowMap" << i << " : register(s" << *sampler << ") \n"; + *sampler = *sampler + 1; + *texCoord = *texCoord + 1; + if (prof->getReceiveDynamicShadowsDepth()) + { + outStream << + ", uniform float inverseShadowmapSize" << i << " \n"; + } + } + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::generateFpDynamicShadows( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) + { + if (prof->getReceiveDynamicShadowsPSSM()) + { + uint numTextures = prof->getReceiveDynamicShadowsPSSM()->getSplitCount(); + outStream << + " float camDepth = uvMisc.z;\n"; + + if (prof->getReceiveDynamicShadowsDepth()) + { + outStream << + " float rtshadow = calcPSSMDepthShadow("; + } + else + { + outStream << + " float rtshadow = calcPSSMSimpleShadow("; + } + for (uint i = 0; i < numTextures; ++i) + outStream << "shadowMap" << i << ", "; + outStream << "\n "; + + for (uint i = 0; i < numTextures; ++i) + outStream << "lightSpacePos" << i << ", "; + if (prof->getReceiveDynamicShadowsDepth()) + { + outStream << "\n "; + for (uint i = 0; i < numTextures; ++i) + outStream << "inverseShadowmapSize" << i << ", "; + } + outStream << "\n" << + " pssmSplitPoints, camDepth);\n"; + + } + else + { + if (prof->getReceiveDynamicShadowsDepth()) + { + outStream << + " float rtshadow = calcDepthShadow(shadowMap0, lightSpacePos0, inverseShadowmapSize0);"; + } + else + { + outStream << + " float rtshadow = calcSimpleShadow(shadowMap0, lightSpacePos0);"; + } + } + + outStream << + " shadow = min(shadow, rtshadow);\n"; + + } + //--------------------------------------------------------------------- + //--------------------------------------------------------------------- + HighLevelGpuProgramPtr + TerrainMaterialGeneratorB::SM2Profile::ShaderHelperHLSL::createVertexProgram( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) + { + HighLevelGpuProgramManager& mgr = HighLevelGpuProgramManager::getSingleton(); + String progName = getVertexProgramName(prof, terrain, tt); + + HighLevelGpuProgramPtr ret = mgr.getByName(progName); + if (ret.isNull()) + { + ret = mgr.createProgram(progName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + "hlsl", GPT_VERTEX_PROGRAM); + } + else + { + ret->unload(); + } + + if (prof->_isSM3Available()) + ret->setParameter("target", "vs_3_0"); + else + ret->setParameter("target", "vs_2_0"); + ret->setParameter("entry_point", "main_vp"); + + return ret; + + } + //--------------------------------------------------------------------- + HighLevelGpuProgramPtr + TerrainMaterialGeneratorB::SM2Profile::ShaderHelperHLSL::createFragmentProgram( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) + { + HighLevelGpuProgramManager& mgr = HighLevelGpuProgramManager::getSingleton(); + String progName = getFragmentProgramName(prof, terrain, tt); + + + HighLevelGpuProgramPtr ret = mgr.getByName(progName); + if (ret.isNull()) + { + ret = mgr.createProgram(progName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + "hlsl", GPT_FRAGMENT_PROGRAM); + } + else + { + ret->unload(); + } + + if (prof->_isSM3Available()) + ret->setParameter("target", "ps_3_0"); + else + ret->setParameter("target", "ps_2_x"); + ret->setParameter("entry_point", "main_fp"); + + return ret; + + } + //--------------------------------------------------------------------- + //--------------------------------------------------------------------- + HighLevelGpuProgramPtr + TerrainMaterialGeneratorB::SM2Profile::ShaderHelperGLSL::createVertexProgram( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) + { + HighLevelGpuProgramManager& mgr = HighLevelGpuProgramManager::getSingleton(); + String progName = getVertexProgramName(prof, terrain, tt); + + switch(tt) + { + case HIGH_LOD: + progName += "/hlod"; + break; + case LOW_LOD: + progName += "/llod"; + break; + case RENDER_COMPOSITE_MAP: + progName += "/comp"; + break; + } + + HighLevelGpuProgramPtr ret = mgr.getByName(progName); + if (ret.isNull()) + { + ret = mgr.createProgram(progName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + "glsl", GPT_VERTEX_PROGRAM); + } + else + { + ret->unload(); + } + + return ret; + + } + //--------------------------------------------------------------------- + HighLevelGpuProgramPtr + TerrainMaterialGeneratorB::SM2Profile::ShaderHelperGLSL::createFragmentProgram( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) + { + HighLevelGpuProgramManager& mgr = HighLevelGpuProgramManager::getSingleton(); + String progName = getFragmentProgramName(prof, terrain, tt); + + HighLevelGpuProgramPtr ret = mgr.getByName(progName); + if (ret.isNull()) + { + ret = mgr.createProgram(progName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + "glsl", GPT_FRAGMENT_PROGRAM); + } + else + { + ret->unload(); + } + + return ret; + + } + + +} diff --git a/apps/openmw/mwrender/terrainmaterial.hpp b/apps/openmw/mwrender/terrainmaterial.hpp new file mode 100644 index 000000000..3cb316347 --- /dev/null +++ b/apps/openmw/mwrender/terrainmaterial.hpp @@ -0,0 +1,266 @@ +/* +----------------------------------------------------------------------------- +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 __Ogre_TerrainMaterialGeneratorB_H__ +#define __Ogre_TerrainMaterialGeneratorB_H__ + +#include "OgreTerrainPrerequisites.h" +#include "OgreTerrainMaterialGenerator.h" +#include "OgreGpuProgramParams.h" + +namespace Ogre +{ + class PSSMShadowCameraSetup; + + /** \addtogroup Optional Components + * @{ + */ + /** \addtogroup Terrain + * Some details on the terrain component + * @{ + */ + + + /** A TerrainMaterialGenerator which can cope with normal mapped, specular mapped + terrain. + @note Requires the Cg plugin to render correctly + */ + class TerrainMaterialGeneratorB : public TerrainMaterialGenerator + { + public: + TerrainMaterialGeneratorB(); + ~TerrainMaterialGeneratorB(); + + /** Shader model 2 profile target. + */ + class SM2Profile : public TerrainMaterialGenerator::Profile + { + public: + SM2Profile(TerrainMaterialGenerator* parent, const String& name, const String& desc); + ~SM2Profile(); + + bool isVertexCompressionSupported() const {return false;} + + MaterialPtr generate(const Terrain* terrain); + MaterialPtr generateForCompositeMap(const Terrain* terrain); + uint8 getMaxLayers(const Terrain* terrain) const; + void updateParams(const MaterialPtr& mat, const Terrain* terrain); + void updateParamsForCompositeMap(const MaterialPtr& mat, const Terrain* terrain); + void requestOptions(Terrain* terrain); + + /** Whether to support normal mapping per layer in the shader (default true). + */ + bool isLayerNormalMappingEnabled() const { return mLayerNormalMappingEnabled; } + /** Whether to support normal mapping per layer in the shader (default true). + */ + void setLayerNormalMappingEnabled(bool enabled); + /** Whether to support parallax mapping per layer in the shader (default true). + */ + bool isLayerParallaxMappingEnabled() const { return mLayerParallaxMappingEnabled; } + /** Whether to support parallax mapping per layer in the shader (default true). + */ + void setLayerParallaxMappingEnabled(bool enabled); + /** Whether to support specular mapping per layer in the shader (default true). + */ + bool isLayerSpecularMappingEnabled() const { return mLayerSpecularMappingEnabled; } + /** Whether to support specular mapping per layer in the shader (default true). + */ + void setLayerSpecularMappingEnabled(bool enabled); + /** Whether to support a global colour map over the terrain in the shader, + if it's present (default true). + */ + bool isGlobalColourMapEnabled() const { return mGlobalColourMapEnabled; } + /** Whether to support a global colour map over the terrain in the shader, + if it's present (default true). + */ + void setGlobalColourMapEnabled(bool enabled); + /** Whether to support a light map over the terrain in the shader, + if it's present (default true). + */ + bool isLightmapEnabled() const { return mLightmapEnabled; } + /** Whether to support a light map over the terrain in the shader, + if it's present (default true). + */ + void setLightmapEnabled(bool enabled); + /** Whether to use the composite map to provide a lower LOD technique + in the distance (default true). + */ + bool isCompositeMapEnabled() const { return mCompositeMapEnabled; } + /** Whether to use the composite map to provide a lower LOD technique + in the distance (default true). + */ + void setCompositeMapEnabled(bool enabled); + /** Whether to support dynamic texture shadows received from other + objects, on the terrain (default true). + */ + bool getReceiveDynamicShadowsEnabled() const { return mReceiveDynamicShadows; } + /** Whether to support dynamic texture shadows received from other + objects, on the terrain (default true). + */ + void setReceiveDynamicShadowsEnabled(bool enabled); + + /** Whether to use PSSM support dynamic texture shadows, and if so the + settings to use (default 0). + */ + void setReceiveDynamicShadowsPSSM(PSSMShadowCameraSetup* pssmSettings); + /** Whether to use PSSM support dynamic texture shadows, and if so the + settings to use (default 0). + */ + PSSMShadowCameraSetup* getReceiveDynamicShadowsPSSM() const { return mPSSM; } + /** Whether to use depth shadows (default false). + */ + void setReceiveDynamicShadowsDepth(bool enabled); + /** Whether to use depth shadows (default false). + */ + bool getReceiveDynamicShadowsDepth() const { return mDepthShadows; } + /** Whether to use shadows on low LOD material rendering (when using composite map) (default false). + */ + void setReceiveDynamicShadowsLowLod(bool enabled); + /** Whether to use shadows on low LOD material rendering (when using composite map) (default false). + */ + bool getReceiveDynamicShadowsLowLod() const { return mLowLodShadows; } + + int getNumberOfLightsSupported() const; + + /// Internal + bool _isSM3Available() const { return mSM3Available; } + + protected: + + enum TechniqueType + { + HIGH_LOD, + LOW_LOD, + RENDER_COMPOSITE_MAP + }; + void addTechnique(const MaterialPtr& mat, const Terrain* terrain, TechniqueType tt); + + /// Interface definition for helper class to generate shaders + class ShaderHelper : public TerrainAlloc + { + public: + ShaderHelper() {} + virtual ~ShaderHelper() {} + virtual HighLevelGpuProgramPtr generateVertexProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); + virtual HighLevelGpuProgramPtr generateFragmentProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); + virtual void updateParams(const SM2Profile* prof, const MaterialPtr& mat, const Terrain* terrain, bool compositeMap); + protected: + virtual String getVertexProgramName(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); + virtual String getFragmentProgramName(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); + virtual HighLevelGpuProgramPtr createVertexProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) = 0; + virtual HighLevelGpuProgramPtr createFragmentProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) = 0; + virtual void generateVertexProgramSource(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); + virtual void generateFragmentProgramSource(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); + virtual void generateVpHeader(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) = 0; + virtual void generateFpHeader(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) = 0; + virtual void generateVpLayer(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, uint layer, StringUtil::StrStreamType& outStream) = 0; + virtual void generateFpLayer(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, uint layer, StringUtil::StrStreamType& outStream) = 0; + virtual void generateVpFooter(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) = 0; + virtual void generateFpFooter(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) = 0; + virtual void defaultVpParams(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, const HighLevelGpuProgramPtr& prog); + virtual void defaultFpParams(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, const HighLevelGpuProgramPtr& prog); + virtual void updateVpParams(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, const GpuProgramParametersSharedPtr& params); + virtual void updateFpParams(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, const GpuProgramParametersSharedPtr& params); + static String getChannel(uint idx); + + size_t mShadowSamplerStartHi; + size_t mShadowSamplerStartLo; + + }; + + /// Utility class to help with generating shaders for Cg / HLSL. + class ShaderHelperCg : public ShaderHelper + { + protected: + HighLevelGpuProgramPtr createVertexProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); + HighLevelGpuProgramPtr createFragmentProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); + void generateVpHeader(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); + void generateFpHeader(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); + void generateVpLayer(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, uint layer, StringUtil::StrStreamType& outStream); + void generateFpLayer(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, uint layer, StringUtil::StrStreamType& outStream); + void generateVpFooter(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); + void generateFpFooter(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); + uint generateVpDynamicShadowsParams(uint texCoordStart, const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); + void generateVpDynamicShadows(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); + void generateFpDynamicShadowsHelpers(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); + void generateFpDynamicShadowsParams(uint* texCoord, uint* sampler, const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); + void generateFpDynamicShadows(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); + }; + + class ShaderHelperHLSL : public ShaderHelperCg + { + protected: + HighLevelGpuProgramPtr createVertexProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); + HighLevelGpuProgramPtr createFragmentProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); + }; + + /// Utility class to help with generating shaders for GLSL. + class ShaderHelperGLSL : public ShaderHelper + { + protected: + HighLevelGpuProgramPtr createVertexProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); + HighLevelGpuProgramPtr createFragmentProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); + void generateVpHeader(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) {} + void generateFpHeader(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) {} + void generateVpLayer(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, uint layer, StringUtil::StrStreamType& outStream) {} + void generateFpLayer(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, uint layer, StringUtil::StrStreamType& outStream) {} + void generateVpFooter(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) {} + void generateFpFooter(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) {} + }; + + ShaderHelper* mShaderGen; + bool mLayerNormalMappingEnabled; + bool mLayerParallaxMappingEnabled; + bool mLayerSpecularMappingEnabled; + bool mGlobalColourMapEnabled; + bool mLightmapEnabled; + bool mCompositeMapEnabled; + bool mReceiveDynamicShadows; + PSSMShadowCameraSetup* mPSSM; + bool mDepthShadows; + bool mLowLodShadows; + bool mSM3Available; + + bool isShadowingEnabled(TechniqueType tt, const Terrain* terrain) const; + + }; + + + + + }; + + + + /** @} */ + /** @} */ + + +} + +#endif diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp new file mode 100644 index 000000000..9de55e3a5 --- /dev/null +++ b/apps/openmw/mwrender/water.cpp @@ -0,0 +1,95 @@ +#include "water.hpp" + +namespace MWRender +{ + +Water::Water (Ogre::Camera *camera, const ESM::Cell* cell) : + mCamera (camera), mViewport (camera->getViewport()), mSceneManager (camera->getSceneManager()), + mIsUnderwater(false) +{ + try + { + Ogre::CompositorManager::getSingleton().addCompositor(mViewport, "Water", -1); + Ogre::CompositorManager::getSingleton().setCompositorEnabled(mViewport, "Water", false); + } catch(...) {} + + mTop = cell->water; + + mIsUnderwater = false; + + mWaterPlane = Ogre::Plane(Ogre::Vector3::UNIT_Y, 0); + + Ogre::MeshManager::getSingleton().createPlane("water", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, mWaterPlane, CELL_SIZE*5, CELL_SIZE * 5, 10, 10, true, 1, 3,5, Ogre::Vector3::UNIT_Z); + + mWater = mSceneManager->createEntity("water"); + + mWater->setMaterialName("Examples/Water0"); + + mWaterNode = mSceneManager->getRootSceneNode()->createChildSceneNode(); + mWaterNode->setPosition(0, mTop, 0); + + if(!(cell->data.flags & cell->Interior)) + { + mWaterNode->setPosition(getSceneNodeCoordinates(cell->data.gridX, cell->data.gridY)); + } + mWaterNode->attachObject(mWater); +} + + +Water::~Water() +{ + Ogre::MeshManager::getSingleton().remove("water"); + + mWaterNode->detachObject(mWater); + mSceneManager->destroyEntity(mWater); + mSceneManager->destroySceneNode(mWaterNode); + + Ogre::CompositorManager::getSingleton().removeCompositorChain(mViewport); +} + +void Water::changeCell(const ESM::Cell* cell) +{ + mTop = cell->water; + + if(!(cell->data.flags & cell->Interior)) + mWaterNode->setPosition(getSceneNodeCoordinates(cell->data.gridX, cell->data.gridY)); + else + setHeight(mTop); +} + +void Water::setHeight(const float height) +{ + mTop = height; + mWaterNode->setPosition(0, height, 0); +} + +void Water::toggle() +{ + mWater->setVisible(!mWater->getVisible()); +} + +void Water::checkUnderwater(float y) +{ + if ((mIsUnderwater && y > mTop) || !mWater->isVisible()) + { + try { + Ogre::CompositorManager::getSingleton().setCompositorEnabled(mViewport, "Water", false); + } catch(...) {} + mIsUnderwater = false; + } + + if (!mIsUnderwater && y < mTop && mWater->isVisible()) + { + try { + Ogre::CompositorManager::getSingleton().setCompositorEnabled(mViewport, "Water", true); + } catch(...) {} + mIsUnderwater = true; + } +} + +Ogre::Vector3 Water::getSceneNodeCoordinates(int gridX, int gridY) +{ + return Ogre::Vector3(gridX * CELL_SIZE + (CELL_SIZE / 2), mTop, -gridY * CELL_SIZE - (CELL_SIZE / 2)); +} + +} // namespace diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp new file mode 100644 index 000000000..5a5d1cca0 --- /dev/null +++ b/apps/openmw/mwrender/water.hpp @@ -0,0 +1,40 @@ +#ifndef GAME_MWRENDER_WATER_H +#define GAME_MWRENDER_WATER_H + +#include +#include + +namespace MWRender { + + /// Water rendering + class Water : Ogre::RenderTargetListener, Ogre::Camera::Listener + { + static const int CELL_SIZE = 8192; + Ogre::Camera *mCamera; + Ogre::SceneManager *mSceneManager; + Ogre::Viewport *mViewport; + + Ogre::Plane mWaterPlane; + Ogre::SceneNode *mWaterNode; + Ogre::Entity *mWater; + + bool mIsUnderwater; + int mTop; + + Ogre::Vector3 getSceneNodeCoordinates(int gridX, int gridY); + + public: + Water (Ogre::Camera *camera, const ESM::Cell* cell); + ~Water(); + + void toggle(); + + void checkUnderwater(float y); + void changeCell(const ESM::Cell* cell); + void setHeight(const float height); + + }; + +} + +#endif diff --git a/apps/openmw/mwscript/cellextensions.cpp b/apps/openmw/mwscript/cellextensions.cpp index bb5263203..d69c42ab3 100644 --- a/apps/openmw/mwscript/cellextensions.cpp +++ b/apps/openmw/mwscript/cellextensions.cpp @@ -133,11 +133,70 @@ namespace MWScript } }; + class OpGetWaterLevel : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + InterpreterContext& context + = static_cast (runtime.getContext()); + + MWWorld::Ptr::CellStore *cell = context.getWorld().getPlayer().getPlayer().getCell(); + runtime.push (cell->mWaterLevel); + } + }; + + class OpSetWaterLevel : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + InterpreterContext& context + = static_cast (runtime.getContext()); + + Interpreter::Type_Float level = runtime[0].mFloat; + + MWWorld::Ptr::CellStore *cell = context.getWorld().getPlayer().getPlayer().getCell(); + + if (!(cell->cell->data.flags & ESM::Cell::Interior)) + throw std::runtime_error("Can't set water level in exterior cell"); + + cell->mWaterLevel = level; + context.getEnvironment().mWorld->setWaterHeight(cell->mWaterLevel); + } + }; + + class OpModWaterLevel : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + InterpreterContext& context + = static_cast (runtime.getContext()); + + Interpreter::Type_Float level = runtime[0].mFloat; + + MWWorld::Ptr::CellStore *cell = context.getWorld().getPlayer().getPlayer().getCell(); + + if (!(cell->cell->data.flags & ESM::Cell::Interior)) + throw std::runtime_error("Can't set water level in exterior cell"); + + cell->mWaterLevel +=level; + context.getEnvironment().mWorld->setWaterHeight(cell->mWaterLevel); + } + }; + const int opcodeCellChanged = 0x2000000; const int opcodeCOC = 0x2000026; const int opcodeCOE = 0x200008e; const int opcodeGetInterior = 0x2000131; const int opcodeGetPCCell = 0x2000136; + const int opcodeGetWaterLevel = 0x2000141; + const int opcodeSetWaterLevel = 0x2000142; + const int opcodeModWaterLevel = 0x2000143; void registerExtensions (Compiler::Extensions& extensions) { @@ -146,8 +205,11 @@ namespace MWScript extensions.registerInstruction ("centeroncell", "S", opcodeCOC); extensions.registerInstruction ("coe", "ll", opcodeCOE); extensions.registerInstruction ("centeronexterior", "ll", opcodeCOE); + extensions.registerInstruction ("setwaterlevel", "f", opcodeSetWaterLevel); + extensions.registerInstruction ("modwaterlevel", "f", opcodeModWaterLevel); extensions.registerFunction ("getinterior", 'l', "", opcodeGetInterior); extensions.registerFunction ("getpccell", 'l', "c", opcodeGetPCCell); + extensions.registerFunction ("getwaterlevel", 'f', "", opcodeGetWaterLevel); } void installOpcodes (Interpreter::Interpreter& interpreter) @@ -157,6 +219,9 @@ namespace MWScript interpreter.installSegment5 (opcodeCOE, new OpCOE); interpreter.installSegment5 (opcodeGetInterior, new OpGetInterior); interpreter.installSegment5 (opcodeGetPCCell, new OpGetPCCell); + interpreter.installSegment5 (opcodeGetWaterLevel, new OpGetWaterLevel); + interpreter.installSegment5 (opcodeSetWaterLevel, new OpSetWaterLevel); + interpreter.installSegment5 (opcodeModWaterLevel, new OpModWaterLevel); } } } diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index 713ac43b0..7ff60a203 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -123,5 +123,8 @@ op 0x200013d: FadeOut op 0x200013e: FadeTo op 0x200013f: GetCurrentWeather op 0x2000140: ChangeWeather -op 0x2000141: OpPCJoinFaction -opcodes 0x2000142-0x3ffffff unused +op 0x2000141: GetWaterLevel +op 0x2000142: SetWaterLevel +op 0x2000143: ModWaterLevel +op 0x2000144: ToggleWater, twa +opcodes 0x2000145-0x3ffffff unused diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 5bfffd3a2..5109319ed 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -175,6 +175,19 @@ namespace MWScript } }; + class OpToggleWater : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + InterpreterContext& context = + static_cast (runtime.getContext()); + + context.getWorld().toggleWater(); + } + }; + const int opcodeXBox = 0x200000c; const int opcodeOnActivate = 0x200000d; const int opcodeActivate = 0x2000075; @@ -187,6 +200,7 @@ namespace MWScript const int opcodeFadeIn = 0x200013c; const int opcodeFadeOut = 0x200013d; const int opcodeFadeTo = 0x200013e; + const int opcodeToggleWater = 0x2000144; void registerExtensions (Compiler::Extensions& extensions) { @@ -204,6 +218,8 @@ namespace MWScript extensions.registerInstruction ("fadein", "f", opcodeFadeIn); extensions.registerInstruction ("fadeout", "f", opcodeFadeOut); extensions.registerInstruction ("fadeto", "ff", opcodeFadeTo); + extensions.registerInstruction ("togglewater", "", opcodeToggleWater); + extensions.registerInstruction ("twa", "", opcodeToggleWater); } void installOpcodes (Interpreter::Interpreter& interpreter) @@ -220,6 +236,7 @@ namespace MWScript interpreter.installSegment5 (opcodeFadeIn, new OpFadeIn); interpreter.installSegment5 (opcodeFadeOut, new OpFadeOut); interpreter.installSegment5 (opcodeFadeTo, new OpFadeTo); + interpreter.installSegment5 (opcodeToggleWater, new OpToggleWater); } } } diff --git a/apps/openmw/mwscript/soundextensions.cpp b/apps/openmw/mwscript/soundextensions.cpp index d5cc41b76..b4386a8a0 100644 --- a/apps/openmw/mwscript/soundextensions.cpp +++ b/apps/openmw/mwscript/soundextensions.cpp @@ -130,7 +130,7 @@ namespace MWScript std::string sound = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - context.getSoundManager().playSound3D (ptr, sound, 1.0, 1.0, mLoop); + context.getSoundManager().playSound3D (ptr, sound, 1.0, 1.0, mLoop ? MWSound::Play_Loop : 0); } }; @@ -159,7 +159,7 @@ namespace MWScript Interpreter::Type_Float pitch = runtime[0].mFloat; runtime.pop(); - context.getSoundManager().playSound3D (ptr, sound, volume, pitch, mLoop); + context.getSoundManager().playSound3D (ptr, sound, volume, pitch, mLoop ? MWSound::Play_Loop : 0); } }; diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index edd923f43..610a797a2 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -25,14 +25,20 @@ static void throwALCerror(ALCdevice *device) { ALCenum err = alcGetError(device); if(err != ALC_NO_ERROR) - fail(alcGetString(device, err)); + { + const ALCchar *errstring = alcGetString(device, err); + fail(errstring ? errstring : ""); + } } static void throwALerror() { ALenum err = alGetError(); if(err != AL_NO_ERROR) - fail(alGetString(err)); + { + const ALchar *errstring = alGetString(err); + fail(errstring ? errstring : ""); + } } @@ -89,7 +95,7 @@ public: virtual void stop(); virtual bool isPlaying(); - virtual void update(const float *pos); + virtual void update(); void play(); bool process(); @@ -186,7 +192,6 @@ OpenAL_SoundStream::OpenAL_SoundStream(OpenAL_Output &output, ALuint src, Decode } catch(std::exception &e) { - mOutput.mFreeSources.push_back(mSource); alDeleteBuffers(sNumBuffers, mBuffers); alGetError(); throw; @@ -254,9 +259,19 @@ bool OpenAL_SoundStream::isPlaying() return !mIsFinished; } -void OpenAL_SoundStream::update(const float *pos) +void OpenAL_SoundStream::update() { - alSource3f(mSource, AL_POSITION, pos[0], pos[2], -pos[1]); + ALfloat gain = mVolume*mBaseVolume; + ALfloat pitch = mPitch; + if(!(mFlags&Play_NoEnv) && mOutput.mLastEnvironment == Env_Underwater) + { + gain *= 0.9f; + pitch *= 0.7f; + } + + alSourcef(mSource, AL_GAIN, gain); + alSourcef(mSource, AL_PITCH, pitch); + alSource3f(mSource, AL_POSITION, mPos[0], mPos[2], -mPos[1]); alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f); throwALerror(); @@ -313,15 +328,17 @@ bool OpenAL_SoundStream::process() } // -// A regular OpenAL sound +// A regular 2D OpenAL sound // class OpenAL_Sound : public Sound { +protected: OpenAL_Output &mOutput; ALuint mSource; ALuint mBuffer; +private: OpenAL_Sound(const OpenAL_Sound &rhs); OpenAL_Sound& operator=(const OpenAL_Sound &rhs); @@ -331,7 +348,23 @@ public: virtual void stop(); virtual bool isPlaying(); - virtual void update(const float *pos); + virtual void update(); +}; + +// +// A regular 3D OpenAL sound +// +class OpenAL_Sound3D : public OpenAL_Sound +{ + OpenAL_Sound3D(const OpenAL_Sound &rhs); + OpenAL_Sound3D& operator=(const OpenAL_Sound &rhs); + +public: + OpenAL_Sound3D(OpenAL_Output &output, ALuint src, ALuint buf) + : OpenAL_Sound(output, src, buf) + { } + + virtual void update(); }; OpenAL_Sound::OpenAL_Sound(OpenAL_Output &output, ALuint src, ALuint buf) @@ -363,9 +396,39 @@ bool OpenAL_Sound::isPlaying() return state==AL_PLAYING; } -void OpenAL_Sound::update(const float *pos) +void OpenAL_Sound::update() { - alSource3f(mSource, AL_POSITION, pos[0], pos[2], -pos[1]); + ALfloat gain = mVolume*mBaseVolume; + ALfloat pitch = mPitch; + if(!(mFlags&Play_NoEnv) && mOutput.mLastEnvironment == Env_Underwater) + { + gain *= 0.9f; + pitch *= 0.7f; + } + + alSourcef(mSource, AL_GAIN, gain); + alSourcef(mSource, AL_PITCH, pitch); + alSource3f(mSource, AL_POSITION, mPos[0], mPos[2], -mPos[1]); + alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); + alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f); + throwALerror(); +} + +void OpenAL_Sound3D::update() +{ + ALfloat gain = mVolume*mBaseVolume; + ALfloat pitch = mPitch; + if(mPos.squaredDistance(mOutput.mPos) > mMaxDistance*mMaxDistance) + gain = 0.0f; + else if(!(mFlags&Play_NoEnv) && mOutput.mLastEnvironment == Env_Underwater) + { + gain *= 0.9f; + pitch *= 0.7f; + } + + alSourcef(mSource, AL_GAIN, gain); + alSourcef(mSource, AL_PITCH, pitch); + alSource3f(mSource, AL_POSITION, mPos[0], mPos[2], -mPos[1]); alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f); throwALerror(); @@ -394,8 +457,7 @@ std::vector OpenAL_Output::enumerate() void OpenAL_Output::init(const std::string &devname) { - if(mDevice || mContext) - fail("Device already open"); + deinit(); mDevice = alcOpenDevice(devname.c_str()); if(!mDevice) @@ -412,29 +474,39 @@ void OpenAL_Output::init(const std::string &devname) mContext = alcCreateContext(mDevice, NULL); if(!mContext || alcMakeContextCurrent(mContext) == ALC_FALSE) + { + if(mContext) + alcDestroyContext(mContext); + mContext = 0; fail(std::string("Failed to setup context: ")+alcGetString(mDevice, alcGetError(mDevice))); + } alDistanceModel(AL_LINEAR_DISTANCE_CLAMPED); throwALerror(); - ALCint maxmono, maxstereo; + ALCint maxmono=0, maxstereo=0; alcGetIntegerv(mDevice, ALC_MONO_SOURCES, 1, &maxmono); alcGetIntegerv(mDevice, ALC_STEREO_SOURCES, 1, &maxstereo); throwALCerror(mDevice); - mFreeSources.resize(std::min(maxmono+maxstereo, 256)); - for(size_t i = 0;i < mFreeSources.size();i++) + try { - ALuint src; - alGenSources(1, &src); - if(alGetError() != AL_NO_ERROR) + ALCuint maxtotal = std::min(maxmono+maxstereo, 256); + if (maxtotal == 0) // workaround for broken implementations + maxtotal = 256; + for(size_t i = 0;i < maxtotal;i++) { - mFreeSources.resize(i); - break; + ALuint src = 0; + alGenSources(1, &src); + throwALerror(); + mFreeSources.push_back(src); } - mFreeSources[i] = src; } - if(mFreeSources.size() == 0) + catch(std::exception &e) + { + std::cout <<"Error: "<removeAll(); - if(!mFreeSources.empty()) + while(!mFreeSources.empty()) { - alDeleteSources(mFreeSources.size(), mFreeSources.data()); - mFreeSources.clear(); + alDeleteSources(1, &mFreeSources.front()); + mFreeSources.pop_front(); } mBufferRefs.clear(); @@ -561,17 +633,15 @@ void OpenAL_Output::bufferFinished(ALuint buf) } -Sound* OpenAL_Output::playSound(const std::string &fname, float volume, float pitch, bool loop) +SoundPtr OpenAL_Output::playSound(const std::string &fname, float volume, float pitch, int flags) { - throwALerror(); - - std::auto_ptr sound; + boost::shared_ptr sound; ALuint src=0, buf=0; if(mFreeSources.empty()) fail("No free sources"); - src = mFreeSources.back(); - mFreeSources.pop_back(); + src = mFreeSources.front(); + mFreeSources.pop_front(); try { @@ -595,37 +665,40 @@ Sound* OpenAL_Output::playSound(const std::string &fname, float volume, float pi alSourcef(src, AL_MAX_DISTANCE, 1000.0f); alSourcef(src, AL_ROLLOFF_FACTOR, 0.0f); + if(!(flags&Play_NoEnv) && mLastEnvironment == Env_Underwater) + { + volume *= 0.9f; + pitch *= 0.7f; + } alSourcef(src, AL_GAIN, volume); alSourcef(src, AL_PITCH, pitch); alSourcei(src, AL_SOURCE_RELATIVE, AL_TRUE); - alSourcei(src, AL_LOOPING, (loop?AL_TRUE:AL_FALSE)); + alSourcei(src, AL_LOOPING, (flags&Play_Loop) ? AL_TRUE : AL_FALSE); throwALerror(); alSourcei(src, AL_BUFFER, buf); alSourcePlay(src); throwALerror(); - return sound.release(); + return sound; } -Sound* OpenAL_Output::playSound3D(const std::string &fname, const float *pos, float volume, float pitch, - float min, float max, bool loop) +SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre::Vector3 &pos, float volume, float pitch, + float min, float max, int flags) { - throwALerror(); - - std::auto_ptr sound; + boost::shared_ptr sound; ALuint src=0, buf=0; if(mFreeSources.empty()) fail("No free sources"); - src = mFreeSources.back(); - mFreeSources.pop_back(); + src = mFreeSources.front(); + mFreeSources.pop_front(); try { buf = getBuffer(fname); - sound.reset(new OpenAL_Sound(*this, src, buf)); + sound.reset(new OpenAL_Sound3D(*this, src, buf)); } catch(std::exception &e) { @@ -636,7 +709,7 @@ Sound* OpenAL_Output::playSound3D(const std::string &fname, const float *pos, fl throw; } - alSource3f(src, AL_POSITION, pos[0], pos[2], -pos[1]); + alSource3f(src, AL_POSITION, pos.x, pos.z, -pos.y); alSource3f(src, AL_DIRECTION, 0.0f, 0.0f, 0.0f); alSource3f(src, AL_VELOCITY, 0.0f, 0.0f, 0.0f); @@ -644,35 +717,41 @@ Sound* OpenAL_Output::playSound3D(const std::string &fname, const float *pos, fl alSourcef(src, AL_MAX_DISTANCE, max); alSourcef(src, AL_ROLLOFF_FACTOR, 1.0f); - alSourcef(src, AL_GAIN, volume); + if(!(flags&Play_NoEnv) && mLastEnvironment == Env_Underwater) + { + volume *= 0.9f; + pitch *= 0.7f; + } + alSourcef(src, AL_GAIN, (pos.squaredDistance(mPos) > max*max) ? + 0.0f : volume); alSourcef(src, AL_PITCH, pitch); alSourcei(src, AL_SOURCE_RELATIVE, AL_FALSE); - alSourcei(src, AL_LOOPING, (loop?AL_TRUE:AL_FALSE)); + alSourcei(src, AL_LOOPING, (flags&Play_Loop) ? AL_TRUE : AL_FALSE); throwALerror(); alSourcei(src, AL_BUFFER, buf); alSourcePlay(src); throwALerror(); - return sound.release(); + return sound; } -Sound* OpenAL_Output::streamSound(const std::string &fname, float volume, float pitch) +SoundPtr OpenAL_Output::streamSound(const std::string &fname, float volume, float pitch, int flags) { - throwALerror(); - - std::auto_ptr sound; + boost::shared_ptr sound; ALuint src; if(mFreeSources.empty()) fail("No free sources"); - src = mFreeSources.back(); - mFreeSources.pop_back(); + src = mFreeSources.front(); + mFreeSources.pop_front(); try { + if((flags&Play_Loop)) + std::cout <<"Warning: cannot loop stream "<open(fname); sound.reset(new OpenAL_SoundStream(*this, src, decoder)); @@ -691,78 +770,44 @@ Sound* OpenAL_Output::streamSound(const std::string &fname, float volume, float alSourcef(src, AL_MAX_DISTANCE, 1000.0f); alSourcef(src, AL_ROLLOFF_FACTOR, 0.0f); - alSourcef(src, AL_GAIN, volume); - alSourcef(src, AL_PITCH, pitch); - - alSourcei(src, AL_SOURCE_RELATIVE, AL_TRUE); - alSourcei(src, AL_LOOPING, AL_FALSE); - throwALerror(); - - sound->play(); - return sound.release(); -} - -Sound* OpenAL_Output::streamSound3D(const std::string &fname, const float *pos, float volume, float pitch, - float min, float max) -{ - throwALerror(); - - std::auto_ptr sound; - ALuint src; - - if(mFreeSources.empty()) - fail("No free sources"); - src = mFreeSources.back(); - mFreeSources.pop_back(); - - try - { - DecoderPtr decoder = mManager.getDecoder(); - decoder->open(fname); - sound.reset(new OpenAL_SoundStream(*this, src, decoder)); - } - catch(std::exception &e) + if(!(flags&Play_NoEnv) && mLastEnvironment == Env_Underwater) { - mFreeSources.push_back(src); - throw; + volume *= 0.9f; + pitch *= 0.7f; } - - alSource3f(src, AL_POSITION, pos[0], pos[2], -pos[1]); - alSource3f(src, AL_DIRECTION, 0.0f, 0.0f, 0.0f); - alSource3f(src, AL_VELOCITY, 0.0f, 0.0f, 0.0f); - - alSourcef(src, AL_REFERENCE_DISTANCE, min); - alSourcef(src, AL_MAX_DISTANCE, max); - alSourcef(src, AL_ROLLOFF_FACTOR, 1.0f); - alSourcef(src, AL_GAIN, volume); alSourcef(src, AL_PITCH, pitch); - alSourcei(src, AL_SOURCE_RELATIVE, AL_FALSE); + alSourcei(src, AL_SOURCE_RELATIVE, AL_TRUE); alSourcei(src, AL_LOOPING, AL_FALSE); throwALerror(); sound->play(); - return sound.release(); + return sound; } -void OpenAL_Output::updateListener(const float *pos, const float *atdir, const float *updir) +void OpenAL_Output::updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir, Environment env) { - float orient[6] = { - atdir[0], atdir[2], -atdir[1], - updir[0], updir[2], -updir[1] - }; + mPos = pos; + mLastEnvironment = env; - alListener3f(AL_POSITION, pos[0], pos[2], -pos[1]); - alListenerfv(AL_ORIENTATION, orient); - throwALerror(); + if(mContext) + { + ALfloat orient[6] = { + atdir.x, atdir.z, -atdir.y, + updir.x, updir.z, -updir.y + }; + alListener3f(AL_POSITION, mPos.x, mPos.z, -mPos.y); + alListenerfv(AL_ORIENTATION, orient); + throwALerror(); + } } OpenAL_Output::OpenAL_Output(SoundManager &mgr) : Sound_Output(mgr), mDevice(0), mContext(0), mBufferCacheMemSize(0), - mStreamThread(new StreamThread) + mLastEnvironment(Env_Normal), mStreamThread(new StreamThread) { } diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index e8154e906..d62d20286 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -21,8 +21,9 @@ namespace MWSound ALCdevice *mDevice; ALCcontext *mContext; - typedef std::vector IDVec; - IDVec mFreeSources; + typedef std::deque IDDq; + IDDq mFreeSources; + IDDq mUnusedBuffers; typedef std::map NameMap; NameMap mBufferCache; @@ -30,27 +31,23 @@ namespace MWSound typedef std::map IDRefMap; IDRefMap mBufferRefs; - typedef std::deque IDDq; - IDDq mUnusedBuffers; - uint64_t mBufferCacheMemSize; ALuint getBuffer(const std::string &fname); void bufferFinished(ALuint buffer); + Environment mLastEnvironment; + virtual std::vector enumerate(); virtual void init(const std::string &devname=""); virtual void deinit(); - virtual Sound *playSound(const std::string &fname, float volume, float pitch, bool loop); - virtual Sound *playSound3D(const std::string &fname, const float *pos, float volume, float pitch, - float min, float max, bool loop); - - virtual Sound *streamSound(const std::string &fname, float volume, float pitch); - virtual Sound *streamSound3D(const std::string &fname, const float *pos, float volume, float pitch, - float min, float max); + virtual SoundPtr playSound(const std::string &fname, float volume, float pitch, int flags); + virtual SoundPtr playSound3D(const std::string &fname, const Ogre::Vector3 &pos, + float volume, float pitch, float min, float max, int flags); + virtual SoundPtr streamSound(const std::string &fname, float volume, float pitch, int flags); - virtual void updateListener(const float *pos, const float *atdir, const float *updir); + virtual void updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir, Environment env); OpenAL_Output& operator=(const OpenAL_Output &rhs); OpenAL_Output(const OpenAL_Output &rhs); @@ -62,6 +59,7 @@ namespace MWSound std::auto_ptr mStreamThread; friend class OpenAL_Sound; + friend class OpenAL_Sound3D; friend class OpenAL_SoundStream; friend class SoundManager; }; diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp index f9e7ab427..a33892548 100644 --- a/apps/openmw/mwsound/sound.hpp +++ b/apps/openmw/mwsound/sound.hpp @@ -1,19 +1,40 @@ #ifndef GAME_SOUND_SOUND_H #define GAME_SOUND_SOUND_H +#include + namespace MWSound { class Sound { - virtual void stop() = 0; - virtual bool isPlaying() = 0; - virtual void update(const float *pos) = 0; + virtual void update() = 0; Sound& operator=(const Sound &rhs); Sound(const Sound &rhs); + protected: + Ogre::Vector3 mPos; + float mVolume; /* NOTE: Real volume = mVolume*mBaseVolume */ + float mBaseVolume; + float mPitch; + float mMinDistance; + float mMaxDistance; + int mFlags; + public: - Sound() { } + virtual void stop() = 0; + virtual bool isPlaying() = 0; + void setPosition(const Ogre::Vector3 &pos) { mPos = pos; } + void setVolume(float volume) { mVolume = volume; } + + Sound() : mPos(0.0f, 0.0f, 0.0f) + , mVolume(1.0f) + , mBaseVolume(1.0f) + , mPitch(1.0f) + , mMinDistance(20.0f) /* 1 * min_range_scale */ + , mMaxDistance(12750.0f) /* 255 * max_range_scale */ + , mFlags(Play_Normal) + { } virtual ~Sound() { } friend class OpenAL_Output; diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index 1722165e4..774e42efa 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -4,6 +4,10 @@ #include #include +#include + +#include "soundmanager.hpp" + #include "../mwworld/ptr.hpp" namespace MWSound @@ -20,19 +24,23 @@ namespace MWSound virtual void init(const std::string &devname="") = 0; virtual void deinit() = 0; - virtual Sound *playSound(const std::string &fname, float volume, float pitch, bool loop) = 0; - virtual Sound *playSound3D(const std::string &fname, const float *pos, float volume, float pitch, - float min, float max, bool loop) = 0; - virtual Sound *streamSound(const std::string &fname, float volume, float pitch) = 0; - virtual Sound *streamSound3D(const std::string &fname, const float *pos, float volume, float pitch, - float min, float max) = 0; + virtual SoundPtr playSound(const std::string &fname, float volume, float pitch, int flags) = 0; + virtual SoundPtr playSound3D(const std::string &fname, const Ogre::Vector3 &pos, + float volume, float pitch, float min, float max, int flags) = 0; + virtual SoundPtr streamSound(const std::string &fname, float volume, float pitch, int flags) = 0; - virtual void updateListener(const float *pos, const float *atdir, const float *updir) = 0; + virtual void updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir, Environment env) = 0; Sound_Output& operator=(const Sound_Output &rhs); Sound_Output(const Sound_Output &rhs); - Sound_Output(SoundManager &mgr) : mManager(mgr) { } + protected: + Ogre::Vector3 mPos; + + Sound_Output(SoundManager &mgr) + : mManager(mgr) + , mPos(0.0f, 0.0f, 0.0f) + { } public: virtual ~Sound_Output() { } diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index f626ec158..a96aac6c5 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -41,6 +41,8 @@ namespace MWSound SoundManager::SoundManager(bool useSound, MWWorld::Environment& environment) : mResourceMgr(Ogre::ResourceGroupManager::getSingleton()) , mEnvironment(environment) + , mOutput(new DEFAULT_OUTPUT(*this)) + { if(!useSound) return; @@ -50,8 +52,6 @@ namespace MWSound try { - mOutput.reset(new DEFAULT_OUTPUT(*this)); - std::vector names = mOutput->enumerate(); std::cout <<"Enumerated output devices:"<< std::endl; for(size_t i = 0;i < names.size();i++) @@ -62,14 +62,11 @@ namespace MWSound catch(std::exception &e) { std::cout <<"Sound init failed: "<data.volume == 0) - volume = 0.0f; - else - volume *= pow(10.0, (snd->data.volume/255.0f*3348.0 - 3348.0) / 2000.0); + volume *= pow(10.0, (snd->data.volume/255.0*3348.0 - 3348.0) / 2000.0); if(snd->data.minRange == 0 && snd->data.maxRange == 0) { @@ -109,21 +103,20 @@ namespace MWSound max = std::max(min, max); } - return std::string("Sound/")+snd->sound; + return "Sound/"+snd->sound; } bool SoundManager::isPlaying(MWWorld::Ptr ptr, const std::string &id) const { - SoundMap::const_iterator snditer = mActiveSounds.find(ptr); - if(snditer == mActiveSounds.end()) - return false; - - IDMap::const_iterator iditer = snditer->second.find(id); - if(iditer == snditer->second.end()) - return false; - - return true; + SoundMap::const_iterator snditer = mActiveSounds.begin(); + while(snditer != mActiveSounds.end()) + { + if(snditer->second.first == ptr && snditer->second.second == id) + return snditer->first->isPlaying(); + snditer++; + } + return false; } @@ -139,9 +132,10 @@ namespace MWSound std::cout <<"Playing "<stop(); - mMusic.reset(mOutput->streamSound(filename, 0.4f, 1.0f)); + stopMusic(); + mMusic = mOutput->streamSound(filename, 0.4f, 1.0f, Play_NoEnv); + mMusic->mBaseVolume = 0.4f; + mMusic->mFlags = Play_NoEnv; } catch(std::exception &e) { @@ -182,11 +176,17 @@ namespace MWSound try { // The range values are not tested + float basevol = 1.0f; /* TODO: volume settings */ + std::string filePath = "Sound/"+filename; const ESM::Position &pos = ptr.getCellRef().pos; - std::string filePath = std::string("Sound/")+filename; + const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]); + + SoundPtr sound = mOutput->playSound3D(filePath, objpos, basevol, 1.0f, + 20.0f, 12750.0f, Play_Normal); + sound->mPos = objpos; + sound->mBaseVolume = basevol; - SoundPtr sound(mOutput->playSound3D(filePath, pos.pos, 1.0f, 1.0f, 100.0f, 20000.0f, false)); - mActiveSounds[ptr]["_say_sound"] = sound; + mActiveSounds[sound] = std::make_pair(ptr, std::string("_say_sound")); } catch(std::exception &e) { @@ -200,86 +200,105 @@ namespace MWSound } - void SoundManager::playSound(const std::string& soundId, float volume, float pitch, bool loop) + SoundPtr SoundManager::playSound(const std::string& soundId, float volume, float pitch, int mode) { - float min, max; + SoundPtr sound; try { - std::string file = lookup(soundId, volume, min, max); - Sound *sound = mOutput->playSound(file, volume, pitch, loop); - mLooseSounds[soundId] = SoundPtr(sound); + float basevol = 1.0f; /* TODO: volume settings */ + float min, max; + std::string file = lookup(soundId, basevol, min, max); + + sound = mOutput->playSound(file, volume*basevol, pitch, mode); + sound->mVolume = volume; + sound->mBaseVolume = basevol; + sound->mPitch = pitch; + sound->mMinDistance = min; + sound->mMaxDistance = max; + sound->mFlags = mode; + + mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), soundId); } catch(std::exception &e) { std::cout <<"Sound Error: "<playSound3D(file, pos.pos, volume, pitch, min, max, loop)); - if(untracked) mLooseSounds[soundId] = sound; - else mActiveSounds[ptr][soundId] = sound; + const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]); + + sound = mOutput->playSound3D(file, objpos, volume*basevol, pitch, min, max, mode); + sound->mPos = objpos; + sound->mVolume = volume; + sound->mBaseVolume = basevol; + sound->mPitch = pitch; + sound->mMinDistance = min; + sound->mMaxDistance = max; + sound->mFlags = mode; + + if((mode&Play_NoTrack)) + mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), soundId); + else + mActiveSounds[sound] = std::make_pair(ptr, soundId); } catch(std::exception &e) { std::cout <<"Sound Error: "<second.find(soundId); - if(iditer != snditer->second.end()) + if(snditer->second.first == ptr && snditer->second.second == soundId) { - iditer->second->stop(); - snditer->second.erase(iditer); - if(snditer->second.empty()) - mActiveSounds.erase(snditer); + snditer->first->stop(); + mActiveSounds.erase(snditer++); } + else + snditer++; } - else + } + + void SoundManager::stopSound3D(MWWorld::Ptr ptr) + { + SoundMap::iterator snditer = mActiveSounds.begin(); + while(snditer != mActiveSounds.end()) { - IDMap::iterator iditer = snditer->second.begin(); - while(iditer != snditer->second.end()) + if(snditer->second.first == ptr) { - iditer->second->stop(); - iditer++; + snditer->first->stop(); + mActiveSounds.erase(snditer++); } - mActiveSounds.erase(snditer); + else + snditer++; } } - void SoundManager::stopSound(MWWorld::Ptr::CellStore *cell) + void SoundManager::stopSound(const MWWorld::Ptr::CellStore *cell) { - // Remove all references to objects belonging to a given cell SoundMap::iterator snditer = mActiveSounds.begin(); while(snditer != mActiveSounds.end()) { - if(snditer->first.getCell() == cell) + if(snditer->second.first != MWWorld::Ptr() && + snditer->second.first.getCell() == cell) { - IDMap::iterator iditer = snditer->second.begin(); - while(iditer != snditer->second.end()) - { - iditer->second->stop(); - iditer++; - } + snditer->first->stop(); mActiveSounds.erase(snditer++); } else @@ -289,11 +308,17 @@ namespace MWSound void SoundManager::stopSound(const std::string& soundId) { - IDMap::iterator iditer = mLooseSounds.find(soundId); - if(iditer != mLooseSounds.end()) + SoundMap::iterator snditer = mActiveSounds.begin(); + while(snditer != mActiveSounds.end()) { - iditer->second->stop(); - mLooseSounds.erase(iditer); + if(snditer->second.first == MWWorld::Ptr() && + snditer->second.second == soundId) + { + snditer->first->stop(); + mActiveSounds.erase(snditer++); + } + else + snditer++; } } @@ -304,16 +329,14 @@ namespace MWSound void SoundManager::updateObject(MWWorld::Ptr ptr) { - SoundMap::iterator snditer = mActiveSounds.find(ptr); - if(snditer == mActiveSounds.end()) - return; - const ESM::Position &pos = ptr.getCellRef().pos; - IDMap::iterator iditer = snditer->second.begin(); - while(iditer != snditer->second.end()) + const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]); + SoundMap::iterator snditer = mActiveSounds.begin(); + while(snditer != mActiveSounds.end()) { - iditer->second->update(pos.pos); - iditer++; + if(snditer->second.first == ptr) + snditer->first->setPosition(objpos); + snditer++; } } @@ -384,45 +407,37 @@ namespace MWSound if(!isMusicPlaying()) startRandomTitle(); + MWWorld::Ptr::CellStore *current = mEnvironment.mWorld->getPlayer().getPlayer().getCell(); Ogre::Camera *cam = mEnvironment.mWorld->getPlayer().getRenderer()->getCamera(); Ogre::Vector3 nPos, nDir, nUp; nPos = cam->getRealPosition(); nDir = cam->getRealDirection(); nUp = cam->getRealUp(); + Environment env = Env_Normal; + if(nPos.y < current->cell->water) + env = Env_Underwater; + // The output handler is expecting vectors oriented like the game // (that is, -Z goes down, +Y goes forward), but that's not what we // get from Ogre's camera, so we have to convert. - float pos[3] = { nPos[0], -nPos[2], nPos[1] }; - float at[3] = { nDir[0], -nDir[2], nDir[1] }; - float up[3] = { nUp[0], -nUp[2], nUp[1] }; - mOutput->updateListener(pos, at, up); + const Ogre::Vector3 pos(nPos[0], -nPos[2], nPos[1]); + const Ogre::Vector3 at(nDir[0], -nDir[2], nDir[1]); + const Ogre::Vector3 up(nUp[0], -nUp[2], nUp[1]); + + mOutput->updateListener(pos, at, up, env); // Check if any sounds are finished playing, and trash them SoundMap::iterator snditer = mActiveSounds.begin(); while(snditer != mActiveSounds.end()) { - IDMap::iterator iditer = snditer->second.begin(); - while(iditer != snditer->second.end()) - { - if(!iditer->second->isPlaying()) - snditer->second.erase(iditer++); - else - iditer++; - } - if(snditer->second.empty()) + if(!snditer->first->isPlaying()) mActiveSounds.erase(snditer++); else + { + snditer->first->update(); snditer++; - } - - IDMap::iterator iditer = mLooseSounds.begin(); - while(iditer != mLooseSounds.end()) - { - if(!iditer->second->isPlaying()) - mLooseSounds.erase(iditer++); - else - iditer++; + } } } diff --git a/apps/openmw/mwsound/soundmanager.hpp b/apps/openmw/mwsound/soundmanager.hpp index a076c1cc0..cad5f6187 100644 --- a/apps/openmw/mwsound/soundmanager.hpp +++ b/apps/openmw/mwsound/soundmanager.hpp @@ -2,11 +2,11 @@ #define GAME_SOUND_SOUNDMANAGER_H #include +#include +#include #include -#include - #include "../mwworld/ptr.hpp" @@ -28,6 +28,25 @@ namespace MWSound class Sound; typedef boost::shared_ptr DecoderPtr; + typedef boost::shared_ptr SoundPtr; + + enum PlayMode { + 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 + * but do not keep it updated (the sound will not move with + * the object and will not stop when the object is deleted. */ + }; + static inline int operator|(const PlayMode &a, const PlayMode &b) + { return (int)a | (int)b; } + static inline int operator&(const PlayMode &a, const PlayMode &b) + { return (int)a & (int)b; } + + enum Environment { + Env_Normal, + Env_Underwater, + }; class SoundManager { @@ -40,11 +59,9 @@ namespace MWSound boost::shared_ptr mMusic; std::string mCurrentPlaylist; - typedef boost::shared_ptr SoundPtr; - typedef std::map IDMap; - typedef std::map SoundMap; + typedef std::pair PtrIDPair; + typedef std::map SoundMap; SoundMap mActiveSounds; - IDMap mLooseSounds; std::string lookup(const std::string &soundId, float &volume, float &min, float &max); @@ -88,19 +105,20 @@ namespace MWSound bool sayDone(MWWorld::Ptr reference) const; ///< Is actor not speaking? - void playSound(const std::string& soundId, float volume, float pitch, bool loop=false); + SoundPtr playSound(const std::string& soundId, float volume, float pitch, int mode=Play_Normal); ///< Play a sound, independently of 3D-position - void playSound3D(MWWorld::Ptr reference, const std::string& soundId, - float volume, float pitch, bool loop, - bool untracked=false); + SoundPtr playSound3D(MWWorld::Ptr reference, const std::string& soundId, + float volume, float pitch, int mode=Play_Normal); ///< Play a sound from an object - void stopSound3D(MWWorld::Ptr reference, const std::string& soundId=""); - ///< Stop the given object from playing the given sound, If no soundId is given, - /// all sounds for this reference will stop. + void stopSound3D(MWWorld::Ptr reference, const std::string& soundId); + ///< Stop the given object from playing the given sound, + + void stopSound3D(MWWorld::Ptr reference); + ///< Stop the given object from playing all sounds. - void stopSound(MWWorld::Ptr::CellStore *cell); + void stopSound(const MWWorld::Ptr::CellStore *cell); ///< Stop all sounds for the given cell. void stopSound(const std::string& soundId); diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index bb2f9f8a9..83c3ef2ba 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -50,6 +50,28 @@ namespace MWWorld return mEngine->rayTest(from,to); } + + std::vector < std::pair > PhysicsSystem::getFacedObjects () + { + //get a ray pointing to the center of the viewport + Ray centerRay = mRender.getCamera()->getCameraToViewportRay( + mRender.getViewport()->getWidth()/2, + mRender.getViewport()->getHeight()/2); + btVector3 from(centerRay.getOrigin().x,-centerRay.getOrigin().z,centerRay.getOrigin().y); + btVector3 to(centerRay.getPoint(500).x,-centerRay.getPoint(500).z,centerRay.getPoint(500).y); + + return mEngine->rayTest2(from,to); + } + + 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(500*extent).x,-centerRay.getPoint(500*extent).z,centerRay.getPoint(500*extent).y); + return result; + } bool PhysicsSystem::castRay(const Vector3& from, const Vector3& to) { diff --git a/apps/openmw/mwworld/physicssystem.hpp b/apps/openmw/mwworld/physicssystem.hpp index 78cbde083..7b2d77325 100644 --- a/apps/openmw/mwworld/physicssystem.hpp +++ b/apps/openmw/mwworld/physicssystem.hpp @@ -35,7 +35,11 @@ namespace MWWorld bool toggleCollisionMode(); std::pair getFacedHandle (MWWorld::World& world); - + + btVector3 getRayPoint(float extent); + + std::vector < std::pair > getFacedObjects (); + // cast ray, return true if it hit something bool castRay(const Ogre::Vector3& from, const Ogre::Vector3& to); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 22955bf32..df7d20bb6 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -54,9 +54,11 @@ void insertCellRefList(MWRender::RenderingManager& rendering, MWWorld::Environme namespace MWWorld { + void Scene::update (float duration){ mRendering.update (duration); } + void Scene::unloadCell (CellStoreCollection::iterator iter) { std::cout << "Unloading cell\n"; @@ -79,6 +81,7 @@ namespace MWWorld mPhysics->removeObject (node->getName()); } } + mRendering.removeCell(*iter); //mPhysics->removeObject("Unnamed_43"); @@ -87,6 +90,7 @@ namespace MWWorld mEnvironment.mSoundManager->stopSound (*iter); mActiveCells.erase(*iter); + } @@ -101,7 +105,7 @@ namespace MWWorld mActiveCells.insert(cell); if(result.second){ insertCell(*cell, mEnvironment); - mRendering.cellAdded (cell); + mRendering.cellAdded(cell); mRendering.configureAmbient(*cell); mRendering.requestMap(cell); mRendering.configureAmbient(*cell); @@ -192,6 +196,7 @@ namespace MWWorld mCurrentCell = *iter; + // adjust player playerCellChange (mWorld->getExterior(X, Y), position, adjustPlayerPos); @@ -199,6 +204,7 @@ namespace MWWorld mWorld->adjustSky(); mCellChanged = true; + mRendering.waterAdded(mCurrentCell); } //We need the ogre renderer and a scene node. @@ -238,6 +244,7 @@ namespace MWWorld Ptr::CellStore *cell = mWorld->getInterior(cellName); loadCell (cell); + // adjust player mCurrentCell = cell; @@ -250,6 +257,8 @@ namespace MWWorld mWorld->adjustSky(); mCellChanged = true; + + mRendering.waterAdded(cell); } void Scene::changeToExteriorCell (const ESM::Position& position) diff --git a/apps/openmw/mwworld/world.cpp b/apps/openmw/mwworld/world.cpp index a636ce288..a48cc7e72 100644 --- a/apps/openmw/mwworld/world.cpp +++ b/apps/openmw/mwworld/world.cpp @@ -146,10 +146,10 @@ namespace MWWorld mRendering->skySetDate (mGlobalVariables->getInt ("day"), mGlobalVariables->getInt ("month")); - mRendering->getSkyManager()->enable(); + mRendering->skyEnable(); } else - mRendering->getSkyManager()->disable(); + mRendering->skyDisable(); } World::World (OEngine::Render::OgreRenderer& renderer, @@ -157,7 +157,8 @@ namespace MWWorld const std::string& master, const boost::filesystem::path& resDir, bool newGame, Environment& environment, const std::string& encoding) : mPlayer (0), mLocalScripts (mStore), mGlobalVariables (0), - mSky (true), mEnvironment (environment), mNextDynamicRecord (0), mCells (mStore, mEsm, *this) + mSky (true), mEnvironment (environment), mNextDynamicRecord (0), mCells (mStore, mEsm, *this), + mNumFacing(0) { mPhysics = new PhysicsSystem(renderer); mPhysEngine = mPhysics->getEngine(); @@ -498,13 +499,21 @@ namespace MWWorld std::string World::getFacedHandle() { - std::pair result = mPhysics->getFacedHandle (*this); + if (!mRendering->occlusionQuerySupported()) + { + std::pair result = mPhysics->getFacedHandle (*this); - if (result.first.empty() || - result.second>getStore().gameSettings.find ("iMaxActivateDist")->i) - return ""; + if (result.first.empty() || + result.second>getStore().gameSettings.find ("iMaxActivateDist")->i) + return ""; - return result.first; + return result.first; + } + else + { + // updated every few frames in update() + return mFacedHandle; + } } void World::deleteObject (Ptr ptr) @@ -531,9 +540,10 @@ namespace MWWorld ptr.getRefData().getPosition().pos[0] = x; ptr.getRefData().getPosition().pos[1] = y; ptr.getRefData().getPosition().pos[2] = z; - if (ptr==mPlayer->getPlayer()) { + //std::cout << "X:" << ptr.getRefData().getPosition().pos[0] << " Z: " << ptr.getRefData().getPosition().pos[1] << "\n"; + Ptr::CellStore *currentCell = mWorldScene->getCurrentCell(); if (currentCell) { @@ -705,13 +715,82 @@ namespace MWWorld mWeatherManager->update (duration); - // cast a ray from player to sun to detect if the sun is visible - // this is temporary until we find a better place to put this code - // currently its here because we need to access the physics system - float* p = mPlayer->getPlayer().getRefData().getPosition().pos; - Vector3 sun = mRendering->getSkyManager()->getRealSunPos(); - sun = Vector3(sun.x, -sun.z, sun.y); - mRendering->getSkyManager()->setGlare(!mPhysics->castRay(Ogre::Vector3(p[0], p[1], p[2]), sun)); + if (!mRendering->occlusionQuerySupported()) + { + // cast a ray from player to sun to detect if the sun is visible + // this is temporary until we find a better place to put this code + // currently its here because we need to access the physics system + float* p = mPlayer->getPlayer().getRefData().getPosition().pos; + Vector3 sun = mRendering->getSkyManager()->getRealSunPos(); + sun = Vector3(sun.x, -sun.z, sun.y); + mRendering->getSkyManager()->setGlare(!mPhysics->castRay(Ogre::Vector3(p[0], p[1], p[2]), sun)); + } + + // update faced handle (object the player is looking at) + // this uses a mixture of raycasts and occlusion queries. + else // if (mRendering->occlusionQuerySupported()) + { + MWRender::OcclusionQuery* query = mRendering->getOcclusionQuery(); + if (!query->occlusionTestPending()) + { + // get result of last query + if (mNumFacing == 0) mFacedHandle = ""; + else if (mNumFacing == 1) + { + bool result = query->getTestResult(); + mFacedHandle = result ? mFaced1Name : ""; + } + else if (mNumFacing == 2) + { + bool result = query->getTestResult(); + mFacedHandle = result ? mFaced2Name : mFaced1Name; + } + + // send new query + // figure out which object we want to test against + std::vector < std::pair < float, std::string > > results = mPhysics->getFacedObjects(); + + // ignore the player + for (std::vector < std::pair < float, std::string > >::iterator it = results.begin(); + it != results.end(); ++it) + { + if ( (*it).second == mPlayer->getPlayer().getRefData().getHandle() ) + { + results.erase(it); + break; + } + } + + if (results.size() == 0) + { + mNumFacing = 0; + } + else if (results.size() == 1) + { + mFaced1 = getPtrViaHandle(results.front().second); + mFaced1Name = results.front().second; + mNumFacing = 1; + + btVector3 p = mPhysics->getRayPoint(results.front().first); + Ogre::Vector3 pos(p.x(), p.z(), -p.y()); + Ogre::SceneNode* node = mFaced1.getRefData().getBaseNode(); + query->occlusionTest(pos, node); + } + else + { + mFaced1Name = results.front().second; + mFaced2Name = results[1].second; + mFaced1 = getPtrViaHandle(results.front().second); + mFaced2 = getPtrViaHandle(results[1].second); + mNumFacing = 2; + + btVector3 p = mPhysics->getRayPoint(results[1].first); + Ogre::Vector3 pos(p.x(), p.z(), -p.y()); + Ogre::SceneNode* node = mFaced2.getRefData().getBaseNode(); + query->occlusionTest(pos, node); + } + } + } } bool World::isCellExterior() const @@ -754,4 +833,15 @@ namespace MWWorld { return mRendering->getFader(); } + + void World::setWaterHeight(const float height) + { + mRendering->setWaterHeight(height); + } + + void World::toggleWater() + { + mRendering->toggleWater(); + } + } diff --git a/apps/openmw/mwworld/world.hpp b/apps/openmw/mwworld/world.hpp index 71cca3545..7f8b7e861 100644 --- a/apps/openmw/mwworld/world.hpp +++ b/apps/openmw/mwworld/world.hpp @@ -93,6 +93,12 @@ namespace MWWorld Ptr getPtrViaHandle (const std::string& handle, Ptr::CellStore& cellStore); + std::string mFacedHandle; + Ptr mFaced1; + Ptr mFaced2; + std::string mFaced1Name; + std::string mFaced2Name; + int mNumFacing; int getDaysPerMonth (int month) const; @@ -112,6 +118,9 @@ namespace MWWorld Ptr::CellStore *getExterior (int x, int y); Ptr::CellStore *getInterior (const std::string& name); + + void setWaterHeight(const float height); + void toggleWater(); void adjustSky(); diff --git a/cmake/FindBullet.cmake b/cmake/FindBullet.cmake index 7bdf75a0a..552a0651a 100644 --- a/cmake/FindBullet.cmake +++ b/cmake/FindBullet.cmake @@ -51,13 +51,13 @@ find_path(BULLET_INCLUDE_DIR NAMES btBulletCollisionCommon.h # Find the libraries _FIND_BULLET_LIBRARY(BULLET_DYNAMICS_LIBRARY BulletDynamics) -_FIND_BULLET_LIBRARY(BULLET_DYNAMICS_LIBRARY_DEBUG BulletDynamics_d) +_FIND_BULLET_LIBRARY(BULLET_DYNAMICS_LIBRARY_DEBUG BulletDynamics_Debug BulletDynamics_d) _FIND_BULLET_LIBRARY(BULLET_COLLISION_LIBRARY BulletCollision) -_FIND_BULLET_LIBRARY(BULLET_COLLISION_LIBRARY_DEBUG BulletCollision_d) -_FIND_BULLET_LIBRARY(BULLET_MATH_LIBRARY LinearMath BulletMath) -_FIND_BULLET_LIBRARY(BULLET_MATH_LIBRARY_DEBUG LinearMath_d BulletMath_d) +_FIND_BULLET_LIBRARY(BULLET_COLLISION_LIBRARY_DEBUG BulletCollision_Debug BulletCollision_d) +_FIND_BULLET_LIBRARY(BULLET_MATH_LIBRARY BulletMath LinearMath) +_FIND_BULLET_LIBRARY(BULLET_MATH_LIBRARY_DEBUG BulletMath_Debug BulletMath_d LinearMath_debug LinearMath_d) _FIND_BULLET_LIBRARY(BULLET_SOFTBODY_LIBRARY BulletSoftBody) -_FIND_BULLET_LIBRARY(BULLET_SOFTBODY_LIBRARY_DEBUG BulletSoftBody_d) +_FIND_BULLET_LIBRARY(BULLET_SOFTBODY_LIBRARY_DEBUG BulletSoftBody_Debug BulletSoftBody_d) # handle the QUIETLY and REQUIRED arguments and set BULLET_FOUND to TRUE if diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index 72d15944d..0e3563b26 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -41,7 +41,21 @@ struct ciLessBoost : std::binary_function { bool operator() (const std::string & s1, const std::string & s2) const { //case insensitive version of is_less - return lexicographical_compare(s1, s2, boost::algorithm::is_iless()); + return boost::ilexicographical_compare(s1, s2); + } +}; + +struct pathComparer +{ +private: + std::string find; + +public: + pathComparer(const std::string& toFind) : find(toFind) { } + + bool operator() (const std::string& other) + { + return boost::iequals(find, other); } }; @@ -55,16 +69,62 @@ class DirArchive: public Ogre::FileSystemArchive std::map, ciLessBoost> m; unsigned int cutoff; - bool comparePortion(std::string file1, std::string file2, int start, int size) const + bool findFile(const String& filename, std::string& copy) const { - for(int i = start; i < start+size; i++) { - char one = file1.at(i); - char two = file2.at(i); - if(tolower(one) != tolower(two) ) + String passed = filename; + if(filename.at(filename.length() - 1) == '*' || filename.at(filename.length() - 1) == '?' || filename.at(filename.length() - 1) == '<' + || filename.at(filename.length() - 1) == '"' || filename.at(filename.length() - 1) == '>' || filename.at(filename.length() - 1) == ':' + || filename.at(filename.length() - 1) == '|') + { + passed = filename.substr(0, filename.length() - 2); + } + if(filename.at(filename.length() - 2) == '>') + passed = filename.substr(0, filename.length() - 6); + copy = passed; + } + + std::replace(copy.begin(), copy.end(), '\\', '/'); + + if(copy.at(0) == '/') + copy.erase(0, 1); + + if(fsstrict == true) + return true; + + std::string folder; + int delimiter = 0; + size_t lastSlash = copy.rfind('/'); + if (lastSlash != std::string::npos) + { + folder = copy.substr(0, lastSlash); + delimiter = lastSlash+1; + } + + std::vector current; + { + std::map,ciLessBoost>::const_iterator found = m.find(folder); + + if (found == m.end()) + { return false; + } + else + current = found->second; + } + + std::vector::iterator find = std::lower_bound(current.begin(), current.end(), copy, ciLessBoost()); + if (find != current.end() && !ciLessBoost()(copy, current.front())) + { + if (!boost::iequals(copy, *find)) + if ((find = std::find_if(current.begin(), current.end(), pathComparer(copy))) == current.end()) //\todo Check if this line is actually needed + return false; + + copy = *find; + return true; } - return true; + + return false; } public: @@ -83,16 +143,14 @@ class DirArchive: public Ogre::FileSystemArchive //need to cut off first boost::filesystem::directory_iterator dir_iter(d), dir_end; std::vector filesind; - boost::filesystem::path f; for(;dir_iter != dir_end; dir_iter++) { if(boost::filesystem::is_directory(*dir_iter)) populateMap(*dir_iter); else { - - f = *dir_iter; - std::string s = f.string(); + std::string s = dir_iter->path().string(); + std::replace(s.begin(), s.end(), '\\', '/'); std::string small; if(cutoff < s.size()) @@ -103,14 +161,17 @@ class DirArchive: public Ogre::FileSystemArchive filesind.push_back(small); } } + std::sort(filesind.begin(), filesind.end(), ciLessBoost()); + std::string small; std::string original = d.string(); + std::replace(original.begin(), original.end(), '\\', '/'); if(cutoff < original.size()) small = original.substr(cutoff, original.size() - cutoff); else small = original.substr(cutoff - 1, original.size() - cutoff); - m[small] = filesind; + m[small] = filesind; } bool isCaseSensitive() const { return fsstrict; } @@ -120,97 +181,21 @@ class DirArchive: public Ogre::FileSystemArchive void unload() {} bool exists(const String& filename) { - std::string copy = filename; - - - - for (unsigned int i = 0; i < filename.size(); i++) - { - if(copy.at(i) == '\\' ){ - copy.replace(i, 1, "/"); - } - } + std::string copy; - - if(copy.at(0) == '\\' || copy.at(0) == '/') - { - copy.erase(0, 1); - } - if(fsstrict == true) - { - //std::cout << "fsstrict " << copy << "\n"; + if (findFile(filename, copy)) return FileSystemArchive::exists(copy); - } - - - int last = copy.size() - 1; - int i = last; - - for (;last >= 0; i--) - { - if(copy.at(i) == '/' || copy.at(i) == '\\') - break; - } - - std::string folder = copy.substr(0, i); //folder with no slash - - std::vector& current = m[folder]; - - for(std::vector::iterator iter = current.begin(); iter != current.end(); iter++) - { - if(comparePortion(*iter, copy, i + 1, copy.size() - i -1) == true){ - return FileSystemArchive::exists(*iter); - } - } - return false; } DataStreamPtr open(const String& filename, bool readonly = true) const { - std::map, ciLessBoost> mlocal = m; - std::string copy = filename; - - - - for (unsigned int i = 0; i < filename.size(); i++) - { - if(copy.at(i) == '\\' ){ - copy.replace(i, 1, "/"); - } - } - - - if(copy.at(0) == '\\' || copy.at(0) == '/') - { - copy.erase(0, 1); - } + std::string copy; - if(fsstrict == true) - { + if (findFile(filename, copy)) return FileSystemArchive::open(copy, readonly); - } - - - int last = copy.size() - 1; - int i = last; - - for (;last >= 0; i--) - { - if(copy.at(i) == '/' || copy.at(i) == '\\') - break; - } - - std::string folder = copy.substr(0, i); //folder with no slash - std::vector current = mlocal[folder]; - for(std::vector::iterator iter = current.begin(); iter != current.end(); iter++) - { - if(comparePortion(*iter, copy, i + 1, copy.size() - i -1) == true){ - return FileSystemArchive::open(*iter, readonly); - } - } DataStreamPtr p; return p; } @@ -256,8 +241,12 @@ public: return DataStreamPtr(new Mangle2OgreStream(strm)); } +bool exists(const String& filename) { + return cexists(filename); +} + // Check if the file exists. - bool exists(const String& filename) { + bool cexists(const String& filename) const { String passed = filename; if(filename.at(filename.length() - 1) == '*' || filename.at(filename.length() - 1) == '?' || filename.at(filename.length() - 1) == '<' || filename.at(filename.length() - 1) == '"' || filename.at(filename.length() - 1) == '>' || filename.at(filename.length() - 1) == ':' @@ -268,7 +257,7 @@ public: if(filename.at(filename.length() - 2) == '>') passed = filename.substr(0, filename.length() - 6); -return arc.exists(passed.c_str()); +return arc.exists(passed.c_str()); } time_t getModifiedTime(const String&) { return 0; } @@ -308,6 +297,29 @@ return arc.exists(passed.c_str()); located in BSAs. So instead we channel it through exists() and set up a single-element result list if the file is found. */ + FileInfoListPtr findFileInfo(const String& pattern, bool recursive = true, + bool dirs = false) const + { + FileInfoListPtr ptr = FileInfoListPtr(new FileInfoList()); + + // Check if the file exists (only works for single files - wild + // cards and recursive search isn't implemented.) + if(cexists(pattern)) + { + FileInfo fi; + fi.archive = this; + fi.filename = pattern; + // It apparently doesn't matter that we return bogus + // information + fi.path = ""; + fi.compressedSize = fi.uncompressedSize = 0; + + ptr->push_back(fi); + } + + return ptr; + } + FileInfoListPtr findFileInfo(const String& pattern, bool recursive = true, bool dirs = false) { @@ -315,7 +327,7 @@ return arc.exists(passed.c_str()); // Check if the file exists (only works for single files - wild // cards and recursive search isn't implemented.) - if(exists(pattern)) + if(cexists(pattern)) { FileInfo fi; fi.archive = this; diff --git a/components/bsa/bsa_file.cpp b/components/bsa/bsa_file.cpp index 95358a362..f19606703 100644 --- a/components/bsa/bsa_file.cpp +++ b/components/bsa/bsa_file.cpp @@ -148,9 +148,9 @@ void BSAFile::readHeader() } /// Get the index of a given file name, or -1 if not found -int BSAFile::getIndex(const char *str) +int BSAFile::getIndex(const char *str) const { - Lookup::iterator it; + Lookup::const_iterator it; it = lookup.find(str); if(it == lookup.end()) return -1; diff --git a/components/bsa/bsa_file.hpp b/components/bsa/bsa_file.hpp index f54a64d2a..95fac0f4d 100644 --- a/components/bsa/bsa_file.hpp +++ b/components/bsa/bsa_file.hpp @@ -93,7 +93,7 @@ class BSAFile void readHeader(); /// Get the index of a given file name, or -1 if not found - int getIndex(const char *str); + int getIndex(const char *str) const; public: @@ -119,7 +119,7 @@ class BSAFile */ /// Check if a file exists - bool exists(const char *file) { return getIndex(file) != -1; } + bool exists(const char *file) const { return getIndex(file) != -1; } /** Open a file contained in the archive. Throws an exception if the file doesn't exist. diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp index 80a0f3e5a..158cc0867 100644 --- a/components/esm/loadcell.cpp +++ b/components/esm/loadcell.cpp @@ -21,8 +21,13 @@ void Cell::load(ESMReader &esm) if (data.flags & Interior) { // Interior cells - - if (esm.isNextSub("INTV") || esm.isNextSub("WHGT")) + if (esm.isNextSub("INTV")) + { + int waterl; + esm.getHT(waterl); + water = (float) waterl; + } + else if (esm.isNextSub("WHGT")) esm.getHT(water); // Quasi-exterior cells have a region (which determines the diff --git a/components/esm/loadcell.hpp b/components/esm/loadcell.hpp index 671f702ca..8070f9c03 100644 --- a/components/esm/loadcell.hpp +++ b/components/esm/loadcell.hpp @@ -114,11 +114,26 @@ struct Cell ESM_Context context; // File position DATAstruct data; AMBIstruct ambi; - int water; // Water level + float water; // Water level int mapColor; void load(ESMReader &esm); + bool isExterior() const + { + return !(data.flags & Interior); + } + + int getGridX() const + { + return data.gridX; + } + + int getGridY() const + { + return data.gridY; + } + // 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.cpp b/components/esm/loadland.cpp index 740d15a40..cd2cf1d91 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -4,6 +4,8 @@ namespace ESM { void Land::load(ESMReader &esm) { + mEsm = &esm; + // Get the grid location esm.getSubNameIs("INTV"); esm.getSubHeaderIs(8); @@ -19,14 +21,117 @@ void Land::load(ESMReader &esm) int cnt = 0; // Skip these here. Load the actual data when the cell is loaded. - if(esm.isNextSub("VNML")) {esm.skipHSubSize(12675);cnt++;} - if(esm.isNextSub("VHGT")) {esm.skipHSubSize(4232);cnt++;} - if(esm.isNextSub("WNAM")) esm.skipHSubSize(81); - if(esm.isNextSub("VCLR")) esm.skipHSubSize(12675); - if(esm.isNextSub("VTEX")) {esm.skipHSubSize(512);cnt++;} + if (esm.isNextSub("VNML")) + { + esm.skipHSubSize(12675); + cnt++; + } + if (esm.isNextSub("VHGT")) + { + esm.skipHSubSize(4232); + cnt++; + } + if (esm.isNextSub("WNAM")) + { + esm.skipHSubSize(81); + } + if (esm.isNextSub("VCLR")) + { + esm.skipHSubSize(12675); + } + if (esm.isNextSub("VTEX")) + { + esm.skipHSubSize(512); + cnt++; + } // We need all three of VNML, VHGT and VTEX in order to use the // landscape. hasData = (cnt == 3); + + dataLoaded = false; + landData = NULL; +} + +void Land::loadData() +{ + if (dataLoaded) + { + return; + } + + landData = new LandData; + + if (hasData) + { + mEsm->restoreContext(context); + + //esm.getHNExact(landData->normals, sizeof(VNML), "VNML"); + if (mEsm->isNextSub("VNML")) + { + mEsm->skipHSubSize(12675); + } + + VHGT rawHeights; + + mEsm->getHNExact(&rawHeights, sizeof(VHGT), "VHGT"); + int currentHeightOffset = rawHeights.heightOffset; + for (int y = 0; y < LAND_SIZE; y++) + { + currentHeightOffset += rawHeights.heightData[y * LAND_SIZE]; + landData->heights[y * LAND_SIZE] = currentHeightOffset * HEIGHT_SCALE; + + int tempOffset = currentHeightOffset; + for (int x = 1; x < LAND_SIZE; x++) + { + tempOffset += rawHeights.heightData[y * LAND_SIZE + x]; + landData->heights[x + y * LAND_SIZE] = tempOffset * HEIGHT_SCALE; + } + } + + if (mEsm->isNextSub("WNAM")) + { + mEsm->skipHSubSize(81); + } + if (mEsm->isNextSub("VCLR")) + { + landData->usingColours = true; + mEsm->getHExact(&landData->colours, 3*LAND_NUM_VERTS); + }else{ + landData->usingColours = false; + } + //TODO fix magic numbers + uint16_t vtex[512]; + mEsm->getHNExact(&vtex, 512, "VTEX"); + + int readPos = 0; //bit ugly, but it works + for ( int y1 = 0; y1 < 4; y1++ ) + for ( int x1 = 0; x1 < 4; x1++ ) + for ( int y2 = 0; y2 < 4; y2++) + for ( int x2 = 0; x2 < 4; x2++ ) + landData->textures[(y1*4+y2)*16+(x1*4+x2)] = vtex[readPos++]; + } + else + { + landData->usingColours = false; + memset(&landData->textures, 0, 512 * sizeof(uint16_t)); + for (int i = 0; i < LAND_NUM_VERTS; i++) + { + landData->heights[i] = -256.0f * HEIGHT_SCALE; + } + } + + dataLoaded = true; } + +void Land::unloadData() +{ + if (dataLoaded) + { + delete landData; + landData = NULL; + dataLoaded = false; + } +} + } diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp index af91850ac..5ccd966d9 100644 --- a/components/esm/loadland.hpp +++ b/components/esm/loadland.hpp @@ -17,11 +17,66 @@ struct Land // File context. This allows the ESM reader to be 'reset' to this // location later when we are ready to load the full data set. + ESMReader* mEsm; ESM_Context context; bool hasData; + bool dataLoaded; + + // number of vertices per side + static const int LAND_SIZE = 65; + + // cell terrain size in world coords + static const int REAL_SIZE = 8192; + + // total number of vertices + static const int LAND_NUM_VERTS = LAND_SIZE * LAND_SIZE; + + static const int HEIGHT_SCALE = 8; + + //number of textures per side of land + static const int LAND_TEXTURE_SIZE = 16; + + //total number of textures per land + static const int LAND_NUM_TEXTURES = LAND_TEXTURE_SIZE * LAND_TEXTURE_SIZE; + +#pragma pack(push,1) + struct VHGT + { + float heightOffset; + int8_t heightData[LAND_NUM_VERTS]; + short unknown1; + char unknown2; + }; +#pragma pack(pop) + + typedef uint8_t VNML[LAND_NUM_VERTS * 3]; + + struct LandData + { + float heightOffset; + float heights[LAND_NUM_VERTS]; + //float normals[LAND_NUM_VERTS * 3]; + uint16_t textures[LAND_NUM_TEXTURES]; + + bool usingColours; + char colours[3 * LAND_NUM_VERTS]; + }; + + LandData *landData; + void load(ESMReader &esm); + + /** + * Actually loads data + */ + void loadData(); + + /** + * Frees memory allocated for land data + */ + void unloadData(); }; } #endif diff --git a/components/esm_store/cell_store.hpp b/components/esm_store/cell_store.hpp index c4bcf84d8..024412291 100644 --- a/components/esm_store/cell_store.hpp +++ b/components/esm_store/cell_store.hpp @@ -95,12 +95,17 @@ namespace ESMS State_Unloaded, State_Preloaded, State_Loaded }; - CellStore (const ESM::Cell *cell_) : cell (cell_), mState (State_Unloaded) {} + CellStore (const ESM::Cell *cell_) : cell (cell_), mState (State_Unloaded) + { + mWaterLevel = cell->water; + } const ESM::Cell *cell; State mState; std::vector mIds; + float mWaterLevel; + // Lists for each individual object type CellRefList activators; CellRefList potions; diff --git a/components/esm_store/reclists.hpp b/components/esm_store/reclists.hpp index 678f794c8..16d37bec7 100644 --- a/components/esm_store/reclists.hpp +++ b/components/esm_store/reclists.hpp @@ -201,15 +201,21 @@ namespace ESMS // TODO: For multiple ESM/ESP files we need one list per file. std::vector ltex; - int count; - LTexList() : count(0) + LTexList() { // More than enough to hold Morrowind.esm. ltex.reserve(128); } - int getSize() { return count; } + const LandTexture* search(size_t index) const + { + assert(index < ltex.size()); + return <ex.at(index); + } + + int getSize() { return ltex.size(); } + int getSize() const { return ltex.size(); } virtual void listIdentifier (std::vector& identifier) const {} @@ -233,12 +239,18 @@ namespace ESMS */ struct LandList : RecList { - virtual ~LandList() {} + virtual ~LandList() + { + for ( LandMap::iterator itr = lands.begin(); itr != lands.end(); ++itr ) + { + delete itr->second; + } + } // Map containing all landscapes - typedef std::map LandsCol; - typedef std::map Lands; - Lands lands; + typedef std::pair LandCoord; + typedef std::map LandMap; + LandMap lands; int count; LandList() : count(0) {} @@ -247,17 +259,15 @@ namespace ESMS virtual void listIdentifier (std::vector& identifier) const {} // Find land for the given coordinates. Return null if no data. - const Land *search(int x, int y) const + Land *search(int x, int y) const { - Lands::const_iterator it = lands.find(x); - if(it==lands.end()) - return NULL; - - LandsCol::const_iterator it2 = it->second.find(y); - if(it2 == it->second.end()) + LandMap::const_iterator itr = lands.find(std::make_pair(x, y)); + if ( itr == lands.end() ) + { return NULL; + } - return it2->second; + return itr->second; } void load(ESMReader &esm, const std::string &id) @@ -266,11 +276,11 @@ namespace ESMS // Create the structure and load it. This actually skips the // landscape data and remembers the file position for later. - Land *land = new Land; + Land *land = new Land(); land->load(esm); // Store the structure - lands[land->X][land->Y] = land; + lands[std::make_pair(land->X, land->Y)] = land; } }; diff --git a/components/esm_store/store.hpp b/components/esm_store/store.hpp index fab04d3e9..857682089 100644 --- a/components/esm_store/store.hpp +++ b/components/esm_store/store.hpp @@ -116,7 +116,7 @@ namespace ESMS recLists[REC_GLOB] = &globals; recLists[REC_GMST] = &gameSettings; recLists[REC_INGR] = &ingreds; - //recLists[REC_LAND] = &lands; + recLists[REC_LAND] = &lands; recLists[REC_LEVC] = &creatureLists; recLists[REC_LEVI] = &itemLists; recLists[REC_LIGH] = &lights; diff --git a/components/files/collections.cpp b/components/files/collections.cpp index 424b558e6..50340dca4 100644 --- a/components/files/collections.cpp +++ b/components/files/collections.cpp @@ -30,4 +30,9 @@ namespace Files return iter->second; } + + const Files::PathContainer& Collections::getPaths() const + { + return mDirectories; + } } diff --git a/components/files/collections.hpp b/components/files/collections.hpp index 1ddca9a5b..70aaec55e 100644 --- a/components/files/collections.hpp +++ b/components/files/collections.hpp @@ -21,6 +21,8 @@ namespace Files /// leading dot and must be all lower-case. const MultiDirCollection& getCollection(const std::string& extension) const; + const Files::PathContainer& getPaths() const; + private: typedef std::map MultiDirCollectionContainer; Files::PathContainer mDirectories; diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index f943231d0..2ab6ae621 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -1368,7 +1368,7 @@ void NIFLoader::loadResource(Resource *resource) if (!vfs->isFile(resourceName)) { - warn("File not found."); + warn("File "+resourceName+" not found."); return; } diff --git a/files/CMakeLists.txt b/files/CMakeLists.txt new file mode 100644 index 000000000..203b40681 --- /dev/null +++ b/files/CMakeLists.txt @@ -0,0 +1,13 @@ +project(resources) + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/caustic_0.png "${OpenMW_BINARY_DIR}/resources/water/caustic_0.png" COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/Example_Fresnel.cg "${OpenMW_BINARY_DIR}/resources/water/Example_Fresnel.cg" COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/Example_FresnelPS.asm "${OpenMW_BINARY_DIR}/resources/water/Example_FresnelPS.asm" COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/GlassFP.cg "${OpenMW_BINARY_DIR}/resources/water/GlassFP.cg" COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/GlassVP.cg "${OpenMW_BINARY_DIR}/resources/water/GlassVP.cg" COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/perlinvolume.dds "${OpenMW_BINARY_DIR}/resources/water/perlinvolume.dds" COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/Water02.jpg "${OpenMW_BINARY_DIR}/resources/water/Water02.jpg" COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/water.compositor "${OpenMW_BINARY_DIR}/resources/water/water.compositor" COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/waves2.dds "${OpenMW_BINARY_DIR}/resources/water/waves2.dds" COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/Examples-Water.material "${OpenMW_BINARY_DIR}/resources/water/Examples-Water.material" COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/WaterNormal1.tga "${OpenMW_BINARY_DIR}/resources/water/WaterNormal1.tga" COPYONLY) diff --git a/files/mac/openmw.icns b/files/mac/openmw.icns index dfea24660..3ff899a79 100644 Binary files a/files/mac/openmw.icns and b/files/mac/openmw.icns differ diff --git a/files/mygui/openmw_dialogue_window_layout.xml b/files/mygui/openmw_dialogue_window_layout.xml index 11ac41cb3..29a3b511e 100644 --- a/files/mygui/openmw_dialogue_window_layout.xml +++ b/files/mygui/openmw_dialogue_window_layout.xml @@ -1,34 +1,29 @@ - - - - - - + - + - + - - + - + diff --git a/files/mygui/openmw_windows.skin.xml b/files/mygui/openmw_windows.skin.xml index a986dcffc..3ee33124c 100644 --- a/files/mygui/openmw_windows.skin.xml +++ b/files/mygui/openmw_windows.skin.xml @@ -294,7 +294,7 @@ - + diff --git a/files/water/Example_Fresnel.cg b/files/water/Example_Fresnel.cg new file mode 100644 index 000000000..e091fc587 --- /dev/null +++ b/files/water/Example_Fresnel.cg @@ -0,0 +1,116 @@ +// Vertex program for fresnel reflections / refractions +void main_vp( + float4 pos : POSITION, + float4 normal : NORMAL, + float2 tex : TEXCOORD0, + + out float4 oPos : POSITION, + out float3 noiseCoord : TEXCOORD0, + out float4 projectionCoord : TEXCOORD1, + out float3 oEyeDir : TEXCOORD2, + out float3 oNormal : TEXCOORD3, + + uniform float4x4 worldViewProjMatrix, + uniform float3 eyePosition, // object space + uniform float timeVal, + uniform float scale, // the amount to scale the noise texture by + uniform float scroll, // the amount by which to scroll the noise + uniform float noise // the noise perturb as a factor of the time + ) +{ + oPos = mul(worldViewProjMatrix, pos); + // Projective texture coordinates, adjust for mapping + float4x4 scalemat = float4x4(0.5, 0, 0, 0.5, + 0,-0.5, 0, 0.5, + 0, 0, 0.5, 0.5, + 0, 0, 0, 1); + projectionCoord = mul(scalemat, oPos); + // Noise map coords + noiseCoord.xy = (tex + (timeVal * scroll)) * scale; + noiseCoord.z = noise * timeVal; + + oEyeDir = normalize(pos.xyz - eyePosition); + oNormal = normal.rgb; + +} + +// Fragment program for distorting a texture using a 3D noise texture +void main_fp( + float3 noiseCoord : TEXCOORD0, + float4 projectionCoord : TEXCOORD1, + float3 eyeDir : TEXCOORD2, + float3 normal : TEXCOORD3, + + out float4 col : COLOR, + + uniform float4 tintColour, + uniform float noiseScale, + uniform float fresnelBias, + uniform float fresnelScale, + uniform float fresnelPower, + uniform sampler2D waterTex : register(s0), + uniform sampler2D noiseMap : register(s1), + uniform sampler2D reflectMap : register(s2), + uniform sampler2D refractMap : register(s3) + ) +{ + // Do the tex projection manually so we can distort _after_ + float2 final = projectionCoord.xy / projectionCoord.w; + + // Noise + float3 noiseNormal = (tex2D(noiseMap, (noiseCoord.xy / 5)).rgb - 0.5).rbg * noiseScale; + final += noiseNormal.xz; + + // Fresnel + //normal = normalize(normal + noiseNormal.xz); + float fresnel = fresnelBias + fresnelScale * pow(1 + dot(eyeDir, normal), fresnelPower); + + // Reflection / refraction + float4 reflectionColour = tex2D(reflectMap, final); + float4 refractionColour = tex2D(refractMap, final) + tintColour; + + // Final colour + col = lerp(refractionColour, reflectionColour, fresnel) * tex2D(waterTex, noiseNormal) / 3 ; + + +} + + +// Old version to match ATI PS 1.3 implementation +void main_vp_old( + float4 pos : POSITION, + float4 normal : NORMAL, + float2 tex : TEXCOORD0, + + out float4 oPos : POSITION, + out float fresnel : COLOR, + out float3 noiseCoord : TEXCOORD0, + out float4 projectionCoord : TEXCOORD1, + + uniform float4x4 worldViewProjMatrix, + uniform float3 eyePosition, // object space + uniform float fresnelBias, + uniform float fresnelScale, + uniform float fresnelPower, + uniform float timeVal, + uniform float scale, // the amount to scale the noise texture by + uniform float scroll, // the amount by which to scroll the noise + uniform float noise // the noise perturb as a factor of the time + ) +{ + oPos = mul(worldViewProjMatrix, pos); + // Projective texture coordinates, adjust for mapping + float4x4 scalemat = float4x4(0.5, 0, 0, 0.5, + 0,-0.5, 0, 0.5, + 0, 0, 0.5, 0.5, + 0, 0, 0, 1); + projectionCoord = mul(scalemat, oPos); + // Noise map coords + noiseCoord.xy = (tex + (timeVal * scroll)) * scale; + noiseCoord.z = noise * timeVal; + + // calc fresnel factor (reflection coefficient) + float3 eyeDir = normalize(pos.xyz - eyePosition); + fresnel = fresnelBias + fresnelScale * pow(1 + dot(eyeDir, normal), fresnelPower); + +} diff --git a/files/water/Example_FresnelPS.asm b/files/water/Example_FresnelPS.asm new file mode 100644 index 000000000..2de078ef5 --- /dev/null +++ b/files/water/Example_FresnelPS.asm @@ -0,0 +1,72 @@ +ps.1.4 + // conversion from Cg generated ARB_fragment_program to ps.1.4 by NFZ + // command line args: -profile arbfp1 -entry main_fp + // program main_fp + // c0 : distortionRange + // c1 : tintColour + // testure 0 : noiseMap + // texture 1 : reflectMap + // texture 2 : refractMap + // v0.x : fresnel + // t0.xyz : noiseCoord + // t1.xyw : projectionCoord + +def c2, 2, 1, 0, 0 + + // Cg: distort.x = tex3D(noiseMap, noiseCoord).x; + // arbfp1: TEX R0.x, fragment.texcoord[0], texture[0], 3D; + // sample noise map using noiseCoord in TEX unit 0 + +texld r0, t0.xyz + + // get projected texture coordinates from TEX coord 1 + // will be used in phase 2 + +texcrd r1.xy, t1_dw.xyw +mov r1.z, c2.y + + // Cg: distort.y = tex3D(noiseMap, noiseCoord + yoffset).x; + // arbfp1: ADD R1.xyz, fragment.texcoord[0], c1; + // arbfp1: TEX R1.x, R1, texture[0], 3D; + // arbfp1: MOV R0.y, R1.x; + + // Cg: distort = (distort * 2 - 1) * distortionRange; + // arbfp1: MAD R0.xy, R0, c0.x, -c0.y; + // arbfp1: MUL R0.xy, R0, u0.x; + // (distort * 2 - 1) same as 2*(distort -.5) so use _bx2 + + + // Cg: final = projectionCoord.xy / projectionCoord.w; + // Cg: final += distort; + // arbfp1: RCP R0.w, fragment.texcoord[1].w; + // arbfp1: MAD R0.xy, fragment.texcoord[1], R0.w, R0; + // final = (distort * projectionCoord.w) + projectionCoord.xy + // for ps.1.4 have to re-arrange things a bit to perturb projected texture coordinates + +mad r0.xyz, r0_bx2, c0.x, r1 + +phase + + // do dependant texture reads + // Cg: reflectionColour = tex2D(reflectMap, final); + // arbfp1: TEX R0, R0, texture[1], 2D; + // sampe reflectMap using dependant read : texunit 1 + +texld r1, r0.xyz + + // Cg: refractionColour = tex2D(refractMap, final) + tintColour; + // arbfp1: TEX R1, R0, texture[2], 2D; + // sample refractMap : texunit 2 + +texld r2, r0.xyz + + // adding tintColour that is in global c1 + // arbfp1: ADD R1, R1, u1; + +add r2, r2, c1 + + // Cg: col = lerp(refractionColour, reflectionColour, fresnel); + // arbfp1: ADD R0, R0, -R1; + // arbfp1: MAD result.color, fragment.color.primary.x, R0, R1; + +lrp r0, v0.x, r1, r2 diff --git a/files/water/Examples-Water.material b/files/water/Examples-Water.material new file mode 100644 index 000000000..2b46d6e08 --- /dev/null +++ b/files/water/Examples-Water.material @@ -0,0 +1,149 @@ + +vertex_program Water/GlassVP cg +{ + source GlassVP.cg + entry_point glass_vp + profiles vs_1_1 arbvp1 + + default_params + { + param_named_auto worldViewProj worldviewproj_matrix + } +} + + +fragment_program Water/GlassFP cg +{ + source GlassFP.cg + entry_point main_ps + profiles ps_2_0 arbfp1 +} + +material Water/Compositor +{ + technique + { + pass + { + depth_check off + vertex_program_ref Water/GlassVP + { + param_named_auto timeVal time 0.25 + param_named scale float 0.1 + } + + fragment_program_ref Water/GlassFP + { + param_named tintColour float4 0 0.35 0.35 1 + } + + texture_unit RT + { + tex_coord_set 0 + tex_address_mode clamp + filtering linear linear linear + } + + texture_unit + { + texture WaterNormal1.tga 2d + tex_coord_set 1 + //tex_address_mode clamp + filtering linear linear linear + } + texture_unit + { + texture caustic_0.png 2d + tex_coord_set 2 + //tex_address_mode clamp + filtering linear linear linear + } + } + } +} +vertex_program Water/RefractReflectVP cg +{ + source Example_Fresnel.cg + entry_point main_vp + profiles vs_1_1 arbvp1 +} +vertex_program Water/RefractReflectVPold cg +{ + source Example_Fresnel.cg + entry_point main_vp_old + profiles vs_1_1 arbvp1 +} + +fragment_program Water/RefractReflectFP cg +{ + source Example_Fresnel.cg + entry_point main_fp + // sorry, ps_1_1 and fp20 can't do this + profiles ps_2_0 arbfp1 +} + +fragment_program Water/RefractReflectPS asm +{ + source Example_FresnelPS.asm + // sorry, only for ps_1_4 :) + syntax ps_1_4 + +} +material Examples/Water0 +{ + + technique + { + pass + { + // + + depth_write off + vertex_program_ref Water/RefractReflectVP + { + param_named_auto worldViewProjMatrix worldviewproj_matrix + param_named_auto eyePosition camera_position_object_space + param_named_auto timeVal time 0.15 + param_named scroll float 1 + param_named scale float 1 + param_named noise float 1 + // scroll and noisePos will need updating per frame + } + fragment_program_ref Water/RefractReflectFP + { + param_named fresnelBias float -0.1 + param_named fresnelScale float 0.8 + param_named fresnelPower float 20 + param_named tintColour float4 1 1 1 1 + param_named noiseScale float 0.05 + } + // Water + scene_blend alpha_blend + texture_unit + { + + // Water texture + texture Water02.jpg + // min / mag filtering, no mip + filtering linear linear none + alpha_op_ex source1 src_manual src_current 0.9 + + } + // Noise + texture_unit + { + alpha_op_ex source1 src_manual src_current 0.9 + // Perlin noise volume + texture waves2.dds + // min / mag filtering, no mip + filtering linear linear none + } + + + } + + + } + +} + diff --git a/files/water/GlassFP.cg b/files/water/GlassFP.cg new file mode 100644 index 000000000..eb18885d2 --- /dev/null +++ b/files/water/GlassFP.cg @@ -0,0 +1,15 @@ +sampler RT : register(s0); +sampler NormalMap : register(s1); +sampler CausticMap : register(s2); + +float4 main_ps(float2 iTexCoord : TEXCOORD0, + float3 noiseCoord : TEXCOORD1, + uniform float4 tintColour) : COLOR +{ + float4 normal = tex2D(NormalMap, noiseCoord); + + + return tex2D(RT, iTexCoord + normal.xy * 0.05) + + (tex2D(CausticMap, noiseCoord) / 5) + + tintColour ; +} diff --git a/files/water/GlassVP.cg b/files/water/GlassVP.cg new file mode 100644 index 000000000..71153769c --- /dev/null +++ b/files/water/GlassVP.cg @@ -0,0 +1,24 @@ +void glass_vp +( + in float4 inPos : POSITION, + + out float4 pos : POSITION, + out float2 uv0 : TEXCOORD0, + out float4 noiseCoord : TEXCOORD1, + + uniform float4x4 worldViewProj, + uniform float timeVal, + uniform float scale +) +{ + // Use standardise transform, so work accord with render system specific (RS depth, requires texture flipping, etc) + pos = mul(worldViewProj, inPos); + + // The input positions adjusted by texel offsets, so clean up inaccuracies + inPos.xy = sign(inPos.xy); + + // Convert to image-space + uv0 = (float2(inPos.x, -inPos.y) + 1.0f) * 0.5f; + noiseCoord = (pos + timeVal) * scale; +} + diff --git a/files/water/Water02.jpg b/files/water/Water02.jpg new file mode 100644 index 000000000..3efda7b6a Binary files /dev/null and b/files/water/Water02.jpg differ diff --git a/files/water/WaterNormal1.tga b/files/water/WaterNormal1.tga new file mode 100644 index 000000000..a9ca11b7e Binary files /dev/null and b/files/water/WaterNormal1.tga differ diff --git a/files/water/caustic_0.png b/files/water/caustic_0.png new file mode 100644 index 000000000..fee464860 Binary files /dev/null and b/files/water/caustic_0.png differ diff --git a/files/water/perlinvolume.dds b/files/water/perlinvolume.dds new file mode 100644 index 000000000..bd8147d49 Binary files /dev/null and b/files/water/perlinvolume.dds differ diff --git a/files/water/water.compositor b/files/water/water.compositor new file mode 100644 index 000000000..67bf90896 --- /dev/null +++ b/files/water/water.compositor @@ -0,0 +1,21 @@ +compositor Water +{ + technique + { + texture rt0 target_width target_height PF_R8G8B8 + + target rt0 { input previous } + + target_output + { + // Start with clear output + input none + + pass render_quad + { + material Water/Compositor + input 0 rt0 + } + } + } +} diff --git a/files/water/waves2.dds b/files/water/waves2.dds new file mode 100644 index 000000000..c379886fa Binary files /dev/null and b/files/water/waves2.dds differ diff --git a/libs/openengine/bullet/BulletShapeLoader.cpp b/libs/openengine/bullet/BulletShapeLoader.cpp index 4593bad52..59a414f30 100644 --- a/libs/openengine/bullet/BulletShapeLoader.cpp +++ b/libs/openengine/bullet/BulletShapeLoader.cpp @@ -63,17 +63,17 @@ size_t BulletShape::calculateSize() const //============================================================================================================= -template<> BulletShapeManager *Ogre::Singleton::ms_Singleton = 0; +template<> BulletShapeManager *Ogre::Singleton::msSingleton = 0; BulletShapeManager *BulletShapeManager::getSingletonPtr() { - return ms_Singleton; + return msSingleton; } BulletShapeManager &BulletShapeManager::getSingleton() { - assert(ms_Singleton); - return(*ms_Singleton); + assert(msSingleton); + return(*msSingleton); } BulletShapeManager::BulletShapeManager() diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index 8b9f3dfec..e7da9f085 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -151,7 +151,8 @@ namespace Physic - PhysicEngine::PhysicEngine(BulletShapeLoader* shapeLoader) + PhysicEngine::PhysicEngine(BulletShapeLoader* shapeLoader) : + mDebugActive(0) { // Set up the collision configuration and dispatcher collisionConfiguration = new btDefaultCollisionConfiguration(); @@ -203,6 +204,13 @@ namespace Physic createDebugRendering(); } mDebugDrawer->setDebugMode(mode); + mDebugActive = mode; + } + + bool PhysicEngine::toggleDebugRendering() + { + setDebugRenderingMode(!mDebugActive); + return mDebugActive; } PhysicEngine::~PhysicEngine() @@ -418,4 +426,35 @@ namespace Physic return std::pair(name,d); } + + std::vector< std::pair > PhysicEngine::rayTest2(btVector3& from, btVector3& to) + { + MyRayResultCallback resultCallback1; + resultCallback1.m_collisionFilterMask = COL_WORLD; + dynamicsWorld->rayTest(from, to, resultCallback1); + std::vector< std::pair > results = resultCallback1.results; + + MyRayResultCallback resultCallback2; + resultCallback2.m_collisionFilterMask = COL_ACTOR_INTERNAL|COL_ACTOR_EXTERNAL; + dynamicsWorld->rayTest(from, to, resultCallback2); + std::vector< std::pair > actorResults = resultCallback2.results; + + std::vector< std::pair > results2; + + for (std::vector< std::pair >::iterator it=results.begin(); + it != results.end(); ++it) + { + results2.push_back( std::make_pair( (*it).first, static_cast(*(*it).second).mName ) ); + } + + for (std::vector< std::pair >::iterator it=actorResults.begin(); + it != actorResults.end(); ++it) + { + results2.push_back( std::make_pair( (*it).first, static_cast(*(*it).second).mName ) ); + } + + std::sort(results2.begin(), results2.end(), MyRayResultCallback::cmp); + + return results2; + } }}; diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp index 57ffe9130..8d177efda 100644 --- a/libs/openengine/bullet/physic.hpp +++ b/libs/openengine/bullet/physic.hpp @@ -199,11 +199,18 @@ namespace Physic */ void setDebugRenderingMode(int mode); + bool toggleDebugRendering(); + /** * Return the closest object hit by a ray. If there are no objects, it will return ("",-1). */ std::pair rayTest(btVector3& from,btVector3& to); + /** + * Return all objects hit by a ray. + */ + std::vector< std::pair > rayTest2(btVector3& from, btVector3& to); + //event list of non player object std::list NPEventList; @@ -230,6 +237,26 @@ namespace Physic //debug rendering BtOgre::DebugDrawer* mDebugDrawer; bool isDebugCreated; + bool mDebugActive; + }; + + + struct MyRayResultCallback : public btCollisionWorld::RayResultCallback + { + virtual btScalar addSingleResult( btCollisionWorld::LocalRayResult& rayResult, bool bNormalInWorldSpace) + { + results.push_back( std::make_pair(rayResult.m_hitFraction, rayResult.m_collisionObject) ); + return rayResult.m_hitFraction; + } + + static bool cmp( const std::pair& i, const std::pair& j ) + { + if( i.first > j.first ) return false; + if( j.first > i.first ) return true; + return false; + } + + std::vector < std::pair > results; }; }} diff --git a/readme.txt b/readme.txt index 36a732015..17806172f 100644 --- a/readme.txt +++ b/readme.txt @@ -148,6 +148,7 @@ Bug #207: Ogre.log not written Bug #209: Sounds do not play Bug #210: Ogre crash at Dren plantation Bug #214: Unsupported file format version +Bug #222: Launcher is writing openmw.cfg file to wrong location Feature #9: NPC Dialogue Window Feature #16/42: New sky/weather implementation Feature #40: Fading