diff --git a/.travis.yml b/.travis.yml index 39a02de63..5d0326a07 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,6 @@ branches: - /openmw-.*$/ before_install: - pwd - - git fetch --tags - echo "yes" | sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu `lsb_release -sc` main universe restricted multiverse" - echo "yes" | sudo apt-add-repository ppa:openmw/openmw - sudo apt-get update -qq diff --git a/CMakeLists.txt b/CMakeLists.txt index b5fd2f4bf..a9293fa32 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,34 +6,63 @@ if (APPLE) set(APP_BUNDLE_DIR "${OpenMW_BINARY_DIR}/${APP_BUNDLE_NAME}") endif (APPLE) -# Macros - set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/) -include(OpenMWMacros) - # Version +message(STATUS "Configuring OpenMW...") + +set(OPENMW_VERSION_MAJOR 0) +set(OPENMW_VERSION_MINOR 29) +set(OPENMW_VERSION_RELEASE 0) + +set(OPENMW_VERSION_COMMITHASH "") +set(OPENMW_VERSION_TAGHASH "") + +set(OPENMW_VERSION "${OPENMW_VERSION_MAJOR}.${OPENMW_VERSION_MINOR}.${OPENMW_VERSION_RELEASE}") + +if(EXISTS ${PROJECT_SOURCE_DIR}/.git) + if(NOT EXISTS ${PROJECT_SOURCE_DIR}/.git/shallow) + find_package(Git) + + if(GIT_FOUND) + include(GetGitRevisionDescription) + get_git_tag_revision(TAGHASH --tags --max-count=1) + get_git_head_revision(REFSPEC COMMITHASH) + git_describe(VERSION --tags ${TAGHASH}) + + string(REGEX MATCH "^openmw-[^0-9]*[0-9]+\\.[0-9]+\\.[0-9]+.*" MATCH "${VERSION}") + if(MATCH) + string(REGEX REPLACE "^openmw-([0-9]+)\\..*" "\\1" GIT_VERSION_MAJOR "${VERSION}") + string(REGEX REPLACE "^openmw-[0-9]+\\.([0-9]+).*" "\\1" GIT_VERSION_MINOR "${VERSION}") + string(REGEX REPLACE "^openmw-[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" GIT_VERSION_RELEASE "${VERSION}") + + set(GIT_VERSION "${GIT_VERSION_MAJOR}.${GIT_VERSION_MINOR}.${GIT_VERSION_RELEASE}") + + if(NOT ${OPENMW_VERSION} STREQUAL ${GIT_VERSION}) + message(FATAL_ERROR "Silly Zini forgot to update the version again...") + else(NOT ${OPENMW_VERSION} STREQUAL ${GIT_VERSION}) + set(OPENMW_VERSION_MAJOR ${GIT_VERSION_MAJOR}) + set(OPENMW_VERSION_MINOR ${GIT_VERSION_MINOR}) + set(OPENMW_VERSION_RELEASE ${GIT_VERSION_RELEASE}) + + set(OPENMW_VERSION_COMMITHASH "${COMMITHASH}") + set(OPENMW_VERSION_TAGHASH "${TAGHASH}") + endif(NOT ${OPENMW_VERSION} STREQUAL ${GIT_VERSION}) + + message(STATUS "OpenMW version ${OPENMW_VERSION}") + else(MATCH) + message(WARNING "Failed to get valid version information from Git") + endif(MATCH) + else(GIT_FOUND) + message(WARNING "Git executable not found") + endif(GIT_FOUND) + else(NOT EXISTS ${PROJECT_SOURCE_DIR}/.git/shallow) + message(STATUS "Shallow Git clone detected, not attempting to retrieve version info") + endif(NOT EXISTS ${PROJECT_SOURCE_DIR}/.git/shallow) +endif(EXISTS ${PROJECT_SOURCE_DIR}/.git) -include(GetGitRevisionDescription) - -get_git_tag_revision(TAGHASH --tags --max-count=1 "HEAD...") -get_git_head_revision(REFSPEC COMMITHASH) -git_describe(VERSION --tags ${TAGHASH}) - -string(REGEX MATCH "^openmw-[^0-9]*[0-9]+\\.[0-9]+\\.[0-9]+.*" MATCH "${VERSION}") -if (MATCH) - string(REGEX REPLACE "^openmw-([0-9]+)\\..*" "\\1" OPENMW_VERSION_MAJOR "${VERSION}") - string(REGEX REPLACE "^openmw-[0-9]+\\.([0-9]+).*" "\\1" OPENMW_VERSION_MINOR "${VERSION}") - string(REGEX REPLACE "^openmw-[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" OPENMW_VERSION_RELEASE "${VERSION}") - - set(OPENMW_VERSION "${OPENMW_VERSION_MAJOR}.${OPENMW_VERSION_MINOR}.${OPENMW_VERSION_RELEASE}") - set(OPENMW_VERSION_COMMITHASH "${COMMITHASH}") - set(OPENMW_VERSION_TAGHASH "${TAGHASH}") - - message(STATUS "Configuring OpenMW ${OPENMW_VERSION}...") -else (MATCH) - message(FATAL_ERROR "Failed to get valid version information from Git") -endif (MATCH) +# Macros +include(OpenMWMacros) # doxygen main page @@ -210,6 +239,14 @@ if (UNIX AND NOT APPLE) find_package (Threads) endif() +# Look for stdint.h +include(CheckIncludeFile) +check_include_file(stdint.h HAVE_STDINT_H) +if(NOT HAVE_STDINT_H) + unset(HAVE_STDINT_H CACHE) + message(FATAL_ERROR "stdint.h was not found" ) +endif() + include (CheckIncludeFileCXX) check_include_file_cxx(unordered_map HAVE_UNORDERED_MAP) if (HAVE_UNORDERED_MAP) diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 3b7a3273a..0eaba34eb 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -79,17 +79,20 @@ Launcher::MainDialog::MainDialog(QWidget *parent) QString revision(OPENMW_VERSION_COMMITHASH); QString tag(OPENMW_VERSION_TAGHASH); - if (revision == tag) { - versionLabel->setText(tr("OpenMW %0 release").arg(OPENMW_VERSION)); - } else { - versionLabel->setText(tr("OpenMW development (%0)").arg(revision.left(10))); - } + if (!revision.isEmpty() && !tag.isEmpty()) + { + if (revision == tag) { + versionLabel->setText(tr("OpenMW %0 release").arg(OPENMW_VERSION)); + } else { + versionLabel->setText(tr("OpenMW development (%0)").arg(revision.left(10))); + } - // Add the compile date and time - versionLabel->setToolTip(tr("Compiled on %0 %1").arg(QLocale(QLocale::C).toDate(QString(__DATE__).simplified(), - QLatin1String("MMM d yyyy")).toString(Qt::SystemLocaleLongDate), - QLocale(QLocale::C).toTime(QString(__TIME__).simplified(), - QLatin1String("hh:mm:ss")).toString(Qt::SystemLocaleShortDate))); + // Add the compile date and time + versionLabel->setToolTip(tr("Compiled on %0 %1").arg(QLocale(QLocale::C).toDate(QString(__DATE__).simplified(), + QLatin1String("MMM d yyyy")).toString(Qt::SystemLocaleLongDate), + QLocale(QLocale::C).toTime(QString(__TIME__).simplified(), + QLatin1String("hh:mm:ss")).toString(Qt::SystemLocaleShortDate))); + } createIcons(); } diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 6bcad1d08..621b9bf86 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -64,8 +64,12 @@ opencs_units (view/world ) opencs_units (view/render - scenewidget - ) + scenewidget worldspacewidget pagedworldspacewidget unpagedworldspacewidget + ) + +opencs_units_noqt (view/render + navigation navigation1st navigationfree navigationorbit + ) opencs_units_noqt (view/world dialoguesubview subviews diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 5d5ac4c55..2b2f41754 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -9,15 +9,22 @@ #include #include +#include + #include "model/doc/document.hpp" #include "model/world/data.hpp" -CS::Editor::Editor() - : mDocumentManager (mCfgMgr), mViewManager (mDocumentManager) +CS::Editor::Editor (OgreInit::OgreInit& ogreInit) +: mDocumentManager (mCfgMgr), mViewManager (mDocumentManager), + mIpcServerName ("org.openmw.OpenCS") { - mIpcServerName = "org.openmw.OpenCS"; + Files::PathContainer dataDirs = readConfig(); + + setupDataFiles (dataDirs); + + CSMSettings::UserSettings::instance().loadSettings ("opencs.cfg"); - setupDataFiles(); + ogreInit.init ((mCfgMgr.getUserConfigPath() / "opencsOgre.log").string()); mNewGame.setLocalData (mLocal); mFileDialog.setLocalData (mLocal); @@ -42,7 +49,16 @@ CS::Editor::Editor() this, SLOT (createNewGame (const boost::filesystem::path&))); } -void CS::Editor::setupDataFiles() +void CS::Editor::setupDataFiles (const Files::PathContainer& dataDirs) +{ + for (Files::PathContainer::const_iterator iter = dataDirs.begin(); iter != dataDirs.end(); ++iter) + { + QString path = QString::fromStdString(iter->string()); + mFileDialog.addFiles(path); + } +} + +Files::PathContainer CS::Editor::readConfig() { boost::program_options::variables_map variables; boost::program_options::options_description desc("Syntax: opencs \nAllowed options"); @@ -58,6 +74,8 @@ void CS::Editor::setupDataFiles() mCfgMgr.readConfiguration(variables, desc); + mDocumentManager.setResourceDir (variables["resources"].as()); + Files::PathContainer dataDirs, dataLocal; if (!variables["data"].empty()) { dataDirs = Files::PathContainer(variables["data"].as()); @@ -83,23 +101,11 @@ void CS::Editor::setupDataFiles() messageBox.exec(); QApplication::exit (1); - return; } dataDirs.insert (dataDirs.end(), dataLocal.begin(), dataLocal.end()); - mDocumentManager.setResourceDir (variables["resources"].as()); - - for (Files::PathContainer::const_iterator iter = dataDirs.begin(); iter != dataDirs.end(); ++iter) - { - - QString path = QString::fromStdString(iter->string()); - mFileDialog.addFiles(path); - } - - //load the settings into the userSettings instance. - const QString settingFileName = "opencs.cfg"; - CSMSettings::UserSettings::instance().loadSettings(settingFileName); + return dataDirs; } void CS::Editor::createGame() @@ -210,8 +216,6 @@ int CS::Editor::run() if (mLocal.empty()) return 1; -// temporarily disable OGRE-integration (need to fix path problem first) -#if 0 // TODO: setting Ogre::Root::getSingleton().setRenderSystem(Ogre::Root::getSingleton().getRenderSystemByName("OpenGL Rendering Subsystem")); @@ -228,7 +232,6 @@ int CS::Editor::run() #endif Ogre::RenderWindow* hiddenWindow = Ogre::Root::getSingleton().createRenderWindow("InactiveHidden", 1, 1, false, ¶ms); hiddenWindow->setActive(false); -#endif mStartup.show(); diff --git a/apps/opencs/editor.hpp b/apps/opencs/editor.hpp index 930aa9d64..0f1c7a682 100644 --- a/apps/opencs/editor.hpp +++ b/apps/opencs/editor.hpp @@ -10,6 +10,8 @@ #include #endif +#include + #include "model/settings/usersettings.hpp" #include "model/doc/documentmanager.hpp" @@ -20,6 +22,11 @@ #include "view/settings/usersettingsdialog.hpp" +namespace OgreInit +{ + class OgreInit; +} + namespace CS { class Editor : public QObject @@ -37,7 +44,10 @@ namespace CS boost::filesystem::path mLocal; - void setupDataFiles(); + void setupDataFiles (const Files::PathContainer& dataDirs); + + Files::PathContainer readConfig(); + ///< \return data paths // not implemented Editor (const Editor&); @@ -45,7 +55,7 @@ namespace CS public: - Editor(); + Editor (OgreInit::OgreInit& ogreInit); bool makeIPCServer(); void connectToIPCServer(); diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp index 931d63312..212ed0836 100644 --- a/apps/opencs/main.cpp +++ b/apps/opencs/main.cpp @@ -40,15 +40,9 @@ int main(int argc, char *argv[]) { Q_INIT_RESOURCE (resources); - // TODO: Ogre startup shouldn't be here, but it currently has to: - // SceneWidget destructor will delete the created render window, which would be called _after_ Root has shut down :( - - Application mApplication (argc, argv); -// temporarily disable OGRE-integration (need to fix path problem first) -#if 0 OgreInit::OgreInit ogreInit; - ogreInit.init("./opencsOgre.log"); // TODO log path? -#endif + + Application application (argc, argv); #ifdef Q_OS_MAC QDir dir(QCoreApplication::applicationDirPath()); @@ -66,12 +60,12 @@ int main(int argc, char *argv[]) QStringList libraryPaths; libraryPaths << pluginsPath.path() << QCoreApplication::applicationDirPath(); - mApplication.setLibraryPaths(libraryPaths); + application.setLibraryPaths(libraryPaths); #endif - mApplication.setWindowIcon (QIcon (":./opencs.png")); + application.setWindowIcon (QIcon (":./opencs.png")); - CS::Editor editor; + CS::Editor editor (ogreInit); if(!editor.makeIPCServer()) { diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp index d342e88a4..9d97e36c7 100644 --- a/apps/opencs/model/world/collection.hpp +++ b/apps/opencs/model/world/collection.hpp @@ -98,7 +98,7 @@ namespace CSMWorld UniversalId::Type type = UniversalId::Type_None); ///< \param type Will be ignored, unless the collection supports multiple record types - virtual void cloneRecord(const std::string& origin, + virtual void cloneRecord(const std::string& origin, const std::string& destination, const UniversalId::Type type); @@ -198,7 +198,7 @@ namespace CSMWorld } template - void Collection::cloneRecord(const std::string& origin, + void Collection::cloneRecord(const std::string& origin, const std::string& destination, const UniversalId::Type type) { diff --git a/apps/opencs/model/world/collectionbase.hpp b/apps/opencs/model/world/collectionbase.hpp index 408a89d92..442055d5f 100644 --- a/apps/opencs/model/world/collectionbase.hpp +++ b/apps/opencs/model/world/collectionbase.hpp @@ -74,7 +74,7 @@ namespace CSMWorld UniversalId::Type type = UniversalId::Type_None) = 0; ///< If the record type does not match, an exception is thrown. - virtual void cloneRecord(const std::string& origin, + virtual void cloneRecord(const std::string& origin, const std::string& destination, const UniversalId::Type type) = 0; diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 8d53c4e53..d68b79ff0 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -247,12 +247,12 @@ CSMWorld::Data::Data() : mRefs (mCells) addModel (new IdTable (&mSpells), UniversalId::Type_Spells, UniversalId::Type_Spell); addModel (new IdTable (&mTopics), UniversalId::Type_Topics, UniversalId::Type_Topic); addModel (new IdTable (&mJournals), UniversalId::Type_Journals, UniversalId::Type_Journal); - addModel (new IdTable (&mTopicInfos), UniversalId::Type_TopicInfos, UniversalId::Type_TopicInfo); - addModel (new IdTable (&mJournalInfos), UniversalId::Type_JournalInfos, UniversalId::Type_JournalInfo); - addModel (new IdTable (&mCells), UniversalId::Type_Cells, UniversalId::Type_Cell); + addModel (new IdTable (&mTopicInfos, IdTable::Reordering_WithinTopic), UniversalId::Type_TopicInfos, UniversalId::Type_TopicInfo); + addModel (new IdTable (&mJournalInfos, IdTable::Reordering_WithinTopic), UniversalId::Type_JournalInfos, UniversalId::Type_JournalInfo); + addModel (new IdTable (&mCells, IdTable::Reordering_None, IdTable::Viewing_Id), UniversalId::Type_Cells, UniversalId::Type_Cell); addModel (new IdTable (&mReferenceables), UniversalId::Type_Referenceables, UniversalId::Type_Referenceable); - addModel (new IdTable (&mRefs), UniversalId::Type_References, UniversalId::Type_Reference, false); + addModel (new IdTable (&mRefs, IdTable::Reordering_None, IdTable::Viewing_Cell), UniversalId::Type_References, UniversalId::Type_Reference, false); addModel (new IdTable (&mFilters), UniversalId::Type_Filters, UniversalId::Type_Filter, false); } diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp index bea307aa2..453a7da6a 100644 --- a/apps/opencs/model/world/idtable.cpp +++ b/apps/opencs/model/world/idtable.cpp @@ -4,8 +4,9 @@ #include "collectionbase.hpp" #include "columnbase.hpp" -CSMWorld::IdTable::IdTable (CollectionBase *idCollection, Reordering reordering) -: mIdCollection (idCollection), mReordering (reordering) +CSMWorld::IdTable::IdTable (CollectionBase *idCollection, Reordering reordering, + Viewing viewing) +: mIdCollection (idCollection), mReordering (reordering), mViewing (viewing) {} CSMWorld::IdTable::~IdTable() @@ -188,4 +189,45 @@ void CSMWorld::IdTable::reorderRows (int baseIndex, const std::vector& newO CSMWorld::IdTable::Reordering CSMWorld::IdTable::getReordering() const { return mReordering; +} + +CSMWorld::IdTable::Viewing CSMWorld::IdTable::getViewing() const +{ + return mViewing; +} + +std::pair CSMWorld::IdTable::view (int row) const +{ + std::string id; + std::string hint; + + if (mViewing==Viewing_Cell) + { + int cellColumn = mIdCollection->searchColumnIndex (Columns::ColumnId_Cell); + int idColumn = mIdCollection->searchColumnIndex (Columns::ColumnId_Id); + + if (cellColumn!=-1 && idColumn!=-1) + { + id = mIdCollection->getData (row, cellColumn).toString().toUtf8().constData(); + hint = "r:" + std::string (mIdCollection->getData (row, idColumn).toString().toUtf8().constData()); + } + } + else if (mViewing==Viewing_Id) + { + int column = mIdCollection->searchColumnIndex (Columns::ColumnId_Id); + + if (column!=-1) + { + id = mIdCollection->getData (row, column).toString().toUtf8().constData(); + hint = "c:" + id; + } + } + + if (id.empty()) + return std::make_pair (UniversalId::Type_None, ""); + + if (id[0]=='#') + id = "sys::default"; + + return std::make_pair (UniversalId (UniversalId::Type_Scene, id), hint); } \ No newline at end of file diff --git a/apps/opencs/model/world/idtable.hpp b/apps/opencs/model/world/idtable.hpp index 74923867d..5a271de44 100644 --- a/apps/opencs/model/world/idtable.hpp +++ b/apps/opencs/model/world/idtable.hpp @@ -25,10 +25,20 @@ namespace CSMWorld Reordering_WithinTopic }; + enum Viewing + { + Viewing_None, + Viewing_Id, // use ID column to generate view request (ID is transformed into + // worldspace and original ID is passed as hint with c: prefix) + Viewing_Cell // use cell column to generate view request (cell ID is transformed + // into worldspace and record ID is passed as hint with r: prefix) + }; + private: CollectionBase *mIdCollection; Reordering mReordering; + Viewing mViewing; // not implemented IdTable (const IdTable&); @@ -36,7 +46,8 @@ namespace CSMWorld public: - IdTable (CollectionBase *idCollection, Reordering reordering = Reordering_WithinTopic); + IdTable (CollectionBase *idCollection, Reordering reordering = Reordering_None, + Viewing viewing = Viewing_None); ///< The ownership of \a idCollection is not transferred. virtual ~IdTable(); @@ -63,8 +74,8 @@ namespace CSMWorld void addRecord (const std::string& id, UniversalId::Type type = UniversalId::Type_None); ///< \param type Will be ignored, unless the collection supports multiple record types - void cloneRecord(const std::string& origin, - const std::string& destination, + void cloneRecord(const std::string& origin, + const std::string& destination, UniversalId::Type type = UniversalId::Type_None); QModelIndex getModelIndex (const std::string& id, int column) const; @@ -86,6 +97,12 @@ namespace CSMWorld /// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex). Reordering getReordering() const; + + Viewing getViewing() const; + + std::pair view (int row) const; + ///< Return the UniversalId and the hint for viewing \a row. If viewing is not + /// supported by this table, return (UniversalId::Type_None, ""). }; } diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp index 8301ebfd3..1d1b3c960 100644 --- a/apps/opencs/model/world/universalid.cpp +++ b/apps/opencs/model/world/universalid.cpp @@ -90,14 +90,14 @@ namespace { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Weapon, "Weapon", ":./weapon.png" }, { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_Reference, "Reference", 0 }, { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_Filter, "Filter", ":./filter.png" }, + { CSMWorld::UniversalId::Class_Collection, CSMWorld::UniversalId::Type_Scene, "Scene", 0 }, + { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0, 0 } // end marker }; static const TypeData sIndexArg[] = { { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_VerificationResults, "Verification Results", 0 }, - { CSMWorld::UniversalId::Class_Collection, CSMWorld::UniversalId::Type_Scene, "Scene", 0 }, - { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0, 0 } // end marker }; } diff --git a/apps/opencs/view/doc/subview.cpp b/apps/opencs/view/doc/subview.cpp index 09361a1c0..6160f2673 100644 --- a/apps/opencs/view/doc/subview.cpp +++ b/apps/opencs/view/doc/subview.cpp @@ -16,4 +16,6 @@ void CSVDoc::SubView::updateEditorSetting (const QString &settingName, const QSt { } -void CSVDoc::SubView::setStatusBar (bool show) {} \ No newline at end of file +void CSVDoc::SubView::setStatusBar (bool show) {} + +void CSVDoc::SubView::useHint (const std::string& hint) {} \ No newline at end of file diff --git a/apps/opencs/view/doc/subview.hpp b/apps/opencs/view/doc/subview.hpp index aa073f81d..59781f869 100644 --- a/apps/opencs/view/doc/subview.hpp +++ b/apps/opencs/view/doc/subview.hpp @@ -40,9 +40,12 @@ namespace CSVDoc virtual void setStatusBar (bool show); ///< Default implementation: ignored + virtual void useHint (const std::string& hint); + ///< Default implementation: ignored + signals: - void focusId (const CSMWorld::UniversalId& universalId); + void focusId (const CSMWorld::UniversalId& universalId, const std::string& hint); }; } diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index 533ca7f57..de3f476af 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -115,10 +115,6 @@ void CSVDoc::View::setupWorldMenu() world->addSeparator(); // items that don't represent single record lists follow here - QAction *scene = new QAction (tr ("Scene"), this); - connect (scene, SIGNAL (triggered()), this, SLOT (addSceneSubView())); - world->addAction (scene); - QAction *regionMap = new QAction (tr ("Region Map"), this); connect (regionMap, SIGNAL (triggered()), this, SLOT (addRegionMapSubView())); world->addAction (regionMap); @@ -310,7 +306,7 @@ void CSVDoc::View::updateProgress (int current, int max, int type, int threads) mOperations->setProgress (current, max, type, threads); } -void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id) +void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id, const std::string& hint) { /// \todo add an user setting for limiting the number of sub views per top level view. Automatically open a new top level view if this /// number is exceeded @@ -322,12 +318,15 @@ void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id) SubView *view = mSubViewFactory.makeSubView (id, *mDocument); + if (!hint.empty()) + view->useHint (hint); + view->setStatusBar (mShowStatusBar->isChecked()); mSubViewWindow.addDockWidget (Qt::TopDockWidgetArea, view); - connect (view, SIGNAL (focusId (const CSMWorld::UniversalId&)), this, - SLOT (addSubView (const CSMWorld::UniversalId&))); + connect (view, SIGNAL (focusId (const CSMWorld::UniversalId&, const std::string&)), this, + SLOT (addSubView (const CSMWorld::UniversalId&, const std::string&))); CSMSettings::UserSettings::instance().updateSettings("Display Format"); @@ -429,11 +428,6 @@ void CSVDoc::View::addFiltersSubView() addSubView (CSMWorld::UniversalId::Type_Filters); } -void CSVDoc::View::addSceneSubView() -{ - addSubView (CSMWorld::UniversalId::Type_Scene); -} - void CSVDoc::View::addTopicsSubView() { addSubView (CSMWorld::UniversalId::Type_Topics); diff --git a/apps/opencs/view/doc/view.hpp b/apps/opencs/view/doc/view.hpp index 13c15ec9b..ee7380e2b 100644 --- a/apps/opencs/view/doc/view.hpp +++ b/apps/opencs/view/doc/view.hpp @@ -120,7 +120,9 @@ namespace CSVDoc public slots: - void addSubView (const CSMWorld::UniversalId& id); + void addSubView (const CSMWorld::UniversalId& id, const std::string& hint = ""); + ///< \param hint Suggested view point (e.g. coordinates in a 3D scene or a line number + /// in a script). void abortOperation (int type); @@ -166,8 +168,6 @@ namespace CSVDoc void addFiltersSubView(); - void addSceneSubView(); - void addTopicsSubView(); void addJournalsSubView(); diff --git a/apps/opencs/view/render/navigation.cpp b/apps/opencs/view/render/navigation.cpp new file mode 100644 index 000000000..14ae7f0b7 --- /dev/null +++ b/apps/opencs/view/render/navigation.cpp @@ -0,0 +1,19 @@ + +#include "navigation.hpp" + +float CSVRender::Navigation::getFactor (bool mouse) const +{ + float factor = mFastModeFactor; + + if (mouse) + factor /= 2; /// \todo make this configurable + + return factor; +} + +CSVRender::Navigation::~Navigation() {} + +void CSVRender::Navigation::setFastModeFactor (float factor) +{ + mFastModeFactor = factor; +} \ No newline at end of file diff --git a/apps/opencs/view/render/navigation.hpp b/apps/opencs/view/render/navigation.hpp new file mode 100644 index 000000000..873519609 --- /dev/null +++ b/apps/opencs/view/render/navigation.hpp @@ -0,0 +1,46 @@ +#ifndef OPENCS_VIEW_NAVIGATION_H +#define OPENCS_VIEW_NAVIGATION_H + +class QPoint; + +namespace Ogre +{ + class Camera; +} + +namespace CSVRender +{ + class Navigation + { + float mFastModeFactor; + + protected: + + float getFactor (bool mouse) const; + + public: + + virtual ~Navigation(); + + void setFastModeFactor (float factor); + ///< Set currently applying fast mode factor. + + virtual bool activate (Ogre::Camera *camera) = 0; + ///< \return Update required? + + virtual bool wheelMoved (int delta) = 0; + ///< \return Update required? + + virtual bool mouseMoved (const QPoint& delta, int mode) = 0; + ///< \param mode: 0: default mouse key, 1: default mouse key and modifier key 1 + /// \return Update required? + + virtual bool handleMovementKeys (int vertical, int horizontal) = 0; + ///< \return Update required? + + virtual bool handleRollKeys (int delta) = 0; + ///< \return Update required? + }; +} + +#endif diff --git a/apps/opencs/view/render/navigation1st.cpp b/apps/opencs/view/render/navigation1st.cpp new file mode 100644 index 000000000..b892d3e3e --- /dev/null +++ b/apps/opencs/view/render/navigation1st.cpp @@ -0,0 +1,85 @@ + +#include "navigation1st.hpp" + +#include + +#include + +CSVRender::Navigation1st::Navigation1st() : mCamera (0) {} + +bool CSVRender::Navigation1st::activate (Ogre::Camera *camera) +{ + mCamera = camera; + mCamera->setFixedYawAxis (true); + + Ogre::Radian pitch = mCamera->getOrientation().getPitch(); + + Ogre::Radian limit (Ogre::Math::PI/2-0.5); + + if (pitch>limit) + mCamera->pitch (-(pitch-limit)); + else if (pitch<-limit) + mCamera->pitch (pitch-limit); + + return true; +} + +bool CSVRender::Navigation1st::wheelMoved (int delta) +{ + mCamera->move (getFactor (true) * mCamera->getDirection() * delta); + return true; +} + +bool CSVRender::Navigation1st::mouseMoved (const QPoint& delta, int mode) +{ + if (mode==0) + { + // turn camera + if (delta.x()) + mCamera->yaw (Ogre::Degree (getFactor (true) * delta.x())); + + if (delta.y()) + { + Ogre::Radian oldPitch = mCamera->getOrientation().getPitch(); + float deltaPitch = getFactor (true) * delta.y(); + Ogre::Radian newPitch = oldPitch + Ogre::Degree (deltaPitch); + + Ogre::Radian limit (Ogre::Math::PI/2-0.5); + + if ((deltaPitch>0 && newPitch-limit)) + mCamera->pitch (Ogre::Degree (deltaPitch)); + } + + return true; + } + else if (mode==1) + { + // pan camera + if (delta.x()) + mCamera->move (getFactor (true) * mCamera->getDerivedRight() * delta.x()); + + if (delta.y()) + mCamera->move (getFactor (true) * -mCamera->getDerivedUp() * delta.y()); + + return true; + } + + return false; +} + +bool CSVRender::Navigation1st::handleMovementKeys (int vertical, int horizontal) +{ + if (vertical) + mCamera->move (getFactor (false) * mCamera->getDirection() * vertical); + + if (horizontal) + mCamera->move (getFactor (true) * mCamera->getDerivedRight() * horizontal); + + return true; +} + +bool CSVRender::Navigation1st::handleRollKeys (int delta) +{ + // we don't roll this way in 1st person mode + return false; +} diff --git a/apps/opencs/view/render/navigation1st.hpp b/apps/opencs/view/render/navigation1st.hpp new file mode 100644 index 000000000..d1e09d236 --- /dev/null +++ b/apps/opencs/view/render/navigation1st.hpp @@ -0,0 +1,35 @@ +#ifndef OPENCS_VIEW_NAVIGATION1ST_H +#define OPENCS_VIEW_NAVIGATION1ST_H + +#include "navigation.hpp" + +namespace CSVRender +{ + /// \brief First person-like camera controls + class Navigation1st : public Navigation + { + Ogre::Camera *mCamera; + + public: + + Navigation1st(); + + virtual bool activate (Ogre::Camera *camera); + ///< \return Update required? + + virtual bool wheelMoved (int delta); + ///< \return Update required? + + virtual bool mouseMoved (const QPoint& delta, int mode); + ///< \param mode: 0: default mouse key, 1: default mouse key and modifier key 1 + /// \return Update required? + + virtual bool handleMovementKeys (int vertical, int horizontal); + ///< \return Update required? + + virtual bool handleRollKeys (int delta); + ///< \return Update required? + }; +} + +#endif diff --git a/apps/opencs/view/render/navigationfree.cpp b/apps/opencs/view/render/navigationfree.cpp new file mode 100644 index 000000000..950e137a7 --- /dev/null +++ b/apps/opencs/view/render/navigationfree.cpp @@ -0,0 +1,66 @@ + +#include "navigationfree.hpp" + +#include + +#include + +CSVRender::NavigationFree::NavigationFree() : mCamera (0) {} + +bool CSVRender::NavigationFree::activate (Ogre::Camera *camera) +{ + mCamera = camera; + mCamera->setFixedYawAxis (false); + return false; +} + +bool CSVRender::NavigationFree::wheelMoved (int delta) +{ + mCamera->move (getFactor (true) * mCamera->getDirection() * delta); + return true; +} + +bool CSVRender::NavigationFree::mouseMoved (const QPoint& delta, int mode) +{ + if (mode==0) + { + // turn camera + if (delta.x()) + mCamera->yaw (Ogre::Degree (getFactor (true) * delta.x())); + + if (delta.y()) + mCamera->pitch (Ogre::Degree (getFactor (true) * delta.y())); + + return true; + } + else if (mode==1) + { + // pan camera + if (delta.x()) + mCamera->move (getFactor (true) * mCamera->getDerivedRight() * delta.x()); + + if (delta.y()) + mCamera->move (getFactor (true) * -mCamera->getDerivedUp() * delta.y()); + + return true; + } + + return false; +} + +bool CSVRender::NavigationFree::handleMovementKeys (int vertical, int horizontal) +{ + if (vertical) + mCamera->move (getFactor (false) * mCamera->getDerivedUp() * vertical); + + if (horizontal) + mCamera->move (getFactor (true) * mCamera->getDerivedRight() * horizontal); + + return true; +} + +bool CSVRender::NavigationFree::handleRollKeys (int delta) +{ + mCamera->roll (Ogre::Degree (getFactor (false) * delta)); + return true; +} diff --git a/apps/opencs/view/render/navigationfree.hpp b/apps/opencs/view/render/navigationfree.hpp new file mode 100644 index 000000000..e30722f75 --- /dev/null +++ b/apps/opencs/view/render/navigationfree.hpp @@ -0,0 +1,35 @@ +#ifndef OPENCS_VIEW_NAVIGATIONFREE_H +#define OPENCS_VIEW_NAVIGATIONFREE_H + +#include "navigation.hpp" + +namespace CSVRender +{ + /// \brief Free camera controls + class NavigationFree : public Navigation + { + Ogre::Camera *mCamera; + + public: + + NavigationFree(); + + virtual bool activate (Ogre::Camera *camera); + ///< \return Update required? + + virtual bool wheelMoved (int delta); + ///< \return Update required? + + virtual bool mouseMoved (const QPoint& delta, int mode); + ///< \param mode: 0: default mouse key, 1: default mouse key and modifier key 1 + /// \return Update required? + + virtual bool handleMovementKeys (int vertical, int horizontal); + ///< \return Update required? + + virtual bool handleRollKeys (int delta); + ///< \return Update required? + }; +} + +#endif diff --git a/apps/opencs/view/render/navigationorbit.cpp b/apps/opencs/view/render/navigationorbit.cpp new file mode 100644 index 000000000..c6e729e96 --- /dev/null +++ b/apps/opencs/view/render/navigationorbit.cpp @@ -0,0 +1,100 @@ + +#include "navigationorbit.hpp" + +#include + +#include + +void CSVRender::NavigationOrbit::rotateCamera (const Ogre::Vector3& diff) +{ + Ogre::Vector3 pos = mCamera->getPosition(); + + float distance = (pos-mCentre).length(); + + Ogre::Vector3 direction = (pos+diff)-mCentre; + direction.normalise(); + + mCamera->setPosition (mCentre + direction*distance); + mCamera->lookAt (mCentre); +} + +CSVRender::NavigationOrbit::NavigationOrbit() : mCamera (0), mCentre (0, 0, 0), mDistance (100) +{} + +bool CSVRender::NavigationOrbit::activate (Ogre::Camera *camera) +{ + mCamera = camera; + mCamera->setFixedYawAxis (false); + + if ((mCamera->getPosition()-mCentre).length()getPosition(); + direction.normalise(); + + if (direction.length()==0) + direction = Ogre::Vector3 (1, 0, 0); + + mCamera->setPosition (mCentre - direction * mDistance); + } + + mCamera->lookAt (mCentre); + + return true; +} + +bool CSVRender::NavigationOrbit::wheelMoved (int delta) +{ + Ogre::Vector3 diff = getFactor (true) * mCamera->getDirection() * delta; + + Ogre::Vector3 pos = mCamera->getPosition(); + + if (delta>0 && diff.length()>=(pos-mCentre).length()-mDistance) + { + pos = mCentre-(mCamera->getDirection() * mDistance); + } + else + { + pos += diff; + } + + mCamera->setPosition (pos); + + return true; +} + +bool CSVRender::NavigationOrbit::mouseMoved (const QPoint& delta, int mode) +{ + Ogre::Vector3 diff = + getFactor (true) * -mCamera->getDerivedRight() * delta.x() + + getFactor (true) * mCamera->getDerivedUp() * delta.y(); + + if (mode==0) + { + rotateCamera (diff); + return true; + } + else if (mode==1) + { + mCamera->move (diff); + mCentre += diff; + return true; + } + + return false; +} + +bool CSVRender::NavigationOrbit::handleMovementKeys (int vertical, int horizontal) +{ + rotateCamera ( + - getFactor (false) * -mCamera->getDerivedRight() * horizontal + + getFactor (false) * mCamera->getDerivedUp() * vertical); + + return true; +} + +bool CSVRender::NavigationOrbit::handleRollKeys (int delta) +{ + mCamera->roll (Ogre::Degree (getFactor (false) * delta)); + return true; +} \ No newline at end of file diff --git a/apps/opencs/view/render/navigationorbit.hpp b/apps/opencs/view/render/navigationorbit.hpp new file mode 100644 index 000000000..7796eb9e7 --- /dev/null +++ b/apps/opencs/view/render/navigationorbit.hpp @@ -0,0 +1,42 @@ +#ifndef OPENCS_VIEW_NAVIGATIONORBIT_H +#define OPENCS_VIEW_NAVIGATIONORBIT_H + +#include "navigation.hpp" + +#include + +namespace CSVRender +{ + /// \brief Orbiting camera controls + class NavigationOrbit : public Navigation + { + Ogre::Camera *mCamera; + Ogre::Vector3 mCentre; + int mDistance; + + void rotateCamera (const Ogre::Vector3& diff); + ///< Rotate camera around centre. + + public: + + NavigationOrbit(); + + virtual bool activate (Ogre::Camera *camera); + ///< \return Update required? + + virtual bool wheelMoved (int delta); + ///< \return Update required? + + virtual bool mouseMoved (const QPoint& delta, int mode); + ///< \param mode: 0: default mouse key, 1: default mouse key and modifier key 1 + /// \return Update required? + + virtual bool handleMovementKeys (int vertical, int horizontal); + ///< \return Update required? + + virtual bool handleRollKeys (int delta); + ///< \return Update required? + }; +} + +#endif diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp new file mode 100644 index 000000000..fa32e3959 --- /dev/null +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -0,0 +1,6 @@ + +#include "pagedworldspacewidget.hpp" + +CSVRender::PagedWorldspaceWidget::PagedWorldspaceWidget (QWidget *parent) +: WorldspaceWidget (parent) +{} \ No newline at end of file diff --git a/apps/opencs/view/render/pagedworldspacewidget.hpp b/apps/opencs/view/render/pagedworldspacewidget.hpp new file mode 100644 index 000000000..172e2477a --- /dev/null +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -0,0 +1,18 @@ +#ifndef OPENCS_VIEW_PAGEDWORLDSPACEWIDGET_H +#define OPENCS_VIEW_PAGEDWORLDSPACEWIDGET_H + +#include "worldspacewidget.hpp" + +namespace CSVRender +{ + class PagedWorldspaceWidget : public WorldspaceWidget + { + Q_OBJECT + + public: + + PagedWorldspaceWidget (QWidget *parent); + }; +} + +#endif diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index 6e327bb6e..31d5d0318 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -2,24 +2,34 @@ #include #include +#include #include #include #include #include +#include +#include + +#include "navigation.hpp" namespace CSVRender { - SceneWidget::SceneWidget(QWidget *parent) : QWidget(parent) , mWindow(NULL) , mCamera(NULL) - , mSceneMgr(NULL) + , mSceneMgr(NULL), mNavigation (0), mUpdate (false) + , mKeyForward (false), mKeyBackward (false), mKeyLeft (false), mKeyRight (false) + , mKeyRollLeft (false), mKeyRollRight (false) + , mFast (false), mDragging (false), mMod1 (false) + , mFastFactor (4) /// \todo make this configurable { setAttribute(Qt::WA_PaintOnScreen); setAttribute(Qt::WA_NoSystemBackground); + setFocusPolicy (Qt::StrongFocus); + mSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC); // Throw in a random color just to make sure multiple scenes work @@ -44,6 +54,16 @@ namespace CSVRender mCamera->lookAt(0,0,0); mCamera->setNearClipDistance(0.1); mCamera->setFarClipDistance(3000); + + QTimer *timer = new QTimer (this); + + connect (timer, SIGNAL (timeout()), this, SLOT (update())); + timer->start (20); /// \todo make this configurable + } + + void SceneWidget::setAmbient (const Ogre::ColourValue& colour) + { + mSceneMgr->setAmbientLight (colour); } void SceneWidget::updateOgreWindow() @@ -81,7 +101,21 @@ namespace CSVRender SceneWidget::~SceneWidget() { - Ogre::Root::getSingleton().destroyRenderTarget(mWindow); + if (mWindow) + Ogre::Root::getSingleton().destroyRenderTarget (mWindow); + + if (mSceneMgr) + Ogre::Root::getSingleton().destroySceneManager (mSceneMgr); + } + + void SceneWidget::setNavigation (Navigation *navigation) + { + if ((mNavigation = navigation)) + { + mNavigation->setFastModeFactor (mFast ? mFastFactor : 1); + if (mNavigation->activate (mCamera)) + mUpdate = true; + } } void SceneWidget::paintEvent(QPaintEvent* e) @@ -93,7 +127,6 @@ namespace CSVRender e->accept(); } - QPaintEngine* SceneWidget::paintEngine() const { // We don't want another paint engine to get in the way. @@ -130,4 +163,151 @@ namespace CSVRender return QWidget::event(e); } + void SceneWidget::keyPressEvent (QKeyEvent *event) + { + switch (event->key()) + { + case Qt::Key_W: mKeyForward = true; break; + case Qt::Key_S: mKeyBackward = true; break; + case Qt::Key_A: mKeyLeft = true; break; + case Qt::Key_D: mKeyRight = true; break; + case Qt::Key_Q: mKeyRollLeft = true; break; + case Qt::Key_E: mKeyRollRight = true; break; + case Qt::Key_Control: mMod1 = true; break; + + case Qt::Key_Shift: + + mFast = true; + + if (mNavigation) + mNavigation->setFastModeFactor (mFastFactor); + + break; + + default: QWidget::keyPressEvent (event); + } + } + + void SceneWidget::keyReleaseEvent (QKeyEvent *event) + { + switch (event->key()) + { + case Qt::Key_W: mKeyForward = false; break; + case Qt::Key_S: mKeyBackward = false; break; + case Qt::Key_A: mKeyLeft = false; break; + case Qt::Key_D: mKeyRight = false; break; + case Qt::Key_Q: mKeyRollLeft = false; break; + case Qt::Key_E: mKeyRollRight = false; break; + case Qt::Key_Control: mMod1 = false; break; + + case Qt::Key_Shift: + + mFast = false; + + if (mNavigation) + mNavigation->setFastModeFactor (1); + + break; + + default: QWidget::keyReleaseEvent (event); + } + } + + void SceneWidget::wheelEvent (QWheelEvent *event) + { + if (mNavigation) + if (event->delta()) + if (mNavigation->wheelMoved (event->delta())) + mUpdate = true; + } + + void SceneWidget::leaveEvent (QEvent *event) + { + mDragging = false; + } + + void SceneWidget::mouseMoveEvent (QMouseEvent *event) + { + if (event->buttons() & Qt::LeftButton) + { + if (mDragging) + { + QPoint diff = mOldPos-event->pos(); + mOldPos = event->pos(); + + if (mNavigation) + if (mNavigation->mouseMoved (diff, mMod1 ? 1 : 0)) + mUpdate = true; + } + else + { + mDragging = true; + mOldPos = event->pos(); + } + } + } + + void SceneWidget::mouseReleaseEvent (QMouseEvent *event) + { + if (!(event->buttons() & Qt::LeftButton)) + mDragging = false; + } + + void SceneWidget::focusOutEvent (QFocusEvent *event) + { + mKeyForward = false; + mKeyBackward = false; + mKeyLeft = false; + mKeyRight = false; + mFast = false; + mMod1 = false; + + QWidget::focusOutEvent (event); + } + + void SceneWidget::update() + { + if (mNavigation) + { + int horizontal = 0; + int vertical = 0; + + if (mKeyForward && !mKeyBackward) + vertical = 1; + else if (!mKeyForward && mKeyBackward) + vertical = -1; + + if (mKeyLeft && !mKeyRight) + horizontal = -1; + else if (!mKeyLeft && mKeyRight) + horizontal = 1; + + if (horizontal || vertical) + if (mNavigation->handleMovementKeys (vertical, horizontal)) + mUpdate = true; + + int roll = 0; + + if (mKeyRollLeft && !mKeyRollRight) + roll = 1; + else if (!mKeyRollLeft && mKeyRollRight) + roll = -1; + + if (roll) + if (mNavigation->handleRollKeys (roll)) + mUpdate = true; + + } + + if (mUpdate) + { + mUpdate = false; + mWindow->update(); + } + } + + int SceneWidget::getFastFactor() const + { + return mFast ? mFastFactor : 1; + } } diff --git a/apps/opencs/view/render/scenewidget.hpp b/apps/opencs/view/render/scenewidget.hpp index 355a6331e..ad68897ac 100644 --- a/apps/opencs/view/render/scenewidget.hpp +++ b/apps/opencs/view/render/scenewidget.hpp @@ -8,33 +8,77 @@ namespace Ogre class Camera; class SceneManager; class RenderWindow; + class ColourValue; } namespace CSVRender { + class Navigation; class SceneWidget : public QWidget { Q_OBJECT - public: - SceneWidget(QWidget *parent); - virtual ~SceneWidget(void); + public: - QPaintEngine* paintEngine() const; + SceneWidget(QWidget *parent); + virtual ~SceneWidget(); - private: - void paintEvent(QPaintEvent* e); - void resizeEvent(QResizeEvent* e); - bool event(QEvent* e); + QPaintEngine* paintEngine() const; - void updateOgreWindow(); + void setAmbient (const Ogre::ColourValue& colour); + ///< \note The actual ambient colour may differ based on lighting settings. - Ogre::Camera* mCamera; - Ogre::SceneManager* mSceneMgr; - Ogre::RenderWindow* mWindow; - }; + protected: + + void setNavigation (Navigation *navigation); + ///< \attention The ownership of \a navigation is not transferred to *this. + + private: + void paintEvent(QPaintEvent* e); + void resizeEvent(QResizeEvent* e); + bool event(QEvent* e); + + void keyPressEvent (QKeyEvent *event); + + void keyReleaseEvent (QKeyEvent *event); + + void focusOutEvent (QFocusEvent *event); + + void wheelEvent (QWheelEvent *event); + + void leaveEvent (QEvent *event); + void mouseMoveEvent (QMouseEvent *event); + + void mouseReleaseEvent (QMouseEvent *event); + + void updateOgreWindow(); + + int getFastFactor() const; + + Ogre::Camera* mCamera; + Ogre::SceneManager* mSceneMgr; + Ogre::RenderWindow* mWindow; + + Navigation *mNavigation; + bool mUpdate; + bool mKeyForward; + bool mKeyBackward; + bool mKeyLeft; + bool mKeyRight; + bool mKeyRollLeft; + bool mKeyRollRight; + bool mFast; + bool mDragging; + bool mMod1; + QPoint mOldPos; + int mFastFactor; + + private slots: + + void update(); + }; } #endif diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp new file mode 100644 index 000000000..c7edbe79b --- /dev/null +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -0,0 +1,66 @@ + +#include "unpagedworldspacewidget.hpp" + +#include + +#include "../../model/doc/document.hpp" + +#include "../../model/world/data.hpp" +#include "../../model/world/idtable.hpp" + +void CSVRender::UnpagedWorldspaceWidget::update() +{ + const CSMWorld::Record& record = + dynamic_cast&> (mCellsModel->getRecord (mCellId)); + + Ogre::ColourValue colour; + colour.setAsABGR (record.get().mAmbi.mAmbient); + setAmbient (colour); + + /// \todo deal with mSunlight and mFog/mForDensity +} + +CSVRender::UnpagedWorldspaceWidget::UnpagedWorldspaceWidget (const std::string& cellId, + CSMDoc::Document& document, QWidget *parent) +: WorldspaceWidget (parent), mCellId (cellId) +{ + mCellsModel = &dynamic_cast ( + *document.getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); + + connect (mCellsModel, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (cellDataChanged (const QModelIndex&, const QModelIndex&))); + connect (mCellsModel, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), + this, SLOT (cellRowsAboutToBeRemoved (const QModelIndex&, int, int))); + + update(); +} + +void CSVRender::UnpagedWorldspaceWidget::cellDataChanged (const QModelIndex& topLeft, + const QModelIndex& bottomRight) +{ + int index = mCellsModel->findColumnIndex (CSMWorld::Columns::ColumnId_Modification); + QModelIndex cellIndex = mCellsModel->getModelIndex (mCellId, index); + + if (cellIndex.row()>=topLeft.row() && cellIndex.row()<=bottomRight.row()) + { + if (mCellsModel->data (cellIndex).toInt()==CSMWorld::RecordBase::State_Deleted) + { + emit closeRequest(); + } + else + { + /// \todo possible optimisation: check columns and update only if relevant columns have + /// changed + update(); + } + } +} + +void CSVRender::UnpagedWorldspaceWidget::cellRowsAboutToBeRemoved (const QModelIndex& parent, + int start, int end) +{ + QModelIndex cellIndex = mCellsModel->getModelIndex (mCellId, 0); + + if (cellIndex.row()>=start && cellIndex.row()<=end) + emit closeRequest(); +} \ No newline at end of file diff --git a/apps/opencs/view/render/unpagedworldspacewidget.hpp b/apps/opencs/view/render/unpagedworldspacewidget.hpp new file mode 100644 index 000000000..17dc46918 --- /dev/null +++ b/apps/opencs/view/render/unpagedworldspacewidget.hpp @@ -0,0 +1,44 @@ +#ifndef OPENCS_VIEW_UNPAGEDWORLDSPACEWIDGET_H +#define OPENCS_VIEW_UNPAGEDWORLDSPACEWIDGET_H + +#include + +#include "worldspacewidget.hpp" + +class QModelIndex; + +namespace CSMDoc +{ + class Document; +} + +namespace CSMWorld +{ + class IdTable; +} + +namespace CSVRender +{ + class UnpagedWorldspaceWidget : public WorldspaceWidget + { + Q_OBJECT + + std::string mCellId; + CSMWorld::IdTable *mCellsModel; + + void update(); + + public: + + UnpagedWorldspaceWidget (const std::string& cellId, CSMDoc::Document& document, + QWidget *parent); + + private slots: + + void cellDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + void cellRowsAboutToBeRemoved (const QModelIndex& parent, int start, int end); + }; +} + +#endif diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp new file mode 100644 index 000000000..dcd152bb3 --- /dev/null +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -0,0 +1,38 @@ + +#include "worldspacewidget.hpp" + +#include "../world/scenetoolmode.hpp" + +CSVRender::WorldspaceWidget::WorldspaceWidget (QWidget *parent) +: SceneWidget (parent) +{} + +void CSVRender::WorldspaceWidget::selectNavigationMode (const std::string& mode) +{ + if (mode=="1st") + setNavigation (&m1st); + else if (mode=="free") + setNavigation (&mFree); + else if (mode=="orbit") + setNavigation (&mOrbit); +} + +void CSVRender::WorldspaceWidget::selectDefaultNavigationMode() +{ + setNavigation (&m1st); +} + +CSVWorld::SceneToolMode *CSVRender::WorldspaceWidget::makeNavigationSelector ( + CSVWorld::SceneToolbar *parent) +{ + CSVWorld::SceneToolMode *tool = new CSVWorld::SceneToolMode (parent); + + tool->addButton (":door.png", "1st"); /// \todo replace icons + tool->addButton (":GMST.png", "free"); + tool->addButton (":Info.png", "orbit"); + + connect (tool, SIGNAL (modeChanged (const std::string&)), + this, SLOT (selectNavigationMode (const std::string&))); + + return tool; +} \ No newline at end of file diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp new file mode 100644 index 000000000..2eccca3bf --- /dev/null +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -0,0 +1,46 @@ +#ifndef OPENCS_VIEW_WORLDSPACEWIDGET_H +#define OPENCS_VIEW_WORLDSPACEWIDGET_H + +#include "scenewidget.hpp" + +#include "navigation1st.hpp" +#include "navigationfree.hpp" +#include "navigationorbit.hpp" + +namespace CSVWorld +{ + class SceneToolMode; + class SceneToolbar; +} + +namespace CSVRender +{ + class WorldspaceWidget : public SceneWidget + { + Q_OBJECT + + CSVRender::Navigation1st m1st; + CSVRender::NavigationFree mFree; + CSVRender::NavigationOrbit mOrbit; + + public: + + WorldspaceWidget (QWidget *parent = 0); + + CSVWorld::SceneToolMode *makeNavigationSelector (CSVWorld::SceneToolbar *parent); + ///< \important The created tool is not added to the toolbar (via addTool). Doing that + /// is the responsibility of the calling function. + + void selectDefaultNavigationMode(); + + private slots: + + void selectNavigationMode (const std::string& mode); + + signals: + + void closeRequest(); + }; +} + +#endif \ No newline at end of file diff --git a/apps/opencs/view/tools/reportsubview.cpp b/apps/opencs/view/tools/reportsubview.cpp index 182d1cdd6..d59f0c234 100644 --- a/apps/opencs/view/tools/reportsubview.cpp +++ b/apps/opencs/view/tools/reportsubview.cpp @@ -40,5 +40,5 @@ void CSVTools::ReportSubView::updateEditorSetting (const QString& key, const QSt void CSVTools::ReportSubView::show (const QModelIndex& index) { - focusId (mModel->getUniversalId (index.row())); + focusId (mModel->getUniversalId (index.row()), ""); } \ No newline at end of file diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index 33ae327a0..3601ae094 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -9,7 +9,8 @@ #include "../filter/filterbox.hpp" -#include "../render/scenewidget.hpp" +#include "../render/pagedworldspacewidget.hpp" +#include "../render/unpagedworldspacewidget.hpp" #include "tablebottombox.hpp" #include "creator.hpp" @@ -32,38 +33,21 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D layout2->setContentsMargins (QMargins (0, 0, 0, 0)); SceneToolbar *toolbar = new SceneToolbar (48, this); -// test -SceneToolMode *tool = new SceneToolMode (toolbar); -tool->addButton (":door.png", "a"); -tool->addButton (":GMST.png", "b"); -tool->addButton (":Info.png", "c"); -toolbar->addTool (tool); -toolbar->addTool (new SceneToolMode (toolbar)); -toolbar->addTool (new SceneToolMode (toolbar)); -toolbar->addTool (new SceneToolMode (toolbar)); - layout2->addWidget (toolbar, 0); -// temporarily disable OGRE-integration (need to fix path problem first) -#if 0 - CSVRender::SceneWidget* sceneWidget = new CSVRender::SceneWidget(this); + if (id.getId()[0]=='#') + mScene = new CSVRender::PagedWorldspaceWidget (this); + else + mScene = new CSVRender::UnpagedWorldspaceWidget (id.getId(), document, this); - layout2->addWidget (sceneWidget, 1); + SceneToolMode *tool = mScene->makeNavigationSelector (toolbar); + toolbar->addTool (tool); - layout->insertLayout (0, layout2, 1); -#endif - /// \todo replace with rendering widget - QPalette palette2 (palette()); - palette2.setColor (QPalette::Background, Qt::white); - QLabel *placeholder = new QLabel ("Here goes the 3D scene", this); - placeholder->setAutoFillBackground (true); - placeholder->setPalette (palette2); - placeholder->setAlignment (Qt::AlignHCenter); + layout2->addWidget (toolbar, 0); - layout2->addWidget (placeholder, 1); + layout2->addWidget (mScene, 1); layout->insertLayout (0, layout2, 1); - CSVFilter::FilterBox *filterBox = new CSVFilter::FilterBox (document.getData(), this); layout->insertWidget (0, filterBox); @@ -73,6 +57,10 @@ toolbar->addTool (new SceneToolMode (toolbar)); widget->setLayout (layout); setWidget (widget); + + mScene->selectDefaultNavigationMode(); + + connect (mScene, SIGNAL (closeRequest()), this, SLOT (closeRequest())); } void CSVWorld::SceneSubView::setEditLock (bool locked) @@ -91,3 +79,8 @@ void CSVWorld::SceneSubView::setStatusBar (bool show) { mBottom->setStatusBar (show); } + +void CSVWorld::SceneSubView::closeRequest() +{ + deleteLater(); +} \ No newline at end of file diff --git a/apps/opencs/view/world/scenesubview.hpp b/apps/opencs/view/world/scenesubview.hpp index a0fed908d..ecf3fe4e4 100644 --- a/apps/opencs/view/world/scenesubview.hpp +++ b/apps/opencs/view/world/scenesubview.hpp @@ -10,6 +10,11 @@ namespace CSMDoc class Document; } +namespace CSVRender +{ + class WorldspaceWidget; +} + namespace CSVWorld { class Table; @@ -21,6 +26,7 @@ namespace CSVWorld Q_OBJECT TableBottomBox *mBottom; + CSVRender::WorldspaceWidget *mScene; public: @@ -31,6 +37,10 @@ namespace CSVWorld virtual void updateEditorSetting (const QString& key, const QString& value); virtual void setStatusBar (bool show); + + private slots: + + void closeRequest(); }; } diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index edf3bc6de..4bb9955e6 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -2,7 +2,6 @@ #include "table.hpp" #include - #include #include #include @@ -10,6 +9,8 @@ #include #include +#include "../../model/doc/document.hpp" + #include "../../model/world/data.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/idtableproxymodel.hpp" @@ -35,8 +36,21 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) if (selectedRows.size()==1) { menu.addAction (mEditAction); + if (mCreateAction) menu.addAction(mCloneAction); + + if (mModel->getViewing()!=CSMWorld::IdTable::Viewing_None) + { + int row = selectedRows.begin()->row(); + + row = mProxyModel->mapToSource (mProxyModel->index (row, 0)).row(); + + CSMWorld::UniversalId id = mModel->view (row).first; + + if (!mDocument.getData().getCells().getRecord (id.getId()).isDeleted()) + menu.addAction (mViewAction); + } } if (mCreateAction) @@ -162,11 +176,12 @@ std::vector CSVWorld::Table::listDeletableSelectedIds() const return deletableIds; } -CSVWorld::Table::Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, QUndoStack& undoStack, - bool createAndDelete, bool sorting, const CSMDoc::Document& document) - : mUndoStack (undoStack), mCreateAction (0), mCloneAction(0), mEditLock (false), mRecordStatusDisplay (0), mDocument(document) +CSVWorld::Table::Table (const CSMWorld::UniversalId& id, + bool createAndDelete, bool sorting, CSMDoc::Document& document) +: mCreateAction (0), mCloneAction(0), mEditLock (false), mRecordStatusDisplay (0), + mDocument (document) { - mModel = &dynamic_cast (*data.getTableModel (id)); + mModel = &dynamic_cast (*mDocument.getData().getTableModel (id)); mProxyModel = new CSMWorld::IdTableProxyModel (this); mProxyModel->setSourceModel (mModel); @@ -190,7 +205,7 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, Q mModel->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); CommandDelegate *delegate = CommandDelegateFactoryCollection::get().makeDelegate (display, - undoStack, this); + mDocument.getUndoStack(), this); mDelegates.push_back (delegate); setItemDelegateForColumn (i, delegate); @@ -230,6 +245,10 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, Q connect (mMoveDownAction, SIGNAL (triggered()), this, SLOT (moveDownRecord())); addAction (mMoveDownAction); + mViewAction = new QAction (tr ("View"), this); + connect (mViewAction, SIGNAL (triggered()), this, SLOT (viewRecord())); + addAction (mViewAction); + connect (mProxyModel, SIGNAL (rowsInserted (const QModelIndex&, int, int)), this, SLOT (tableSizeUpdate())); @@ -268,13 +287,13 @@ void CSVWorld::Table::revertRecord() if (revertableIds.size()>0) { if (revertableIds.size()>1) - mUndoStack.beginMacro (tr ("Revert multiple records")); + mDocument.getUndoStack().beginMacro (tr ("Revert multiple records")); for (std::vector::const_iterator iter (revertableIds.begin()); iter!=revertableIds.end(); ++iter) - mUndoStack.push (new CSMWorld::RevertCommand (*mModel, *iter)); + mDocument.getUndoStack().push (new CSMWorld::RevertCommand (*mModel, *iter)); if (revertableIds.size()>1) - mUndoStack.endMacro(); + mDocument.getUndoStack().endMacro(); } } } @@ -288,13 +307,13 @@ void CSVWorld::Table::deleteRecord() if (deletableIds.size()>0) { if (deletableIds.size()>1) - mUndoStack.beginMacro (tr ("Delete multiple records")); + mDocument.getUndoStack().beginMacro (tr ("Delete multiple records")); for (std::vector::const_iterator iter (deletableIds.begin()); iter!=deletableIds.end(); ++iter) - mUndoStack.push (new CSMWorld::DeleteCommand (*mModel, *iter)); + mDocument.getUndoStack().push (new CSMWorld::DeleteCommand (*mModel, *iter)); if (deletableIds.size()>1) - mUndoStack.endMacro(); + mDocument.getUndoStack().endMacro(); } } } @@ -306,7 +325,7 @@ void CSVWorld::Table::editRecord() QModelIndexList selectedRows = selectionModel()->selectedRows(); if (selectedRows.size()==1) - emit editRequest (selectedRows.begin()->row()); + emit editRequest (getUniversalId (selectedRows.begin()->row()), ""); } } @@ -347,7 +366,7 @@ void CSVWorld::Table::moveUpRecord() for (int i=1; iselectedRows(); + + if (selectedRows.size()==1) + { + int row =selectedRows.begin()->row(); + + row = mProxyModel->mapToSource (mProxyModel->index (row, 0)).row(); + + std::pair params = mModel->view (row); + + if (params.first.getType()!=CSMWorld::UniversalId::Type_None) + emit editRequest (params.first, params.second); + } +} + void CSVWorld::Table::updateEditorSetting (const QString &settingName, const QString &settingValue) { int columns = mModel->columnCount(); @@ -517,7 +553,7 @@ void CSVWorld::Table::dropEvent(QDropEvent *event) std::auto_ptr command (new CSMWorld::ModifyCommand (*mProxyModel, index, QVariant (QString::fromUtf8 (record.getId().c_str())))); - mUndoStack.push (command.release()); + mDocument.getUndoStack().push (command.release()); } } //TODO handle drops from different document } diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index 615a31b4d..e8d5648d1 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -10,13 +10,14 @@ #include "../../model/filter/node.hpp" #include "../../model/world/columnbase.hpp" -namespace CSMDoc { - class Document; -} - class QUndoStack; class QAction; +namespace CSMDoc +{ + class Document; +} + namespace CSMWorld { class Data; @@ -35,7 +36,6 @@ namespace CSVWorld Q_OBJECT std::vector mDelegates; - QUndoStack& mUndoStack; QAction *mEditAction; QAction *mCreateAction; QAction *mCloneAction; @@ -43,14 +43,12 @@ namespace CSVWorld QAction *mDeleteAction; QAction *mMoveUpAction; QAction *mMoveDownAction; + QAction *mViewAction; CSMWorld::IdTableProxyModel *mProxyModel; CSMWorld::IdTable *mModel; bool mEditLock; int mRecordStatusDisplay; - - /// \brief This variable is used exclusivly for checking if dropEvents came from the same document. Most likely you - /// should NOT use it for anything else. - const CSMDoc::Document& mDocument; + CSMDoc::Document& mDocument; private: @@ -70,9 +68,8 @@ namespace CSVWorld public: - Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, QUndoStack& undoStack, bool createAndDelete, - bool sorting, const CSMDoc::Document& document); - + Table (const CSMWorld::UniversalId& id, bool createAndDelete, + bool sorting, CSMDoc::Document& document); ///< \param createAndDelete Allow creation and deletion of records. /// \param sorting Allow changing order of rows in the view via column headers. @@ -86,7 +83,7 @@ namespace CSVWorld signals: - void editRequest (int row); + void editRequest (const CSMWorld::UniversalId& id, const std::string& hint); void selectionSizeChanged (int size); @@ -112,6 +109,8 @@ namespace CSVWorld void moveDownRecord(); + void viewRecord(); + public slots: void tableSizeUpdate(); diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index e330d4775..2d08d186e 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -24,7 +24,7 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D new TableBottomBox (creatorFactory, document.getData(), document.getUndoStack(), id, this), 0); layout->insertWidget (0, mTable = - new Table (id, document.getData(), document.getUndoStack(), mBottom->canCreateAndDelete(), sorting, document), 2); + new Table (id, mBottom->canCreateAndDelete(), sorting, document), 2); CSVFilter::FilterBox *filterBox = new CSVFilter::FilterBox (document.getData(), this); @@ -36,7 +36,8 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D setWidget (widget); - connect (mTable, SIGNAL (editRequest (int)), this, SLOT (editRequest (int))); + connect (mTable, SIGNAL (editRequest (const CSMWorld::UniversalId&, const std::string&)), + this, SLOT (editRequest (const CSMWorld::UniversalId&, const std::string&))); connect (mTable, SIGNAL (selectionSizeChanged (int)), mBottom, SLOT (selectionSizeChanged (int))); @@ -81,9 +82,9 @@ void CSVWorld::TableSubView::setEditLock (bool locked) mBottom->setEditLock (locked); } -void CSVWorld::TableSubView::editRequest (int row) +void CSVWorld::TableSubView::editRequest (const CSMWorld::UniversalId& id, const std::string& hint) { - focusId (mTable->getUniversalId (row)); + focusId (id, hint); } void CSVWorld::TableSubView::updateEditorSetting(const QString &settingName, const QString &settingValue) diff --git a/apps/opencs/view/world/tablesubview.hpp b/apps/opencs/view/world/tablesubview.hpp index b3c253919..910fec325 100644 --- a/apps/opencs/view/world/tablesubview.hpp +++ b/apps/opencs/view/world/tablesubview.hpp @@ -53,7 +53,7 @@ namespace CSVWorld private slots: - void editRequest (int row); + void editRequest (const CSMWorld::UniversalId& id, const std::string& hint); void cloneRequest (const CSMWorld::UniversalId& toClone); void createFilterRequest(std::vector< CSMWorld::UniversalId >& types, Qt::DropAction action); diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index be583ea74..5e108edaf 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -15,7 +15,7 @@ add_openmw_dir (mwrender renderingmanager debugging sky camera animation npcanimation creatureanimation activatoranimation actors objects renderinginterface localmap occlusionquery water shadows characterpreview globalmap videoplayer ripplesimulation refraction - terrainstorage renderconst effectmanager + terrainstorage renderconst effectmanager weaponanimation ) add_openmw_dir (mwinput @@ -159,3 +159,10 @@ if (BUILD_WITH_CODE_COVERAGE) add_definitions (--coverage) target_link_libraries(openmw gcov) endif() + +if (MSVC) + # Debug version needs increased number of sections beyond 2^16 + if (CMAKE_CL_64) + set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /bigobj") + endif (CMAKE_CL_64) +endif (MSVC) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index e80bd954e..7d0626913 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -1,6 +1,7 @@ #include "engine.hpp" #include +#include #include #include @@ -403,7 +404,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) // Create the world mEnvironment.setWorld( new MWWorld::World (*mOgre, mFileCollections, mContentFiles, mResDir, mCfgMgr.getCachePath(), mEncoder, mFallbackMap, - mActivationDistanceOverride)); + mActivationDistanceOverride, mCellName)); MWBase::Environment::get().getWorld()->setupPlayer(); input->setPlayer(&mEnvironment.getWorld()->getPlayer()); @@ -439,31 +440,6 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mechanics->buildPlayer(); window->updatePlayer(); - // load cell - ESM::Position pos; - MWBase::World *world = MWBase::Environment::get().getWorld(); - - if (!mCellName.empty()) - { - if (world->findExteriorPosition(mCellName, pos)) { - world->changeToExteriorCell (pos); - } - else { - world->findInteriorPosition(mCellName, pos); - world->changeToInteriorCell (mCellName, pos); - } - } - else - { - pos.pos[0] = pos.pos[1] = pos.pos[2] = 0; - pos.rot[0] = pos.rot[1] = pos.pos[2] = 0; - world->changeToExteriorCell (pos); - } - - Ogre::FrameEvent event; - event.timeSinceLastEvent = 0; - event.timeSinceLastFrame = 0; - frameRenderingQueued(event); mOgre->getRoot()->addFrameListener (this); // scripts @@ -501,15 +477,15 @@ void OMW::Engine::go() // Play some good 'ol tunes MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore")); - if (!mStartupScript.empty()) - MWBase::Environment::get().getWindowManager()->executeInConsole (mStartupScript); - // start in main menu if (!mSkipMenu) MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); else MWBase::Environment::get().getStateManager()->newGame (true); + if (!mStartupScript.empty()) + MWBase::Environment::get().getWindowManager()->executeInConsole (mStartupScript); + // Start the main rendering loop while (!mEnvironment.get().getStateManager()->hasQuitRequest()) Ogre::Root::getSingleton().renderOneFrame(); @@ -621,4 +597,4 @@ void OMW::Engine::setActivationDistanceOverride (int distance) void OMW::Engine::setWarningsMode (int mode) { mWarningsMode = mode; -} \ No newline at end of file +} diff --git a/apps/openmw/mwbase/dialoguemanager.hpp b/apps/openmw/mwbase/dialoguemanager.hpp index 33bba07e1..3d70fdc6a 100644 --- a/apps/openmw/mwbase/dialoguemanager.hpp +++ b/apps/openmw/mwbase/dialoguemanager.hpp @@ -3,7 +3,7 @@ #include -#include +#include namespace ESM { diff --git a/apps/openmw/mwbase/inputmanager.hpp b/apps/openmw/mwbase/inputmanager.hpp index 8293cbfa7..42922a5b3 100644 --- a/apps/openmw/mwbase/inputmanager.hpp +++ b/apps/openmw/mwbase/inputmanager.hpp @@ -20,6 +20,9 @@ namespace MWBase InputManager() {} + /// Clear all savegame-specific data + virtual void clear() = 0; + virtual ~InputManager() {} virtual void update(float dt, bool loading) = 0; diff --git a/apps/openmw/mwbase/journal.hpp b/apps/openmw/mwbase/journal.hpp index 56d9601fc..8e4e9703f 100644 --- a/apps/openmw/mwbase/journal.hpp +++ b/apps/openmw/mwbase/journal.hpp @@ -5,7 +5,7 @@ #include #include -#include +#include #include "../mwdialogue/journalentry.hpp" #include "../mwdialogue/topic.hpp" diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index fd3978943..bb6f5741d 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -101,7 +101,8 @@ namespace MWBase virtual ~World() {} - virtual void startNewGame() = 0; + virtual void startNewGame (bool bypass) = 0; + ///< \param bypass Bypass regular game start. virtual void clear() = 0; @@ -274,7 +275,7 @@ namespace MWBase virtual void moveObject (const MWWorld::Ptr& ptr, float x, float y, float z) = 0; virtual void - moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore &newCell, float x, float y, float z) = 0; + moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, float x, float y, float z) = 0; virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0; @@ -282,7 +283,7 @@ namespace MWBase virtual void localRotateObject (const MWWorld::Ptr& ptr, float x, float y, float z) = 0; - virtual MWWorld::Ptr safePlaceObject(const MWWorld::Ptr& ptr,MWWorld::CellStore &Cell,ESM::Position pos) = 0; + virtual MWWorld::Ptr safePlaceObject(const MWWorld::Ptr& ptr, MWWorld::CellStore* cell, ESM::Position pos) = 0; ///< place an object in a "safe" location (ie not in the void, etc). virtual void indexToPosition (int cellX, int cellY, float &x, float &y, bool centre = false) @@ -464,8 +465,10 @@ namespace MWBase virtual void castSpell (const MWWorld::Ptr& actor) = 0; - virtual void launchProjectile (const std::string& id, bool stack, const ESM::EffectList& effects, + virtual void launchMagicBolt (const std::string& id, bool stack, const ESM::EffectList& effects, const MWWorld::Ptr& actor, const std::string& sourceName) = 0; + virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::Ptr projectile, + const Ogre::Vector3& worldPos, const Ogre::Quaternion& orient, MWWorld::Ptr bow, float speed) = 0; virtual const std::vector& getContentFiles() const = 0; diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 2939a5431..c66bda09f 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -7,6 +7,7 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/magiceffects.hpp" #include "../mwmechanics/movement.hpp" +#include "../mwmechanics/disease.hpp" #include "../mwmechanics/spellcasting.hpp" #include "../mwbase/environment.hpp" @@ -243,15 +244,7 @@ namespace MWClass Ogre::Vector3 hitPosition = result.second; - MWMechanics::CreatureStats &otherstats = victim.getClass().getCreatureStats(victim); - const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects(); - float hitchance = ref->mBase->mData.mCombat + - (stats.getAttribute(ESM::Attribute::Agility).getModified() / 5.0f) + - (stats.getAttribute(ESM::Attribute::Luck).getModified() / 10.0f); - hitchance *= stats.getFatigueTerm(); - hitchance += mageffects.get(ESM::MagicEffect::FortifyAttack).mMagnitude - - mageffects.get(ESM::MagicEffect::Blind).mMagnitude; - hitchance -= otherstats.getEvasion(); + float hitchance = MWMechanics::getHitChance(ptr, victim, ref->mBase->mData.mCombat); if((::rand()/(RAND_MAX+1.0)) > hitchance/100.0f) { @@ -334,6 +327,8 @@ namespace MWClass if (damage > 0) MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition); + MWMechanics::diseaseContact(victim, ptr); + victim.getClass().onHit(victim, damage, true, weapon, ptr, true); } @@ -530,7 +525,7 @@ namespace MWClass float moveSpeed; if(normalizedEncumbrance >= 1.0f) moveSpeed = 0.0f; - else if(isFlying(ptr) || (mageffects.get(ESM::MagicEffect::Levitate).mMagnitude > 0 && + else if(canFly(ptr) || (mageffects.get(ESM::MagicEffect::Levitate).mMagnitude > 0 && world->isLevitationEnabled())) { float flySpeed = 0.01f*(stats.getAttribute(ESM::Attribute::Speed).getModified() + @@ -683,7 +678,15 @@ namespace MWClass return MWWorld::Ptr(&cell.get().insert(*ref), &cell); } - bool Creature::isFlying(const MWWorld::Ptr &ptr) const + bool Creature::isBipedal(const MWWorld::Ptr &ptr) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + + return ref->mBase->mFlags & ESM::Creature::Bipedal; + } + + bool Creature::canFly(const MWWorld::Ptr &ptr) const { MWWorld::LiveCellRef *ref = ptr.get(); @@ -691,6 +694,22 @@ namespace MWClass return ref->mBase->mFlags & ESM::Creature::Flies; } + bool Creature::canSwim(const MWWorld::Ptr &ptr) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + + return ref->mBase->mFlags & ESM::Creature::Swims; + } + + bool Creature::canWalk(const MWWorld::Ptr &ptr) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + + return ref->mBase->mFlags & ESM::Creature::Walks; + } + int Creature::getSndGenTypeFromName(const MWWorld::Ptr &ptr, const std::string &name) { if(name == "left") diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index 6df6db297..c1bcb8739 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -124,7 +124,10 @@ namespace MWClass return true; } - virtual bool isFlying (const MWWorld::Ptr &ptr) const; + virtual bool isBipedal (const MWWorld::Ptr &ptr) const; + virtual bool canFly (const MWWorld::Ptr &ptr) const; + virtual bool canSwim (const MWWorld::Ptr &ptr) const; + virtual bool canWalk (const MWWorld::Ptr &ptr) const; virtual int getSkill(const MWWorld::Ptr &ptr, int skill) const; diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index ea586e5b6..caef521af 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -57,7 +57,7 @@ namespace MWClass MWWorld::ManualRef ref(store, id); ref.getPtr().getCellRef().mPos = ptr.getCellRef().mPos; // TODO: hold on to this for respawn purposes later - MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(), *ptr.getCell() , ptr.getCellRef().mPos); + MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(), ptr.getCell() , ptr.getCellRef().mPos); } ptr.getRefData().setCustomData(data.release()); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 20c7d9a0e..a1d2ef848 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -498,15 +498,7 @@ namespace MWClass if(!weapon.isEmpty()) weapskill = get(weapon).getEquipmentSkill(weapon); - MWMechanics::NpcStats &stats = getNpcStats(ptr); - const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects(); - float hitchance = stats.getSkill(weapskill).getModified() + - (stats.getAttribute(ESM::Attribute::Agility).getModified() / 5.0f) + - (stats.getAttribute(ESM::Attribute::Luck).getModified() / 10.0f); - hitchance *= stats.getFatigueTerm(); - hitchance += mageffects.get(ESM::MagicEffect::FortifyAttack).mMagnitude - - mageffects.get(ESM::MagicEffect::Blind).mMagnitude; - hitchance -= otherstats.getEvasion(); + float hitchance = MWMechanics::getHitChance(ptr, victim, ptr.getClass().getSkill(ptr, weapskill)); if((::rand()/(RAND_MAX+1.0)) > hitchance/100.0f) { @@ -516,6 +508,7 @@ namespace MWClass bool healthdmg; float damage = 0.0f; + MWMechanics::NpcStats &stats = getNpcStats(ptr); if(!weapon.isEmpty()) { const bool weaphashealth = get(weapon).hasItemHealth(weapon); @@ -615,6 +608,8 @@ namespace MWClass if (healthdmg && damage > 0) MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition); + MWMechanics::diseaseContact(victim, ptr); + othercls.onHit(victim, damage, healthdmg, weapon, ptr, true); } @@ -648,9 +643,6 @@ namespace MWClass ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1); } - if (!attacker.isEmpty()) - MWMechanics::diseaseContact(ptr, attacker); - if (damage > 0.0f && !object.isEmpty()) MWMechanics::resistNormalWeapon(ptr, attacker, object, damage); @@ -681,12 +673,7 @@ namespace MWClass else getCreatureStats(ptr).setHitRecovery(true); // Is this supposed to always occur? - if(object.isEmpty()) - { - if(ishealth) - damage /= std::min(1.0f + getArmorRating(ptr)/std::max(1.0f, damage), 4.0f); - } - else if(ishealth) + if(ishealth) { // Hit percentages: // cuirass = 30% diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp index 694970e23..52682342f 100644 --- a/apps/openmw/mwgui/bookpage.cpp +++ b/apps/openmw/mwgui/bookpage.cpp @@ -6,7 +6,7 @@ #include "MyGUI_TextureUtility.h" #include "MyGUI_FactoryManager.h" -#include +#include #include #include #include diff --git a/apps/openmw/mwgui/bookpage.hpp b/apps/openmw/mwgui/bookpage.hpp index 28aa371cf..458cf2a19 100644 --- a/apps/openmw/mwgui/bookpage.hpp +++ b/apps/openmw/mwgui/bookpage.hpp @@ -5,7 +5,7 @@ #include "MyGUI_Widget.h" #include -#include +#include #include #include diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index 92205c3e9..ada004612 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -1,5 +1,7 @@ #include "enchantingdialog.hpp" +#include + #include #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index a6ad43ce5..06a228a1f 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -604,15 +604,22 @@ namespace MWGui mEffectBox->setPosition((viewSize.width - mEffectBoxBaseRight) - mEffectBox->getWidth() + effectsDx, mEffectBox->getTop()); } + void HUD::updateEnemyHealthBar() + { + MWMechanics::CreatureStats& stats = MWWorld::Class::get(mEnemy).getCreatureStats(mEnemy); + mEnemyHealth->setProgressRange(100); + // Health is usually cast to int before displaying. Actors die whenever they are < 1 health. + // Therefore any value < 1 should show as an empty health bar. We do the same in statswindow :) + mEnemyHealth->setProgressPosition(int(stats.getHealth().getCurrent()) / stats.getHealth().getModified() * 100); + } + void HUD::update() { mSpellIcons->updateWidgets(mEffectBox, true); if (!mEnemy.isEmpty() && mEnemyHealth->getVisible()) { - MWMechanics::CreatureStats& stats = MWWorld::Class::get(mEnemy).getCreatureStats(mEnemy); - mEnemyHealth->setProgressRange(100); - mEnemyHealth->setProgressPosition(stats.getHealth().getCurrent() / stats.getHealth().getModified() * 100); + updateEnemyHealthBar(); } if (mIsDrowning) @@ -629,9 +636,7 @@ namespace MWGui if (!mEnemyHealth->getVisible()) mWeaponSpellBox->setPosition(mWeaponSpellBox->getPosition() - MyGUI::IntPoint(0,20)); mEnemyHealth->setVisible(true); - MWMechanics::CreatureStats& stats = MWWorld::Class::get(mEnemy).getCreatureStats(mEnemy); - mEnemyHealth->setProgressRange(100); - mEnemyHealth->setProgressPosition(stats.getHealth().getCurrent() / stats.getHealth().getModified() * 100); + updateEnemyHealthBar(); } } diff --git a/apps/openmw/mwgui/hud.hpp b/apps/openmw/mwgui/hud.hpp index 04206fbc8..6d1ffd03c 100644 --- a/apps/openmw/mwgui/hud.hpp +++ b/apps/openmw/mwgui/hud.hpp @@ -112,6 +112,8 @@ namespace MWGui void onMagicClicked(MyGUI::Widget* _sender); void onMapClicked(MyGUI::Widget* _sender); + void updateEnemyHealthBar(); + void updatePositions(); }; } diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 2ea09db61..5fa1d3bb1 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -514,6 +514,9 @@ namespace MWGui void InventoryWindow::pickUpObject (MWWorld::Ptr object) { + // If the inventory is not yet enabled, don't pick anything up + if (!MWBase::Environment::get().getWindowManager()->isAllowed(GW_Inventory)) + return; // make sure the object is of a type that can be picked up std::string type = object.getTypeName(); if ( (type != typeid(ESM::Apparatus).name()) diff --git a/apps/openmw/mwgui/journalviewmodel.cpp b/apps/openmw/mwgui/journalviewmodel.cpp index a0d67b025..c6bd6d15d 100644 --- a/apps/openmw/mwgui/journalviewmodel.cpp +++ b/apps/openmw/mwgui/journalviewmodel.cpp @@ -206,9 +206,9 @@ struct JournalViewModelImpl : JournalViewModel const MWDialogue::Quest& quest = i->second; // Unfortunately Morrowind.esm has no quest names, since the quest book was added with tribunal. - if (quest.getName().empty()) - visitor (reinterpret_cast (&i->second), toUtf8Span (i->first)); - else + // Note that even with Tribunal, some quests still don't have quest names. I'm assuming those are not supposed + // to appear in the quest book. + if (!quest.getName().empty()) visitor (reinterpret_cast (&i->second), toUtf8Span (quest.getName())); } } diff --git a/apps/openmw/mwgui/journalviewmodel.hpp b/apps/openmw/mwgui/journalviewmodel.hpp index 21c3a1178..9efdeae54 100644 --- a/apps/openmw/mwgui/journalviewmodel.hpp +++ b/apps/openmw/mwgui/journalviewmodel.hpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #include diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 868b58209..37e29591b 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -1,6 +1,13 @@ #include "loadingscreen.hpp" #include +#include +#include +#include +#include +#include +#include +#include #include @@ -66,6 +73,10 @@ namespace MWGui void LoadingScreen::loadingOn() { + // Early-out if already on + if (mRectangle->getVisible()) + return; + // Temporarily turn off VSync, we want to do actual loading rather than waiting for the screen to sync. // Threaded loading would be even better, of course - especially because some drivers force VSync to on and we can't change it. // In Ogre 1.8, the swapBuffers argument is useless and setVSyncEnabled is bugged with GLX, nothing we can do :/ @@ -74,16 +85,36 @@ namespace MWGui mWindow->setVSyncEnabled(false); #endif + if (!mFirstLoad) + { + mBackgroundImage->setImageTexture(""); + int width = mWindow->getWidth(); + int height = mWindow->getHeight(); + const std::string textureName = "@loading_background"; + Ogre::TexturePtr texture; + texture = Ogre::TextureManager::getSingleton().getByName(textureName); + if (texture.isNull()) + { + texture = Ogre::TextureManager::getSingleton().createManual(textureName, + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + Ogre::TEX_TYPE_2D, + width, height, 0, mWindow->suggestPixelFormat(), Ogre::TU_DYNAMIC_WRITE_ONLY); + } + texture->unload(); + texture->setWidth(width); + texture->setHeight(height); + texture->createInternalResources(); + mWindow->copyContentsToMemory(texture->getBuffer()->lock(Ogre::Image::Box(0,0,width,height), Ogre::HardwareBuffer::HBL_DISCARD)); + texture->getBuffer()->unlock(); + mBackgroundImage->setImageTexture(texture->getName()); + } + setVisible(true); if (mFirstLoad) { changeWallpaper(); } - else - { - mBackgroundImage->setImageTexture(""); - } MWBase::Environment::get().getWindowManager()->pushGuiMode(mFirstLoad ? GM_LoadingWallpaper : GM_Loading); } @@ -197,8 +228,6 @@ namespace MWGui MWBase::Environment::get().getInputManager()->update(0, true); - mWindow->getViewport(0)->setClearEveryFrame(false); - // First, swap buffers from last draw, then, queue an update of the // window contents, but don't swap buffers (which would have // caused a sync / flush and would be expensive). @@ -208,9 +237,6 @@ namespace MWGui mWindow->update(false); - mWindow->getViewport(0)->setClearEveryFrame(true); - - mRectangle->setVisible(false); // resume 3d rendering diff --git a/apps/openmw/mwgui/loadingscreen.hpp b/apps/openmw/mwgui/loadingscreen.hpp index 2d1d7431f..e91e5951d 100644 --- a/apps/openmw/mwgui/loadingscreen.hpp +++ b/apps/openmw/mwgui/loadingscreen.hpp @@ -2,6 +2,7 @@ #define MWGUI_LOADINGSCREEN_H #include +#include #include "windowbase.hpp" diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index da1992474..4ad260fd9 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -1,5 +1,7 @@ #include "mainmenu.hpp" +#include + #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" @@ -20,6 +22,22 @@ namespace MWGui , mButtonBox(0), mWidth (w), mHeight (h) , mSaveGameDialog(NULL) { + getWidget(mVersionText, "VersionText"); + std::stringstream sstream; + sstream << "OpenMW version: " << OPENMW_VERSION; + + // adding info about git hash if availible + std::string rev = OPENMW_VERSION_COMMITHASH; + std::string tag = OPENMW_VERSION_TAGHASH; + if (!rev.empty() && !tag.empty()) + { + rev = rev.substr(0,10); + sstream << "\nrevision: " << rev; + } + + std::string output = sstream.str(); + mVersionText->setCaption(output); + updateMenu(); } diff --git a/apps/openmw/mwgui/mainmenu.hpp b/apps/openmw/mwgui/mainmenu.hpp index 6d52f26d5..48b515d65 100644 --- a/apps/openmw/mwgui/mainmenu.hpp +++ b/apps/openmw/mwgui/mainmenu.hpp @@ -24,6 +24,7 @@ namespace MWGui private: MyGUI::Widget* mButtonBox; + MyGUI::TextBox* mVersionText; std::map mButtons; diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index 51e160d26..1cc9610df 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -3,6 +3,7 @@ #include #include +#include #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index 1e52ff26a..249477551 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -1,7 +1,7 @@ #ifndef MWGUI_MAPWINDOW_H #define MWGUI_MAPWINDOW_H -#include +#include #include "windowpinnablebase.hpp" diff --git a/apps/openmw/mwgui/race.cpp b/apps/openmw/mwgui/race.cpp index 3dff1b7e4..499c1e191 100644 --- a/apps/openmw/mwgui/race.cpp +++ b/apps/openmw/mwgui/race.cpp @@ -41,9 +41,13 @@ namespace MWGui getWidget(mPreviewImage, "PreviewImage"); getWidget(mHeadRotate, "HeadRotate"); - mHeadRotate->setScrollRange(50); - mHeadRotate->setScrollPosition(25); - mHeadRotate->setScrollViewPage(10); + + // Mouse wheel step is hardcoded to 50 in MyGUI 3.2 ("FIXME"). + // Give other steps the same value to accomodate. + mHeadRotate->setScrollRange(1000); + mHeadRotate->setScrollPosition(500); + mHeadRotate->setScrollViewPage(50); + mHeadRotate->setScrollPage(50); mHeadRotate->eventScrollChangePosition += MyGUI::newDelegate(this, &RaceDialog::onHeadRotate); // Set up next/previous buttons @@ -171,9 +175,9 @@ namespace MWGui eventBack(); } - void RaceDialog::onHeadRotate(MyGUI::ScrollBar*, size_t _position) + void RaceDialog::onHeadRotate(MyGUI::ScrollBar* scroll, size_t _position) { - float angle = (float(_position) / 49.f - 0.5) * 3.14 * 2; + float angle = (float(_position) / (scroll->getScrollRange()-1) - 0.5) * 3.14 * 2; float diff = angle - mCurrentAngle; mPreview->update (diff); mPreviewDirty = true; diff --git a/apps/openmw/mwgui/repair.cpp b/apps/openmw/mwgui/repair.cpp index d729ee7fa..de96bcacd 100644 --- a/apps/openmw/mwgui/repair.cpp +++ b/apps/openmw/mwgui/repair.cpp @@ -1,5 +1,7 @@ #include "repair.hpp" +#include + #include #include "../mwbase/world.hpp" diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 8716c4dea..f941c699b 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -1,5 +1,7 @@ #include "tooltips.hpp" +#include + #include #include "../mwbase/world.hpp" diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index f44078256..1b71157a7 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -3,6 +3,8 @@ #include #include +#include + #include "MyGUI_UString.h" #include "MyGUI_IPointer.h" #include "MyGUI_ResourceImageSetPointer.h" diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 314053799..4bfd3f465 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -143,6 +143,13 @@ namespace MWInput mControlSwitch["vanitymode"] = true; } + void InputManager::clear() + { + // Enable all controls + for (std::map::iterator it = mControlSwitch.begin(); it != mControlSwitch.end(); ++it) + it->second = true; + } + InputManager::~InputManager() { mInputBinder->save (mUserFile); @@ -455,7 +462,7 @@ namespace MWInput mInputBinder->adjustMouseRegion(width, height); } - bool InputManager::keyPressed( const SDL_KeyboardEvent &arg ) + void InputManager::keyPressed( const SDL_KeyboardEvent &arg ) { // Cut, copy & paste MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); @@ -501,7 +508,6 @@ namespace MWInput if (kc != OIS::KC_UNASSIGNED) MyGUI::InputManager::getInstance().injectKeyPress(MyGUI::KeyCode::Enum(kc), 0); - return true; } void InputManager::textInput(const SDL_TextInputEvent &arg) @@ -512,23 +518,21 @@ namespace MWInput MyGUI::InputManager::getInstance().injectKeyPress(MyGUI::KeyCode::None, *it); } - bool InputManager::keyReleased(const SDL_KeyboardEvent &arg ) + void InputManager::keyReleased(const SDL_KeyboardEvent &arg ) { mInputBinder->keyReleased (arg); OIS::KeyCode kc = mInputManager->sdl2OISKeyCode(arg.keysym.sym); MyGUI::InputManager::getInstance().injectKeyRelease(MyGUI::KeyCode::Enum(kc)); - - return true; } - bool InputManager::mousePressed( const SDL_MouseButtonEvent &arg, Uint8 id ) + void InputManager::mousePressed( const SDL_MouseButtonEvent &arg, Uint8 id ) { mInputBinder->mousePressed (arg, id); if (id != SDL_BUTTON_LEFT && id != SDL_BUTTON_RIGHT) - return true; // MyGUI has no use for these events + return; // MyGUI has no use for these events MyGUI::InputManager::getInstance().injectMousePress(mMouseX, mMouseY, sdlButtonToMyGUI(id)); if (MyGUI::InputManager::getInstance ().getMouseFocusWidget () != 0) @@ -539,20 +543,16 @@ namespace MWInput MWBase::Environment::get().getSoundManager ()->playSound ("Menu Click", 1.f, 1.f); } } - - return true; } - bool InputManager::mouseReleased( const SDL_MouseButtonEvent &arg, Uint8 id ) + void InputManager::mouseReleased( const SDL_MouseButtonEvent &arg, Uint8 id ) { mInputBinder->mouseReleased (arg, id); MyGUI::InputManager::getInstance().injectMouseRelease(mMouseX, mMouseY, sdlButtonToMyGUI(id)); - - return true; } - bool InputManager::mouseMoved(const SFO::MouseMotionEvent &arg ) + void InputManager::mouseMoved(const SFO::MouseMotionEvent &arg ) { mInputBinder->mouseMoved (arg); @@ -585,13 +585,13 @@ namespace MWInput float rot[3]; rot[0] = -y; rot[1] = 0.0f; - rot[2] = x; + rot[2] = -x; // Only actually turn player when we're not in vanity mode if(!MWBase::Environment::get().getWorld()->vanityRotateCamera(rot)) { mPlayer->yaw(x); - mPlayer->pitch(-y); + mPlayer->pitch(y); } if (arg.zrel && mControlSwitch["playerviewswitch"]) //Check to make sure you are allowed to zoomout and there is a change @@ -600,8 +600,6 @@ namespace MWInput MWBase::Environment::get().getWorld()->setCameraDistance(arg.zrel, true, true); } } - - return true; } void InputManager::windowFocusChange(bool have_focus) diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index d41b4c3f3..2eab03a34 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -65,6 +65,9 @@ namespace MWInput virtual ~InputManager(); + /// Clear all savegame-specific data + virtual void clear(); + virtual void update(float dt, bool loading); void setPlayer (MWWorld::Player* player) { mPlayer = player; } @@ -86,13 +89,13 @@ namespace MWInput virtual void resetToDefaultBindings(); public: - virtual bool keyPressed(const SDL_KeyboardEvent &arg ); - virtual bool keyReleased( const SDL_KeyboardEvent &arg ); + virtual void keyPressed(const SDL_KeyboardEvent &arg ); + virtual void keyReleased( const SDL_KeyboardEvent &arg ); virtual void textInput (const SDL_TextInputEvent &arg); - virtual bool mousePressed( const SDL_MouseButtonEvent &arg, Uint8 id ); - virtual bool mouseReleased( const SDL_MouseButtonEvent &arg, Uint8 id ); - virtual bool mouseMoved( const SFO::MouseMotionEvent &arg ); + virtual void mousePressed( const SDL_MouseButtonEvent &arg, Uint8 id ); + virtual void mouseReleased( const SDL_MouseButtonEvent &arg, Uint8 id ); + virtual void mouseMoved( const SFO::MouseMotionEvent &arg ); virtual void windowVisibilityChange( bool visible ); virtual void windowFocusChange( bool have_focus ); diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 1fb22ce63..92be89f2f 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -210,7 +210,7 @@ namespace MWMechanics && LOS ) { - creatureStats.getAiSequence().stack(AiCombat(MWBase::Environment::get().getWorld()->getPlayer().getPlayer())); + creatureStats.getAiSequence().stack(AiCombat(MWBase::Environment::get().getWorld()->getPlayerPtr())); creatureStats.setHostile(true); } } @@ -541,7 +541,7 @@ namespace MWMechanics // TODO: Add AI to follow player and fight for him // TODO: VFX_SummonStart, VFX_SummonEnd creatureStats.mSummonedCreatures.insert(std::make_pair(it->first, - MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),*store,ipos).getRefData().getHandle())); + MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,ipos).getRefData().getHandle())); } } else diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 5be90bf2c..fd2fa61ba 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -15,7 +15,7 @@ #include "../mwbase/dialoguemanager.hpp" -#include "npcstats.hpp" +#include "creaturestats.hpp" #include "steering.hpp" #include "movement.hpp" #include "character.hpp" // fixme: for getActiveWeapon @@ -140,11 +140,12 @@ namespace MWMechanics { MWMechanics::DrawState_ state = actor.getClass().getCreatureStats(actor).getDrawState(); if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing) - actor.getClass().getNpcStats(actor).setDrawState(MWMechanics::DrawState_Weapon); + actor.getClass().getCreatureStats(actor).setDrawState(MWMechanics::DrawState_Weapon); //Get weapon speed and range MWWorld::ContainerStoreIterator weaponSlot = - MWMechanics::getActiveWeapon(cls.getNpcStats(actor), cls.getInventoryStore(actor), &weaptype); + MWMechanics::getActiveWeapon(cls.getCreatureStats(actor), cls.getInventoryStore(actor), &weaptype); + if (weaptype == WeapType_HandToHand) { const MWWorld::Store &gmst = @@ -242,17 +243,21 @@ namespace MWMechanics //target is at far distance: build path to target OR follow target (if previously actor had reached it once) mFollowTarget = false; - buildNewPath(actor); + buildNewPath(actor); //may fail to build a path, check before use //delete visited path node mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]); - //try shortcut - if(vDir.length() < mPathFinder.getDistToNext(pos.pos[0],pos.pos[1],pos.pos[2]) && MWBase::Environment::get().getWorld()->getLOS(actor, mTarget)) - mTargetAngle = Ogre::Radian( Ogre::Math::ACos(vDir.y / vDir.length()) * sgn(Ogre::Math::ASin(vDir.x / vDir.length())) ).valueDegrees(); - else - mTargetAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); - mRotate = true; + //if no new path leave mTargetAngle unchanged + if(!mPathFinder.getPath().empty()) + { + //try shortcut + if(vDir.length() < mPathFinder.getDistToNext(pos.pos[0],pos.pos[1],pos.pos[2]) && MWBase::Environment::get().getWorld()->getLOS(actor, mTarget)) + mTargetAngle = Ogre::Radian( Ogre::Math::ACos(vDir.y / vDir.length()) * sgn(Ogre::Math::ASin(vDir.x / vDir.length())) ).valueDegrees(); + else + mTargetAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); + mRotate = true; + } mMovement.mPosition[1] = 1; mReadyToAttack = false; @@ -302,9 +307,13 @@ namespace MWMechanics dest.mZ = mTarget.getRefData().getPosition().pos[2]; Ogre::Vector3 newPathTarget = Ogre::Vector3(dest.mX, dest.mY, dest.mZ); - ESM::Pathgrid::Point lastPt = mPathFinder.getPath().back(); - Ogre::Vector3 currPathTarget(lastPt.mX, lastPt.mY, lastPt.mZ); - float dist = Ogre::Math::Abs((newPathTarget - currPathTarget).length()); + float dist = -1; //hack to indicate first time, to construct a new path + if(!mPathFinder.getPath().empty()) + { + ESM::Pathgrid::Point lastPt = mPathFinder.getPath().back(); + Ogre::Vector3 currPathTarget(lastPt.mX, lastPt.mY, lastPt.mZ); + dist = Ogre::Math::Abs((newPathTarget - currPathTarget).length()); + } float targetPosThreshold; bool isOutside = actor.getCell()->getCell()->isExterior(); @@ -313,7 +322,7 @@ namespace MWMechanics else targetPosThreshold = 100; - if(dist > targetPosThreshold) + if((dist < 0) || (dist > targetPosThreshold)) { //construct new path only if target has moved away more than on ESM::Position pos = actor.getRefData().getPosition(); @@ -334,8 +343,11 @@ namespace MWMechanics //maybe here is a mistake (?): PathFinder::getPathSize() returns number of grid points in the path, //not the actual path length. Here we should know if the new path is actually more effective. //if(pathFinder2.getPathSize() < mPathFinder.getPathSize()) - newPathFinder.syncStart(mPathFinder.getPath()); - mPathFinder = newPathFinder; + if(!mPathFinder.getPath().empty()) + { + newPathFinder.syncStart(mPathFinder.getPath()); + mPathFinder = newPathFinder; + } } } } diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 9a95822f5..4da325abd 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -27,6 +27,11 @@ namespace namespace MWMechanics { + // NOTE: determined empirically but probably need further tweaking + static const int COUNT_BEFORE_STUCK = 20; + static const int COUNT_BEFORE_RESET = 200; + static const int COUNT_EVADE = 7; + AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): mDistance(distance), mDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), mRepeat(repeat) , mCellX(std::numeric_limits::max()) @@ -36,6 +41,11 @@ namespace MWMechanics , mX(0) , mY(0) , mZ(0) + , mPrevX(0) + , mPrevY(0) + , mWalkState(State_Norm) + , mStuckCount(0) + , mEvadeCount(0) , mSaidGreeting(false) { for(unsigned short counter = 0; counter < mIdle.size(); counter++) @@ -298,9 +308,91 @@ namespace MWMechanics } else { - zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); + /* 1 n + * State_Norm <---> State_CheckStuck --> State_Evade + * ^ ^ | ^ | ^ | | + * | | | | | | | | + * | +---+ +---+ +---+ | m + * | any < n < m | + * +--------------------------------------------+ + */ + bool samePosition = (abs(pos.pos[0] - mPrevX) < 1) && (abs(pos.pos[1] - mPrevY) < 1); + switch(mWalkState) + { + case State_Norm: + { + if(!samePosition) + break; + else + mWalkState = State_CheckStuck; + } + /* FALL THROUGH */ + case State_CheckStuck: + { + if(!samePosition) + { + mWalkState = State_Norm; + // to do this properly need yet another variable, simply don't clear for now + //mStuckCount = 0; + break; + } + else + { + // consider stuck only if position unchanges consecutively + if((mStuckCount++ % COUNT_BEFORE_STUCK) == 0) + mWalkState = State_Evade; + // NOTE: mStuckCount is purposely not cleared here + else + break; // still in the same state, but counter got incremented + } + } + /* FALL THROUGH */ + case State_Evade: + { + if(mEvadeCount++ < COUNT_EVADE) + break; + else + { + mWalkState = State_Norm; // tried to evade, assume all is ok and start again + // NOTE: mStuckCount is purposely not cleared here + mEvadeCount = 0; + } + } + /* NO DEFAULT CASE */ + } + + if(mWalkState == State_Evade) + { + //std::cout << "Stuck \""<= COUNT_BEFORE_RESET) // something has gone wrong, reset + { + //std::cout << "Reset \""<disable(mCurrentMovement); mCurrentMovement = movement; + mMovementAnimVelocity = 0.0f; if(!mCurrentMovement.empty()) { float vel, speedmult = 1.0f; @@ -320,7 +321,10 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat bool isrunning = mPtr.getClass().getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run); if(mMovementSpeed > 0.0f && (vel=mAnimation->getVelocity(mCurrentMovement)) > 1.0f) + { + mMovementAnimVelocity = vel; speedmult = mMovementSpeed / vel; + } else if (mMovementState == CharState_TurnLeft || mMovementState == CharState_TurnRight) speedmult = 1.f; // TODO: should get a speed mult depending on the current turning speed else if (mMovementSpeed > 0.0f) @@ -330,10 +334,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat speedmult = mMovementSpeed / (isrunning ? 222.857f : 154.064f); mAnimation->play(mCurrentMovement, Priority_Movement, movegroup, false, speedmult, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul); - - mMovementAnimVelocity = vel; } - else mMovementAnimVelocity = 0.0f; } } @@ -1194,7 +1195,10 @@ void CharacterController::update(float duration) : (sneak ? CharState_SneakBack : (isrunning ? CharState_RunBack : CharState_WalkBack))); } - else if(rot.z != 0.0f && !inwater && !sneak) + // Don't play turning animations during attack. It would break positioning of the arrow bone when releasing a shot. + // Actually, in vanilla the turning animation is not even played when merely having equipped the weapon, + // but I don't think we need to go as far as that. + else if(rot.z != 0.0f && !inwater && !sneak && mUpperBodyState < UpperCharState_StartToMinAttack) { if(rot.z > 0.0f) movestate = CharState_TurnRight; diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 6aeb90dad..cdc12e210 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -4,9 +4,12 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/soundmanager.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/movement.hpp" +#include "../mwmechanics/spellcasting.hpp" #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" @@ -17,14 +20,30 @@ namespace { -Ogre::Radian signedAngle(Ogre::Vector3 v1, Ogre::Vector3 v2, Ogre::Vector3 n) +Ogre::Radian signedAngle(Ogre::Vector3 v1, Ogre::Vector3 v2, Ogre::Vector3 normal) { return Ogre::Math::ATan2( - n.dotProduct( v1.crossProduct(v2) ), + normal.dotProduct( v1.crossProduct(v2) ), v1.dotProduct(v2) ); } +void applyEnchantment (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& object, const Ogre::Vector3& hitPosition) +{ + std::string enchantmentName = !object.isEmpty() ? object.getClass().getEnchantment(object) : ""; + if (!enchantmentName.empty()) + { + const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find( + enchantmentName); + if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes) + { + MWMechanics::CastSpell cast(attacker, victim); + cast.mHitPosition = hitPosition; + cast.cast(object); + } + } +} + } namespace MWMechanics @@ -135,4 +154,94 @@ namespace MWMechanics MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResistsWeapons}"); } + void projectileHit(const MWWorld::Ptr &attacker, const MWWorld::Ptr &victim, MWWorld::Ptr weapon, const MWWorld::Ptr &projectile, + const Ogre::Vector3& hitPosition) + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + const MWWorld::Store &gmst = world->getStore().get(); + + MWMechanics::CreatureStats& attackerStats = attacker.getClass().getCreatureStats(attacker); + + const MWWorld::Class &othercls = victim.getClass(); + if(!othercls.isActor()) // Can't hit non-actors + return; + MWMechanics::CreatureStats &otherstats = victim.getClass().getCreatureStats(victim); + if(otherstats.isDead()) // Can't hit dead actors + return; + + if(attacker.getRefData().getHandle() == "player") + MWBase::Environment::get().getWindowManager()->setEnemy(victim); + + int weapskill = ESM::Skill::Marksman; + if(!weapon.isEmpty()) + weapskill = weapon.getClass().getEquipmentSkill(weapon); + + float skillValue = attacker.getClass().getSkill(attacker, + weapon.getClass().getEquipmentSkill(weapon)); + + if((::rand()/(RAND_MAX+1.0)) > getHitChance(attacker, victim, skillValue)/100.0f) + { + victim.getClass().onHit(victim, 0.0f, false, projectile, attacker, false); + return; + } + + float damage = 0.0f; + + float fDamageStrengthBase = gmst.find("fDamageStrengthBase")->getFloat(); + float fDamageStrengthMult = gmst.find("fDamageStrengthMult")->getFloat(); + + const unsigned char* attack = weapon.get()->mBase->mData.mChop; + damage = attack[0] + ((attack[1]-attack[0])*attackerStats.getAttackStrength()); // Bow/crossbow damage + if (weapon != projectile) + { + // Arrow/bolt damage + attack = projectile.get()->mBase->mData.mChop; + damage += attack[0] + ((attack[1]-attack[0])*attackerStats.getAttackStrength()); + } + + damage *= fDamageStrengthBase + + (attackerStats.getAttribute(ESM::Attribute::Strength).getModified() * fDamageStrengthMult * 0.1); + + if(attacker.getRefData().getHandle() == "player") + attacker.getClass().skillUsageSucceeded(attacker, weapskill, 0); + + bool detected = MWBase::Environment::get().getMechanicsManager()->awarenessCheck(attacker, victim); + if(!detected) + { + damage *= gmst.find("fCombatCriticalStrikeMult")->getFloat(); + MWBase::Environment::get().getWindowManager()->messageBox("#{sTargetCriticalStrike}"); + MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f); + } + if (victim.getClass().getCreatureStats(victim).getKnockedDown()) + damage *= gmst.find("fCombatKODamageMult")->getFloat(); + + // Apply "On hit" effect of the weapon + applyEnchantment(attacker, victim, weapon, hitPosition); + if (weapon != projectile) + applyEnchantment(attacker, victim, projectile, hitPosition); + + if (damage > 0) + MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition); + + float fProjectileThrownStoreChance = gmst.find("fProjectileThrownStoreChance")->getFloat(); + if ((::rand()/(RAND_MAX+1.0)) < fProjectileThrownStoreChance/100.f) + victim.getClass().getContainerStore(victim).add(projectile, 1, victim); + + victim.getClass().onHit(victim, damage, true, projectile, attacker, true); + } + + float getHitChance(const MWWorld::Ptr &attacker, const MWWorld::Ptr &victim, int skillValue) + { + MWMechanics::CreatureStats &stats = attacker.getClass().getCreatureStats(attacker); + const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects(); + float hitchance = skillValue + + (stats.getAttribute(ESM::Attribute::Agility).getModified() / 5.0f) + + (stats.getAttribute(ESM::Attribute::Luck).getModified() / 10.0f); + hitchance *= stats.getFatigueTerm(); + hitchance += mageffects.get(ESM::MagicEffect::FortifyAttack).mMagnitude - + mageffects.get(ESM::MagicEffect::Blind).mMagnitude; + hitchance -= victim.getClass().getCreatureStats(victim).getEvasion(); + return hitchance; + } + } diff --git a/apps/openmw/mwmechanics/combat.hpp b/apps/openmw/mwmechanics/combat.hpp index 7f2415697..bc58227bf 100644 --- a/apps/openmw/mwmechanics/combat.hpp +++ b/apps/openmw/mwmechanics/combat.hpp @@ -2,6 +2,7 @@ #define OPENMW_MECHANICS_COMBAT_H #include "../mwworld/ptr.hpp" +#include namespace MWMechanics { @@ -11,6 +12,13 @@ bool blockMeleeAttack (const MWWorld::Ptr& attacker, const MWWorld::Ptr& blocker void resistNormalWeapon (const MWWorld::Ptr& actor, const MWWorld::Ptr& attacker, const MWWorld::Ptr& weapon, float& damage); +/// @note for a thrown weapon, \a weapon == \a projectile, for bows/crossbows, \a projectile is the arrow/bolt +void projectileHit (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, MWWorld::Ptr weapon, const MWWorld::Ptr& projectile, + const Ogre::Vector3& hitPosition); + +/// Get the chance (in percent) for \a attacker to successfully hit \a victim with a given weapon skill value +float getHitChance (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, int skillValue); + } #endif diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 8918bfbe7..74587a626 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -6,6 +6,8 @@ #include #include +#include + #include #include @@ -509,4 +511,4 @@ void MWMechanics::NpcStats::readState (const ESM::NpcStats& state) mTimeToStartDrowning = state.mTimeToStartDrowning; mLastDrowningHit = state.mLastDrowningHit; mLevelHealthBonus = state.mLevelHealthBonus; -} \ No newline at end of file +} diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index efa2bdf69..5314b5919 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -393,6 +393,8 @@ namespace MWMechanics void PathFinder::syncStart(const std::list &path) { + if (mPath.size() < 2) + return; //nothing to pop std::list::const_iterator oldStart = path.begin(); std::list::iterator iter = ++mPath.begin(); diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 9745cdebe..7f5a7fac5 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -587,7 +587,7 @@ namespace MWMechanics inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Touch); } - MWBase::Environment::get().getWorld()->launchProjectile(mId, false, enchantment->mEffects, mCaster, mSourceName); + MWBase::Environment::get().getWorld()->launchMagicBolt(mId, false, enchantment->mEffects, mCaster, mSourceName); return true; } @@ -666,7 +666,7 @@ namespace MWMechanics } } - MWBase::Environment::get().getWorld()->launchProjectile(mId, false, spell->mEffects, mCaster, mSourceName); + MWBase::Environment::get().getWorld()->launchMagicBolt(mId, false, spell->mEffects, mCaster, mSourceName); return true; } diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 2ddea7682..e62fee6e2 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include #include @@ -293,6 +295,17 @@ void Animation::addAnimSource(const std::string &model) } } + if (grp == 0 && dstval->getNode()->getName() == "Bip01") + { + mNonAccumRoot = dstval->getNode(); + mAccumRoot = mNonAccumRoot->getParent(); + if(!mAccumRoot) + { + std::cerr<< "Non-Accum root for "<getParentNode()->setOrientation(xr); } else { - Ogre::Quaternion zr(Ogre::Radian(getYaw()), Ogre::Vector3::NEGATIVE_UNIT_Z); + Ogre::Quaternion zr(Ogre::Radian(getYaw()), Ogre::Vector3::UNIT_Z); mCamera->getParentNode()->setOrientation(zr * xr); } } diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 280828652..013e3daf4 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -1,10 +1,13 @@ #include "characterpreview.hpp" - #include #include #include #include +#include +#include +#include +#include #include diff --git a/apps/openmw/mwrender/characterpreview.hpp b/apps/openmw/mwrender/characterpreview.hpp index cd30cdf46..16e6ab017 100644 --- a/apps/openmw/mwrender/characterpreview.hpp +++ b/apps/openmw/mwrender/characterpreview.hpp @@ -3,6 +3,7 @@ #include #include +#include #include diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 8ef584154..0eb883953 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -55,6 +55,8 @@ CreatureWeaponAnimation::CreatureWeaponAnimation(const MWWorld::Ptr &ptr) updateParts(); } + + mWeaponAnimationTime = Ogre::SharedPtr(new WeaponAnimationTime(this)); } void CreatureWeaponAnimation::showWeapons(bool showWeapon) @@ -110,6 +112,20 @@ void CreatureWeaponAnimation::updatePart(NifOgre::ObjectScenePtr& scene, int slo setRenderProperties(scene, RV_Actors, RQG_Main, RQG_Alpha, 0, !item.getClass().getEnchantment(item).empty(), &glowColor); + // Crossbows start out with a bolt attached + if (slot == MWWorld::InventoryStore::Slot_CarriedRight && + item.getTypeName() == typeid(ESM::Weapon).name() && + item.get()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow) + { + MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + if (ammo != inv.end() && ammo->get()->mBase->mData.mType == ESM::Weapon::Bolt) + attachArrow(); + else + mAmmunition.setNull(); + } + else + mAmmunition.setNull(); + if(scene->mSkelBase) { Ogre::SkeletonInstance *skel = scene->mSkelBase->getSkeleton(); @@ -133,15 +149,42 @@ void CreatureWeaponAnimation::updatePart(NifOgre::ObjectScenePtr& scene, int slo updateSkeletonInstance(mSkelBase->getSkeleton(), skel); } - // TODO: - // type == ESM::PRT_Weapon should get an animation source based on the current offset - // of the weapon attack animation (from its beginning, or start marker?) std::vector >::iterator ctrl(scene->mControllers.begin()); for(;ctrl != scene->mControllers.end();ctrl++) { if(ctrl->getSource().isNull()) - ctrl->setSource(Ogre::SharedPtr(new NullAnimationTime())); + { + if (slot == MWWorld::InventoryStore::Slot_CarriedRight) + ctrl->setSource(mWeaponAnimationTime); + else + ctrl->setSource(Ogre::SharedPtr(new NullAnimationTime())); + } } } +void CreatureWeaponAnimation::configureAddedObject(NifOgre::ObjectScenePtr object, MWWorld::Ptr ptr, int slot) +{ + Ogre::Vector3 glowColor = getEnchantmentColor(ptr); + + setRenderProperties(object, RV_Actors, RQG_Main, RQG_Alpha, 0, + !ptr.getClass().getEnchantment(ptr).empty(), &glowColor); +} + +void CreatureWeaponAnimation::attachArrow() +{ + WeaponAnimation::attachArrow(mPtr); +} + +void CreatureWeaponAnimation::releaseArrow() +{ + WeaponAnimation::releaseArrow(mPtr); +} + +Ogre::Vector3 CreatureWeaponAnimation::runAnimation(float duration) +{ + Ogre::Vector3 ret = Animation::runAnimation(duration); + pitchSkeleton(mPtr.getRefData().getPosition().rot[0], mSkelBase->getSkeleton()); + return ret; +} + } diff --git a/apps/openmw/mwrender/creatureanimation.hpp b/apps/openmw/mwrender/creatureanimation.hpp index 37826673d..d6cd8a517 100644 --- a/apps/openmw/mwrender/creatureanimation.hpp +++ b/apps/openmw/mwrender/creatureanimation.hpp @@ -2,6 +2,7 @@ #define GAME_RENDER_CREATUREANIMATION_H #include "animation.hpp" +#include "weaponanimation.hpp" #include "../mwworld/inventorystore.hpp" namespace MWWorld @@ -21,7 +22,7 @@ namespace MWRender // For creatures with weapons and shields // Animation is already virtual anyway, so might as well make a separate class. // Most creatures don't need weapons/shields, so this will save some memory. - class CreatureWeaponAnimation : public Animation, public MWWorld::InventoryStoreListener + class CreatureWeaponAnimation : public Animation, public WeaponAnimation, public MWWorld::InventoryStoreListener { public: CreatureWeaponAnimation(const MWWorld::Ptr& ptr); @@ -36,11 +37,29 @@ namespace MWRender void updatePart(NifOgre::ObjectScenePtr& scene, int slot); + virtual void attachArrow(); + virtual void releaseArrow(); + + virtual Ogre::Vector3 runAnimation(float duration); + + /// A relative factor (0-1) that decides if and how much the skeleton should be pitched + /// to indicate the facing orientation of the character. + virtual void setPitchFactor(float factor) { mPitchFactor = factor; } + + virtual void setWeaponGroup(const std::string& group) { mWeaponAnimationTime->setGroup(group); } + + // WeaponAnimation + virtual NifOgre::ObjectScenePtr getWeapon() { return mWeapon; } + virtual void showWeapon(bool show) { showWeapons(show); } + virtual void configureAddedObject(NifOgre::ObjectScenePtr object, MWWorld::Ptr ptr, int slot); + private: NifOgre::ObjectScenePtr mWeapon; NifOgre::ObjectScenePtr mShield; bool mShowWeapons; bool mShowCarriedLeft; + + Ogre::SharedPtr mWeaponAnimationTime; }; } diff --git a/apps/openmw/mwrender/debugging.cpp b/apps/openmw/mwrender/debugging.cpp index 241f7e470..ba39d10d5 100644 --- a/apps/openmw/mwrender/debugging.cpp +++ b/apps/openmw/mwrender/debugging.cpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include diff --git a/apps/openmw/mwrender/effectmanager.cpp b/apps/openmw/mwrender/effectmanager.cpp index 7d41525b7..1e6119daa 100644 --- a/apps/openmw/mwrender/effectmanager.cpp +++ b/apps/openmw/mwrender/effectmanager.cpp @@ -2,6 +2,8 @@ #include #include +#include +#include #include "animation.hpp" #include "renderconst.hpp" diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index 018dc082a..76ad1890f 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -75,10 +75,9 @@ namespace MWRender if (land) { - if (!land->isDataLoaded(ESM::Land::DATA_VHGT)) - { - land->loadData(ESM::Land::DATA_VHGT); - } + int mask = ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX; + if (!land->isDataLoaded(mask)) + land->loadData(mask); } for (int cellY=0; cellY #include #include +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index d07aad31d..a09a58191 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -4,6 +4,11 @@ #include #include #include +#include +#include +#include +#include +#include #include @@ -71,27 +76,6 @@ float HeadAnimationTime::getValue() const return 1; } -float WeaponAnimationTime::getValue() const -{ - if (mWeaponGroup.empty()) - return 0; - float current = mAnimation->getCurrentTime(mWeaponGroup); - if (current == -1) - return 0; - return current - mStartTime; -} - -void WeaponAnimationTime::setGroup(const std::string &group) -{ - mWeaponGroup = group; - mStartTime = mAnimation->getStartTime(mWeaponGroup); -} - -void WeaponAnimationTime::updateStartTime() -{ - setGroup(mWeaponGroup); -} - static NpcAnimation::PartBoneMap createPartListMap() { NpcAnimation::PartBoneMap result; @@ -142,8 +126,7 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int v mShowCarriedLeft(true), mFirstPersonOffset(0.f, 0.f, 0.f), mAlpha(1.f), - mNpcType(Type_Normal), - mPitchFactor(0) + mNpcType(Type_Normal) { mNpc = mPtr.get()->mBase; @@ -527,20 +510,16 @@ Ogre::Vector3 NpcAnimation::runAnimation(float timepassed) { float pitch = mPtr.getRefData().getPosition().rot[0]; Ogre::Node *node = baseinst->getBone("Bip01 Neck"); - node->pitch(Ogre::Radian(pitch), Ogre::Node::TS_WORLD); + node->pitch(Ogre::Radian(-pitch), Ogre::Node::TS_WORLD); // This has to be done before this function ends; // updateSkeletonInstance, below, touches the hands. node->translate(mFirstPersonOffset, Ogre::Node::TS_WORLD); } - else if (mPitchFactor > 0) + else { // In third person mode we may still need pitch for ranged weapon targeting - float pitch = mPtr.getRefData().getPosition().rot[0] * mPitchFactor; - Ogre::Node *node = baseinst->getBone("Bip01 Spine2"); - node->pitch(Ogre::Radian(pitch/2), Ogre::Node::TS_WORLD); - node = baseinst->getBone("Bip01 Spine1"); - node->pitch(Ogre::Radian(pitch/2), Ogre::Node::TS_WORLD); + pitchSkeleton(mPtr.getRefData().getPosition().rot[0], baseinst); } mFirstPersonOffset = 0.f; // reset the X, Y, Z offset for the next frame. @@ -690,13 +669,14 @@ void NpcAnimation::showWeapons(bool showWeapon) { MWWorld::InventoryStore &inv = MWWorld::Class::get(mPtr).getInventoryStore(mPtr); MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if(weapon != inv.end()) // special case for weapons + if(weapon != inv.end()) { Ogre::Vector3 glowColor = getEnchantmentColor(*weapon); std::string mesh = MWWorld::Class::get(*weapon).getModel(*weapon); addOrReplaceIndividualPart(ESM::PRT_Weapon, MWWorld::InventoryStore::Slot_CarriedRight, 1, mesh, !weapon->getClass().getEnchantment(*weapon).empty(), &glowColor); + // Crossbows start out with a bolt attached if (weapon->getTypeName() == typeid(ESM::Weapon).name() && weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow) { @@ -738,50 +718,24 @@ void NpcAnimation::showCarriedLeft(bool show) removeIndividualPart(ESM::PRT_Shield); } -void NpcAnimation::attachArrow() +void NpcAnimation::configureAddedObject(NifOgre::ObjectScenePtr object, MWWorld::Ptr ptr, int slot) { - MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); - MWWorld::ContainerStoreIterator weaponSlot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if (weaponSlot != inv.end() && weaponSlot->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) - showWeapons(true); - else - { - NifOgre::ObjectScenePtr weapon = mObjectParts[ESM::PRT_Weapon]; - - MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); - if (ammo == inv.end()) - return; - std::string model = ammo->getClass().getModel(*ammo); + Ogre::Vector3 glowColor = getEnchantmentColor(ptr); + setRenderProperties(object, (mViewMode == VM_FirstPerson) ? RV_FirstPerson : mVisibilityFlags, RQG_Main, RQG_Alpha, 0, + !ptr.getClass().getEnchantment(ptr).empty(), &glowColor); - mAmmunition = NifOgre::Loader::createObjects(weapon->mSkelBase, "ArrowBone", mInsert, model); - Ogre::Vector3 glowColor = getEnchantmentColor(*ammo); - setRenderProperties(mAmmunition, (mViewMode == VM_FirstPerson) ? RV_FirstPerson : mVisibilityFlags, RQG_Main, RQG_Alpha, 0, - !ammo->getClass().getEnchantment(*ammo).empty(), &glowColor); + std::for_each(object->mEntities.begin(), object->mEntities.end(), SetObjectGroup(slot)); + std::for_each(object->mParticles.begin(), object->mParticles.end(), SetObjectGroup(slot)); +} - std::for_each(mAmmunition->mEntities.begin(), mAmmunition->mEntities.end(), SetObjectGroup(MWWorld::InventoryStore::Slot_Ammunition)); - std::for_each(mAmmunition->mParticles.begin(), mAmmunition->mParticles.end(), SetObjectGroup(MWWorld::InventoryStore::Slot_Ammunition)); - } +void NpcAnimation::attachArrow() +{ + WeaponAnimation::attachArrow(mPtr); } void NpcAnimation::releaseArrow() { - // Thrown weapons get detached now - MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); - MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if (weapon != inv.end() && weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) - { - showWeapons(false); - inv.remove(*weapon, 1, mPtr); - } - else - { - // With bows and crossbows only the used arrow/bolt gets detached - MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); - if (ammo == inv.end()) - return; - inv.remove(*ammo, 1, mPtr); - mAmmunition.setNull(); - } + WeaponAnimation::releaseArrow(mPtr); } void NpcAnimation::permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew, bool playSound) diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 725fde01d..8ec46facd 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -5,6 +5,8 @@ #include "../mwworld/inventorystore.hpp" +#include "weaponanimation.hpp" + namespace ESM { struct NPC; @@ -25,24 +27,7 @@ public: { } }; -class WeaponAnimationTime : public Ogre::ControllerValue -{ -private: - Animation* mAnimation; - std::string mWeaponGroup; - float mStartTime; -public: - WeaponAnimationTime(Animation* animation) : mAnimation(animation), mStartTime(0) {} - void setGroup(const std::string& group); - void updateStartTime(); - - virtual Ogre::Real getValue() const; - virtual void setValue(Ogre::Real value) - { } -}; - - -class NpcAnimation : public Animation, public MWWorld::InventoryStoreListener +class NpcAnimation : public Animation, public WeaponAnimation, public MWWorld::InventoryStoreListener { public: virtual void equipmentChanged() { updateParts(); } @@ -91,7 +76,6 @@ private: Ogre::SharedPtr mWeaponAnimationTime; float mAlpha; - float mPitchFactor; void updateNpcBase(); @@ -138,7 +122,10 @@ public: virtual void attachArrow(); virtual void releaseArrow(); - NifOgre::ObjectScenePtr mAmmunition; + // WeaponAnimation + virtual NifOgre::ObjectScenePtr getWeapon() { return mObjectParts[ESM::PRT_Weapon]; } + virtual void showWeapon(bool show) { showWeapons(show); } + virtual void configureAddedObject(NifOgre::ObjectScenePtr object, MWWorld::Ptr ptr, int slot); void setViewMode(ViewMode viewMode); diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index c97e5279a..9c10ca84b 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -109,6 +109,7 @@ void Objects::insertModel(const MWWorld::Ptr &ptr, const std::string &mesh) { uniqueID = uniqueID+1; sg = mRenderer.getScene()->createStaticGeometry("sg" + Ogre::StringConverter::toString(uniqueID)); + sg->setOrigin(ptr.getRefData().getBaseNode()->getPosition()); mStaticGeometrySmall[ptr.getCell()] = sg; sg->setRenderingDistance(Settings::Manager::getInt("small object distance", "Viewing distance")); @@ -122,6 +123,7 @@ void Objects::insertModel(const MWWorld::Ptr &ptr, const std::string &mesh) { uniqueID = uniqueID+1; sg = mRenderer.getScene()->createStaticGeometry("sg" + Ogre::StringConverter::toString(uniqueID)); + sg->setOrigin(ptr.getRefData().getBaseNode()->getPosition()); mStaticGeometry[ptr.getCell()] = sg; } else diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index 246103471..67bc75e02 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include "renderconst.hpp" diff --git a/apps/openmw/mwrender/refraction.cpp b/apps/openmw/mwrender/refraction.cpp index d590dbf4c..f22968e9d 100644 --- a/apps/openmw/mwrender/refraction.cpp +++ b/apps/openmw/mwrender/refraction.cpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 844be52d8..15d56b8a9 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -5,12 +5,14 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include @@ -278,13 +280,12 @@ void RenderingManager::rotateObject(const MWWorld::Ptr &ptr) if(ptr.getRefData().getHandle() == mCamera->getHandle() && !mCamera->isVanityOrPreviewModeEnabled()) - mCamera->rotateCamera(rot, false); + mCamera->rotateCamera(-rot, false); - Ogre::Quaternion newo = Ogre::Quaternion(Ogre::Radian(-rot.z), Ogre::Vector3::UNIT_Z); + Ogre::Quaternion newo = Ogre::Quaternion(Ogre::Radian(rot.z), Ogre::Vector3::NEGATIVE_UNIT_Z); if(!MWWorld::Class::get(ptr).isActor()) - newo = Ogre::Quaternion(Ogre::Radian(-rot.x), Ogre::Vector3::UNIT_X) * - Ogre::Quaternion(Ogre::Radian(-rot.y), Ogre::Vector3::UNIT_Y) * newo; - + newo = Ogre::Quaternion(Ogre::Radian(rot.x), Ogre::Vector3::NEGATIVE_UNIT_X) * + Ogre::Quaternion(Ogre::Radian(rot.y), Ogre::Vector3::NEGATIVE_UNIT_Y) * newo; ptr.getRefData().getBaseNode()->setOrientation(newo); } @@ -649,6 +650,18 @@ void RenderingManager::setGlare(bool glare) mSkyManager->setGlare(glare); } +void RenderingManager::updateTerrain() +{ + if (mTerrain) + { + // Avoid updating with dims.getCenter for each cell. Player position should be good enough + mTerrain->update(mRendering.getCamera()->getRealPosition()); + mTerrain->syncLoad(); + // need to update again so the chunks that were just loaded can be made visible + mTerrain->update(mRendering.getCamera()->getRealPosition()); + } +} + void RenderingManager::requestMap(MWWorld::CellStore* cell) { if (cell->getCell()->isExterior()) @@ -659,9 +672,6 @@ void RenderingManager::requestMap(MWWorld::CellStore* cell) Ogre::Vector2 center (cell->getCell()->getGridX() + 0.5, cell->getCell()->getGridY() + 0.5); dims.merge(mTerrain->getWorldBoundingBox(center)); - if (dims.isFinite()) - mTerrain->update(dims.getCenter()); - mLocalMap->requestMap(cell, dims.getMinimum().z, dims.getMaximum().z); } else @@ -981,13 +991,11 @@ void RenderingManager::screenshot(Image &image, int w, int h) Ogre::PixelFormat pf = rt->suggestPixelFormat(); - std::vector data; - data.resize(w * h * Ogre::PixelUtil::getNumElemBytes(pf)); - - Ogre::PixelBox pb(w, h, 1, pf, &data[0]); - rt->copyContentsToMemory(pb); - - image.loadDynamicImage(&data[0], w, h, pf); + image.loadDynamicImage( + OGRE_ALLOC_T(Ogre::uchar, w * h * Ogre::PixelUtil::getNumElemBytes(pf), Ogre::MEMCATEGORY_GENERAL), + w, h, 1, pf, true // autoDelete=true, frees memory we allocate + ); + rt->copyContentsToMemory(image.getPixelBox()); // getPixelBox returns a box sharing the same memory as the image Ogre::TextureManager::getSingleton().remove(tempName); mRendering.getCamera()->setAspectRatio(oldAspect); @@ -1045,20 +1053,16 @@ void RenderingManager::enableTerrain(bool enable) { if (!mTerrain) { - Loading::Listener* listener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); - Loading::ScopedLoad load(listener); - mTerrain = new Terrain::World(listener, mRendering.getScene(), new MWRender::TerrainStorage(), RV_Terrain, + mTerrain = new Terrain::World(mRendering.getScene(), new MWRender::TerrainStorage(), RV_Terrain, Settings::Manager::getBool("distant land", "Terrain"), - Settings::Manager::getBool("shader", "Terrain")); + Settings::Manager::getBool("shader", "Terrain"), Terrain::Align_XY, 1, 64); mTerrain->applyMaterials(Settings::Manager::getBool("enabled", "Shadows"), Settings::Manager::getBool("split", "Shadows")); mTerrain->update(mRendering.getCamera()->getRealPosition()); - mTerrain->setLoadingListener(NULL); } mTerrain->setVisible(true); } - else - if (mTerrain) + else if (mTerrain) mTerrain->setVisible(false); } @@ -1069,7 +1073,7 @@ float RenderingManager::getCameraDistance() const void RenderingManager::spawnEffect(const std::string &model, const std::string &texture, const Vector3 &worldPosition, float scale) { - mEffectManager->addEffect(model, "", worldPosition, scale); + mEffectManager->addEffect(model, texture, worldPosition, scale); } } // namespace diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 64ec029ce..115a94786 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -180,6 +180,10 @@ public: void removeWaterRippleEmitter (const MWWorld::Ptr& ptr); void updateWaterRippleEmitterPtr (const MWWorld::Ptr& old, const MWWorld::Ptr& ptr); + void updateTerrain (); + ///< update the terrain according to the player position. Usually done automatically, but should be done manually + /// before calling requestMap + void requestMap (MWWorld::CellStore* cell); ///< request the local map for a cell diff --git a/apps/openmw/mwrender/ripplesimulation.cpp b/apps/openmw/mwrender/ripplesimulation.cpp index e5db8346f..f52deedcc 100644 --- a/apps/openmw/mwrender/ripplesimulation.cpp +++ b/apps/openmw/mwrender/ripplesimulation.cpp @@ -4,6 +4,10 @@ #include #include #include +#include +#include +#include +#include #include diff --git a/apps/openmw/mwrender/shadows.cpp b/apps/openmw/mwrender/shadows.cpp index 9ebb0ab08..33e337649 100644 --- a/apps/openmw/mwrender/shadows.cpp +++ b/apps/openmw/mwrender/shadows.cpp @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 39f7ccc85..90c08c299 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp index 197572db9..2558c95c5 100644 --- a/apps/openmw/mwrender/terrainstorage.cpp +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -13,12 +14,14 @@ #include "../mwbase/environment.hpp" #include "../mwworld/esmstore.hpp" +#include + namespace MWRender { - Ogre::AxisAlignedBox TerrainStorage::getBounds() + void TerrainStorage::getBounds(float& minX, float& maxX, float& minY, float& maxY) { - int minX = 0, minY = 0, maxX = 0, maxY = 0; + minX = 0, minY = 0, maxX = 0, maxY = 0; const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); @@ -39,8 +42,6 @@ namespace MWRender // since grid coords are at cell origin, we need to add 1 cell maxX += 1; maxY += 1; - - return Ogre::AxisAlignedBox(minX, minY, 0, maxX, maxY, 0); } ESM::Land* TerrainStorage::getLand(int cellX, int cellY) @@ -48,10 +49,6 @@ namespace MWRender const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); ESM::Land* land = esmStore.get().search(cellX, cellY); - // Load the data we are definitely going to need - int mask = ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX; - if (land && !land->isDataLoaded(mask)) - land->loadData(mask); return land; } @@ -169,10 +166,10 @@ namespace MWRender } - void TerrainStorage::fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, - Ogre::HardwareVertexBufferSharedPtr vertexBuffer, - Ogre::HardwareVertexBufferSharedPtr normalBuffer, - Ogre::HardwareVertexBufferSharedPtr colourBuffer) + void TerrainStorage::fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align, + std::vector& positions, + std::vector& normals, + std::vector& colours) { // LOD level n means every 2^n-th vertex is kept size_t increment = 1 << lodLevel; @@ -186,11 +183,8 @@ namespace MWRender size_t numVerts = size*(ESM::Land::LAND_SIZE-1)/increment + 1; - std::vector colors; - colors.resize(numVerts*numVerts*4); - std::vector positions; + colours.resize(numVerts*numVerts*4); positions.resize(numVerts*numVerts*3); - std::vector normals; normals.resize(numVerts*numVerts*3); Ogre::Vector3 normal; @@ -276,7 +270,7 @@ namespace MWRender color.a = 1; Ogre::uint32 rsColor; Ogre::Root::getSingleton().getRenderSystem()->convertColourValue(color, &rsColor); - memcpy(&colors[vertX*numVerts*4 + vertY*4], &rsColor, sizeof(Ogre::uint32)); + memcpy(&colours[vertX*numVerts*4 + vertY*4], &rsColor, sizeof(Ogre::uint32)); ++vertX; } @@ -289,10 +283,6 @@ namespace MWRender assert(vertX_ == numVerts); // Ensure we covered whole area } assert(vertY_ == numVerts); // Ensure we covered whole area - - vertexBuffer->writeData(0, vertexBuffer->getSizeInBytes(), &positions[0], true); - normalBuffer->writeData(0, normalBuffer->getSizeInBytes(), &normals[0], true); - colourBuffer->writeData(0, colourBuffer->getSizeInBytes(), &colors[0], true); } TerrainStorage::UniqueTextureId TerrainStorage::getVtexIndexAt(int cellX, int cellY, @@ -318,9 +308,6 @@ namespace MWRender ESM::Land* land = getLand(cellX, cellY); if (land) { - if (!land->isDataLoaded(ESM::Land::DATA_VTEX)) - land->loadData(ESM::Land::DATA_VTEX); - int tex = land->mLandData->mTextures[y * ESM::Land::LAND_TEXTURE_SIZE + x]; if (tex == 0) return std::make_pair(0,0); // vtex 0 is always the base texture, regardless of plugin @@ -345,8 +332,24 @@ namespace MWRender return texture; } + void TerrainStorage::getBlendmaps (const std::vector& nodes, std::vector& out, bool pack) + { + for (std::vector::const_iterator it = nodes.begin(); it != nodes.end(); ++it) + { + out.push_back(Terrain::LayerCollection()); + out.back().mTarget = *it; + getBlendmapsImpl((*it)->getSize(), (*it)->getCenter(), pack, out.back().mBlendmaps, out.back().mLayers); + } + } + void TerrainStorage::getBlendmaps(float chunkSize, const Ogre::Vector2 &chunkCenter, - bool pack, std::vector &blendmaps, std::vector &layerList) + bool pack, std::vector &blendmaps, std::vector &layerList) + { + getBlendmapsImpl(chunkSize, chunkCenter, pack, blendmaps, layerList); + } + + void TerrainStorage::getBlendmapsImpl(float chunkSize, const Ogre::Vector2 &chunkCenter, + bool pack, std::vector &blendmaps, std::vector &layerList) { // TODO - blending isn't completely right yet; the blending radius appears to be // different at a cell transition (2 vertices, not 4), so we may need to create a larger blendmap @@ -391,16 +394,14 @@ namespace MWRender // Second iteration - create and fill in the blend maps const int blendmapSize = ESM::Land::LAND_TEXTURE_SIZE+1; - std::vector data; - data.resize(blendmapSize * blendmapSize * channels, 0); for (int i=0; iloadRawData(stream, blendmapSize, blendmapSize, format); - blendmaps.push_back(map); + blendmaps.push_back(Ogre::PixelBox(blendmapSize, blendmapSize, 1, format, pData)); } } @@ -543,6 +540,12 @@ namespace MWRender info.mSpecular = true; } + // This wasn't cached, so the textures are probably not loaded either. + // Background load them so they are hopefully already loaded once we need them! + Ogre::ResourceBackgroundQueue::getSingleton().load("Texture", info.mDiffuseMap, "General"); + if (!info.mNormalMap.empty()) + Ogre::ResourceBackgroundQueue::getSingleton().load("Texture", info.mNormalMap, "General"); + mLayerInfoMap[texture] = info; return info; diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp index 5c2035952..b5e6012f3 100644 --- a/apps/openmw/mwrender/terrainstorage.hpp +++ b/apps/openmw/mwrender/terrainstorage.hpp @@ -1,6 +1,9 @@ #ifndef MWRENDER_TERRAINSTORAGE_H #define MWRENDER_TERRAINSTORAGE_H +#include +#include + #include namespace MWRender @@ -14,10 +17,10 @@ namespace MWRender public: /// Get bounds of the whole terrain in cell units - virtual Ogre::AxisAlignedBox getBounds(); + virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY); - /// Get the minimum and maximum heights of a terrain chunk. - /// @note Should only be called for chunks <= 1 cell, i.e. leafs of the quad tree. + /// Get the minimum and maximum heights of a terrain region. + /// @note Will only be called for chunks with size = minBatchSize, i.e. leafs of the quad tree. /// Larger chunks can simply merge AABB of children. /// @param size size of the chunk in cell units /// @param center center of the chunk in cell units @@ -27,20 +30,23 @@ namespace MWRender virtual bool getMinMaxHeights (float size, const Ogre::Vector2& center, float& min, float& max); /// Fill vertex buffers for a terrain chunk. + /// @note May be called from background threads. Make sure to only call thread-safe functions from here! + /// @note returned colors need to be in render-system specific format! Use RenderSystem::convertColourValue. /// @param lodLevel LOD level, 0 = most detailed /// @param size size of the terrain chunk in cell units /// @param center center of the chunk in cell units - /// @param vertexBuffer buffer to write vertices - /// @param normalBuffer buffer to write vertex normals - /// @param colourBuffer buffer to write vertex colours - virtual void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, - Ogre::HardwareVertexBufferSharedPtr vertexBuffer, - Ogre::HardwareVertexBufferSharedPtr normalBuffer, - Ogre::HardwareVertexBufferSharedPtr colourBuffer); + /// @param positions buffer to write vertices + /// @param normals buffer to write vertex normals + /// @param colours buffer to write vertex colours + virtual void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align, + std::vector& positions, + std::vector& normals, + std::vector& colours); /// Create textures holding layer blend values for a terrain chunk. /// @note The terrain chunk shouldn't be larger than one cell since otherwise we might /// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used. + /// @note May be called from *one* background thread. /// @param chunkSize size of the terrain chunk in cell units /// @param chunkCenter center of the chunk in cell units /// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) - @@ -49,9 +55,21 @@ namespace MWRender /// @param blendmaps created blendmaps will be written here /// @param layerList names of the layer textures used will be written here virtual void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack, - std::vector& blendmaps, + std::vector& blendmaps, std::vector& layerList); + /// Retrieve pixel data for textures holding layer blend values for terrain chunks and layer texture information. + /// This variant is provided to eliminate the overhead of virtual function calls when retrieving a large number of blendmaps at once. + /// @note The terrain chunks shouldn't be larger than one cell since otherwise we might + /// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used. + /// @note May be called from *one* background thread. + /// @param nodes A collection of nodes for which to retrieve the aforementioned data + /// @param out Output vector + /// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) - + /// otherwise, each texture contains blend values for one layer only. Shader-based rendering + /// can utilize packing, FFP can't. + virtual void getBlendmaps (const std::vector& nodes, std::vector& out, bool pack); + virtual float getHeightAt (const Ogre::Vector3& worldPos); virtual Terrain::LayerInfo getDefaultLayer(); @@ -81,6 +99,11 @@ namespace MWRender std::map mLayerInfoMap; Terrain::LayerInfo getLayerInfo(const std::string& texture); + + // Non-virtual + void getBlendmapsImpl (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack, + std::vector& blendmaps, + std::vector& layerList); }; } diff --git a/apps/openmw/mwrender/videoplayer.cpp b/apps/openmw/mwrender/videoplayer.cpp index adf20dc63..f3c0971e7 100644 --- a/apps/openmw/mwrender/videoplayer.cpp +++ b/apps/openmw/mwrender/videoplayer.cpp @@ -9,6 +9,11 @@ #include #include #include +#include +#include +#include +#include +#include #include diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 9e3105168..1fa5d8834 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -1,11 +1,16 @@ #include "water.hpp" -#include +#include #include #include +#include #include #include #include +#include +#include +#include +#include #include "sky.hpp" #include "renderingmanager.hpp" diff --git a/apps/openmw/mwrender/weaponanimation.cpp b/apps/openmw/mwrender/weaponanimation.cpp new file mode 100644 index 000000000..5f953bd83 --- /dev/null +++ b/apps/openmw/mwrender/weaponanimation.cpp @@ -0,0 +1,159 @@ +#include "weaponanimation.hpp" + +#include +#include +#include +#include + +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" + +#include "../mwworld/inventorystore.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" + +#include "../mwmechanics/creaturestats.hpp" + +#include "animation.hpp" + +namespace MWRender +{ + +float WeaponAnimationTime::getValue() const +{ + if (mWeaponGroup.empty()) + return 0; + float current = mAnimation->getCurrentTime(mWeaponGroup); + if (current == -1) + return 0; + return current - mStartTime; +} + +void WeaponAnimationTime::setGroup(const std::string &group) +{ + mWeaponGroup = group; + mStartTime = mAnimation->getStartTime(mWeaponGroup); +} + +void WeaponAnimationTime::updateStartTime() +{ + setGroup(mWeaponGroup); +} + +void WeaponAnimation::attachArrow(MWWorld::Ptr actor) +{ + MWWorld::InventoryStore& inv = actor.getClass().getInventoryStore(actor); + MWWorld::ContainerStoreIterator weaponSlot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (weaponSlot != inv.end() && weaponSlot->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) + showWeapon(true); + else + { + NifOgre::ObjectScenePtr weapon = getWeapon(); + + MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + if (ammo == inv.end()) + return; + std::string model = ammo->getClass().getModel(*ammo); + + mAmmunition = NifOgre::Loader::createObjects(weapon->mSkelBase, "ArrowBone", weapon->mSkelBase->getParentSceneNode(), model); + configureAddedObject(mAmmunition, *ammo, MWWorld::InventoryStore::Slot_Ammunition); + } +} + +void WeaponAnimation::releaseArrow(MWWorld::Ptr actor) +{ + MWWorld::InventoryStore& inv = actor.getClass().getInventoryStore(actor); + MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (weapon == inv.end()) + return; + + // The orientation of the launched projectile. Always the same as the actor orientation, even if the ArrowBone's orientation dictates otherwise. + Ogre::Quaternion orient = Ogre::Quaternion(Ogre::Radian(actor.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * + Ogre::Quaternion(Ogre::Radian(actor.getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X); + + const MWWorld::Store &gmst = + MWBase::Environment::get().getWorld()->getStore().get(); + + // Reduce fatigue + // somewhat of a guess, but using the weapon weight makes sense + const float fFatigueAttackBase = gmst.find("fFatigueAttackBase")->getFloat(); + const float fFatigueAttackMult = gmst.find("fFatigueAttackMult")->getFloat(); + const float fWeaponFatigueMult = gmst.find("fWeaponFatigueMult")->getFloat(); + MWMechanics::CreatureStats& attackerStats = actor.getClass().getCreatureStats(actor); + MWMechanics::DynamicStat fatigue = attackerStats.getFatigue(); + const float normalizedEncumbrance = actor.getClass().getEncumbrance(actor) / actor.getClass().getCapacity(actor); + float fatigueLoss = fFatigueAttackBase + normalizedEncumbrance * fFatigueAttackMult; + if (!weapon->isEmpty()) + fatigueLoss += weapon->getClass().getWeight(*weapon) * attackerStats.getAttackStrength() * fWeaponFatigueMult; + fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss); + attackerStats.setFatigue(fatigue); + + if (weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) + { + // Thrown weapons get detached now + NifOgre::ObjectScenePtr objects = getWeapon(); + + Ogre::Vector3 launchPos(0,0,0); + if (objects->mSkelBase) + { + launchPos = objects->mSkelBase->getParentNode()->_getDerivedPosition(); + } + else if (objects->mEntities.size()) + { + objects->mEntities[0]->getParentNode()->needUpdate(true); + launchPos = objects->mEntities[0]->getParentNode()->_getDerivedPosition(); + } + + float fThrownWeaponMinSpeed = gmst.find("fThrownWeaponMinSpeed")->getFloat(); + float fThrownWeaponMaxSpeed = gmst.find("fThrownWeaponMaxSpeed")->getFloat(); + float speed = fThrownWeaponMinSpeed + (fThrownWeaponMaxSpeed - fThrownWeaponMinSpeed) * + actor.getClass().getCreatureStats(actor).getAttackStrength(); + + MWBase::Environment::get().getWorld()->launchProjectile(actor, *weapon, launchPos, orient, *weapon, speed); + + showWeapon(false); + + inv.remove(*weapon, 1, actor); + } + else + { + // With bows and crossbows only the used arrow/bolt gets detached + MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + if (ammo == inv.end()) + return; + + Ogre::Vector3 launchPos(0,0,0); + if (mAmmunition->mSkelBase) + { + launchPos = mAmmunition->mSkelBase->getParentNode()->_getDerivedPosition(); + } + else if (mAmmunition->mEntities.size()) + { + mAmmunition->mEntities[0]->getParentNode()->needUpdate(true); + launchPos = mAmmunition->mEntities[0]->getParentNode()->_getDerivedPosition(); + } + + float fProjectileMinSpeed = gmst.find("fProjectileMinSpeed")->getFloat(); + float fProjectileMaxSpeed = gmst.find("fProjectileMaxSpeed")->getFloat(); + float speed = fProjectileMinSpeed + (fProjectileMaxSpeed - fProjectileMinSpeed) * actor.getClass().getCreatureStats(actor).getAttackStrength(); + + MWBase::Environment::get().getWorld()->launchProjectile(actor, *ammo, launchPos, orient, *weapon, speed); + + inv.remove(*ammo, 1, actor); + mAmmunition.setNull(); + } +} + +void WeaponAnimation::pitchSkeleton(float xrot, Ogre::SkeletonInstance* skel) +{ + if (mPitchFactor == 0) + return; + + float pitch = xrot * mPitchFactor; + Ogre::Node *node = skel->getBone("Bip01 Spine2"); + node->pitch(Ogre::Radian(-pitch/2), Ogre::Node::TS_WORLD); + node = skel->getBone("Bip01 Spine1"); + node->pitch(Ogre::Radian(-pitch/2), Ogre::Node::TS_WORLD); +} + +} diff --git a/apps/openmw/mwrender/weaponanimation.hpp b/apps/openmw/mwrender/weaponanimation.hpp new file mode 100644 index 000000000..c09aa65d9 --- /dev/null +++ b/apps/openmw/mwrender/weaponanimation.hpp @@ -0,0 +1,56 @@ +#ifndef OPENMW_MWRENDER_WEAPONANIMATION_H +#define OPENMW_MWRENDER_WEAPONANIMATION_H + +#include + +#include + +#include "../mwworld/ptr.hpp" + +namespace MWRender +{ + + class Animation; + + class WeaponAnimationTime : public Ogre::ControllerValue + { + private: + Animation* mAnimation; + std::string mWeaponGroup; + float mStartTime; + public: + WeaponAnimationTime(Animation* animation) : mAnimation(animation), mStartTime(0) {} + void setGroup(const std::string& group); + void updateStartTime(); + + virtual Ogre::Real getValue() const; + virtual void setValue(Ogre::Real value) + { } + }; + + /// Handles attach & release of projectiles for ranged weapons + class WeaponAnimation + { + public: + WeaponAnimation() : mPitchFactor(0) {} + + virtual void attachArrow(MWWorld::Ptr actor); + virtual void releaseArrow(MWWorld::Ptr actor); + + protected: + NifOgre::ObjectScenePtr mAmmunition; + + virtual NifOgre::ObjectScenePtr getWeapon() = 0; + virtual void showWeapon(bool show) = 0; + virtual void configureAddedObject(NifOgre::ObjectScenePtr object, MWWorld::Ptr ptr, int slot) = 0; + + /// A relative factor (0-1) that decides if and how much the skeleton should be pitched + /// to indicate the facing orientation of the character, for ranged weapon aiming. + float mPitchFactor; + + void pitchSkeleton(float xrot, Ogre::SkeletonInstance* skel); + }; + +} + +#endif diff --git a/apps/openmw/mwscript/globalscripts.hpp b/apps/openmw/mwscript/globalscripts.hpp index a4a766226..de63b9906 100644 --- a/apps/openmw/mwscript/globalscripts.hpp +++ b/apps/openmw/mwscript/globalscripts.hpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include "locals.hpp" diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 43a0fedce..1efc79643 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -82,30 +82,24 @@ namespace MWScript Interpreter::Type_Float angle = runtime[0].mFloat; runtime.pop(); - float ax = Ogre::Radian(ptr.getRefData().getLocalRotation().rot[0]).valueDegrees(); - float ay = Ogre::Radian(ptr.getRefData().getLocalRotation().rot[1]).valueDegrees(); - float az = Ogre::Radian(ptr.getRefData().getLocalRotation().rot[2]).valueDegrees(); - - float *objRot = ptr.getRefData().getPosition().rot; - - float lx = Ogre::Radian(objRot[0]).valueDegrees(); - float ly = Ogre::Radian(objRot[1]).valueDegrees(); - float lz = Ogre::Radian(objRot[2]).valueDegrees(); + float ax = Ogre::Radian(ptr.getRefData().getPosition().rot[0]).valueDegrees(); + float ay = Ogre::Radian(ptr.getRefData().getPosition().rot[1]).valueDegrees(); + float az = Ogre::Radian(ptr.getRefData().getPosition().rot[2]).valueDegrees(); if (axis == "x") { - MWBase::Environment::get().getWorld()->localRotateObject(ptr,angle-lx,ay,az); + MWBase::Environment::get().getWorld()->rotateObject(ptr,angle,ay,az); } else if (axis == "y") { - MWBase::Environment::get().getWorld()->localRotateObject(ptr,ax,angle-ly,az); + MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,angle,az); } else if (axis == "z") { - MWBase::Environment::get().getWorld()->localRotateObject(ptr,ax,ay,angle-lz); + MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,angle); } else - throw std::runtime_error ("invalid ration axis: " + axis); + throw std::runtime_error ("invalid rotation axis: " + axis); } }; @@ -134,7 +128,7 @@ namespace MWScript runtime.push(Ogre::Radian(ptr.getCellRef().mPos.rot[2]).valueDegrees()); } else - throw std::runtime_error ("invalid ration axis: " + axis); + throw std::runtime_error ("invalid rotation axis: " + axis); } }; @@ -152,18 +146,18 @@ namespace MWScript if (axis=="x") { - runtime.push(Ogre::Radian(ptr.getCellRef().mPos.rot[0]).valueDegrees()+Ogre::Radian(ptr.getRefData().getLocalRotation().rot[0]).valueDegrees()); + runtime.push(Ogre::Radian(ptr.getRefData().getPosition().rot[0]).valueDegrees()); } else if (axis=="y") { - runtime.push(Ogre::Radian(ptr.getCellRef().mPos.rot[1]).valueDegrees()+Ogre::Radian(ptr.getRefData().getLocalRotation().rot[1]).valueDegrees()); + runtime.push(Ogre::Radian(ptr.getRefData().getPosition().rot[1]).valueDegrees()); } else if (axis=="z") { - runtime.push(Ogre::Radian(ptr.getCellRef().mPos.rot[2]).valueDegrees()+Ogre::Radian(ptr.getRefData().getLocalRotation().rot[2]).valueDegrees()); + runtime.push(Ogre::Radian(ptr.getRefData().getPosition().rot[2]).valueDegrees()); } else - throw std::runtime_error ("invalid ration axis: " + axis); + throw std::runtime_error ("invalid rotation axis: " + axis); } }; @@ -192,7 +186,7 @@ namespace MWScript runtime.push(ptr.getRefData().getPosition().pos[2]); } else - throw std::runtime_error ("invalid rotation axis: " + axis); + throw std::runtime_error ("invalid axis: " + axis); } }; @@ -313,7 +307,7 @@ namespace MWScript } if(store) { - MWBase::Environment::get().getWorld()->moveObject(ptr,*store,x,y,z); + MWBase::Environment::get().getWorld()->moveObject(ptr,store,x,y,z); float ax = Ogre::Radian(ptr.getRefData().getPosition().rot[0]).valueDegrees(); float ay = Ogre::Radian(ptr.getRefData().getPosition().rot[1]).valueDegrees(); if(ptr.getTypeName() == typeid(ESM::NPC).name())//some morrowind oddity @@ -361,7 +355,7 @@ namespace MWScript int cx,cy; MWBase::Environment::get().getWorld()->positionToIndex(x,y,cx,cy); MWBase::Environment::get().getWorld()->moveObject(ptr, - *MWBase::Environment::get().getWorld()->getExterior(cx,cy),x,y,z); + MWBase::Environment::get().getWorld()->getExterior(cx,cy),x,y,z); float ax = Ogre::Radian(ptr.getRefData().getPosition().rot[0]).valueDegrees(); float ay = Ogre::Radian(ptr.getRefData().getPosition().rot[1]).valueDegrees(); if(ptr.getTypeName() == typeid(ESM::NPC).name())//some morrowind oddity @@ -421,7 +415,7 @@ namespace MWScript pos.rot[2] = zRot; MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(),itemID); ref.getPtr().getCellRef().mPos = pos; - MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),*store,pos); + MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,pos); } else { @@ -462,7 +456,7 @@ namespace MWScript pos.rot[2] = zRot; MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(),itemID); ref.getPtr().getCellRef().mPos = pos; - MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),*store,pos); + MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,pos); } else { @@ -530,7 +524,7 @@ namespace MWScript MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), itemID, count); ref.getPtr().getCellRef().mPos = ipos; - MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),*store,ipos); + MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,ipos); } }; diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 58566225b..9a3dd7342 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -3,6 +3,8 @@ #include #include +#include + #include #include "openal_output.hpp" @@ -172,6 +174,7 @@ class OpenAL_SoundStream : public Sound DecoderPtr mDecoder; volatile bool mIsFinished; + volatile bool mIsInitialBatchEnqueued; void updateAll(bool local); @@ -264,7 +267,7 @@ private: OpenAL_SoundStream::OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder, float basevol, float pitch, int flags) : Sound(Ogre::Vector3(0.0f), 1.0f, basevol, pitch, 1.0f, 1000.0f, flags) - , mOutput(output), mSource(src), mSamplesQueued(0), mDecoder(decoder), mIsFinished(true) + , mOutput(output), mSource(src), mSamplesQueued(0), mDecoder(decoder), mIsFinished(true), mIsInitialBatchEnqueued(false) { throwALerror(); @@ -315,26 +318,8 @@ void OpenAL_SoundStream::play() alSourcei(mSource, AL_BUFFER, 0); throwALerror(); mSamplesQueued = 0; - - std::vector data(mBufferSize); - - bool finished = false; - for(ALuint i = 0;i < sNumBuffers && !finished;i++) - { - size_t got = mDecoder->read(&data[0], data.size()); - finished = (got < data.size()); - if(got > 0) - { - ALuint bufid = mBuffers[i]; - alBufferData(bufid, mFormat, &data[0], got, mSampleRate); - alSourceQueueBuffers(mSource, 1, &bufid); - throwALerror(); - mSamplesQueued += getBufferSampleCount(bufid); - } - } - - mIsFinished = finished; - alSourcePlay(mSource); + mIsFinished = false; + mIsInitialBatchEnqueued = false; mOutput.mStreamThread->add(this); } @@ -342,6 +327,7 @@ void OpenAL_SoundStream::stop() { mOutput.mStreamThread->remove(this); mIsFinished = true; + mIsInitialBatchEnqueued = false; alSourceStop(mSource); alSourcei(mSource, AL_BUFFER, 0); @@ -454,6 +440,24 @@ bool OpenAL_SoundStream::process() } while(processed > 0); throwALerror(); } + else if (!mIsInitialBatchEnqueued) { // nothing enqueued yet + std::vector data(mBufferSize); + + for(ALuint i = 0;i < sNumBuffers && !finished;i++) + { + size_t got = mDecoder->read(&data[0], data.size()); + finished = (got < data.size()); + if(got > 0) + { + ALuint bufid = mBuffers[i]; + alBufferData(bufid, mFormat, &data[0], got, mSampleRate); + alSourceQueueBuffers(mSource, 1, &bufid); + throwALerror(); + mSamplesQueued += getBufferSampleCount(bufid); + } + } + mIsInitialBatchEnqueued = true; + } if(state != AL_PLAYING && state != AL_PAUSED) { @@ -471,6 +475,7 @@ bool OpenAL_SoundStream::process() std::cout<< "Error updating stream \""<getName()<<"\"" <getGlobalScripts().clear(); MWBase::Environment::get().getWorld()->clear(); MWBase::Environment::get().getWindowManager()->clear(); + MWBase::Environment::get().getInputManager()->clear(); mState = State_NoGame; mCharacterManager.clearCurrentCharacter(); @@ -123,11 +125,10 @@ void MWState::StateManager::newGame (bool bypass) { cleanup(); + MWBase::Environment::get().getWorld()->startNewGame (bypass); + if (!bypass) - { - MWBase::Environment::get().getWorld()->startNewGame(); MWBase::Environment::get().getWindowManager()->setNewGame (true); - } else MWBase::Environment::get().getWorld()->setGlobalInt ("chargenstate", -1); @@ -148,7 +149,7 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot MWBase::World& world = *MWBase::Environment::get().getWorld(); - MWWorld::Ptr player = world.getPlayer().getPlayer(); + MWWorld::Ptr player = world.getPlayerPtr(); profile.mContentFiles = world.getContentFiles(); @@ -177,7 +178,7 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot else slot = mCharacterManager.getCurrentCharacter()->updateSlot (slot, profile); - std::ofstream stream (slot->mPath.string().c_str()); + std::ofstream stream (slot->mPath.string().c_str(), std::ios::binary); ESM::ESMWriter writer; @@ -300,7 +301,7 @@ void MWState::StateManager::loadGame (const Character *character, const Slot *sl MWBase::Environment::get().getWindowManager()->updatePlayer(); MWBase::Environment::get().getMechanicsManager()->playerLoaded(); - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayerPtr(); ESM::CellId cellId = ptr.getCell()->getCell()->getCellId(); diff --git a/apps/openmw/mwworld/actionteleport.cpp b/apps/openmw/mwworld/actionteleport.cpp index b4c572ba9..150f0bed2 100644 --- a/apps/openmw/mwworld/actionteleport.cpp +++ b/apps/openmw/mwworld/actionteleport.cpp @@ -41,11 +41,11 @@ namespace MWWorld int cellX; int cellY; world->positionToIndex(mPosition.pos[0],mPosition.pos[1],cellX,cellY); - world->moveObject(actor,*world->getExterior(cellX,cellY), + world->moveObject(actor,world->getExterior(cellX,cellY), mPosition.pos[0],mPosition.pos[1],mPosition.pos[2]); } else - world->moveObject(actor,*world->getInterior(mCellName),mPosition.pos[0],mPosition.pos[1],mPosition.pos[2]); + world->moveObject(actor,world->getInterior(mCellName),mPosition.pos[0],mPosition.pos[1],mPosition.pos[2]); } } } diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 2110086d3..39d48f95b 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -363,7 +363,22 @@ namespace MWWorld return newPtr; } - bool Class::isFlying(const Ptr &ptr) const + bool Class::isBipedal(const Ptr &ptr) const + { + return false; + } + + bool Class::canFly(const Ptr &ptr) const + { + return false; + } + + bool Class::canSwim(const Ptr &ptr) const + { + return false; + } + + bool Class::canWalk(const Ptr &ptr) const { return false; } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index ad2cc3af4..739fd5942 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -307,7 +307,10 @@ namespace MWWorld return false; } - virtual bool isFlying(const MWWorld::Ptr& ptr) const; + virtual bool isBipedal(const MWWorld::Ptr& ptr) const; + virtual bool canFly(const MWWorld::Ptr& ptr) const; + virtual bool canSwim(const MWWorld::Ptr& ptr) const; + virtual bool canWalk(const MWWorld::Ptr& ptr) const; virtual int getSkill(const MWWorld::Ptr& ptr, int skill) const; diff --git a/apps/openmw/mwworld/globals.hpp b/apps/openmw/mwworld/globals.hpp index 8f521c8a6..d8d2cefbf 100644 --- a/apps/openmw/mwworld/globals.hpp +++ b/apps/openmw/mwworld/globals.hpp @@ -5,7 +5,7 @@ #include #include -#include +#include #include #include diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index 1a16870da..3c0c3ffaa 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -119,11 +120,9 @@ namespace MWWorld OEngine::Physic::PhysicActor *physicActor = engine->getCharacter(ptr.getRefData().getHandle()); if(!physicActor || !physicActor->getCollisionMode()) { - // FIXME: This works, but it's inconcsistent with how the rotations are applied elsewhere. Why? - return position + (Ogre::Quaternion(Ogre::Radian(-refpos.rot[2]), Ogre::Vector3::UNIT_Z)* - Ogre::Quaternion(Ogre::Radian(-refpos.rot[1]), Ogre::Vector3::UNIT_Y)* - Ogre::Quaternion(Ogre::Radian( refpos.rot[0]), Ogre::Vector3::UNIT_X)) * - movement * time; + return position + (Ogre::Quaternion(Ogre::Radian(refpos.rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * + Ogre::Quaternion(Ogre::Radian(refpos.rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X)) + * movement * time; } btCollisionObject *colobj = physicActor->getCollisionBody(); @@ -131,33 +130,52 @@ namespace MWWorld position.z += halfExtents.z; waterlevel -= halfExtents.z * 0.5; + /* + * A 3/4 submerged example + * + * +---+ + * | | + * | | <- (original waterlevel) + * | | + * | | <- position <- waterlevel + * | | + * | | + * | | + * +---+ <- (original position) + */ OEngine::Physic::ActorTracer tracer; bool wasOnGround = false; bool isOnGround = false; Ogre::Vector3 inertia(0.0f); Ogre::Vector3 velocity; - if(position.z < waterlevel || isFlying) + + bool canWalk = ptr.getClass().canWalk(ptr); + bool isBipedal = ptr.getClass().isBipedal(ptr); + bool isNpc = ptr.getClass().isNpc(); + + if(position.z < waterlevel || isFlying) // under water by 3/4 or can fly { - velocity = (Ogre::Quaternion(Ogre::Radian(-refpos.rot[2]), Ogre::Vector3::UNIT_Z)* - Ogre::Quaternion(Ogre::Radian(-refpos.rot[1]), Ogre::Vector3::UNIT_Y)* - Ogre::Quaternion(Ogre::Radian( refpos.rot[0]), Ogre::Vector3::UNIT_X)) * - movement; + // TODO: Shouldn't water have higher drag in calculating velocity? + velocity = (Ogre::Quaternion(Ogre::Radian(refpos.rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z)* + Ogre::Quaternion(Ogre::Radian(refpos.rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X)) * movement; } else { - velocity = Ogre::Quaternion(Ogre::Radian(-refpos.rot[2]), Ogre::Vector3::UNIT_Z) * movement; + velocity = Ogre::Quaternion(Ogre::Radian(refpos.rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * movement; + // not in water nor can fly, so need to deal with gravity if(!physicActor->getOnGround()) { // If falling, add part of the incoming velocity with the current inertia - velocity = velocity*time + physicActor->getInertialForce(); + velocity = velocity * time + physicActor->getInertialForce(); } - inertia = velocity; + inertia = velocity; // REM velocity is for z axis only in this code block if(!(movement.z > 0.0f)) { wasOnGround = physicActor->getOnGround(); - tracer.doTrace(colobj, position, position-Ogre::Vector3(0,0,2), engine); + // TODO: Find out if there is a significance with the value 2 used here + tracer.doTrace(colobj, position, position - Ogre::Vector3(0,0,2), engine); if(tracer.mFraction < 1.0f && getSlope(tracer.mPlaneNormal) <= sMaxSlope) isOnGround = true; } @@ -166,24 +184,38 @@ namespace MWWorld if(isOnGround) { // if we're on the ground, don't try to fall - velocity.z = std::max(0.0f, velocity.z); + velocity.z = std::max(0.0f, velocity.z); // NOTE: two different velocity assignments above } Ogre::Vector3 newPosition = position; + /* + * A loop to find newPosition using tracer, if successful different from the starting position. + * nextpos is the local variable used to find potential newPosition, using velocity and remainingTime + * The initial velocity was set earlier (see above). + */ float remainingTime = time; - for(int iterations = 0;iterations < sMaxIterations && remainingTime > 0.01f;++iterations) + for(int iterations = 0; iterations < sMaxIterations && remainingTime > 0.01f; ++iterations) { - Ogre::Vector3 nextpos = newPosition + velocity*remainingTime; - - if(newPosition.z < waterlevel && !isFlying && - nextpos.z > waterlevel && newPosition.z <= waterlevel) + Ogre::Vector3 nextpos = newPosition + velocity * remainingTime; + + // If not able to fly, walk or bipedal don't allow to move out of water + // TODO: this if condition may not work for large creatures or situations + // where the creature gets above the waterline for some reason + if(newPosition.z < waterlevel && // started 3/4 under water + !isFlying && // can't fly + !canWalk && // can't walk + !isBipedal && // not bipedal (assume bipedals can walk) + !isNpc && // FIXME: shouldn't really need this + nextpos.z > waterlevel && // but about to go above water + newPosition.z <= waterlevel) { const Ogre::Vector3 down(0,0,-1); Ogre::Real movelen = velocity.normalise(); Ogre::Vector3 reflectdir = velocity.reflect(down); reflectdir.normalise(); velocity = slide(reflectdir, down)*movelen; - continue; + // NOTE: remainingTime is unchanged before the loop continues + continue; // velocity updated, calculate nextpos again } // trace to where character would go if there were no obstructions @@ -192,13 +224,14 @@ namespace MWWorld // check for obstructions if(tracer.mFraction >= 1.0f) { - newPosition = tracer.mEndPos; - remainingTime *= (1.0f-tracer.mFraction); + newPosition = tracer.mEndPos; // ok to move, so set newPosition + remainingTime *= (1.0f-tracer.mFraction); // FIXME: remainingTime is no longer used so don't set it? break; } // We hit something. Try to step up onto it. - if(stepMove(colobj, newPosition, velocity, remainingTime, engine)) + // NOTE: May need to stop slaughterfish step out of the water. + if((canWalk || isBipedal || isNpc) && stepMove(colobj, newPosition, velocity, remainingTime, engine)) isOnGround = !(newPosition.z < waterlevel || isFlying); // Only on the ground if there's gravity else { @@ -217,7 +250,7 @@ namespace MWWorld if(isOnGround || wasOnGround) { - tracer.doTrace(colobj, newPosition, newPosition-Ogre::Vector3(0,0,sStepSize+2.0f), engine); + tracer.doTrace(colobj, newPosition, newPosition - Ogre::Vector3(0,0,sStepSize+2.0f), engine); if(tracer.mFraction < 1.0f && getSlope(tracer.mPlaneNormal) <= sMaxSlope) { newPosition.z = tracer.mEndPos.z + 1.0f; @@ -239,7 +272,7 @@ namespace MWWorld } physicActor->setOnGround(isOnGround); - newPosition.z -= halfExtents.z; + newPosition.z -= halfExtents.z; // remove what was added at the beggining return newPosition; } }; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 10afbc675..3d4413a35 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -43,7 +43,7 @@ namespace mPhysics (physics), mRendering (rendering) {} - bool InsertFunctor::InsertFunctor::operator() (const MWWorld::Ptr& ptr) + bool InsertFunctor::operator() (const MWWorld::Ptr& ptr) { if (mRescale) { @@ -79,55 +79,6 @@ namespace return true; } - - template - void insertCellRefList(MWRender::RenderingManager& rendering, - T& cellRefList, MWWorld::CellStore &cell, MWWorld::PhysicsSystem& physics, bool rescale, Loading::Listener* loadingListener) - { - if (!cellRefList.mList.empty()) - { - const MWWorld::Class& class_ = - MWWorld::Class::get (MWWorld::Ptr (&*cellRefList.mList.begin(), &cell)); - for (typename T::List::iterator it = cellRefList.mList.begin(); - it != cellRefList.mList.end(); it++) - { - if (rescale) - { - if (it->mRef.mScale<0.5) - it->mRef.mScale = 0.5; - else if (it->mRef.mScale>2) - it->mRef.mScale = 2; - } - - if (it->mData.getCount() && it->mData.isEnabled()) - { - MWWorld::Ptr ptr (&*it, &cell); - - try - { - rendering.addObject(ptr); - class_.insertObject(ptr, physics); - - float ax = Ogre::Radian(ptr.getRefData().getLocalRotation().rot[0]).valueDegrees(); - float ay = Ogre::Radian(ptr.getRefData().getLocalRotation().rot[1]).valueDegrees(); - float az = Ogre::Radian(ptr.getRefData().getLocalRotation().rot[2]).valueDegrees(); - MWBase::Environment::get().getWorld()->localRotateObject(ptr, ax, ay, az); - - MWBase::Environment::get().getWorld()->scaleObject(ptr, ptr.getCellRef().mScale); - class_.adjustPosition(ptr); - } - catch (const std::exception& e) - { - std::string error ("error during rendering: "); - std::cerr << error + e.what() << std::endl; - } - } - - loadingListener->increaseProgress(1); - } - } - } - } @@ -211,8 +162,6 @@ namespace MWWorld mRendering.cellAdded (cell); mRendering.configureAmbient(*cell); - mRendering.requestMap(cell); - mRendering.configureAmbient(*cell); } // register local scripts @@ -246,6 +195,11 @@ namespace MWWorld mechMgr->updateCell(old, player); mechMgr->watchActor(player); + mRendering.updateTerrain(); + + for (CellStoreCollection::iterator active = mActiveCells.begin(); active!=mActiveCells.end(); ++active) + mRendering.requestMap(*active); + MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell); } @@ -260,12 +214,13 @@ namespace MWWorld void Scene::changeCell (int X, int Y, const ESM::Position& position, bool adjustPlayerPos) { - mRendering.enableTerrain(true); Nif::NIFFile::CacheLock cachelock; Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::ScopedLoad load(loadingListener); + mRendering.enableTerrain(true); + std::string loadingExteriorText = "#{sLoadingMessage3}"; loadingListener->setLabel(loadingExteriorText); @@ -410,11 +365,11 @@ namespace MWWorld Nif::NIFFile::CacheLock lock; MWBase::Environment::get().getWorld ()->getFader ()->fadeOut(0.5); - mRendering.enableTerrain(false); - Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::ScopedLoad load(loadingListener); + mRendering.enableTerrain(false); + std::string loadingInteriorText = "#{sLoadingMessage2}"; loadingListener->setLabel(loadingInteriorText); diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 7bd00d6bf..6b99c0a0c 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -388,6 +388,8 @@ namespace MWWorld typedef std::vector::const_iterator iterator; + // Must be threadsafe! Called from terrain background loading threads. + // Not a big deal here, since ESM::LandTexture can never be modified or inserted/erased const ESM::LandTexture *search(size_t index, size_t plugin) const { assert(plugin < mStatic.size()); const LandTextureList <exl = mStatic[plugin]; @@ -487,6 +489,8 @@ namespace MWWorld return iterator(mStatic.end()); } + // Must be threadsafe! Called from terrain background loading threads. + // Not a big deal here, since ESM::Land can never be modified or inserted/erased ESM::Land *search(int x, int y) const { ESM::Land land; land.mX = x, land.mY = y; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index b51e224f8..62bdd38ea 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -28,7 +28,7 @@ #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/spellcasting.hpp" #include "../mwmechanics/levelledlist.hpp" - +#include "../mwmechanics/combat.hpp" #include "../mwrender/sky.hpp" #include "../mwrender/animation.hpp" @@ -121,13 +121,15 @@ namespace MWWorld const Files::Collections& fileCollections, const std::vector& contentFiles, const boost::filesystem::path& resDir, const boost::filesystem::path& cacheDir, - ToUTF8::Utf8Encoder* encoder, const std::map& fallbackMap, int mActivationDistanceOverride) + ToUTF8::Utf8Encoder* encoder, const std::map& fallbackMap, + int activationDistanceOverride, const std::string& startCell) : mPlayer (0), mLocalScripts (mStore), mSky (true), mCells (mStore, mEsm), - mActivationDistanceOverride (mActivationDistanceOverride), + mActivationDistanceOverride (activationDistanceOverride), mFallback(fallbackMap), mPlayIntro(0), mTeleportEnabled(true), mLevitationEnabled(true), mFacedDistance(FLT_MAX), mGodMode(false), mContentFiles (contentFiles), - mGoToJail(false) + mGoToJail(false), + mStartCell (startCell) { mPhysics = new PhysicsSystem(renderer); mPhysEngine = mPhysics->getEngine(); @@ -168,7 +170,7 @@ namespace MWWorld mWorldScene = new Scene(*mRendering, mPhysics); } - void World::startNewGame() + void World::startNewGame (bool bypass) { mGoToJail = false; mLevitationEnabled = true; @@ -181,23 +183,44 @@ namespace MWWorld MWBase::Environment::get().getWindowManager()->updatePlayer(); - // FIXME: this will add cell 0,0 as visible on the global map - ESM::Position pos; - const int cellSize = 8192; - pos.pos[0] = cellSize/2; - pos.pos[1] = cellSize/2; - pos.pos[2] = 0; - pos.rot[0] = 0; - pos.rot[1] = 0; - pos.rot[2] = 0; - mWorldScene->changeToExteriorCell(pos); + if (bypass && !mStartCell.empty()) + { + ESM::Position pos; + + if (findExteriorPosition (mStartCell, pos)) + { + changeToExteriorCell (pos); + } + else + { + findInteriorPosition (mStartCell, pos); + changeToInteriorCell (mStartCell, pos); + } + } + else + { + /// \todo if !bypass, do not add player location to global map for the duration of one + /// frame + ESM::Position pos; + const int cellSize = 8192; + pos.pos[0] = cellSize/2; + pos.pos[1] = cellSize/2; + pos.pos[2] = 0; + pos.rot[0] = 0; + pos.rot[1] = 0; + pos.rot[2] = 0; + mWorldScene->changeToExteriorCell(pos); + } - // FIXME: should be set to 1, but the sound manager won't pause newly started sounds - mPlayIntro = 2; + if (!bypass) + { + // FIXME: should be set to 1, but the sound manager won't pause newly started sounds + mPlayIntro = 2; - // set new game mark - mGlobalVariables["chargenstate"].setInteger (1); - mGlobalVariables["pcrace"].setInteger (3); + // set new game mark + mGlobalVariables["chargenstate"].setInteger (1); + mGlobalVariables["pcrace"].setInteger (3); + } // we don't want old weather to persist on a new game delete mWeatherManager; @@ -228,6 +251,7 @@ namespace MWWorld mCells.clear(); + mMagicBolts.clear(); mProjectiles.clear(); mDoorStates.clear(); @@ -821,7 +845,7 @@ namespace MWWorld const ESM::Position &posdata = ptr.getRefData().getPosition(); Ogre::Vector3 pos(posdata.pos); Ogre::Quaternion rot = Ogre::Quaternion(Ogre::Radian(posdata.rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * - Ogre::Quaternion(Ogre::Radian(posdata.rot[0]), Ogre::Vector3::UNIT_X); + Ogre::Quaternion(Ogre::Radian(posdata.rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X); MWRender::Animation *anim = mRendering->getAnimation(ptr); if(anim != NULL) @@ -858,7 +882,7 @@ namespace MWWorld } } - void World::moveObject(const Ptr &ptr, CellStore &newCell, float x, float y, float z) + void World::moveObject(const Ptr &ptr, CellStore* newCell, float x, float y, float z) { ESM::Position &pos = ptr.getRefData().getPosition(); @@ -872,27 +896,27 @@ namespace MWWorld bool isPlayer = ptr == mPlayer->getPlayer(); bool haveToMove = isPlayer || mWorldScene->isCellActive(*currCell); - if (*currCell != newCell) + if (currCell != newCell) { removeContainerScripts(ptr); if (isPlayer) { - if (!newCell.isExterior()) - changeToInteriorCell(Misc::StringUtils::lowerCase(newCell.getCell()->mName), pos); + if (!newCell->isExterior()) + changeToInteriorCell(Misc::StringUtils::lowerCase(newCell->getCell()->mName), pos); else { - int cellX = newCell.getCell()->getGridX(); - int cellY = newCell.getCell()->getGridY(); + int cellX = newCell->getCell()->getGridX(); + int cellY = newCell->getCell()->getGridY(); mWorldScene->changeCell(cellX, cellY, pos, false); } - addContainerScripts (getPlayerPtr(), &newCell); + addContainerScripts (getPlayerPtr(), newCell); } else { if (!mWorldScene->isCellActive(*currCell)) - copyObjectToCell(ptr, newCell, pos); - else if (!mWorldScene->isCellActive(newCell)) + ptr.getClass().copyToCell(ptr, *newCell, pos); + else if (!mWorldScene->isCellActive(*newCell)) { mWorldScene->removeObjectFromScene(ptr); mLocalScripts.remove(ptr); @@ -900,7 +924,7 @@ namespace MWWorld haveToMove = false; MWWorld::Ptr newPtr = MWWorld::Class::get(ptr) - .copyToCell(ptr, newCell); + .copyToCell(ptr, *newCell); newPtr.getRefData().setBaseNode(0); objectLeftActiveCell(ptr, newPtr); @@ -908,7 +932,7 @@ namespace MWWorld else { MWWorld::Ptr copy = - MWWorld::Class::get(ptr).copyToCell(ptr, newCell, pos); + MWWorld::Class::get(ptr).copyToCell(ptr, *newCell, pos); mRendering->updateObjectCell(ptr, copy); MWBase::Environment::get().getSoundManager()->updatePtr (ptr, copy); @@ -923,7 +947,7 @@ namespace MWWorld mLocalScripts.remove(ptr); removeContainerScripts (ptr); mLocalScripts.add(script, copy); - addContainerScripts (copy, &newCell); + addContainerScripts (copy, newCell); } } ptr.getRefData().setCount(0); @@ -947,7 +971,7 @@ namespace MWWorld cell = getExterior(cellX, cellY); } - moveObject(ptr, *cell, x, y, z); + moveObject(ptr, cell, x, y, z); return cell != ptr.getCell(); } @@ -1041,15 +1065,13 @@ namespace MWWorld while(ptr.getRefData().getLocalRotation().rot[2]<=-fullRotateRad) ptr.getRefData().getLocalRotation().rot[2]+=fullRotateRad; - float *worldRot = ptr.getRefData().getPosition().rot; + Ogre::Quaternion worldRotQuat(Ogre::Quaternion(Ogre::Radian(ptr.getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X)* + Ogre::Quaternion(Ogre::Radian(ptr.getRefData().getPosition().rot[1]), Ogre::Vector3::NEGATIVE_UNIT_Y)* + Ogre::Quaternion(Ogre::Radian(ptr.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z)); - Ogre::Quaternion worldRotQuat(Ogre::Quaternion(Ogre::Radian(-worldRot[0]), Ogre::Vector3::UNIT_X)* - Ogre::Quaternion(Ogre::Radian(-worldRot[1]), Ogre::Vector3::UNIT_Y)* - Ogre::Quaternion(Ogre::Radian(-worldRot[2]), Ogre::Vector3::UNIT_Z)); - - Ogre::Quaternion rot(Ogre::Quaternion(Ogre::Radian(Ogre::Degree(-x).valueRadians()), Ogre::Vector3::UNIT_X)* - Ogre::Quaternion(Ogre::Radian(Ogre::Degree(-y).valueRadians()), Ogre::Vector3::UNIT_Y)* - Ogre::Quaternion(Ogre::Radian(Ogre::Degree(-z).valueRadians()), Ogre::Vector3::UNIT_Z)); + Ogre::Quaternion rot(Ogre::Quaternion(Ogre::Degree(x), Ogre::Vector3::NEGATIVE_UNIT_X)* + Ogre::Quaternion(Ogre::Degree(y), Ogre::Vector3::NEGATIVE_UNIT_Y)* + Ogre::Quaternion(Ogre::Degree(z), Ogre::Vector3::NEGATIVE_UNIT_Z)); ptr.getRefData().getBaseNode()->setOrientation(worldRotQuat*rot); mPhysics->rotateObject(ptr); @@ -1080,7 +1102,7 @@ namespace MWWorld pos.z = traced.z; } - moveObject(ptr, *ptr.getCell(), pos.x, pos.y, pos.z); + moveObject(ptr, ptr.getCell(), pos.x, pos.y, pos.z); } void World::rotateObject (const Ptr& ptr,float x,float y,float z, bool adjust) @@ -1091,9 +1113,9 @@ namespace MWWorld adjust); } - MWWorld::Ptr World::safePlaceObject(const MWWorld::Ptr& ptr,MWWorld::CellStore &Cell,ESM::Position pos) + MWWorld::Ptr World::safePlaceObject(const MWWorld::Ptr& ptr, MWWorld::CellStore* cell, ESM::Position pos) { - return copyObjectToCell(ptr,Cell,pos,false); + return copyObjectToCell(ptr,cell,pos,false); } void World::indexToPosition (int cellX, int cellY, float &x, float &y, bool centre) const @@ -1127,6 +1149,7 @@ namespace MWWorld { processDoors(duration); + moveMagicBolts(duration); moveProjectiles(duration); const PtrVelocityList &results = mPhysics->applyQueuedMovement(duration); @@ -1506,15 +1529,7 @@ namespace MWWorld if (!result.first) return false; - CellStore* cell; - if (isCellExterior()) - { - int cellX, cellY; - positionToIndex(result.second[0], result.second[1], cellX, cellY); - cell = mCells.getExterior(cellX, cellY); - } - else - cell = getPlayerPtr().getCell(); + CellStore* cell = getPlayerPtr().getCell(); ESM::Position pos = getPlayerPtr().getRefData().getPosition(); pos.pos[0] = result.second[0]; @@ -1527,7 +1542,7 @@ namespace MWWorld // copy the object and set its count int origCount = object.getRefData().getCount(); object.getRefData().setCount(amount); - Ptr dropped = copyObjectToCell(object, *cell, pos, true); + Ptr dropped = copyObjectToCell(object, cell, pos, true); object.getRefData().setCount(origCount); // only the player place items in the world, so no need to check actor @@ -1548,7 +1563,7 @@ namespace MWWorld } - Ptr World::copyObjectToCell(const Ptr &object, CellStore &cell, ESM::Position pos, bool adjustPos) + Ptr World::copyObjectToCell(const Ptr &object, CellStore* cell, ESM::Position pos, bool adjustPos) { if (object.getClass().isActor() || adjustPos) { @@ -1560,17 +1575,17 @@ namespace MWWorld } } - if (cell.isExterior()) + if (cell->isExterior()) { int cellX, cellY; positionToIndex(pos.pos[0], pos.pos[1], cellX, cellY); - cell = *mCells.getExterior(cellX, cellY); + cell = mCells.getExterior(cellX, cellY); } MWWorld::Ptr dropped = - MWWorld::Class::get(object).copyToCell(object, cell, pos); + MWWorld::Class::get(object).copyToCell(object, *cell, pos); - if (mWorldScene->isCellActive(cell)) { + if (mWorldScene->isCellActive(*cell)) { if (dropped.getRefData().isEnabled()) { mWorldScene->addObjectToScene(dropped); } @@ -1578,7 +1593,7 @@ namespace MWWorld if (!script.empty()) { mLocalScripts.add(script, dropped); } - addContainerScripts(dropped, &cell); + addContainerScripts(dropped, cell); } return dropped; @@ -1608,7 +1623,7 @@ namespace MWWorld // copy the object and set its count int origCount = object.getRefData().getCount(); object.getRefData().setCount(amount); - Ptr dropped = copyObjectToCell(object, *cell, pos); + Ptr dropped = copyObjectToCell(object, cell, pos); object.getRefData().setCount(origCount); if(actor == mPlayer->getPlayer()) // Only call if dropped by player @@ -1634,7 +1649,7 @@ namespace MWWorld if (ptr.getClass().getCreatureStats(ptr).isDead()) return false; - if (ptr.getClass().isFlying(ptr)) + if (ptr.getClass().canFly(ptr)) return true; const MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr); @@ -1873,6 +1888,8 @@ namespace MWWorld bool World::getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc) { + if (!targetNpc.getRefData().isEnabled() || !npc.getRefData().isEnabled()) + return false; // cannot get LOS unless both NPC's are enabled Ogre::Vector3 halfExt1 = mPhysEngine->getCharacter(npc.getRefData().getHandle())->getHalfExtents(); float* pos1 = npc.getRefData().getPosition().pos; Ogre::Vector3 halfExt2 = mPhysEngine->getCharacter(targetNpc.getRefData().getHandle())->getHalfExtents(); @@ -2141,7 +2158,37 @@ namespace MWWorld } } - void World::launchProjectile (const std::string& id, bool stack, const ESM::EffectList& effects, + void World::launchProjectile (MWWorld::Ptr actor, MWWorld::Ptr projectile, + const Ogre::Vector3& worldPos, const Ogre::Quaternion& orient, MWWorld::Ptr bow, float speed) + { + ProjectileState state; + state.mActorHandle = actor.getRefData().getHandle(); + state.mBow = bow; + state.mVelocity = orient.yAxis() * speed; + + MWWorld::ManualRef ref(getStore(), projectile.getCellRef().mRefID); + + ESM::Position pos; + pos.pos[0] = worldPos.x; + pos.pos[1] = worldPos.y; + pos.pos[2] = worldPos.z; + + // Do NOT copy actor rotation! actors use a different rotation order, and this will not produce the same facing direction. + Ogre::Matrix3 mat; + orient.ToRotationMatrix(mat); + Ogre::Radian xr,yr,zr; + mat.ToEulerAnglesXYZ(xr, yr, zr); + pos.rot[0] = -xr.valueRadians(); + pos.rot[1] = -yr.valueRadians(); + pos.rot[2] = -zr.valueRadians(); + + MWWorld::Ptr ptr = copyObjectToCell(ref.getPtr(), actor.getCell(), pos, false); + ptr.getRefData().setCount(1); + + mProjectiles[ptr] = state; + } + + void World::launchMagicBolt (const std::string& id, bool stack, const ESM::EffectList& effects, const MWWorld::Ptr& actor, const std::string& sourceName) { std::string projectileModel; @@ -2183,13 +2230,23 @@ namespace MWWorld pos.pos[0] = actor.getRefData().getPosition().pos[0]; pos.pos[1] = actor.getRefData().getPosition().pos[1]; pos.pos[2] = actor.getRefData().getPosition().pos[2] + height; - pos.rot[0] = actor.getRefData().getPosition().rot[0]; - pos.rot[1] = actor.getRefData().getPosition().rot[1]; - pos.rot[2] = actor.getRefData().getPosition().rot[2]; + + // Do NOT copy rotation directly! actors use a different rotation order, and this will not produce the same facing direction. + Ogre::Quaternion orient = Ogre::Quaternion(Ogre::Radian(actor.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * + Ogre::Quaternion(Ogre::Radian(actor.getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X); + Ogre::Matrix3 mat; + orient.ToRotationMatrix(mat); + Ogre::Radian xr,yr,zr; + mat.ToEulerAnglesXYZ(xr, yr, zr); + pos.rot[0] = -xr.valueRadians(); + pos.rot[1] = -yr.valueRadians(); + pos.rot[2] = -zr.valueRadians(); + ref.getPtr().getCellRef().mPos = pos; - MWWorld::Ptr ptr = copyObjectToCell(ref.getPtr(), *actor.getCell(), pos); + CellStore* cell = actor.getCell(); + MWWorld::Ptr ptr = copyObjectToCell(ref.getPtr(), cell, pos); - ProjectileState state; + MagicBoltState state; state.mSourceName = sourceName; state.mId = id; state.mActorHandle = actor.getRefData().getHandle(); @@ -2207,7 +2264,7 @@ namespace MWWorld MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); sndMgr->playSound3D(ptr, sound, 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop); - mProjectiles[ptr] = state; + mMagicBolts[ptr] = state; } void World::moveProjectiles(float duration) @@ -2224,13 +2281,105 @@ namespace MWWorld MWWorld::Ptr ptr = it->first; - Ogre::Vector3 rot(ptr.getRefData().getPosition().rot); + // gravity constant - must be way lower than the gravity affecting actors, since we're not + // simulating aerodynamics at all + it->second.mVelocity -= Ogre::Vector3(0, 0, 627.2f * 0.1f) * duration; - // TODO: Why -rot.z, but not -rot.x? (note: same issue in MovementSolver::move) - Ogre::Quaternion orient = Ogre::Quaternion(Ogre::Radian(-rot.z), Ogre::Vector3::UNIT_Z); - orient = orient * Ogre::Quaternion(Ogre::Radian(rot.x), Ogre::Vector3::UNIT_X); + Ogre::Vector3 pos(ptr.getRefData().getPosition().pos); + Ogre::Vector3 newPos = pos + it->second.mVelocity * duration; + Ogre::Quaternion orient = Ogre::Vector3::UNIT_Y.getRotationTo(it->second.mVelocity); + Ogre::Matrix3 mat; + orient.ToRotationMatrix(mat); + Ogre::Radian xr,yr,zr; + mat.ToEulerAnglesXYZ(xr, yr, zr); + rotateObject(ptr, -xr.valueDegrees(), -yr.valueDegrees(), -zr.valueDegrees()); + // Check for impact + btVector3 from(pos.x, pos.y, pos.z); + btVector3 to(newPos.x, newPos.y, newPos.z); + std::vector > collisions = mPhysEngine->rayTest2(from, to); + bool hit=false; + + // HACK: query against the shape as well, since the ray does not take the volume into account + // really, this should be a convex cast, but the whole physics system needs a rewrite + std::vector col2 = mPhysEngine->getCollisions(ptr.getRefData().getHandle()); + for (std::vector::iterator ci = col2.begin(); ci != col2.end(); ++ci) + collisions.push_back(std::make_pair(0.f,*ci)); + + for (std::vector >::iterator cIt = collisions.begin(); cIt != collisions.end() && !hit; ++cIt) + { + MWWorld::Ptr obstacle = searchPtrViaHandle(cIt->second); + if (obstacle == ptr) + continue; + + MWWorld::Ptr caster = searchPtrViaHandle(it->second.mActorHandle); + + // Arrow intersects with player immediately after shooting :/ + if (obstacle == caster) + continue; + + if (caster.isEmpty()) + caster = obstacle; + + if (obstacle.isEmpty()) + { + // Terrain + } + else if (obstacle.getClass().isActor()) + { + MWMechanics::projectileHit(caster, obstacle, it->second.mBow, ptr, pos + (newPos - pos) * cIt->first); + } + hit = true; + } + if (hit) + { + deleteObject(ptr); + mProjectiles.erase(it++); + continue; + } + + std::string handle = ptr.getRefData().getHandle(); + + moveObject(ptr, newPos.x, newPos.y, newPos.z); + + // HACK: Re-fetch Ptrs if necessary, since the cell might have changed + if (!ptr.getRefData().getCount()) + { + moved[handle] = it->second; + mProjectiles.erase(it++); + } + else + ++it; + } + + // HACK: Re-fetch Ptrs if necessary, since the cell might have changed + for (std::map::iterator it = moved.begin(); it != moved.end(); ++it) + { + MWWorld::Ptr newPtr = searchPtrViaHandle(it->first); + if (newPtr.isEmpty()) // The projectile went into an inactive cell and was deleted + continue; + mProjectiles[getPtrViaHandle(it->first)] = it->second; + } + } + + void World::moveMagicBolts(float duration) + { + std::map moved; + for (std::map::iterator it = mMagicBolts.begin(); it != mMagicBolts.end();) + { + if (!mWorldScene->isCellActive(*it->first.getCell())) + { + deleteObject(it->first); + mMagicBolts.erase(it++); + continue; + } + + MWWorld::Ptr ptr = it->first; + + Ogre::Vector3 rot(ptr.getRefData().getPosition().rot); + + Ogre::Quaternion orient = ptr.getRefData().getBaseNode()->getOrientation(); static float fTargetSpellMaxSpeed = getStore().get().find("fTargetSpellMaxSpeed")->getFloat(); float speed = fTargetSpellMaxSpeed * it->second.mSpeed; @@ -2277,7 +2426,7 @@ namespace MWWorld explodeSpell(Ogre::Vector3(ptr.getRefData().getPosition().pos), ptr, it->second.mEffects, caster, it->second.mId, it->second.mSourceName); deleteObject(ptr); - mProjectiles.erase(it++); + mMagicBolts.erase(it++); continue; } @@ -2289,29 +2438,29 @@ namespace MWWorld if (!ptr.getRefData().getCount()) { moved[handle] = it->second; - mProjectiles.erase(it++); + mMagicBolts.erase(it++); } else ++it; } // HACK: Re-fetch Ptrs if necessary, since the cell might have changed - for (std::map::iterator it = moved.begin(); it != moved.end(); ++it) + for (std::map::iterator it = moved.begin(); it != moved.end(); ++it) { MWWorld::Ptr newPtr = searchPtrViaHandle(it->first); if (newPtr.isEmpty()) // The projectile went into an inactive cell and was deleted continue; - mProjectiles[getPtrViaHandle(it->first)] = it->second; + mMagicBolts[getPtrViaHandle(it->first)] = it->second; } } void World::objectLeftActiveCell(Ptr object, Ptr movedPtr) { // For now, projectiles moved to an inactive cell are just deleted, because there's no reliable way to hold on to the meta information + if (mMagicBolts.find(object) != mMagicBolts.end()) + deleteObject(movedPtr); if (mProjectiles.find(object) != mProjectiles.end()) - { deleteObject(movedPtr); - } } const std::vector& World::getContentFiles() const @@ -2648,7 +2797,7 @@ namespace MWWorld MWWorld::ManualRef ref(getStore(), selectedCreature, 1); ref.getPtr().getCellRef().mPos = ipos; - safePlaceObject(ref.getPtr(),*cell,ipos); + safePlaceObject(ref.getPtr(), cell, ipos); } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 036cafe2d..42f52cb61 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -90,7 +90,7 @@ namespace MWWorld std::map mDoorStates; ///< only holds doors that are currently moving. 0 means closing, 1 opening - struct ProjectileState + struct MagicBoltState { // Id of spell or enchantment to apply when it hits std::string mId; @@ -108,7 +108,21 @@ namespace MWWorld bool mStack; }; + struct ProjectileState + { + // Actor who shot this projectile + std::string mActorHandle; + + MWWorld::Ptr mBow; // bow or crossbow the projectile was fired from + + Ogre::Vector3 mVelocity; + }; + + std::map mMagicBolts; std::map mProjectiles; + + std::string mStartCell; + void updateWeather(float duration); int getDaysPerMonth (int month) const; @@ -117,7 +131,7 @@ namespace MWWorld bool moveObjectImp (const Ptr& ptr, float x, float y, float z); ///< @return true if the active cell (cell player is in) changed - Ptr copyObjectToCell(const Ptr &ptr, CellStore &cell, ESM::Position pos, bool adjustPos=true); + Ptr copyObjectToCell(const Ptr &ptr, CellStore* cell, ESM::Position pos, bool adjustPos=true); void updateWindowManager (); void performUpdateSceneQueries (); @@ -134,6 +148,7 @@ namespace MWWorld void processDoors(float duration); ///< Run physics simulation and modify \a world accordingly. + void moveMagicBolts(float duration); void moveProjectiles(float duration); void doPhysics(float duration); @@ -167,11 +182,13 @@ namespace MWWorld const Files::Collections& fileCollections, const std::vector& contentFiles, const boost::filesystem::path& resDir, const boost::filesystem::path& cacheDir, - ToUTF8::Utf8Encoder* encoder, const std::map& fallbackMap, int mActivationDistanceOverride); + ToUTF8::Utf8Encoder* encoder, const std::map& fallbackMap, + int activationDistanceOverride, const std::string& startCell); virtual ~World(); - virtual void startNewGame(); + virtual void startNewGame (bool bypass); + ///< \param bypass Bypass regular game start. virtual void clear(); @@ -341,7 +358,7 @@ namespace MWWorld virtual void deleteObject (const Ptr& ptr); virtual void moveObject (const Ptr& ptr, float x, float y, float z); - virtual void moveObject (const Ptr& ptr, CellStore &newCell, float x, float y, float z); + virtual void moveObject (const Ptr& ptr, CellStore* newCell, float x, float y, float z); virtual void scaleObject (const Ptr& ptr, float scale); @@ -351,7 +368,7 @@ namespace MWWorld virtual void localRotateObject (const Ptr& ptr, float x, float y, float z); - virtual MWWorld::Ptr safePlaceObject(const MWWorld::Ptr& ptr,MWWorld::CellStore &Cell,ESM::Position pos); + virtual MWWorld::Ptr safePlaceObject(const MWWorld::Ptr& ptr, MWWorld::CellStore* cell, ESM::Position pos); ///< place an object in a "safe" location (ie not in the void, etc). Makes a copy of the Ptr. virtual void indexToPosition (int cellX, int cellY, float &x, float &y, bool centre = false) @@ -552,8 +569,10 @@ namespace MWWorld */ virtual void castSpell (const MWWorld::Ptr& actor); - virtual void launchProjectile (const std::string& id, bool stack, const ESM::EffectList& effects, + virtual void launchMagicBolt (const std::string& id, bool stack, const ESM::EffectList& effects, const MWWorld::Ptr& actor, const std::string& sourceName); + virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::Ptr projectile, + const Ogre::Vector3& worldPos, const Ogre::Quaternion& orient, MWWorld::Ptr bow, float speed); virtual const std::vector& getContentFiles() const; diff --git a/cmake/FindSDL.cmake b/cmake/FindSDL.cmake deleted file mode 100644 index 0dc02f5b6..000000000 --- a/cmake/FindSDL.cmake +++ /dev/null @@ -1,177 +0,0 @@ -# Locate SDL library -# This module defines -# SDL_LIBRARY, the name of the library to link against -# SDL_FOUND, if false, do not try to link to SDL -# SDL_INCLUDE_DIR, where to find SDL.h -# -# This module responds to the the flag: -# SDL_BUILDING_LIBRARY -# If this is defined, then no SDL_main will be linked in because -# only applications need main(). -# Otherwise, it is assumed you are building an application and this -# module will attempt to locate and set the the proper link flags -# as part of the returned SDL_LIBRARY variable. -# -# Don't forget to include SDLmain.h and SDLmain.m your project for the -# OS X framework based version. (Other versions link to -lSDLmain which -# this module will try to find on your behalf.) Also for OS X, this -# module will automatically add the -framework Cocoa on your behalf. -# -# -# Additional Note: If you see an empty SDL_LIBRARY_TEMP in your configuration -# and no SDL_LIBRARY, it means CMake did not find your SDL library -# (SDL.dll, libsdl.so, SDL.framework, etc). -# Set SDL_LIBRARY_TEMP to point to your SDL library, and configure again. -# Similarly, if you see an empty SDLMAIN_LIBRARY, you should set this value -# as appropriate. These values are used to generate the final SDL_LIBRARY -# variable, but when these values are unset, SDL_LIBRARY does not get created. -# -# -# $SDLDIR is an environment variable that would -# correspond to the ./configure --prefix=$SDLDIR -# used in building SDL. -# l.e.galup 9-20-02 -# -# Modified by Eric Wing. -# Added code to assist with automated building by using environmental variables -# and providing a more controlled/consistent search behavior. -# Added new modifications to recognize OS X frameworks and -# additional Unix paths (FreeBSD, etc). -# Also corrected the header search path to follow "proper" SDL guidelines. -# Added a search for SDLmain which is needed by some platforms. -# Added a search for threads which is needed by some platforms. -# Added needed compile switches for MinGW. -# -# On OSX, this will prefer the Framework version (if found) over others. -# People will have to manually change the cache values of -# SDL_LIBRARY to override this selection or set the CMake environment -# CMAKE_INCLUDE_PATH to modify the search paths. -# -# Note that the header path has changed from SDL/SDL.h to just SDL.h -# This needed to change because "proper" SDL convention -# is #include "SDL.h", not . This is done for portability -# reasons because not all systems place things in SDL/ (see FreeBSD). - -#============================================================================= -# Copyright 2003-2009 Kitware, Inc. -# -# Distributed under the OSI-approved BSD License (the "License"); -# see accompanying file Copyright.txt for details. -# -# This software is distributed WITHOUT ANY WARRANTY; without even the -# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the License for more information. -#============================================================================= -# (To distribute this file outside of CMake, substitute the full -# License text for the above reference.) - -FIND_PATH(SDL_INCLUDE_DIR SDL.h - HINTS - $ENV{SDLDIR} - PATH_SUFFIXES include/SDL include - PATHS - ~/Library/Frameworks - /Library/Frameworks - /usr/local/include/SDL12 - /usr/local/include/SDL11 # FreeBSD ports - /usr/include/SDL12 - /usr/include/SDL11 - /sw # Fink - /opt/local # DarwinPorts - /opt/csw # Blastwave - /opt -) -#MESSAGE("SDL_INCLUDE_DIR is ${SDL_INCLUDE_DIR}") - -# SDL-1.1 is the name used by FreeBSD ports... -# don't confuse it for the version number. -FIND_LIBRARY(SDL_LIBRARY_TEMP - NAMES SDL SDL-1.1 - HINTS - $ENV{SDLDIR} - PATH_SUFFIXES lib64 lib - PATHS - /sw - /opt/local - /opt/csw - /opt -) - -#MESSAGE("SDL_LIBRARY_TEMP is ${SDL_LIBRARY_TEMP}") - -IF(NOT SDL_BUILDING_LIBRARY) - IF(NOT ${SDL_INCLUDE_DIR} MATCHES ".framework") - # Non-OS X framework versions expect you to also dynamically link to - # SDLmain. This is mainly for Windows and OS X. Other (Unix) platforms - # seem to provide SDLmain for compatibility even though they don't - # necessarily need it. - FIND_LIBRARY(SDLMAIN_LIBRARY - NAMES SDLmain SDLmain-1.1 - HINTS - $ENV{SDLDIR} - PATH_SUFFIXES lib64 lib - PATHS - /sw - /opt/local - /opt/csw - /opt - ) - ENDIF(NOT ${SDL_INCLUDE_DIR} MATCHES ".framework") -ENDIF(NOT SDL_BUILDING_LIBRARY) - -# SDL may require threads on your system. -# The Apple build may not need an explicit flag because one of the -# frameworks may already provide it. -# But for non-OSX systems, I will use the CMake Threads package. -IF(NOT APPLE) - FIND_PACKAGE(Threads) -ENDIF(NOT APPLE) - -# MinGW needs an additional library, mwindows -# It's total link flags should look like -lmingw32 -lSDLmain -lSDL -lmwindows -# (Actually on second look, I think it only needs one of the m* libraries.) -IF(MINGW) - SET(MINGW32_LIBRARY mingw32 CACHE STRING "mwindows for MinGW") -ENDIF(MINGW) - -SET(SDL_FOUND "NO") -IF(SDL_LIBRARY_TEMP) - # For SDLmain - IF(NOT SDL_BUILDING_LIBRARY) - IF(SDLMAIN_LIBRARY) - SET(SDL_LIBRARY_TEMP ${SDLMAIN_LIBRARY} ${SDL_LIBRARY_TEMP}) - ENDIF(SDLMAIN_LIBRARY) - ENDIF(NOT SDL_BUILDING_LIBRARY) - - # For OS X, SDL uses Cocoa as a backend so it must link to Cocoa. - # CMake doesn't display the -framework Cocoa string in the UI even - # though it actually is there if I modify a pre-used variable. - # I think it has something to do with the CACHE STRING. - # So I use a temporary variable until the end so I can set the - # "real" variable in one-shot. - IF(APPLE) - SET(SDL_LIBRARY_TEMP ${SDL_LIBRARY_TEMP} "-framework Cocoa") - ENDIF(APPLE) - - # For threads, as mentioned Apple doesn't need this. - # In fact, there seems to be a problem if I used the Threads package - # and try using this line, so I'm just skipping it entirely for OS X. - IF(NOT APPLE) - SET(SDL_LIBRARY_TEMP ${SDL_LIBRARY_TEMP} ${CMAKE_THREAD_LIBS_INIT}) - ENDIF(NOT APPLE) - - # For MinGW library - IF(MINGW) - SET(SDL_LIBRARY_TEMP ${MINGW32_LIBRARY} ${SDL_LIBRARY_TEMP}) - ENDIF(MINGW) - - # Set the final string here so the GUI reflects the final state. - SET(SDL_LIBRARY ${SDL_LIBRARY_TEMP} CACHE STRING "Where the SDL Library can be found") - # Set the temp variable to INTERNAL so it is not seen in the CMake GUI - SET(SDL_LIBRARY_TEMP "${SDL_LIBRARY_TEMP}" CACHE INTERNAL "") - - SET(SDL_FOUND "YES") -ENDIF(SDL_LIBRARY_TEMP) - -#MESSAGE("SDL_LIBRARY is ${SDL_LIBRARY}") - diff --git a/cmake/GetGitRevisionDescription.cmake b/cmake/GetGitRevisionDescription.cmake index fecd1654d..56ff1d545 100644 --- a/cmake/GetGitRevisionDescription.cmake +++ b/cmake/GetGitRevisionDescription.cmake @@ -85,10 +85,6 @@ function(get_git_head_revision _refspecvar _hashvar) endfunction() function(git_describe _var) - if(NOT GIT_FOUND) - find_package(Git QUIET) - endif() - #get_git_head_revision(refspec hash) if(NOT GIT_FOUND) @@ -133,7 +129,8 @@ endfunction() function(get_git_tag_revision _var) if(NOT GIT_FOUND) - find_package(Git QUIET) + set(${_var} "GIT-NOTFOUND" PARENT_SCOPE) + return() endif() execute_process(COMMAND diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 9a1557963..9e39b8afc 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -73,8 +73,9 @@ add_component_dir (translation translation ) +add_definitions(-DTERRAIN_USE_SHADER=1) add_component_dir (terrain - quadtreenode chunk world storage material + quadtreenode chunk world storage material buffercache defs ) add_component_dir (loadinglistener diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index eb741fb10..6574f096b 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include "bsa_file.hpp" #include "../files/constrainedfiledatastream.hpp" diff --git a/components/bsa/bsa_file.hpp b/components/bsa/bsa_file.hpp index 6f3ab3bce..017adf1e3 100644 --- a/components/bsa/bsa_file.hpp +++ b/components/bsa/bsa_file.hpp @@ -24,7 +24,7 @@ #ifndef BSA_BSA_FILE_H #define BSA_BSA_FILE_H -#include +#include #include #include #include diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index c1f167992..57842796f 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_ESM_DEFS_H #define OPENMW_ESM_DEFS_H -#include +#include namespace ESM { diff --git a/components/esm/esmcommon.hpp b/components/esm/esmcommon.hpp index 6f51c767e..d3e6e7fea 100644 --- a/components/esm/esmcommon.hpp +++ b/components/esm/esmcommon.hpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include namespace ESM diff --git a/components/esm/esmreader.hpp b/components/esm/esmreader.hpp index 897c8fe73..b6c0ebc70 100644 --- a/components/esm/esmreader.hpp +++ b/components/esm/esmreader.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_ESM_READER_H #define OPENMW_ESM_READER_H -#include +#include #include #include #include diff --git a/components/esm/esmwriter.cpp b/components/esm/esmwriter.cpp index f38591b7b..91f123eb7 100644 --- a/components/esm/esmwriter.cpp +++ b/components/esm/esmwriter.cpp @@ -80,8 +80,8 @@ namespace ESM rec.name = name; rec.position = mStream->tellp(); rec.size = 0; - writeT(0); // Size goes here - writeT(0); // Unused header? + writeT(0); // Size goes here + writeT(0); // Unused header? writeT(flags); mRecords.push_back(rec); @@ -105,7 +105,7 @@ namespace ESM rec.name = name; rec.position = mStream->tellp(); rec.size = 0; - writeT(0); // Size goes here + writeT(0); // Size goes here mRecords.push_back(rec); assert(mRecords.back().size == 0); @@ -120,7 +120,7 @@ namespace ESM mStream->seekp(rec.position); mCounting = false; - write (reinterpret_cast (&rec.size), sizeof(int)); + write (reinterpret_cast (&rec.size), sizeof(uint32_t)); mCounting = true; mStream->seekp(0, std::ios::end); diff --git a/components/esm/esmwriter.hpp b/components/esm/esmwriter.hpp index 94f0a1004..33650e678 100644 --- a/components/esm/esmwriter.hpp +++ b/components/esm/esmwriter.hpp @@ -17,7 +17,7 @@ class ESMWriter { std::string name; std::streampos position; - size_t size; + uint32_t size; }; public: diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp index dd7bf3e42..55d043c8a 100644 --- a/components/esm/loadcell.cpp +++ b/components/esm/loadcell.cpp @@ -249,7 +249,7 @@ bool Cell::getNextMVRF(ESMReader &esm, MovedCellRef &mref) if (id.mPaged) { - id.mWorldspace = "default"; + id.mWorldspace = "sys::default"; id.mIndex.mX = mData.mX; id.mIndex.mY = mData.mY; } diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp index 32abb7799..028341ced 100644 --- a/components/esm/loadland.hpp +++ b/components/esm/loadland.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_ESM_LAND_H #define OPENMW_ESM_LAND_H -#include +#include #include "esmcommon.hpp" diff --git a/components/files/constrainedfiledatastream.cpp b/components/files/constrainedfiledatastream.cpp index 321bcf7c8..66f6fde97 100644 --- a/components/files/constrainedfiledatastream.cpp +++ b/components/files/constrainedfiledatastream.cpp @@ -4,7 +4,7 @@ #include #include -#include +#include namespace { diff --git a/components/files/lowlevelfile.cpp b/components/files/lowlevelfile.cpp index 71fd1b523..06ee9fb4e 100644 --- a/components/files/lowlevelfile.cpp +++ b/components/files/lowlevelfile.cpp @@ -219,7 +219,7 @@ LowLevelFile::LowLevelFile () LowLevelFile::~LowLevelFile () { - if (mHandle == INVALID_HANDLE_VALUE) + if (mHandle != INVALID_HANDLE_VALUE) CloseHandle (mHandle); } diff --git a/components/nif/niffile.hpp b/components/nif/niffile.hpp index 91ae93b40..79e1cc2a5 100644 --- a/components/nif/niffile.hpp +++ b/components/nif/niffile.hpp @@ -42,7 +42,7 @@ #include #include -#include +#include #include "record.hpp" #include "niftypes.hpp" diff --git a/components/nifogre/mesh.cpp b/components/nifogre/mesh.cpp index 43622cb9a..8bebe0589 100644 --- a/components/nifogre/mesh.cpp +++ b/components/nifogre/mesh.cpp @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include #include diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp index 43e8934c8..acab419b0 100644 --- a/components/nifogre/ogrenifloader.cpp +++ b/components/nifogre/ogrenifloader.cpp @@ -38,6 +38,9 @@ #include #include #include +#include +#include +#include #include diff --git a/components/ogreinit/ogreinit.cpp b/components/ogreinit/ogreinit.cpp index 840cf4bb0..1b9a899a0 100644 --- a/components/ogreinit/ogreinit.cpp +++ b/components/ogreinit/ogreinit.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #if OGRE_PLATFORM == OGRE_PLATFORM_APPLE #include diff --git a/components/terrain/buffercache.cpp b/components/terrain/buffercache.cpp new file mode 100644 index 000000000..f693c0e40 --- /dev/null +++ b/components/terrain/buffercache.cpp @@ -0,0 +1,200 @@ +#include "buffercache.hpp" + +#include + +#include "defs.hpp" + +namespace Terrain +{ + + Ogre::HardwareVertexBufferSharedPtr BufferCache::getUVBuffer() + { + if (mUvBufferMap.find(mNumVerts) != mUvBufferMap.end()) + { + return mUvBufferMap[mNumVerts]; + } + + int vertexCount = mNumVerts * mNumVerts; + + std::vector uvs; + uvs.reserve(vertexCount*2); + + for (unsigned int col = 0; col < mNumVerts; ++col) + { + for (unsigned int row = 0; row < mNumVerts; ++row) + { + uvs.push_back(col / static_cast(mNumVerts-1)); // U + uvs.push_back(row / static_cast(mNumVerts-1)); // V + } + } + + Ogre::HardwareBufferManager* mgr = Ogre::HardwareBufferManager::getSingletonPtr(); + Ogre::HardwareVertexBufferSharedPtr buffer = mgr->createVertexBuffer( + Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT2), + vertexCount, Ogre::HardwareBuffer::HBU_STATIC); + + buffer->writeData(0, buffer->getSizeInBytes(), &uvs[0], true); + + mUvBufferMap[mNumVerts] = buffer; + return buffer; + } + + Ogre::HardwareIndexBufferSharedPtr BufferCache::getIndexBuffer(int flags) + { + unsigned int verts = mNumVerts; + + if (mIndexBufferMap.find(flags) != mIndexBufferMap.end()) + { + return mIndexBufferMap[flags]; + } + + // LOD level n means every 2^n-th vertex is kept + size_t lodLevel = (flags >> (4*4)); + + size_t lodDeltas[4]; + for (int i=0; i<4; ++i) + lodDeltas[i] = (flags >> (4*i)) & (0xf); + + bool anyDeltas = (lodDeltas[North] || lodDeltas[South] || lodDeltas[West] || lodDeltas[East]); + + size_t increment = 1 << lodLevel; + assert(increment < verts); + std::vector indices; + indices.reserve((verts-1)*(verts-1)*2*3 / increment); + + size_t rowStart = 0, colStart = 0, rowEnd = verts-1, colEnd = verts-1; + // If any edge needs stitching we'll skip all edges at this point, + // mainly because stitching one edge would have an effect on corners and on the adjacent edges + if (anyDeltas) + { + colStart += increment; + colEnd -= increment; + rowEnd -= increment; + rowStart += increment; + } + for (size_t row = rowStart; row < rowEnd; row += increment) + { + for (size_t col = colStart; col < colEnd; col += increment) + { + indices.push_back(verts*col+row); + indices.push_back(verts*(col+increment)+row+increment); + indices.push_back(verts*col+row+increment); + + indices.push_back(verts*col+row); + indices.push_back(verts*(col+increment)+row); + indices.push_back(verts*(col+increment)+row+increment); + } + } + + size_t innerStep = increment; + if (anyDeltas) + { + // Now configure LOD transitions at the edges - this is pretty tedious, + // and some very long and boring code, but it works great + + // South + size_t row = 0; + size_t outerStep = 1 << (lodDeltas[South] + lodLevel); + for (size_t col = 0; col < verts-1; col += outerStep) + { + indices.push_back(verts*col+row); + indices.push_back(verts*(col+outerStep)+row); + // Make sure not to touch the right edge + if (col+outerStep == verts-1) + indices.push_back(verts*(col+outerStep-innerStep)+row+innerStep); + else + indices.push_back(verts*(col+outerStep)+row+innerStep); + + for (size_t i = 0; i < outerStep; i += innerStep) + { + // Make sure not to touch the left or right edges + if (col+i == 0 || col+i == verts-1-innerStep) + continue; + indices.push_back(verts*(col)+row); + indices.push_back(verts*(col+i+innerStep)+row+innerStep); + indices.push_back(verts*(col+i)+row+innerStep); + } + } + + // North + row = verts-1; + outerStep = 1 << (lodDeltas[North] + lodLevel); + for (size_t col = 0; col < verts-1; col += outerStep) + { + indices.push_back(verts*(col+outerStep)+row); + indices.push_back(verts*col+row); + // Make sure not to touch the left edge + if (col == 0) + indices.push_back(verts*(col+innerStep)+row-innerStep); + else + indices.push_back(verts*col+row-innerStep); + + for (size_t i = 0; i < outerStep; i += innerStep) + { + // Make sure not to touch the left or right edges + if (col+i == 0 || col+i == verts-1-innerStep) + continue; + indices.push_back(verts*(col+i)+row-innerStep); + indices.push_back(verts*(col+i+innerStep)+row-innerStep); + indices.push_back(verts*(col+outerStep)+row); + } + } + + // West + size_t col = 0; + outerStep = 1 << (lodDeltas[West] + lodLevel); + for (size_t row = 0; row < verts-1; row += outerStep) + { + indices.push_back(verts*col+row+outerStep); + indices.push_back(verts*col+row); + // Make sure not to touch the top edge + if (row+outerStep == verts-1) + indices.push_back(verts*(col+innerStep)+row+outerStep-innerStep); + else + indices.push_back(verts*(col+innerStep)+row+outerStep); + + for (size_t i = 0; i < outerStep; i += innerStep) + { + // Make sure not to touch the top or bottom edges + if (row+i == 0 || row+i == verts-1-innerStep) + continue; + indices.push_back(verts*col+row); + indices.push_back(verts*(col+innerStep)+row+i); + indices.push_back(verts*(col+innerStep)+row+i+innerStep); + } + } + + // East + col = verts-1; + outerStep = 1 << (lodDeltas[East] + lodLevel); + for (size_t row = 0; row < verts-1; row += outerStep) + { + indices.push_back(verts*col+row); + indices.push_back(verts*col+row+outerStep); + // Make sure not to touch the bottom edge + if (row == 0) + indices.push_back(verts*(col-innerStep)+row+innerStep); + else + indices.push_back(verts*(col-innerStep)+row); + + for (size_t i = 0; i < outerStep; i += innerStep) + { + // Make sure not to touch the top or bottom edges + if (row+i == 0 || row+i == verts-1-innerStep) + continue; + indices.push_back(verts*col+row+outerStep); + indices.push_back(verts*(col-innerStep)+row+i+innerStep); + indices.push_back(verts*(col-innerStep)+row+i); + } + } + } + + Ogre::HardwareBufferManager* mgr = Ogre::HardwareBufferManager::getSingletonPtr(); + Ogre::HardwareIndexBufferSharedPtr buffer = mgr->createIndexBuffer(Ogre::HardwareIndexBuffer::IT_16BIT, + indices.size(), Ogre::HardwareBuffer::HBU_STATIC); + buffer->writeData(0, buffer->getSizeInBytes(), &indices[0], true); + mIndexBufferMap[flags] = buffer; + return buffer; + } + +} diff --git a/components/terrain/buffercache.hpp b/components/terrain/buffercache.hpp new file mode 100644 index 000000000..f0aea9bfd --- /dev/null +++ b/components/terrain/buffercache.hpp @@ -0,0 +1,36 @@ +#ifndef COMPONENTS_TERRAIN_BUFFERCACHE_H +#define COMPONENTS_TERRAIN_BUFFERCACHE_H + +#include +#include + +#include + +namespace Terrain +{ + + /// @brief Implements creation and caching of vertex buffers for terrain chunks. + class BufferCache + { + public: + BufferCache(unsigned int numVerts) : mNumVerts(numVerts) {} + + /// @param flags first 4*4 bits are LOD deltas on each edge, respectively (4 bits each) + /// next 4 bits are LOD level of the index buffer (LOD 0 = don't omit any vertices) + Ogre::HardwareIndexBufferSharedPtr getIndexBuffer (int flags); + + Ogre::HardwareVertexBufferSharedPtr getUVBuffer (); + + private: + // Index buffers are shared across terrain batches where possible. There is one index buffer for each + // combination of LOD deltas and index buffer LOD we may need. + std::map mIndexBufferMap; + + std::map mUvBufferMap; + + unsigned int mNumVerts; + }; + +} + +#endif diff --git a/components/terrain/chunk.cpp b/components/terrain/chunk.cpp index a5c629088..0820dcc73 100644 --- a/components/terrain/chunk.cpp +++ b/components/terrain/chunk.cpp @@ -2,30 +2,23 @@ #include #include +#include +#include -#include "quadtreenode.hpp" -#include "world.hpp" -#include "storage.hpp" +#include + + +#include "world.hpp" // FIXME: for LoadResponseData, move to backgroundloader.hpp namespace Terrain { - Chunk::Chunk(QuadTreeNode* node, short lodLevel) - : mNode(node) - , mVertexLod(lodLevel) - , mAdditionalLod(0) + Chunk::Chunk(Ogre::HardwareVertexBufferSharedPtr uvBuffer, const Ogre::AxisAlignedBox& bounds, const LoadResponseData& data) + : mBounds(bounds) { mVertexData = OGRE_NEW Ogre::VertexData; mVertexData->vertexStart = 0; - - unsigned int verts = mNode->getTerrain()->getStorage()->getCellVertices(); - - // Set the total number of vertices - size_t numVertsOneSide = mNode->getSize() * (verts-1); - numVertsOneSide /= 1 << lodLevel; - numVertsOneSide += 1; - assert(numVertsOneSide == verts); - mVertexData->vertexCount = numVertsOneSide * numVertsOneSide; + mVertexData->vertexCount = data.mPositions.size()/3; // Set up the vertex declaration, which specifies the info for each vertex (normals, colors, UVs, etc) Ogre::VertexDeclaration* vertexDecl = mVertexData->vertexDeclaration; @@ -35,80 +28,50 @@ namespace Terrain // Positions vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_FLOAT3, Ogre::VES_POSITION); - mVertexBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3), + Ogre::HardwareVertexBufferSharedPtr vertexBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3), mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC); + // Normals vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_FLOAT3, Ogre::VES_NORMAL); - mNormalBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3), + Ogre::HardwareVertexBufferSharedPtr normalBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3), mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC); + // UV texture coordinates vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_FLOAT2, Ogre::VES_TEXTURE_COORDINATES, 0); - Ogre::HardwareVertexBufferSharedPtr uvBuf = mNode->getTerrain()->getVertexBuffer(numVertsOneSide); // Colours vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_COLOUR, Ogre::VES_DIFFUSE); - mColourBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_COLOUR), + Ogre::HardwareVertexBufferSharedPtr colourBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_COLOUR), mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC); - mNode->getTerrain()->getStorage()->fillVertexBuffers(lodLevel, mNode->getSize(), mNode->getCenter(), - mVertexBuffer, mNormalBuffer, mColourBuffer); + vertexBuffer->writeData(0, vertexBuffer->getSizeInBytes(), &data.mPositions[0], true); + normalBuffer->writeData(0, normalBuffer->getSizeInBytes(), &data.mNormals[0], true); + colourBuffer->writeData(0, colourBuffer->getSizeInBytes(), &data.mColours[0], true); - mVertexData->vertexBufferBinding->setBinding(0, mVertexBuffer); - mVertexData->vertexBufferBinding->setBinding(1, mNormalBuffer); - mVertexData->vertexBufferBinding->setBinding(2, uvBuf); - mVertexData->vertexBufferBinding->setBinding(3, mColourBuffer); + mVertexData->vertexBufferBinding->setBinding(0, vertexBuffer); + mVertexData->vertexBufferBinding->setBinding(1, normalBuffer); + mVertexData->vertexBufferBinding->setBinding(2, uvBuffer); + mVertexData->vertexBufferBinding->setBinding(3, colourBuffer); mIndexData = OGRE_NEW Ogre::IndexData(); mIndexData->indexStart = 0; } - - - void Chunk::updateIndexBuffer() + void Chunk::setIndexBuffer(Ogre::HardwareIndexBufferSharedPtr buffer) { - // Fetch a suitable index buffer (which may be shared) - size_t ourLod = mVertexLod + mAdditionalLod; - - int flags = 0; - - for (int i=0; i<4; ++i) - { - QuadTreeNode* neighbour = mNode->getNeighbour((Direction)i); - - // If the neighbour isn't currently rendering itself, - // go up until we find one. NOTE: We don't need to go down, - // because in that case neighbour's detail would be higher than - // our detail and the neighbour would handle stitching by itself. - while (neighbour && !neighbour->hasChunk()) - neighbour = neighbour->getParent(); - - size_t lod = 0; - if (neighbour) - lod = neighbour->getActualLodLevel(); - - if (lod <= ourLod) // We only need to worry about neighbours less detailed than we are - - lod = 0; // neighbours with more detail will do the stitching themselves - - // Use 4 bits for each LOD delta - if (lod > 0) - { - assert (lod - ourLod < (1 << 4)); - flags |= int(lod - ourLod) << (4*i); - } - } - - flags |= ((int)mAdditionalLod) << (4*4); - - size_t numIndices; - mIndexBuffer = mNode->getTerrain()->getIndexBuffer(flags, numIndices); - mIndexData->indexCount = numIndices; - mIndexData->indexBuffer = mIndexBuffer; + mIndexData->indexBuffer = buffer; + mIndexData->indexCount = buffer->getNumIndexes(); } Chunk::~Chunk() { +#if TERRAIN_USE_SHADER + sh::Factory::getInstance().destroyMaterialInstance(mMaterial->getName()); +#endif + Ogre::MaterialManager::getSingleton().remove(mMaterial->getName()); + OGRE_DELETE mVertexData; OGRE_DELETE mIndexData; } @@ -120,12 +83,12 @@ namespace Terrain const Ogre::AxisAlignedBox& Chunk::getBoundingBox(void) const { - return mNode->getBoundingBox(); + return mBounds; } Ogre::Real Chunk::getBoundingRadius(void) const { - return mNode->getBoundingBox().getHalfSize().length(); + return mBounds.getHalfSize().length(); } void Chunk::_updateRenderQueue(Ogre::RenderQueue* queue) @@ -146,7 +109,8 @@ namespace Terrain void Chunk::getRenderOperation(Ogre::RenderOperation& op) { - assert (!mIndexBuffer.isNull() && "Trying to render, but no index buffer set!"); + assert (!mIndexData->indexBuffer.isNull() && "Trying to render, but no index buffer set!"); + assert(!mMaterial.isNull() && "Trying to render, but no material set!"); op.useIndexes = true; op.operationType = Ogre::RenderOperation::OT_TRIANGLE_LIST; op.vertexData = mVertexData; diff --git a/components/terrain/chunk.hpp b/components/terrain/chunk.hpp index d74c65ba6..d037661ae 100644 --- a/components/terrain/chunk.hpp +++ b/components/terrain/chunk.hpp @@ -7,7 +7,8 @@ namespace Terrain { - class QuadTreeNode; + class BufferCache; + struct LoadResponseData; /** * @brief Renders a chunk of terrain, either using alpha splatting or a composite map. @@ -15,18 +16,14 @@ namespace Terrain class Chunk : public Ogre::Renderable, public Ogre::MovableObject { public: - /// @param lodLevel LOD level for the vertex buffer. - Chunk (QuadTreeNode* node, short lodLevel); + Chunk (Ogre::HardwareVertexBufferSharedPtr uvBuffer, const Ogre::AxisAlignedBox& bounds, const LoadResponseData& data); + virtual ~Chunk(); + /// @note Takes ownership of \a material void setMaterial (const Ogre::MaterialPtr& material); - /// Set additional LOD applied on top of vertex LOD. \n - /// This is achieved by changing the index buffer to omit vertices. - void setAdditionalLod (size_t lod) { mAdditionalLod = lod; } - size_t getAdditionalLod() { return mAdditionalLod; } - - void updateIndexBuffer(); + void setIndexBuffer(Ogre::HardwareIndexBufferSharedPtr buffer); // Inherited from MovableObject virtual const Ogre::String& getMovableType(void) const { static Ogre::String t = "MW_TERRAIN"; return t; } @@ -44,18 +41,11 @@ namespace Terrain virtual const Ogre::LightList& getLights(void) const; private: - QuadTreeNode* mNode; + Ogre::AxisAlignedBox mBounds; Ogre::MaterialPtr mMaterial; - size_t mVertexLod; - size_t mAdditionalLod; - Ogre::VertexData* mVertexData; Ogre::IndexData* mIndexData; - Ogre::HardwareVertexBufferSharedPtr mVertexBuffer; - Ogre::HardwareVertexBufferSharedPtr mNormalBuffer; - Ogre::HardwareVertexBufferSharedPtr mColourBuffer; - Ogre::HardwareIndexBufferSharedPtr mIndexBuffer; }; } diff --git a/components/terrain/defs.hpp b/components/terrain/defs.hpp new file mode 100644 index 000000000..685937653 --- /dev/null +++ b/components/terrain/defs.hpp @@ -0,0 +1,65 @@ +#ifndef COMPONENTS_TERRAIN_DEFS_HPP +#define COMPONENTS_TERRAIN_DEFS_HPP + +namespace Terrain +{ + class QuadTreeNode; + + /// The alignment of the terrain + enum Alignment + { + /// Terrain is in the X/Z plane + Align_XZ = 0, + /// Terrain is in the X/Y plane + Align_XY = 1, + /// Terrain is in the Y/Z plane. + /// UNTESTED - use at own risk. + /// Besides, X as up axis? What is wrong with you? ;) + Align_YZ = 2 + }; + + inline void convertPosition(Alignment align, float &x, float &y, float &z) + { + switch (align) + { + case Align_XY: + return; + case Align_XZ: + std::swap(y, z); + // This is since -Z should be going *into* the screen + // If not doing this, we'd get wrong vertex winding + z *= -1; + return; + case Align_YZ: + std::swap(x, y); + std::swap(y, z); + return; + } + } + + enum Direction + { + North = 0, + East = 1, + South = 2, + West = 3 + }; + + struct LayerInfo + { + std::string mDiffuseMap; + std::string mNormalMap; + bool mParallax; // Height info in normal map alpha channel? + bool mSpecular; // Specular info in diffuse map alpha channel? + }; + + struct LayerCollection + { + QuadTreeNode* mTarget; + // Since we can't create a texture from a different thread, this only holds the raw texel data + std::vector mBlendmaps; + std::vector mLayers; + }; +} + +#endif diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index 8e78d2216..faa73a986 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -6,7 +6,9 @@ #include +#if TERRAIN_USE_SHADER #include +#endif namespace { @@ -46,11 +48,15 @@ namespace Terrain Ogre::MaterialPtr MaterialGenerator::generate(Ogre::MaterialPtr mat) { + assert(!mLayerList.empty() && "Can't create material with no layers"); + return create(mat, false, false); } Ogre::MaterialPtr MaterialGenerator::generateForCompositeMapRTT(Ogre::MaterialPtr mat) { + assert(!mLayerList.empty() && "Can't create material with no layers"); + return create(mat, true, false); } @@ -64,7 +70,9 @@ namespace Terrain assert(!renderCompositeMap || !displayCompositeMap); if (!mat.isNull()) { +#if TERRAIN_USE_SHADER sh::Factory::getInstance().destroyMaterialInstance(mat->getName()); +#endif Ogre::MaterialManager::getSingleton().remove(mat->getName()); } @@ -144,6 +152,7 @@ namespace Terrain return mat; } +#if TERRAIN_USE_SHADER else { sh::MaterialInstance* material = sh::Factory::getInstance().createMaterialInstance (name.str()); @@ -235,7 +244,6 @@ namespace Terrain sh::MaterialInstancePass* p = material->createPass (); - p->setProperty ("vertex_program", sh::makeProperty(new sh::StringValue("terrain_vertex"))); p->setProperty ("fragment_program", sh::makeProperty(new sh::StringValue("terrain_fragment"))); if (layerOffset != 0) @@ -344,6 +352,7 @@ namespace Terrain } } } +#endif return Ogre::MaterialManager::getSingleton().getByName(name.str()); } diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index 82ccc7c89..21c1becb0 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -2,11 +2,14 @@ #include #include +#include +#include +#include #include "world.hpp" #include "chunk.hpp" #include "storage.hpp" - +#include "buffercache.hpp" #include "material.hpp" using namespace Terrain; @@ -141,7 +144,6 @@ namespace QuadTreeNode::QuadTreeNode(World* terrain, ChildDirection dir, float size, const Ogre::Vector2 ¢er, QuadTreeNode* parent) : mMaterialGenerator(NULL) - , mIsActive(false) , mIsDummy(false) , mSize(size) , mLodLevel(Log2(mSize)) @@ -153,6 +155,7 @@ QuadTreeNode::QuadTreeNode(World* terrain, ChildDirection dir, float size, const , mParent(parent) , mTerrain(terrain) , mChunk(NULL) + , mLoadState(LS_Unloaded) { mBounds.setNull(); for (int i=0; i<4; ++i) @@ -169,7 +172,11 @@ QuadTreeNode::QuadTreeNode(World* terrain, ChildDirection dir, float size, const pos = mParent->getCenter(); pos = mCenter - pos; float cellWorldSize = mTerrain->getStorage()->getCellWorldSize(); - mSceneNode->setPosition(Ogre::Vector3(pos.x*cellWorldSize, pos.y*cellWorldSize, 0)); + + Ogre::Vector3 sceneNodePos (pos.x*cellWorldSize, pos.y*cellWorldSize, 0); + mTerrain->convertPosition(sceneNodePos); + + mSceneNode->setPosition(sceneNodePos); mMaterialGenerator = new MaterialGenerator(mTerrain->getShadersEnabled()); } @@ -212,11 +219,31 @@ void QuadTreeNode::initAabb() mChildren[i]->initAabb(); mBounds.merge(mChildren[i]->getBoundingBox()); } - mBounds = Ogre::AxisAlignedBox (Ogre::Vector3(-mSize/2*cellWorldSize, -mSize/2*cellWorldSize, mBounds.getMinimum().z), - Ogre::Vector3(mSize/2*cellWorldSize, mSize/2*cellWorldSize, mBounds.getMaximum().z)); + float minH, maxH; + switch (mTerrain->getAlign()) + { + case Terrain::Align_XY: + minH = mBounds.getMinimum().z; + maxH = mBounds.getMaximum().z; + break; + case Terrain::Align_XZ: + minH = mBounds.getMinimum().y; + maxH = mBounds.getMaximum().y; + break; + case Terrain::Align_YZ: + minH = mBounds.getMinimum().x; + maxH = mBounds.getMaximum().x; + break; + } + Ogre::Vector3 min(-mSize/2*cellWorldSize, -mSize/2*cellWorldSize, minH); + Ogre::Vector3 max(Ogre::Vector3(mSize/2*cellWorldSize, mSize/2*cellWorldSize, maxH)); + mBounds = Ogre::AxisAlignedBox (min, max); + mTerrain->convertBounds(mBounds); } - mWorldBounds = Ogre::AxisAlignedBox(mBounds.getMinimum() + Ogre::Vector3(mCenter.x*cellWorldSize, mCenter.y*cellWorldSize, 0), - mBounds.getMaximum() + Ogre::Vector3(mCenter.x*cellWorldSize, mCenter.y*cellWorldSize, 0)); + Ogre::Vector3 offset(mCenter.x*cellWorldSize, mCenter.y*cellWorldSize, 0); + mTerrain->convertPosition(offset); + mWorldBounds = Ogre::AxisAlignedBox(mBounds.getMinimum() + offset, + mBounds.getMaximum() + offset); } void QuadTreeNode::setBoundingBox(const Ogre::AxisAlignedBox &box) @@ -229,15 +256,20 @@ const Ogre::AxisAlignedBox& QuadTreeNode::getBoundingBox() return mBounds; } -void QuadTreeNode::update(const Ogre::Vector3 &cameraPos, Loading::Listener* loadingListener) +const Ogre::AxisAlignedBox& QuadTreeNode::getWorldBoundingBox() { - const Ogre::AxisAlignedBox& bounds = getBoundingBox(); - if (bounds.isNull()) - return; + return mWorldBounds; +} - float dist = distance(mWorldBounds, cameraPos); +bool QuadTreeNode::update(const Ogre::Vector3 &cameraPos) +{ + if (isDummy()) + return true; + + if (mBounds.isNull()) + return true; - bool distantLand = mTerrain->getDistantLandEnabled(); + float dist = distance(mWorldBounds, cameraPos); // Make sure our scene node is attached if (!mSceneNode->isInSceneGraph()) @@ -245,153 +277,206 @@ void QuadTreeNode::update(const Ogre::Vector3 &cameraPos, Loading::Listener* loa mParent->getSceneNode()->addChild(mSceneNode); } - /// \todo implement error metrics or some other means of not using arbitrary values - /// (general quality needs to be user configurable as well) + // Simple LOD selection + /// \todo use error metrics? size_t wantedLod = 0; - if (dist > 8192*1) - wantedLod = 1; - if (dist > 8192*2) - wantedLod = 2; - if (dist > 8192*5) - wantedLod = 3; - if (dist > 8192*12) - wantedLod = 4; - if (dist > 8192*32) - wantedLod = 5; - if (dist > 8192*64) - wantedLod = 6; + float cellWorldSize = mTerrain->getStorage()->getCellWorldSize(); + + if (!mTerrain->getDistantLandEnabled() && dist > cellWorldSize) + return true; - bool hadChunk = hasChunk(); + if (dist > cellWorldSize*64) + wantedLod = 6; + else if (dist > cellWorldSize*32) + wantedLod = 5; + else if (dist > cellWorldSize*12) + wantedLod = 4; + else if (dist > cellWorldSize*5) + wantedLod = 3; + else if (dist > cellWorldSize*2) + wantedLod = 2; + else if (dist > cellWorldSize * 1.42) // < sqrt2 so the 3x3 grid around player is always highest lod + wantedLod = 1; - if (loadingListener) - loadingListener->indicateProgress(); + bool wantToDisplay = mSize <= mTerrain->getMaxBatchSize() && mLodLevel <= wantedLod; - if (!distantLand && dist > 8192*2) + if (wantToDisplay) { - if (mIsActive) + // Wanted LOD is small enough to render this node in one chunk + if (mLoadState == LS_Unloaded) { - destroyChunks(true); - mIsActive = false; + mLoadState = LS_Loading; + mTerrain->queueLoad(this); + return false; } - return; - } - mIsActive = true; + if (mLoadState == LS_Loaded) + { + // Additional (index buffer) LOD is currently disabled. + // This is due to a problem with the LOD selection when a node splits. + // After splitting, the distance is measured from the children's bounding boxes, which are possibly + // further away than the original node's bounding box, possibly causing a child to switch to a *lower* LOD + // than the original node. + // In short, we'd sometimes get a switch to a lesser detail when actually moving closer. + // This wouldn't be so bad, but unfortunately it also breaks LOD edge connections if a neighbour + // node hasn't split yet, and has a higher LOD than our node's child: + // ----- ----- ------------ + // | LOD | LOD | | + // | 1 | 1 | | + // |-----|-----| LOD 0 | + // | LOD | LOD | | + // | 0 | 0 | | + // ----- ----- ------------ + // To prevent this, nodes of the same size need to always select the same LOD, which is basically what we're + // doing here. + // But this "solution" does increase triangle overhead, so eventually we need to find a more clever way. + //mChunk->setAdditionalLod(wantedLod - mLodLevel); + + if (!mChunk->getVisible() && hasChildren()) + { + for (int i=0; i<4; ++i) + mChildren[i]->unload(true); + } + mChunk->setVisible(true); + + return true; + } + return false; // LS_Loading + } - if (mSize <= mTerrain->getMaxBatchSize() && mLodLevel <= wantedLod) + // We do not want to display this node - delegate to children if they are already loaded + if (!wantToDisplay && hasChildren()) { - // Wanted LOD is small enough to render this node in one chunk - if (!mChunk) + if (mChunk) { - mChunk = new Chunk(this, mLodLevel); - mChunk->setVisibilityFlags(mTerrain->getVisiblityFlags()); - mChunk->setCastShadows(true); - mSceneNode->attachObject(mChunk); - - mMaterialGenerator->enableShadows(mTerrain->getShadowsEnabled()); - mMaterialGenerator->enableSplitShadows(mTerrain->getSplitShadowsEnabled()); + // Are children already loaded? + bool childrenLoaded = true; + for (int i=0; i<4; ++i) + if (!mChildren[i]->update(cameraPos)) + childrenLoaded = false; - if (mSize == 1) + if (!childrenLoaded) { - ensureLayerInfo(); - mChunk->setMaterial(mMaterialGenerator->generate(mChunk->getMaterial())); + mChunk->setVisible(true); + // Make sure child scene nodes are detached until all children are loaded + mSceneNode->removeAllChildren(); } else { - ensureCompositeMap(); - mMaterialGenerator->setCompositeMap(mCompositeMap->getName()); - mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap(mChunk->getMaterial())); + // Delegation went well, we can unload now + unload(); + + for (int i=0; i<4; ++i) + { + if (!mChildren[i]->getSceneNode()->isInSceneGraph()) + mSceneNode->addChild(mChildren[i]->getSceneNode()); + } } + return true; } - - // Additional (index buffer) LOD is currently disabled. - // This is due to a problem with the LOD selection when a node splits. - // After splitting, the distance is measured from the children's bounding boxes, which are possibly - // further away than the original node's bounding box, possibly causing a child to switch to a *lower* LOD - // than the original node. - // In short, we'd sometimes get a switch to a lesser detail when actually moving closer. - // This wouldn't be so bad, but unfortunately it also breaks LOD edge connections if a neighbour - // node hasn't split yet, and has a higher LOD than our node's child: - // ----- ----- ------------ - // | LOD | LOD | | - // | 1 | 1 | | - // |-----|-----| LOD 0 | - // | LOD | LOD | | - // | 0 | 0 | | - // ----- ----- ------------ - // To prevent this, nodes of the same size need to always select the same LOD, which is basically what we're - // doing here. - // But this "solution" does increase triangle overhead, so eventually we need to find a more clever way. - //mChunk->setAdditionalLod(wantedLod - mLodLevel); - - mChunk->setVisible(true); - - if (!hadChunk && hasChildren()) + else { - // Make sure child scene nodes are detached - mSceneNode->removeAllChildren(); - - // If distant land is enabled, keep the chunks around in case we need them again, - // otherwise, prefer low memory usage - if (!distantLand) - for (int i=0; i<4; ++i) - mChildren[i]->destroyChunks(true); + bool success = true; + for (int i=0; i<4; ++i) + success = mChildren[i]->update(cameraPos) & success; + return success; } } - else + return false; +} + +void QuadTreeNode::load(const LoadResponseData &data) +{ + assert (!mChunk); + + mChunk = new Chunk(mTerrain->getBufferCache().getUVBuffer(), mBounds, data); + mChunk->setVisibilityFlags(mTerrain->getVisiblityFlags()); + mChunk->setCastShadows(true); + mSceneNode->attachObject(mChunk); + + mMaterialGenerator->enableShadows(mTerrain->getShadowsEnabled()); + mMaterialGenerator->enableSplitShadows(mTerrain->getSplitShadowsEnabled()); + + if (mTerrain->areLayersLoaded()) { - // Wanted LOD is too detailed to be rendered in one chunk, - // so split it up by delegating to child nodes - if (hadChunk) + if (mSize == 1) { - // If distant land is enabled, keep the chunks around in case we need them again, - // otherwise, prefer low memory usage - if (!distantLand) - destroyChunks(false); - else if (mChunk) - mChunk->setVisible(false); + mChunk->setMaterial(mMaterialGenerator->generate(mChunk->getMaterial())); + } + else + { + ensureCompositeMap(); + mMaterialGenerator->setCompositeMap(mCompositeMap->getName()); + mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap(mChunk->getMaterial())); } - assert(hasChildren() && "Leaf node's LOD needs to be 0"); - for (int i=0; i<4; ++i) - mChildren[i]->update(cameraPos, loadingListener); } + // else: will be loaded in loadMaterials() after background thread has finished loading layers + mChunk->setVisible(false); + + mLoadState = LS_Loaded; } -void QuadTreeNode::destroyChunks(bool children) +void QuadTreeNode::unload(bool recursive) { if (mChunk) { - Ogre::MaterialManager::getSingleton().remove(mChunk->getMaterial()->getName()); mSceneNode->detachObject(mChunk); delete mChunk; mChunk = NULL; - // destroy blendmaps - if (mMaterialGenerator) - { - const std::vector& list = mMaterialGenerator->getBlendmapList(); - for (std::vector::const_iterator it = list.begin(); it != list.end(); ++it) - Ogre::TextureManager::getSingleton().remove((*it)->getName()); - mMaterialGenerator->setBlendmapList(std::vector()); - mMaterialGenerator->setLayerList(std::vector()); - mMaterialGenerator->setCompositeMap(""); - } if (!mCompositeMap.isNull()) { Ogre::TextureManager::getSingleton().remove(mCompositeMap->getName()); mCompositeMap.setNull(); } + + // Do *not* set this when we are still loading! + mLoadState = LS_Unloaded; } - else if (children && hasChildren()) + + if (recursive && hasChildren()) + { for (int i=0; i<4; ++i) - mChildren[i]->destroyChunks(true); + mChildren[i]->unload(true); + } } void QuadTreeNode::updateIndexBuffers() { if (hasChunk()) - mChunk->updateIndexBuffer(); + { + // Fetch a suitable index buffer (which may be shared) + size_t ourLod = getActualLodLevel(); + + int flags = 0; + + for (int i=0; i<4; ++i) + { + QuadTreeNode* neighbour = getNeighbour((Direction)i); + + // If the neighbour isn't currently rendering itself, + // go up until we find one. NOTE: We don't need to go down, + // because in that case neighbour's detail would be higher than + // our detail and the neighbour would handle stitching by itself. + while (neighbour && !neighbour->hasChunk()) + neighbour = neighbour->getParent(); + size_t lod = 0; + if (neighbour) + lod = neighbour->getActualLodLevel(); + if (lod <= ourLod) // We only need to worry about neighbours less detailed than we are - + lod = 0; // neighbours with more detail will do the stitching themselves + // Use 4 bits for each LOD delta + if (lod > 0) + { + assert (lod - ourLod < (1 << 4)); + flags |= int(lod - ourLod) << (4*i); + } + } + flags |= 0 /*((int)mAdditionalLod)*/ << (4*4); + + mChunk->setIndexBuffer(mTerrain->getBufferCache().getIndexBuffer(flags)); + } else if (hasChildren()) { for (int i=0; i<4; ++i) @@ -407,20 +492,56 @@ bool QuadTreeNode::hasChunk() size_t QuadTreeNode::getActualLodLevel() { assert(hasChunk() && "Can't get actual LOD level if this node has no render chunk"); - return mLodLevel + mChunk->getAdditionalLod(); + return mLodLevel /* + mChunk->getAdditionalLod() */; +} + +void QuadTreeNode::loadLayers(const LayerCollection& collection) +{ + assert (!mMaterialGenerator->hasLayers()); + + std::vector blendTextures; + for (std::vector::const_iterator it = collection.mBlendmaps.begin(); it != collection.mBlendmaps.end(); ++it) + { + // TODO: clean up blend textures on destruction + static int count=0; + Ogre::TexturePtr map = Ogre::TextureManager::getSingleton().createManual("terrain/blend/" + + Ogre::StringConverter::toString(count++), Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + Ogre::TEX_TYPE_2D, it->getWidth(), it->getHeight(), 0, it->format); + + Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(it->data, it->getWidth()*it->getHeight()*Ogre::PixelUtil::getNumElemBytes(it->format), true)); + map->loadRawData(stream, it->getWidth(), it->getHeight(), it->format); + blendTextures.push_back(map); + } + + mMaterialGenerator->setLayerList(collection.mLayers); + mMaterialGenerator->setBlendmapList(blendTextures); } -void QuadTreeNode::ensureLayerInfo() +void QuadTreeNode::loadMaterials() { - if (mMaterialGenerator->hasLayers()) + if (isDummy()) return; - std::vector blendmaps; - std::vector layerList; - mTerrain->getStorage()->getBlendmaps(mSize, mCenter, mTerrain->getShadersEnabled(), blendmaps, layerList); + // Load children first since we depend on them when creating a composite map + if (hasChildren()) + { + for (int i=0; i<4; ++i) + mChildren[i]->loadMaterials(); + } - mMaterialGenerator->setLayerList(layerList); - mMaterialGenerator->setBlendmapList(blendmaps); + if (mChunk) + { + if (mSize == 1) + { + mChunk->setMaterial(mMaterialGenerator->generate(mChunk->getMaterial())); + } + else + { + ensureCompositeMap(); + mMaterialGenerator->setCompositeMap(mCompositeMap->getName()); + mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap(mChunk->getMaterial())); + } + } } void QuadTreeNode::prepareForCompositeMap(Ogre::TRect area) @@ -456,8 +577,7 @@ void QuadTreeNode::prepareForCompositeMap(Ogre::TRect area) } else { - ensureLayerInfo(); - + // TODO: when to destroy? Ogre::MaterialPtr material = mMaterialGenerator->generateForCompositeMapRTT(Ogre::MaterialPtr()); makeQuad(sceneMgr, area.left, area.top, area.right, area.bottom, material); } @@ -501,13 +621,3 @@ void QuadTreeNode::applyMaterials() for (int i=0; i<4; ++i) mChildren[i]->applyMaterials(); } - -void QuadTreeNode::setVisible(bool visible) -{ - if (!visible && mChunk) - mChunk->setVisible(false); - - if (hasChildren()) - for (int i=0; i<4; ++i) - mChildren[i]->setVisible(visible); -} diff --git a/components/terrain/quadtreenode.hpp b/components/terrain/quadtreenode.hpp index ea299c096..c57589487 100644 --- a/components/terrain/quadtreenode.hpp +++ b/components/terrain/quadtreenode.hpp @@ -5,7 +5,7 @@ #include #include -#include +#include "defs.hpp" namespace Ogre { @@ -17,14 +17,7 @@ namespace Terrain class World; class Chunk; class MaterialGenerator; - - enum Direction - { - North = 0, - East = 1, - South = 2, - West = 3 - }; + struct LoadResponseData; enum ChildDirection { @@ -35,6 +28,13 @@ namespace Terrain Root }; + enum LoadState + { + LS_Unloaded, + LS_Loading, + LS_Loaded + }; + /** * @brief A node in the quad tree for our terrain. Depending on LOD, * a node can either choose to render itself in one batch (merging its children), @@ -51,8 +51,6 @@ namespace Terrain QuadTreeNode (World* terrain, ChildDirection dir, float size, const Ogre::Vector2& center, QuadTreeNode* parent); ~QuadTreeNode(); - void setVisible(bool visible); - /// Rebuild all materials void applyMaterials(); @@ -95,10 +93,14 @@ namespace Terrain /// Get bounding box in local coordinates const Ogre::AxisAlignedBox& getBoundingBox(); + const Ogre::AxisAlignedBox& getWorldBoundingBox(); + World* getTerrain() { return mTerrain; } /// Adjust LODs for the given camera position, possibly splitting up chunks or merging them. - void update (const Ogre::Vector3& cameraPos, Loading::Listener* loadingListener); + /// @param force Always choose to render this node, even if not the perfect LOD. + /// @return Did we (or all of our children) choose to render? + bool update (const Ogre::Vector3& cameraPos); /// Adjust index buffers of chunks to stitch together chunks of different LOD, so that cracks are avoided. /// Call after QuadTreeNode::update! @@ -120,17 +122,25 @@ namespace Terrain /// Add a textured quad to a specific 2d area in the composite map scenemanager. /// Only nodes with size <= 1 can be rendered with alpha blending, so larger nodes will simply /// call this method on their children. + /// @note Do not call this before World::areLayersLoaded() == true /// @param area area in image space to put the quad /// @param quads collect quads here so they can be deleted later void prepareForCompositeMap(Ogre::TRect area); + /// Create a chunk for this node from the given data. + void load (const LoadResponseData& data); + void unload(bool recursive=false); + void loadLayers (const LayerCollection& collection); + /// This is recursive! Call it once on the root node after all leafs have loaded layers. + void loadMaterials(); + + LoadState getLoadState() { return mLoadState; } + private: // Stored here for convenience in case we need layer list again MaterialGenerator* mMaterialGenerator; - /// Is this node (or any of its child nodes) currently configured to render itself? - /// (only relevant when distant land is disabled, otherwise whole terrain is always rendered) - bool mIsActive; + LoadState mLoadState; bool mIsDummy; float mSize; @@ -152,7 +162,6 @@ namespace Terrain Ogre::TexturePtr mCompositeMap; - void ensureLayerInfo(); void ensureCompositeMap(); }; diff --git a/components/terrain/storage.hpp b/components/terrain/storage.hpp index 021e01c7e..d3770ea57 100644 --- a/components/terrain/storage.hpp +++ b/components/terrain/storage.hpp @@ -1,24 +1,12 @@ #ifndef COMPONENTS_TERRAIN_STORAGE_H #define COMPONENTS_TERRAIN_STORAGE_H -#include -#include - -#include - #include +#include "defs.hpp" + namespace Terrain { - - struct LayerInfo - { - std::string mDiffuseMap; - std::string mNormalMap; - bool mParallax; // Height info in normal map alpha channel? - bool mSpecular; // Specular info in diffuse map alpha channel? - }; - /// We keep storage of terrain data abstract here since we need different implementations for game and editor class Storage { @@ -27,10 +15,10 @@ namespace Terrain public: /// Get bounds of the whole terrain in cell units - virtual Ogre::AxisAlignedBox getBounds() = 0; + virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY) = 0; - /// Get the minimum and maximum heights of a terrain chunk. - /// @note Should only be called for chunks <= 1 cell, i.e. leafs of the quad tree. + /// Get the minimum and maximum heights of a terrain region. + /// @note Will only be called for chunks with size = minBatchSize, i.e. leafs of the quad tree. /// Larger chunks can simply merge AABB of children. /// @param size size of the chunk in cell units /// @param center center of the chunk in cell units @@ -40,20 +28,23 @@ namespace Terrain virtual bool getMinMaxHeights (float size, const Ogre::Vector2& center, float& min, float& max) = 0; /// Fill vertex buffers for a terrain chunk. + /// @note May be called from background threads. Make sure to only call thread-safe functions from here! + /// @note returned colors need to be in render-system specific format! Use RenderSystem::convertColourValue. /// @param lodLevel LOD level, 0 = most detailed /// @param size size of the terrain chunk in cell units /// @param center center of the chunk in cell units - /// @param vertexBuffer buffer to write vertices - /// @param normalBuffer buffer to write vertex normals - /// @param colourBuffer buffer to write vertex colours - virtual void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, - Ogre::HardwareVertexBufferSharedPtr vertexBuffer, - Ogre::HardwareVertexBufferSharedPtr normalBuffer, - Ogre::HardwareVertexBufferSharedPtr colourBuffer) = 0; + /// @param positions buffer to write vertices + /// @param normals buffer to write vertex normals + /// @param colours buffer to write vertex colours + virtual void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align, + std::vector& positions, + std::vector& normals, + std::vector& colours) = 0; /// Create textures holding layer blend values for a terrain chunk. /// @note The terrain chunk shouldn't be larger than one cell since otherwise we might /// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used. + /// @note May be called from background threads. Make sure to only call thread-safe functions from here! /// @param chunkSize size of the terrain chunk in cell units /// @param chunkCenter center of the chunk in cell units /// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) - @@ -62,9 +53,21 @@ namespace Terrain /// @param blendmaps created blendmaps will be written here /// @param layerList names of the layer textures used will be written here virtual void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack, - std::vector& blendmaps, + std::vector& blendmaps, std::vector& layerList) = 0; + /// Retrieve pixel data for textures holding layer blend values for terrain chunks and layer texture information. + /// This variant is provided to eliminate the overhead of virtual function calls when retrieving a large number of blendmaps at once. + /// @note The terrain chunks shouldn't be larger than one cell since otherwise we might + /// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used. + /// @note May be called from background threads. Make sure to only call thread-safe functions from here! + /// @param nodes A collection of nodes for which to retrieve the aforementioned data + /// @param out Output vector + /// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) - + /// otherwise, each texture contains blend values for one layer only. Shader-based rendering + /// can utilize packing, FFP can't. + virtual void getBlendmaps (const std::vector& nodes, std::vector& out, bool pack) = 0; + virtual float getHeightAt (const Ogre::Vector3& worldPos) = 0; virtual LayerInfo getDefaultLayer() = 0; diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp index f4070393d..844144d7c 100644 --- a/components/terrain/world.cpp +++ b/components/terrain/world.cpp @@ -2,12 +2,12 @@ #include #include -#include #include +#include +#include +#include #include -#include - #include "storage.hpp" #include "quadtreenode.hpp" @@ -51,23 +51,38 @@ namespace namespace Terrain { - World::World(Loading::Listener* loadingListener, Ogre::SceneManager* sceneMgr, - Storage* storage, int visibilityFlags, bool distantLand, bool shaders) + const Ogre::uint REQ_ID_CHUNK = 1; + const Ogre::uint REQ_ID_LAYERS = 2; + + World::World(Ogre::SceneManager* sceneMgr, + Storage* storage, int visibilityFlags, bool distantLand, bool shaders, Alignment align, float minBatchSize, float maxBatchSize) : mStorage(storage) - , mMinBatchSize(1) - , mMaxBatchSize(64) + , mMinBatchSize(minBatchSize) + , mMaxBatchSize(maxBatchSize) , mSceneMgr(sceneMgr) , mVisibilityFlags(visibilityFlags) , mDistantLand(distantLand) , mShaders(shaders) , mVisible(true) - , mLoadingListener(loadingListener) + , mAlign(align) + , mMaxX(0) + , mMinX(0) + , mMaxY(0) + , mMinY(0) + , mChunksLoading(0) + , mWorkQueueChannel(0) + , mCache(storage->getCellVertices()) + , mLayerLoadPending(true) { - loadingListener->setLabel("Creating terrain"); - loadingListener->indicateProgress(); +#if TERRAIN_USE_SHADER == 0 + if (mShaders) + std::cerr << "Compiled Terrain without shader support, disabling..." << std::endl; + mShaders = false; +#endif mCompositeMapSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC); + /// \todo make composite map size configurable Ogre::Camera* compositeMapCam = mCompositeMapSceneMgr->createCamera("a"); mCompositeMapRenderTexture = Ogre::TextureManager::getSingleton().createManual( "terrain/comp/rt", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, @@ -76,35 +91,52 @@ namespace Terrain mCompositeMapRenderTarget->setAutoUpdated(false); mCompositeMapRenderTarget->addViewport(compositeMapCam); - mBounds = storage->getBounds(); + storage->getBounds(mMinX, mMaxX, mMinY, mMaxY); - int origSizeX = mBounds.getSize().x; - int origSizeY = mBounds.getSize().y; + int origSizeX = mMaxX-mMinX; + int origSizeY = mMaxY-mMinY; // Dividing a quad tree only works well for powers of two, so round up to the nearest one int size = nextPowerOfTwo(std::max(origSizeX, origSizeY)); // Adjust the center according to the new size - Ogre::Vector3 center = mBounds.getCenter() + Ogre::Vector3((size-origSizeX)/2.f, (size-origSizeY)/2.f, 0); + float centerX = (mMinX+mMaxX)/2.f + (size-origSizeX)/2.f; + float centerY = (mMinY+mMaxY)/2.f + (size-origSizeY)/2.f; mRootSceneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(); - mRootNode = new QuadTreeNode(this, Root, size, Ogre::Vector2(center.x, center.y), NULL); - buildQuadTree(mRootNode); - loadingListener->indicateProgress(); + // While building the quadtree, remember leaf nodes since we need to load their layers + LayersRequestData data; + data.mPack = getShadersEnabled(); + + mRootNode = new QuadTreeNode(this, Root, size, Ogre::Vector2(centerX, centerY), NULL); + buildQuadTree(mRootNode, data.mNodes); + //loadingListener->indicateProgress(); mRootNode->initAabb(); - loadingListener->indicateProgress(); + //loadingListener->indicateProgress(); mRootNode->initNeighbours(); - loadingListener->indicateProgress(); + //loadingListener->indicateProgress(); + + Ogre::WorkQueue* wq = Ogre::Root::getSingleton().getWorkQueue(); + mWorkQueueChannel = wq->getChannel("LargeTerrain"); + wq->addRequestHandler(mWorkQueueChannel, this); + wq->addResponseHandler(mWorkQueueChannel, this); + + // Start loading layers in the background (for leaf nodes) + wq->addRequest(mWorkQueueChannel, REQ_ID_LAYERS, Ogre::Any(data)); } World::~World() { + Ogre::WorkQueue* wq = Ogre::Root::getSingleton().getWorkQueue(); + wq->removeRequestHandler(mWorkQueueChannel, this); + wq->removeResponseHandler(mWorkQueueChannel, this); + delete mRootNode; delete mStorage; } - void World::buildQuadTree(QuadTreeNode *node) + void World::buildQuadTree(QuadTreeNode *node, std::vector& leafs) { float halfSize = node->getSize()/2.f; @@ -115,17 +147,22 @@ namespace Terrain Ogre::Vector2 center = node->getCenter(); float cellWorldSize = getStorage()->getCellWorldSize(); if (mStorage->getMinMaxHeights(node->getSize(), center, minZ, maxZ)) - node->setBoundingBox(Ogre::AxisAlignedBox(Ogre::Vector3(-halfSize*cellWorldSize, -halfSize*cellWorldSize, minZ), - Ogre::Vector3(halfSize*cellWorldSize, halfSize*cellWorldSize, maxZ))); + { + Ogre::AxisAlignedBox bounds(Ogre::Vector3(-halfSize*cellWorldSize, -halfSize*cellWorldSize, minZ), + Ogre::Vector3(halfSize*cellWorldSize, halfSize*cellWorldSize, maxZ)); + convertBounds(bounds); + node->setBoundingBox(bounds); + leafs.push_back(node); + } else node->markAsDummy(); // no data available for this node, skip it return; } - if (node->getCenter().x - halfSize > mBounds.getMaximum().x - || node->getCenter().x + halfSize < mBounds.getMinimum().x - || node->getCenter().y - halfSize > mBounds.getMaximum().y - || node->getCenter().y + halfSize < mBounds.getMinimum().y ) + if (node->getCenter().x - halfSize > mMaxX + || node->getCenter().x + halfSize < mMinX + || node->getCenter().y - halfSize > mMaxY + || node->getCenter().y + halfSize < mMinY ) // Out of bounds of the actual terrain - this will happen because // we rounded the size up to the next power of two { @@ -138,10 +175,10 @@ namespace Terrain node->createChild(SE, halfSize, node->getCenter() + Ogre::Vector2(halfSize/2.f, -halfSize/2.f)); node->createChild(NW, halfSize, node->getCenter() + Ogre::Vector2(-halfSize/2.f, halfSize/2.f)); node->createChild(NE, halfSize, node->getCenter() + halfSize/2.f); - buildQuadTree(node->getChild(SW)); - buildQuadTree(node->getChild(SE)); - buildQuadTree(node->getChild(NW)); - buildQuadTree(node->getChild(NE)); + buildQuadTree(node->getChild(SW), leafs); + buildQuadTree(node->getChild(SE), leafs); + buildQuadTree(node->getChild(NW), leafs); + buildQuadTree(node->getChild(NE), leafs); // if all children are dummy, we are also dummy for (int i=0; i<4; ++i) @@ -156,218 +193,19 @@ namespace Terrain { if (!mVisible) return; - mRootNode->update(cameraPos, mLoadingListener); + mRootNode->update(cameraPos); mRootNode->updateIndexBuffers(); } Ogre::AxisAlignedBox World::getWorldBoundingBox (const Ogre::Vector2& center) { - if (center.x > mBounds.getMaximum().x - || center.x < mBounds.getMinimum().x - || center.y > mBounds.getMaximum().y - || center.y < mBounds.getMinimum().y) + if (center.x > mMaxX + || center.x < mMinX + || center.y > mMaxY + || center.y < mMinY) return Ogre::AxisAlignedBox::BOX_NULL; QuadTreeNode* node = findNode(center, mRootNode); - Ogre::AxisAlignedBox box = node->getBoundingBox(); - float cellWorldSize = getStorage()->getCellWorldSize(); - box.setExtents(box.getMinimum() + Ogre::Vector3(center.x, center.y, 0) * cellWorldSize, - box.getMaximum() + Ogre::Vector3(center.x, center.y, 0) * cellWorldSize); - return box; - } - - Ogre::HardwareVertexBufferSharedPtr World::getVertexBuffer(int numVertsOneSide) - { - if (mUvBufferMap.find(numVertsOneSide) != mUvBufferMap.end()) - { - return mUvBufferMap[numVertsOneSide]; - } - - int vertexCount = numVertsOneSide * numVertsOneSide; - - std::vector uvs; - uvs.reserve(vertexCount*2); - - for (int col = 0; col < numVertsOneSide; ++col) - { - for (int row = 0; row < numVertsOneSide; ++row) - { - uvs.push_back(col / static_cast(numVertsOneSide-1)); // U - uvs.push_back(row / static_cast(numVertsOneSide-1)); // V - } - } - - Ogre::HardwareBufferManager* mgr = Ogre::HardwareBufferManager::getSingletonPtr(); - Ogre::HardwareVertexBufferSharedPtr buffer = mgr->createVertexBuffer( - Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT2), - vertexCount, Ogre::HardwareBuffer::HBU_STATIC); - - buffer->writeData(0, buffer->getSizeInBytes(), &uvs[0], true); - - mUvBufferMap[numVertsOneSide] = buffer; - return buffer; - } - - Ogre::HardwareIndexBufferSharedPtr World::getIndexBuffer(int flags, size_t& numIndices) - { - unsigned int verts = mStorage->getCellVertices(); - - if (mIndexBufferMap.find(flags) != mIndexBufferMap.end()) - { - numIndices = mIndexBufferMap[flags]->getNumIndexes(); - return mIndexBufferMap[flags]; - } - - // LOD level n means every 2^n-th vertex is kept - size_t lodLevel = (flags >> (4*4)); - - size_t lodDeltas[4]; - for (int i=0; i<4; ++i) - lodDeltas[i] = (flags >> (4*i)) & (0xf); - - bool anyDeltas = (lodDeltas[North] || lodDeltas[South] || lodDeltas[West] || lodDeltas[East]); - - size_t increment = 1 << lodLevel; - assert(increment < verts); - std::vector indices; - indices.reserve((verts-1)*(verts-1)*2*3 / increment); - - size_t rowStart = 0, colStart = 0, rowEnd = verts-1, colEnd = verts-1; - // If any edge needs stitching we'll skip all edges at this point, - // mainly because stitching one edge would have an effect on corners and on the adjacent edges - if (anyDeltas) - { - colStart += increment; - colEnd -= increment; - rowEnd -= increment; - rowStart += increment; - } - for (size_t row = rowStart; row < rowEnd; row += increment) - { - for (size_t col = colStart; col < colEnd; col += increment) - { - indices.push_back(verts*col+row); - indices.push_back(verts*(col+increment)+row+increment); - indices.push_back(verts*col+row+increment); - - indices.push_back(verts*col+row); - indices.push_back(verts*(col+increment)+row); - indices.push_back(verts*(col+increment)+row+increment); - } - } - - size_t innerStep = increment; - if (anyDeltas) - { - // Now configure LOD transitions at the edges - this is pretty tedious, - // and some very long and boring code, but it works great - - // South - size_t row = 0; - size_t outerStep = 1 << (lodDeltas[South] + lodLevel); - for (size_t col = 0; col < verts-1; col += outerStep) - { - indices.push_back(verts*col+row); - indices.push_back(verts*(col+outerStep)+row); - // Make sure not to touch the right edge - if (col+outerStep == verts-1) - indices.push_back(verts*(col+outerStep-innerStep)+row+innerStep); - else - indices.push_back(verts*(col+outerStep)+row+innerStep); - - for (size_t i = 0; i < outerStep; i += innerStep) - { - // Make sure not to touch the left or right edges - if (col+i == 0 || col+i == verts-1-innerStep) - continue; - indices.push_back(verts*(col)+row); - indices.push_back(verts*(col+i+innerStep)+row+innerStep); - indices.push_back(verts*(col+i)+row+innerStep); - } - } - - // North - row = verts-1; - outerStep = 1 << (lodDeltas[North] + lodLevel); - for (size_t col = 0; col < verts-1; col += outerStep) - { - indices.push_back(verts*(col+outerStep)+row); - indices.push_back(verts*col+row); - // Make sure not to touch the left edge - if (col == 0) - indices.push_back(verts*(col+innerStep)+row-innerStep); - else - indices.push_back(verts*col+row-innerStep); - - for (size_t i = 0; i < outerStep; i += innerStep) - { - // Make sure not to touch the left or right edges - if (col+i == 0 || col+i == verts-1-innerStep) - continue; - indices.push_back(verts*(col+i)+row-innerStep); - indices.push_back(verts*(col+i+innerStep)+row-innerStep); - indices.push_back(verts*(col+outerStep)+row); - } - } - - // West - size_t col = 0; - outerStep = 1 << (lodDeltas[West] + lodLevel); - for (size_t row = 0; row < verts-1; row += outerStep) - { - indices.push_back(verts*col+row+outerStep); - indices.push_back(verts*col+row); - // Make sure not to touch the top edge - if (row+outerStep == verts-1) - indices.push_back(verts*(col+innerStep)+row+outerStep-innerStep); - else - indices.push_back(verts*(col+innerStep)+row+outerStep); - - for (size_t i = 0; i < outerStep; i += innerStep) - { - // Make sure not to touch the top or bottom edges - if (row+i == 0 || row+i == verts-1-innerStep) - continue; - indices.push_back(verts*col+row); - indices.push_back(verts*(col+innerStep)+row+i); - indices.push_back(verts*(col+innerStep)+row+i+innerStep); - } - } - - // East - col = verts-1; - outerStep = 1 << (lodDeltas[East] + lodLevel); - for (size_t row = 0; row < verts-1; row += outerStep) - { - indices.push_back(verts*col+row); - indices.push_back(verts*col+row+outerStep); - // Make sure not to touch the bottom edge - if (row == 0) - indices.push_back(verts*(col-innerStep)+row+innerStep); - else - indices.push_back(verts*(col-innerStep)+row); - - for (size_t i = 0; i < outerStep; i += innerStep) - { - // Make sure not to touch the top or bottom edges - if (row+i == 0 || row+i == verts-1-innerStep) - continue; - indices.push_back(verts*col+row+outerStep); - indices.push_back(verts*(col-innerStep)+row+i+innerStep); - indices.push_back(verts*(col-innerStep)+row+i); - } - } - } - - - - numIndices = indices.size(); - - Ogre::HardwareBufferManager* mgr = Ogre::HardwareBufferManager::getSingletonPtr(); - Ogre::HardwareIndexBufferSharedPtr buffer = mgr->createIndexBuffer(Ogre::HardwareIndexBuffer::IT_16BIT, - numIndices, Ogre::HardwareBuffer::HBU_STATIC); - buffer->writeData(0, buffer->getSizeInBytes(), &indices[0], true); - mIndexBufferMap[flags] = buffer; - return buffer; + return node->getWorldBoundingBox(); } void World::renderCompositeMap(Ogre::TexturePtr target) @@ -409,5 +247,108 @@ namespace Terrain return mVisible; } + void World::convertPosition(float &x, float &y, float &z) + { + Terrain::convertPosition(mAlign, x, y, z); + } + + void World::convertPosition(Ogre::Vector3 &pos) + { + convertPosition(pos.x, pos.y, pos.z); + } + + void World::convertBounds(Ogre::AxisAlignedBox& bounds) + { + switch (mAlign) + { + case Align_XY: + return; + case Align_XZ: + convertPosition(bounds.getMinimum()); + convertPosition(bounds.getMaximum()); + // Because we changed sign of Z + std::swap(bounds.getMinimum().z, bounds.getMaximum().z); + return; + case Align_YZ: + convertPosition(bounds.getMinimum()); + convertPosition(bounds.getMaximum()); + return; + } + } + + void World::syncLoad() + { + while (mChunksLoading || mLayerLoadPending) + { + OGRE_THREAD_SLEEP(0); + Ogre::Root::getSingleton().getWorkQueue()->processResponses(); + } + } + + Ogre::WorkQueue::Response* World::handleRequest(const Ogre::WorkQueue::Request *req, const Ogre::WorkQueue *srcQ) + { + if (req->getType() == REQ_ID_CHUNK) + { + const LoadRequestData data = Ogre::any_cast(req->getData()); + + QuadTreeNode* node = data.mNode; + + LoadResponseData* responseData = new LoadResponseData(); + + getStorage()->fillVertexBuffers(node->getNativeLodLevel(), node->getSize(), node->getCenter(), getAlign(), + responseData->mPositions, responseData->mNormals, responseData->mColours); + + return OGRE_NEW Ogre::WorkQueue::Response(req, true, Ogre::Any(responseData)); + } + else // REQ_ID_LAYERS + { + const LayersRequestData data = Ogre::any_cast(req->getData()); + + LayersResponseData* responseData = new LayersResponseData(); + + getStorage()->getBlendmaps(data.mNodes, responseData->mLayerCollections, data.mPack); + + return OGRE_NEW Ogre::WorkQueue::Response(req, true, Ogre::Any(responseData)); + } + } + + void World::handleResponse(const Ogre::WorkQueue::Response *res, const Ogre::WorkQueue *srcQ) + { + assert(res->succeeded() && "Response failure not handled"); + + if (res->getRequest()->getType() == REQ_ID_CHUNK) + { + LoadResponseData* data = Ogre::any_cast(res->getData()); + + const LoadRequestData requestData = Ogre::any_cast(res->getRequest()->getData()); + + requestData.mNode->load(*data); + + delete data; + + --mChunksLoading; + } + else // REQ_ID_LAYERS + { + LayersResponseData* data = Ogre::any_cast(res->getData()); + + for (std::vector::iterator it = data->mLayerCollections.begin(); it != data->mLayerCollections.end(); ++it) + { + it->mTarget->loadLayers(*it); + } + + mRootNode->loadMaterials(); + + mLayerLoadPending = false; + } + } + + void World::queueLoad(QuadTreeNode *node) + { + LoadRequestData data; + data.mNode = node; + Ogre::Root::getSingleton().getWorkQueue()->addRequest(mWorkQueueChannel, REQ_ID_CHUNK, Ogre::Any(data)); + ++mChunksLoading; + } } diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index b8c1b0a7d..26a6d034d 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -1,15 +1,12 @@ #ifndef COMPONENTS_TERRAIN_H #define COMPONENTS_TERRAIN_H -#include -#include #include #include +#include -namespace Loading -{ - class Listener; -} +#include "defs.hpp" +#include "buffercache.hpp" namespace Ogre { @@ -29,11 +26,10 @@ namespace Terrain * Cracks at LOD transitions are avoided using stitching. * @note Multiple cameras are not supported yet */ - class World + class World : public Ogre::WorkQueue::RequestHandler, public Ogre::WorkQueue::ResponseHandler { public: /// @note takes ownership of \a storage - /// @param loadingListener Listener to update with progress /// @param sceneMgr scene manager to use /// @param storage Storage instance to get terrain data from (heights, normals, colors, textures..) /// @param visbilityFlags visibility flags for the created meshes @@ -41,12 +37,13 @@ namespace Terrain /// This is a temporary option until it can be streamlined. /// @param shaders Whether to use splatting shader, or multi-pass fixed function splatting. Shader is usually /// faster so this is just here for compatibility. - World(Loading::Listener* loadingListener, Ogre::SceneManager* sceneMgr, - Storage* storage, int visiblityFlags, bool distantLand, bool shaders); + /// @param align The align of the terrain, see Alignment enum + /// @param minBatchSize Minimum size of a terrain batch along one side (in cell units). Used for building the quad tree. + /// @param maxBatchSize Maximum size of a terrain batch along one side (in cell units). Used when traversing the quad tree. + World(Ogre::SceneManager* sceneMgr, + Storage* storage, int visiblityFlags, bool distantLand, bool shaders, Alignment align, float minBatchSize, float maxBatchSize); ~World(); - void setLoadingListener(Loading::Listener* loadingListener) { mLoadingListener = loadingListener; } - bool getDistantLandEnabled() { return mDistantLand; } bool getShadersEnabled() { return mShaders; } bool getShadowsEnabled() { return mShadows; } @@ -86,14 +83,24 @@ namespace Terrain void enableSplattingShader(bool enabled); + Alignment getAlign() { return mAlign; } + + /// Wait until all background loading is complete. + void syncLoad(); + private: + // Called from a background worker thread + Ogre::WorkQueue::Response* handleRequest(const Ogre::WorkQueue::Request* req, const Ogre::WorkQueue* srcQ); + // Called from the main thread + void handleResponse(const Ogre::WorkQueue::Response* res, const Ogre::WorkQueue* srcQ); + Ogre::uint16 mWorkQueueChannel; + bool mDistantLand; bool mShaders; bool mShadows; bool mSplitShadows; bool mVisible; - - Loading::Listener* mLoadingListener; + Alignment mAlign; QuadTreeNode* mRootNode; Ogre::SceneNode* mRootSceneNode; @@ -101,54 +108,86 @@ namespace Terrain int mVisibilityFlags; + /// The number of chunks currently loading in a background thread. If 0, we have finished loading! + int mChunksLoading; + Ogre::SceneManager* mSceneMgr; Ogre::SceneManager* mCompositeMapSceneMgr; /// Bounds in cell units - Ogre::AxisAlignedBox mBounds; + float mMinX, mMaxX, mMinY, mMaxY; /// Minimum size of a terrain batch along one side (in cell units) float mMinBatchSize; /// Maximum size of a terrain batch along one side (in cell units) float mMaxBatchSize; - void buildQuadTree(QuadTreeNode* node); + void buildQuadTree(QuadTreeNode* node, std::vector& leafs); - public: - // ----INTERNAL---- - - enum IndexBufferFlags - { - IBF_North = 1 << 0, - IBF_East = 1 << 1, - IBF_South = 1 << 2, - IBF_West = 1 << 3 - }; + BufferCache mCache; - /// @param flags first 4*4 bits are LOD deltas on each edge, respectively (4 bits each) - /// next 4 bits are LOD level of the index buffer (LOD 0 = don't omit any vertices) - /// @param numIndices number of indices that were used will be written here - Ogre::HardwareIndexBufferSharedPtr getIndexBuffer (int flags, size_t& numIndices); - - Ogre::HardwareVertexBufferSharedPtr getVertexBuffer (int numVertsOneSide); + // Are layers for leaf nodes loaded? This is done once at startup (but in a background thread) + bool mLayerLoadPending; + public: + // ----INTERNAL---- Ogre::SceneManager* getCompositeMapSceneManager() { return mCompositeMapSceneMgr; } + BufferCache& getBufferCache() { return mCache; } + + bool areLayersLoaded() { return !mLayerLoadPending; } // Delete all quads void clearCompositeMapSceneManager(); void renderCompositeMap (Ogre::TexturePtr target); - private: - // Index buffers are shared across terrain batches where possible. There is one index buffer for each - // combination of LOD deltas and index buffer LOD we may need. - std::map mIndexBufferMap; + // Convert the given position from Z-up align, i.e. Align_XY to the wanted align set in mAlign + void convertPosition (float& x, float& y, float& z); + void convertPosition (Ogre::Vector3& pos); + void convertBounds (Ogre::AxisAlignedBox& bounds); - std::map mUvBufferMap; + // Adds a WorkQueue request to load a chunk for this node in the background. + void queueLoad (QuadTreeNode* node); + private: Ogre::RenderTarget* mCompositeMapRenderTarget; Ogre::TexturePtr mCompositeMapRenderTexture; }; + struct LoadRequestData + { + QuadTreeNode* mNode; + + friend std::ostream& operator<<(std::ostream& o, const LoadRequestData& r) + { return o; } + }; + + struct LoadResponseData + { + std::vector mPositions; + std::vector mNormals; + std::vector mColours; + + friend std::ostream& operator<<(std::ostream& o, const LoadResponseData& r) + { return o; } + }; + + struct LayersRequestData + { + std::vector mNodes; + bool mPack; + + friend std::ostream& operator<<(std::ostream& o, const LayersRequestData& r) + { return o; } + }; + + struct LayersResponseData + { + std::vector mLayerCollections; + + friend std::ostream& operator<<(std::ostream& o, const LayersResponseData& r) + { return o; } + }; + } #endif diff --git a/credits.txt b/credits.txt index 561931cde..601255763 100644 --- a/credits.txt +++ b/credits.txt @@ -20,6 +20,7 @@ Artem Kotsynyak (greye) athile Britt Mathis (galdor557) BrotherBrick +cc9cii Chris Robinson (KittyCat) Cory F. Cohen (cfcohen) Cris Mihalache (Mirceam) diff --git a/extern/oics/ICSInputControlSystem.h b/extern/oics/ICSInputControlSystem.h index 907cba5fc..a83ae539e 100644 --- a/extern/oics/ICSInputControlSystem.h +++ b/extern/oics/ICSInputControlSystem.h @@ -102,19 +102,19 @@ namespace ICS JoystickIDList& getJoystickIdList(){ return mJoystickIDList; }; // MouseListener - bool mouseMoved(const SFO::MouseMotionEvent &evt); - bool mousePressed(const SDL_MouseButtonEvent &evt, Uint8); - bool mouseReleased(const SDL_MouseButtonEvent &evt, Uint8); + void mouseMoved(const SFO::MouseMotionEvent &evt); + void mousePressed(const SDL_MouseButtonEvent &evt, Uint8); + void mouseReleased(const SDL_MouseButtonEvent &evt, Uint8); // KeyListener - bool keyPressed(const SDL_KeyboardEvent &evt); - bool keyReleased(const SDL_KeyboardEvent &evt); + void keyPressed(const SDL_KeyboardEvent &evt); + void keyReleased(const SDL_KeyboardEvent &evt); // JoyStickListener - bool buttonPressed(const SDL_JoyButtonEvent &evt, int button); - bool buttonReleased(const SDL_JoyButtonEvent &evt, int button); - bool axisMoved(const SDL_JoyAxisEvent &evt, int axis); - bool povMoved(const SDL_JoyHatEvent &evt, int index); + void buttonPressed(const SDL_JoyButtonEvent &evt, int button); + void buttonReleased(const SDL_JoyButtonEvent &evt, int button); + void axisMoved(const SDL_JoyAxisEvent &evt, int axis); + void povMoved(const SDL_JoyHatEvent &evt, int index); //TODO: does this have an SDL equivalent? //bool sliderMoved(const OIS::JoyStickEvent &evt, int index); diff --git a/extern/oics/ICSInputControlSystem_joystick.cpp b/extern/oics/ICSInputControlSystem_joystick.cpp index 8e501d501..35762d2b3 100644 --- a/extern/oics/ICSInputControlSystem_joystick.cpp +++ b/extern/oics/ICSInputControlSystem_joystick.cpp @@ -318,7 +318,7 @@ namespace ICS } // joyStick listeners - bool InputControlSystem::buttonPressed(const SDL_JoyButtonEvent &evt, int button) + void InputControlSystem::buttonPressed(const SDL_JoyButtonEvent &evt, int button) { if(mActive) { @@ -354,11 +354,9 @@ namespace ICS mDetectingBindingControl, evt.which, button, mDetectingBindingDirection); } } - - return true; } - bool InputControlSystem::buttonReleased(const SDL_JoyButtonEvent &evt, int button) + void InputControlSystem::buttonReleased(const SDL_JoyButtonEvent &evt, int button) { if(mActive) { @@ -371,10 +369,9 @@ namespace ICS } } } - return true; } - bool InputControlSystem::axisMoved(const SDL_JoyAxisEvent &evt, int axis) + void InputControlSystem::axisMoved(const SDL_JoyAxisEvent &evt, int axis) { if(mActive) { @@ -388,7 +385,7 @@ namespace ICS { ctrl->setIgnoreAutoReverse(true); - float axisRange = SDL_JOY_AXIS_MAX - SDL_JOY_AXIS_MAX; + float axisRange = SDL_JOY_AXIS_MAX - SDL_JOY_AXIS_MIN; float valDisplaced = (float)(evt.value - SDL_JOY_AXIS_MIN); if(joystickBinderItem.direction == Control::INCREASE) @@ -417,12 +414,10 @@ namespace ICS } } } - - return true; } //Here be dragons, apparently - bool InputControlSystem::povMoved(const SDL_JoyHatEvent &evt, int index) + void InputControlSystem::povMoved(const SDL_JoyHatEvent &evt, int index) { if(mActive) { @@ -542,13 +537,11 @@ namespace ICS } } } - - return true; } //TODO: does this have an SDL equivalent? /* - bool InputControlSystem::sliderMoved(const OIS::JoyStickEvent &evt, int index) + void InputControlSystem::sliderMoved(const OIS::JoyStickEvent &evt, int index) { if(mActive) { @@ -590,8 +583,6 @@ namespace ICS } } } - - return true; } */ diff --git a/extern/oics/ICSInputControlSystem_keyboard.cpp b/extern/oics/ICSInputControlSystem_keyboard.cpp index 01d68f784..0a9a34d63 100644 --- a/extern/oics/ICSInputControlSystem_keyboard.cpp +++ b/extern/oics/ICSInputControlSystem_keyboard.cpp @@ -85,7 +85,7 @@ namespace ICS return SDLK_UNKNOWN; } - bool InputControlSystem::keyPressed(const SDL_KeyboardEvent &evt) + void InputControlSystem::keyPressed(const SDL_KeyboardEvent &evt) { if(mActive) { @@ -118,11 +118,9 @@ namespace ICS mDetectingBindingControl, evt.keysym.sym, mDetectingBindingDirection); } } - - return true; - } + } - bool InputControlSystem::keyReleased(const SDL_KeyboardEvent &evt) + void InputControlSystem::keyReleased(const SDL_KeyboardEvent &evt) { if(mActive) { @@ -132,8 +130,6 @@ namespace ICS it->second.control->setChangingDirection(Control::STOP); } } - - return true; } void DetectingBindingListener::keyBindingDetected(InputControlSystem* ICS, Control* control diff --git a/extern/oics/ICSInputControlSystem_mouse.cpp b/extern/oics/ICSInputControlSystem_mouse.cpp index 52eb894ed..be18ebbc0 100644 --- a/extern/oics/ICSInputControlSystem_mouse.cpp +++ b/extern/oics/ICSInputControlSystem_mouse.cpp @@ -219,7 +219,7 @@ namespace ICS } // mouse Listeners - bool InputControlSystem::mouseMoved(const SFO::MouseMotionEvent& evt) + void InputControlSystem::mouseMoved(const SFO::MouseMotionEvent& evt) { if(mActive) { @@ -304,11 +304,9 @@ namespace ICS } } } - - return true; } - bool InputControlSystem::mousePressed(const SDL_MouseButtonEvent &evt, Uint8 btn) + void InputControlSystem::mousePressed(const SDL_MouseButtonEvent &evt, Uint8 btn) { if(mActive) { @@ -341,11 +339,9 @@ namespace ICS mDetectingBindingControl, btn, mDetectingBindingDirection); } } - - return true; } - bool InputControlSystem::mouseReleased(const SDL_MouseButtonEvent &evt, Uint8 btn) + void InputControlSystem::mouseReleased(const SDL_MouseButtonEvent &evt, Uint8 btn) { if(mActive) { @@ -355,8 +351,6 @@ namespace ICS it->second.control->setChangingDirection(Control::STOP); } } - - return true; } // mouse auto bindings diff --git a/extern/sdl4ogre/events.h b/extern/sdl4ogre/events.h index 48adb4545..0fb4d6f06 100644 --- a/extern/sdl4ogre/events.h +++ b/extern/sdl4ogre/events.h @@ -26,9 +26,9 @@ class MouseListener { public: virtual ~MouseListener() {} - virtual bool mouseMoved( const MouseMotionEvent &arg ) = 0; - virtual bool mousePressed( const SDL_MouseButtonEvent &arg, Uint8 id ) = 0; - virtual bool mouseReleased( const SDL_MouseButtonEvent &arg, Uint8 id ) = 0; + virtual void mouseMoved( const MouseMotionEvent &arg ) = 0; + virtual void mousePressed( const SDL_MouseButtonEvent &arg, Uint8 id ) = 0; + virtual void mouseReleased( const SDL_MouseButtonEvent &arg, Uint8 id ) = 0; }; class KeyListener @@ -36,8 +36,8 @@ class KeyListener public: virtual ~KeyListener() {} virtual void textInput (const SDL_TextInputEvent& arg) {} - virtual bool keyPressed(const SDL_KeyboardEvent &arg) = 0; - virtual bool keyReleased(const SDL_KeyboardEvent &arg) = 0; + virtual void keyPressed(const SDL_KeyboardEvent &arg) = 0; + virtual void keyReleased(const SDL_KeyboardEvent &arg) = 0; }; class JoyListener @@ -45,18 +45,18 @@ class JoyListener public: virtual ~JoyListener() {} /** @remarks Joystick button down event */ - virtual bool buttonPressed( const SDL_JoyButtonEvent &evt, int button ) = 0; + virtual void buttonPressed( const SDL_JoyButtonEvent &evt, int button ) = 0; /** @remarks Joystick button up event */ - virtual bool buttonReleased( const SDL_JoyButtonEvent &evt, int button ) = 0; + virtual void buttonReleased( const SDL_JoyButtonEvent &evt, int button ) = 0; /** @remarks Joystick axis moved event */ - virtual bool axisMoved( const SDL_JoyAxisEvent &arg, int axis ) = 0; + virtual void axisMoved( const SDL_JoyAxisEvent &arg, int axis ) = 0; //-- Not so common control events, so are not required --// //! Joystick Event, and povID - virtual bool povMoved( const SDL_JoyHatEvent &arg, int index) {return true;} + virtual void povMoved( const SDL_JoyHatEvent &arg, int index) {} }; class WindowListener diff --git a/extern/sdl4ogre/sdlcursormanager.cpp b/extern/sdl4ogre/sdlcursormanager.cpp index 65fb7f98b..5ef274b7e 100644 --- a/extern/sdl4ogre/sdlcursormanager.cpp +++ b/extern/sdl4ogre/sdlcursormanager.cpp @@ -1,6 +1,7 @@ #include "sdlcursormanager.hpp" #include +#include #include #include diff --git a/extern/sdl4ogre/sdlwindowhelper.cpp b/extern/sdl4ogre/sdlwindowhelper.cpp index f819043cf..2a14cc6b4 100644 --- a/extern/sdl4ogre/sdlwindowhelper.cpp +++ b/extern/sdl4ogre/sdlwindowhelper.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include diff --git a/extern/shiny/Main/Preprocessor.hpp b/extern/shiny/Main/Preprocessor.hpp index 7ee30ae7f..4eb533499 100644 --- a/extern/shiny/Main/Preprocessor.hpp +++ b/extern/shiny/Main/Preprocessor.hpp @@ -1,6 +1,8 @@ #ifndef SH_PREPROCESSOR_H #define SH_PREPROCESSOR_H +#include + #include #include diff --git a/extern/shiny/Platforms/Ogre/OgreTextureUnitState.cpp b/extern/shiny/Platforms/Ogre/OgreTextureUnitState.cpp index f215f4ab7..ad8e6d2b0 100644 --- a/extern/shiny/Platforms/Ogre/OgreTextureUnitState.cpp +++ b/extern/shiny/Platforms/Ogre/OgreTextureUnitState.cpp @@ -1,5 +1,7 @@ #include "OgreTextureUnitState.hpp" +#include + #include #include diff --git a/files/mygui/openmw_mainmenu.layout b/files/mygui/openmw_mainmenu.layout index 4479a121f..e8cb23b77 100644 --- a/files/mygui/openmw_mainmenu.layout +++ b/files/mygui/openmw_mainmenu.layout @@ -2,5 +2,11 @@ - + + + + + + + diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index 481b99bad..4484d9862 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -23,7 +23,7 @@ namespace Physic mBody = mEngine->createAndAdjustRigidBody(mMesh, mName, scale, position, rotation, &mBoxScaledTranslation, &mBoxRotation); mRaycastingBody = mEngine->createAndAdjustRigidBody(mMesh, mName, scale, position, rotation, &mBoxScaledTranslation, &mBoxRotation, true); Ogre::Quaternion inverse = mBoxRotation.Inverse(); - mBoxRotationInverse = btQuaternion(inverse.x, inverse.y, inverse.z,inverse.w); + mBoxRotationInverse = Ogre::Quaternion(inverse.w, inverse.x, inverse.y,inverse.z); mEngine->addRigidBody(mBody, false, mRaycastingBody,true); //Add rigid body to dynamics world, but do not add to object map } @@ -85,8 +85,8 @@ namespace Physic Ogre::Quaternion PhysicActor::getRotation() { assert(mBody); - btQuaternion quat = mBody->getWorldTransform().getRotation() * mBoxRotationInverse; - return Ogre::Quaternion(quat.getW(), quat.getX(), quat.getY(), quat.getZ()); + btQuaternion quat = mBody->getWorldTransform().getRotation(); + return Ogre::Quaternion(quat.getW(), quat.getX(), quat.getY(), quat.getZ()) * mBoxRotationInverse; } void PhysicActor::setScale(float scale){ @@ -597,6 +597,8 @@ namespace Physic std::vector PhysicEngine::getCollisions(const std::string& name) { RigidBody* body = getRigidBody(name); + if (!body) // fall back to raycasting body if there is no collision body + body = getRigidBody(name, true); ContactTestResultCallback callback; dynamicsWorld->contactTest(body, callback); return callback.mResult; diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp index 6cd7244b8..4ef611dc8 100644 --- a/libs/openengine/bullet/physic.hpp +++ b/libs/openengine/bullet/physic.hpp @@ -162,7 +162,7 @@ namespace Physic Ogre::Vector3 mBoxScaledTranslation; Ogre::Quaternion mBoxRotation; - btQuaternion mBoxRotationInverse; + Ogre::Quaternion mBoxRotationInverse; Ogre::Vector3 mForce; bool mOnGround; diff --git a/libs/openengine/ogre/fader.cpp b/libs/openengine/ogre/fader.cpp index 923b0b7e3..20b9296da 100644 --- a/libs/openengine/ogre/fader.cpp +++ b/libs/openengine/ogre/fader.cpp @@ -6,6 +6,7 @@ #include #include #include +#include using namespace Ogre; diff --git a/libs/openengine/ogre/imagerotate.cpp b/libs/openengine/ogre/imagerotate.cpp index 9c32924f1..cc5f572cf 100644 --- a/libs/openengine/ogre/imagerotate.cpp +++ b/libs/openengine/ogre/imagerotate.cpp @@ -8,6 +8,13 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include using namespace Ogre; using namespace OEngine::Render; diff --git a/libs/openengine/ogre/renderer.cpp b/libs/openengine/ogre/renderer.cpp index c86697497..c816f2060 100644 --- a/libs/openengine/ogre/renderer.cpp +++ b/libs/openengine/ogre/renderer.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include diff --git a/libs/openengine/ogre/selectionbuffer.cpp b/libs/openengine/ogre/selectionbuffer.cpp index 69375b74d..5aeb35c28 100644 --- a/libs/openengine/ogre/selectionbuffer.cpp +++ b/libs/openengine/ogre/selectionbuffer.cpp @@ -5,6 +5,9 @@ #include #include #include +#include +#include + #include #include diff --git a/libs/openengine/ogre/selectionbuffer.hpp b/libs/openengine/ogre/selectionbuffer.hpp index c487b24b0..b9b4cd9d9 100644 --- a/libs/openengine/ogre/selectionbuffer.hpp +++ b/libs/openengine/ogre/selectionbuffer.hpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace OEngine { diff --git a/libs/platform/stdint.h b/libs/platform/stdint.h deleted file mode 100644 index 00af741b1..000000000 --- a/libs/platform/stdint.h +++ /dev/null @@ -1,21 +0,0 @@ -// Wrapper for MSVC -#ifndef _STDINT_WRAPPER_H -#define _STDINT_WRAPPER_H - -#if (_MSC_VER >= 1600) - -#include - -#else - -#include - -// Pull the boost names into the global namespace for convenience -using boost::int32_t; -using boost::uint32_t; -using boost::int64_t; -using boost::uint64_t; - -#endif - -#endif diff --git a/readme.txt b/readme.txt index 045b18b8b..bb17a68e7 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ OpenMW: A reimplementation of The Elder Scrolls III: Morrowind OpenMW is an attempt at recreating the engine for the popular role-playing game Morrowind by Bethesda Softworks. You need to own and install the original game for OpenMW to work. -Version: 0.28.0 +Version: 0.29.0 License: GPL (see GPL3.txt for more information) Website: http://www.openmw.org @@ -97,6 +97,74 @@ Allowed options: CHANGELOG +0.29.0 + +Bug #556: Video soundtrack not played when music volume is set to zero +Bug #829: OpenMW uses up all available vram, when playing for extended time +Bug #848: Wrong amount of footsteps playing in 1st person +Bug #888: Ascended Sleepers have movement issues +Bug #892: Explicit references are allowed on all script functions +Bug #999: Graphic Herbalism (mod): sometimes doesn't activate properly +Bug #1009: Lake Fjalding AI related slowdown. +Bug #1041: Music playback issues on OS X >= 10.9 +Bug #1043: No message box when advancing skill "Speechcraft" while in dialog window +Bug #1060: Some message boxes are cut off at the bottom +Bug #1062: Bittercup script does not work ('end' variable) +Bug #1074: Inventory paperdoll obscures armour rating +Bug #1077: Message after killing an essential NPC disappears too fast +Bug #1078: "Clutterbane" shows empty charge bar +Bug #1083: UndoWerewolf fails +Bug #1088: Better Clothes Bloodmoon Plus 1.5 by Spirited Treasure pants are not rendered +Bug #1090: Start scripts fail when going to a non-predefined cell +Bug #1091: Crash: Assertion `!q.isNaN() && "Invalid orientation supplied as parameter"' failed. +Bug #1093: Weapons of aggressive NPCs are invisible after you exit and re-enter interior +Bug #1105: Magicka is depleted when using uncastable spells +Bug #1106: Creatures should be able to run +Bug #1107: TR cliffs have way too huge collision boxes in OpenMW +Bug #1109: Cleaning True Light and Darkness with Tes3cmd makes Addamasartus , Zenarbael and Yasamsi flooded. +Bug #1114: Bad output for desktop-file-validate on openmw.desktop (and opencs.desktop) +Bug #1115: Memory leak when spying on Fargoth +Bug #1137: Script execution fails (drenSlaveOwners script) +Bug #1143: Mehra Milo quest (vivec informants) is broken +Bug #1145: Issues with moving gold between inventory and containers +Bug #1146: Issues with picking up stacks of gold +Bug #1147: Dwemer Crossbows are held incorrectly +Bug #1158: Armor rating should always stay below inventory mannequin +Bug #1159: Quick keys can be set during character generation +Bug #1160: Crash on equip lockpick when +Bug #1167: Editor: Referenceables are not correctly loaded when dealing with more than one content file +Bug #1184: Game Save: overwriting an existing save does not actually overwrites the file +Feature #30: Loading/Saving (still missing a few parts) +Feature #101: AI Package: Activate +Feature #103: AI Package: Follow, FollowCell +Feature #138: Editor: Drag & Drop +Feature #428: Player death +Feature #505: Editor: Record Cloning +Feature #701: Levelled creatures +Feature #708: Improved Local Variable handling +Feature #709: Editor: Script verifier +Feature #764: Missing journal backend features +Feature #777: Creature weapons/shields +Feature #789: Editor: Referenceable record verifier +Feature #924: Load/Save GUI (still missing loading screen and progress bars) +Feature #946: Knockdown +Feature #947: Decrease fatigue when running, swimming and attacking +Feature #956: Melee Combat: Blocking +Feature #957: Area magic +Feature #960: Combat/AI combat for creatures +Feature #962: Combat-Related AI instructions +Feature #1075: Damage/Restore skill/attribute magic effects +Feature #1076: Soultrap magic effect +Feature #1081: Disease contraction +Feature #1086: Blood particles +Feature #1092: Interrupt resting +Feature #1101: Inventory equip scripts +Feature #1116: Version/Build number in Launcher window +Feature #1119: Resistance/weakness to normal weapons magic effect +Feature #1123: Slow Fall magic effect +Feature #1130: Auto-calculate spells +Feature #1164: Editor: Case-insensitive sorting in tables + 0.28.0 Bug #399: Inventory changes are not visible immediately