diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 1ce6250b3..000000000 --- a/.gitmodules +++ /dev/null @@ -1,6 +0,0 @@ -[submodule "libs/mangle"] - path = libs/mangle - url = git://github.com/zinnschlag/mangle.git -[submodule "libs/openengine"] - path = libs/openengine - url = git://github.com/zinnschlag/OpenEngine diff --git a/CMakeLists.txt b/CMakeLists.txt index 6fb0ee34c..ca747f9a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -255,6 +255,19 @@ if (APPLE) "${APP_BUNDLE_DIR}/Contents/Resources/OpenMW.icns" COPYONLY) # prepare plugins + if (${CMAKE_BUILD_TYPE} MATCHES "Release") + set(OPENMW_RELEASE_BUILD 1) + endif() + if (${CMAKE_BUILD_TYPE} MATCHES "RelWithDebugInfo") + set(OPENMW_RELEASE_BUILD 1) + endif() + + if (${OPENMW_RELEASE_BUILD}) + set(OGRE_PLUGIN_DIR ${OGRE_PLUGIN_DIR_REL}) + else() + set(OGRE_PLUGIN_DIR ${OGRE_PLUGIN_DIR_DBG}) + endif() + foreach(plugin ${USED_OGRE_PLUGINS}) configure_file("${OGRE_PLUGIN_DIR}/${plugin}.dylib" "${APP_BUNDLE_DIR}/Contents/Plugins/${plugin}.dylib" @@ -343,6 +356,9 @@ if(WIN32) SET(CPACK_NSIS_HELP_LINK "http:\\\\\\\\www.openmw.org") SET(CPACK_NSIS_URL_INFO_ABOUT "http:\\\\\\\\www.openmw.org") SET(CPACK_NSIS_INSTALLED_ICON_NAME "omwlauncher.exe") + SET(CPACK_NSIS_MUI_ICON "${OpenMW_SOURCE_DIR}/apps/launcher/resources/images/openmw.ico") + SET(CPACK_NSIS_MUI_UNIICON "${OpenMW_SOURCE_DIR}/apps/launcher/resources/images/openmw.ico") + # SET(CPACK_PACKAGE_ICON "${OpenMW_SOURCE_DIR}\\\\files\\\\openmw.bmp") SET(VCREDIST32 "${OpenMW_BINARY_DIR}/vcredist_x86.exe") if(EXISTS ${VCREDIST32}) @@ -434,7 +450,7 @@ if (APPLE) install(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" RENAME "openmw.cfg" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) install(FILES "${OpenMW_BINARY_DIR}/plugins.cfg" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) - install(FILES "${OpenMW_BINARY_DIR}/launcher.qss" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) + set(CPACK_GENERATOR "DragNDrop") set(CPACK_PACKAGE_VERSION ${OPENMW_VERSION}) set(CPACK_PACKAGE_VERSION_MAJOR ${OPENMW_VERSION_MAJOR}) diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index 319bbbc57..a34ad7429 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -41,10 +41,11 @@ source_group(launcher FILES ${LAUNCHER} ${LAUNCHER_HEADER} ${LAUNCHER_HEADER_MOC find_package(Qt4 REQUIRED) set(QT_USE_QTGUI 1) -if (NOT APPLE) # this dependency can be completely removed, but now it only tested on OS X - find_package(PNG REQUIRED) - include_directories(${PNG_INCLUDE_DIR}) -endif() +# Set some platform specific settings +if(WIN32) + set(GUI_TYPE WIN32) + set(QT_USE_QTMAIN TRUE) +endif(WIN32) QT4_ADD_RESOURCES(RCC_SRCS resources.qrc) QT4_WRAP_CPP(MOC_SRCS ${LAUNCHER_HEADER_MOC}) @@ -53,6 +54,7 @@ include(${QT_USE_FILE}) # Main executable add_executable(omwlauncher + ${GUI_TYPE} ${LAUNCHER} ${RCC_SRCS} ${MOC_SRCS} @@ -62,7 +64,6 @@ target_link_libraries(omwlauncher ${Boost_LIBRARIES} ${OGRE_LIBRARIES} ${QT_LIBRARIES} - ${PNG_LIBRARY} components ) @@ -84,5 +85,5 @@ else() "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/launcher.qss") configure_file(${CMAKE_SOURCE_DIR}/files/launcher.cfg - "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/launcher.cfg") + "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}launcher.cfg") endif() diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index b88664f0c..054cbf141 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -1,8 +1,6 @@ #include #include -#include -#include #include #include "datafilespage.hpp" @@ -11,6 +9,23 @@ #include "pluginsmodel.hpp" #include "pluginsview.hpp" +#include +/** + * Workaround for problems with whitespaces in paths in older versions of Boost library + */ +#if (BOOST_VERSION <= 104600) +namespace boost +{ + + template<> + inline boost::filesystem::path lexical_cast(const std::string& arg) + { + return boost::filesystem::path(arg); + } + +} /* namespace boost */ +#endif /* (BOOST_VERSION <= 104600) */ + using namespace ESM; using namespace std; @@ -120,11 +135,12 @@ DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, QWidget *parent) connect(mPluginsTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex))); connect(mPluginsTable, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(showContextMenu(const QPoint&))); + connect(mProfilesComboBox, SIGNAL(textChanged(const QString&, const QString&)), this, SLOT(profileChanged(const QString&, const QString&))); - + createActions(); setupConfig(); setupDataFiles(); - createActions(); + } void DataFilesPage::setupConfig() @@ -138,8 +154,7 @@ void DataFilesPage::setupConfig() // Open our config file mLauncherConfig = new QSettings(config, QSettings::IniFormat); - mLauncherConfig->sync(); - + file.close(); // Make sure we have no groups open while (!mLauncherConfig->group().isEmpty()) { @@ -162,13 +177,14 @@ void DataFilesPage::setupConfig() // No current profile selected currentProfile = "Default"; } - mProfilesComboBox->setCurrentIndex(mProfilesComboBox->findText(currentProfile)); + + const int currentIndex = mProfilesComboBox->findText(currentProfile); + if (currentIndex != -1) { + // Profile is found + mProfilesComboBox->setCurrentIndex(currentIndex); + } mLauncherConfig->endGroup(); - - // Now we connect the combobox to do something if the profile changes - // This prevents strange behaviour while reading and appending the profiles - connect(mProfilesComboBox, SIGNAL(textChanged(const QString&, const QString&)), this, SLOT(profileChanged(const QString&, const QString&))); } @@ -188,18 +204,56 @@ void DataFilesPage::setupDataFiles() // Put the paths in a boost::filesystem vector to use with Files::Collections Files::PathContainer dataDirs(variables["data"].as()); + mDataDirs = dataDirs; -// std::string local(variables["data-local"].as()); -// if (!local.empty()) -// { -// dataDirs.push_back(Files::PathContainer::value_type(local)); -// } +// std::string local = variables["data-local"].as(); +// if (!local.empty()) { +// mDataLocal.push_back(Files::PathContainer::value_type(local)); +// dataDirs.push_back(Files::PathContainer::value_type(local)); +// } if (dataDirs.size()>1) dataDirs.resize (1); mCfgMgr.processPaths(dataDirs); + while (dataDirs.empty()) { + // No valid data files directory found + + QMessageBox msgBox; + msgBox.setWindowTitle("Error detecting Morrowind installation"); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Cancel); + msgBox.setText(tr("
Could not find the Data Files location

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

\ + Press \"Browse...\" to specify the location manually.
")); + + QAbstractButton *dirSelectButton = + msgBox.addButton(tr("B&rowse..."), QMessageBox::ActionRole); + + msgBox.exec(); + + if (msgBox.clickedButton() == dirSelectButton) { + + QString dataDir = QFileDialog::getExistingDirectory( + this, tr("Select Data Files Directory"), + "/home", + QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); + + dataDirs.push_back(Files::PathContainer::value_type(dataDir.toStdString())); + mDataDirs.push_back(Files::PathContainer::value_type(dataDir.toStdString())); + } else { + // Cancel + break; + } + } + + // Check if cancel was clicked because we can't exit from while loop + if (dataDirs.empty()) { + QApplication::exit(1); + return; + } + // Create a file collection for the dataDirs Files::Collections fileCollections(dataDirs, !variables["fs-strict"].as()); @@ -207,15 +261,13 @@ void DataFilesPage::setupDataFiles() const Files::MultiDirCollection &esm = fileCollections.getCollection(".esm"); unsigned int i = 0; // Row number - for (Files::MultiDirCollection::TIter iter(esm.begin()); iter!=esm.end(); ++iter) - { + for (Files::MultiDirCollection::TIter iter(esm.begin()); iter!=esm.end(); ++iter) { QString currentMaster = QString::fromStdString( boost::filesystem::path (iter->second.filename()).string()); const QList itemList = mMastersWidget->findItems(currentMaster, Qt::MatchExactly); - if (itemList.isEmpty()) // Master is not yet in the widget - { + if (itemList.isEmpty()) { // Master is not yet in the widget mMastersWidget->insertRow(i); QTableWidgetItem *item = new QTableWidgetItem(currentMaster); mMastersWidget->setItem(i, 0, item); @@ -226,8 +278,7 @@ void DataFilesPage::setupDataFiles() // Now on to the plugins const Files::MultiDirCollection &esp = fileCollections.getCollection(".esp"); - for (Files::MultiDirCollection::TIter iter(esp.begin()); iter!=esp.end(); ++iter) - { + for (Files::MultiDirCollection::TIter iter(esp.begin()); iter!=esp.end(); ++iter) { ESMReader fileReader; QStringList availableMasters; // Will contain all found masters @@ -243,8 +294,7 @@ void DataFilesPage::setupDataFiles() const QList itemList = mMastersWidget->findItems(currentMaster, Qt::MatchExactly); - if (itemList.isEmpty()) // Master is not yet in the widget - { + if (itemList.isEmpty()) { // Master is not yet in the widget mMastersWidget->insertRow(i); QTableWidgetItem *item = new QTableWidgetItem(currentMaster); @@ -433,18 +483,18 @@ void DataFilesPage::deleteProfile() return; } - QMessageBox deleteMessageBox(this); - deleteMessageBox.setWindowTitle(tr("Delete Profile")); - deleteMessageBox.setText(tr("Are you sure you want to delete %0?").arg(profile)); - deleteMessageBox.setIcon(QMessageBox::Warning); + QMessageBox msgBox(this); + msgBox.setWindowTitle(tr("Delete Profile")); + msgBox.setIcon(QMessageBox::Warning); + msgBox.setStandardButtons(QMessageBox::Cancel); + msgBox.setText(tr("Are you sure you want to delete %0?").arg(profile)); + QAbstractButton *deleteButton = - deleteMessageBox.addButton(tr("Delete"), QMessageBox::ActionRole); + msgBox.addButton(tr("Delete"), QMessageBox::ActionRole); - deleteMessageBox.addButton(QMessageBox::Cancel); + msgBox.exec(); - deleteMessageBox.exec(); - - if (deleteMessageBox.clickedButton() == deleteButton) { + if (msgBox.clickedButton() == deleteButton) { // Make sure we have no groups open while (!mLauncherConfig->group().isEmpty()) { mLauncherConfig->endGroup(); @@ -501,7 +551,6 @@ void DataFilesPage::moveUp() void DataFilesPage::moveDown() { // Shift the selected plugins down one row - if (!mPluginsTable->selectionModel()->hasSelection()) { return; } @@ -917,9 +966,18 @@ void DataFilesPage::filterChanged(const QString filter) void DataFilesPage::profileChanged(const QString &previous, const QString ¤t) { + // Prevent the deletion of the default profile + if (current == "Default") { + mDeleteProfileAction->setEnabled(false); + } else { + mDeleteProfileAction->setEnabled(true); + } + if (!previous.isEmpty()) { writeConfig(previous); mLauncherConfig->sync(); + } else { + return; } uncheckPlugins(); @@ -992,14 +1050,6 @@ void DataFilesPage::writeConfig(QString profile) return; } - if (profile.isEmpty()) { - profile = mProfilesComboBox->currentText(); - } - - if (profile.isEmpty()) { - return; - } - // Prepare the OpenMW config QString config = QString::fromStdString((mCfgMgr.getLocalPath() / "openmw.cfg").string()); QFile file(config); @@ -1028,10 +1078,14 @@ void DataFilesPage::writeConfig(QString profile) QTextStream in(&file); QByteArray buffer; - // Remove all previous master/plugin entries from config + // Remove all previous entries from config while (!in.atEnd()) { QString line = in.readLine(); - if (!line.contains("master") && !line.contains("plugin")) { + if (!line.startsWith("master") && + !line.startsWith("plugin") && + !line.startsWith("data") && + !line.startsWith("data-local")) + { buffer += line += "\n"; } } @@ -1052,9 +1106,52 @@ void DataFilesPage::writeConfig(QString profile) return; } - file.write(buffer); + if (!buffer.isEmpty()) { + file.write(buffer); + } + QTextStream gameConfig(&file); + // First write the list of data dirs + mCfgMgr.processPaths(mDataDirs); + mCfgMgr.processPaths(mDataLocal); + + QString path; + + // data= directories + for (Files::PathContainer::iterator it = mDataDirs.begin(); it != mDataDirs.end(); ++it) { + path = QString::fromStdString(it->string()); + path.remove(QChar('\"')); + + // Make sure the string is quoted when it contains spaces + if (path.contains(" ")) { + gameConfig << "data=\"" << path << "\"" << endl; + } else { + gameConfig << "data=" << path << endl; + } + } + + // data-local directory + if (!mDataLocal.empty()) { + path = QString::fromStdString(mDataLocal.front().string()); + path.remove(QChar('\"')); + + if (path.contains(" ")) { + gameConfig << "data-local=\"" << path << "\"" << endl; + } else { + gameConfig << "data-local=" << path << endl; + } + } + + + if (profile.isEmpty()) { + profile = mProfilesComboBox->currentText(); + } + + if (profile.isEmpty()) { + return; + } + // Make sure we have no groups open while (!mLauncherConfig->group().isEmpty()) { mLauncherConfig->endGroup(); @@ -1067,7 +1164,7 @@ void DataFilesPage::writeConfig(QString profile) mLauncherConfig->beginGroup(profile); mLauncherConfig->remove(""); // Clear the subgroup - // First write the masters to the configs + // Now write the masters to the configs const QStringList masters = selectedMasters(); // We don't use foreach because we need i @@ -1079,11 +1176,10 @@ void DataFilesPage::writeConfig(QString profile) } - // Now write all checked plugins + // And finally write all checked plugins const QStringList plugins = checkedPlugins(); - for (int i = 0; i < plugins.size(); ++i) - { + for (int i = 0; i < plugins.size(); ++i) { const QString currentPlugin = plugins.at(i); mLauncherConfig->setValue(QString("Plugin%1").arg(i), currentPlugin); gameConfig << "plugin=" << currentPlugin << endl; diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index a454fa871..ad5e90511 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -3,6 +3,9 @@ #include #include + +#include + #include "combobox.hpp" class QTableWidget; @@ -77,6 +80,8 @@ private: QAction *mUncheckAction; Files::ConfigurationManager &mCfgMgr; + Files::PathContainer mDataDirs; + Files::PathContainer mDataLocal; QSettings *mLauncherConfig; diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index aaa54dd7f..b7d397f06 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -246,13 +246,7 @@ void GraphicsPage::setupOgre() if (mOpenGLRenderSystem) { mOGLRTTComboBox->addItems(getAvailableOptions(QString("RTT Preferred Mode"), mOpenGLRenderSystem)); mOGLAntiAliasingComboBox->addItems(getAvailableOptions(QString("FSAA"), mOpenGLRenderSystem)); - - QStringList videoModes = getAvailableOptions(QString("Video Mode"), mOpenGLRenderSystem); - // Remove extraneous spaces - videoModes.replaceInStrings(QRegExp("\\s{2,}"), QString(" ")); - videoModes.replaceInStrings(QRegExp("^\\s"), QString()); - - mOGLResolutionComboBox->addItems(videoModes); + mOGLResolutionComboBox->addItems(getAvailableOptions(QString("Video Mode"), mOpenGLRenderSystem)); mOGLFrequencyComboBox->addItems(getAvailableOptions(QString("Display Frequency"), mOpenGLRenderSystem)); } @@ -261,12 +255,7 @@ void GraphicsPage::setupOgre() mD3DRenderDeviceComboBox->addItems(getAvailableOptions(QString("Rendering Device"), mDirect3DRenderSystem)); mD3DAntiAliasingComboBox->addItems(getAvailableOptions(QString("FSAA"), mDirect3DRenderSystem)); mD3DFloatingPointComboBox->addItems(getAvailableOptions(QString("Floating-point mode"), mDirect3DRenderSystem)); - - QStringList videoModes = getAvailableOptions(QString("Video Mode"), mDirect3DRenderSystem); - // Remove extraneous spaces - videoModes.replaceInStrings(QRegExp("\\s{2,}"), QString(" ")); - videoModes.replaceInStrings(QRegExp("^\\s"), QString()); - mD3DResolutionComboBox->addItems(videoModes); + mD3DResolutionComboBox->addItems(getAvailableOptions(QString("Video Mode"), mDirect3DRenderSystem)); } } @@ -482,7 +471,7 @@ QStringList GraphicsPage::getAvailableOptions(const QString &key, Ogre::RenderSy { if (strcmp (key.toStdString().c_str(), i->first.c_str()) == 0) - result << (*opt_it).c_str(); + result << QString::fromStdString((*opt_it).c_str()).simplified(); } } diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 39cd99cf6..77f465b4c 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -44,7 +44,7 @@ add_openmw_dir (mwsound add_openmw_dir (mwworld refdata world physicssystem scene environment globals class action nullaction actionteleport containerstore actiontalk actiontake manualref player cellfunctors - cells localscripts customdata weather + cells localscripts customdata weather inventorystore ) add_openmw_dir (mwclass diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 4765ceadc..0e2189307 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -118,8 +118,7 @@ bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt) // sound if (mUseSound) { - if (!mEnvironment.mSoundManager->isMusicPlaying()) - mEnvironment.mSoundManager->startRandomTitle(); + mEnvironment.mSoundManager->playPlaylist(); mEnvironment.mSoundManager->update (evt.timeSinceLastFrame); } @@ -341,7 +340,6 @@ void OMW::Engine::go() // Create sound system mEnvironment.mSoundManager = new MWSound::SoundManager(mOgre->getRoot(), mOgre->getCamera(), - mEnvironment.mWorld->getStore(), mDataDirs, mUseSound, mFSStrict, mEnvironment); @@ -390,7 +388,7 @@ void OMW::Engine::go() mOgre->getRoot()->addFrameListener (this); // Play some good 'ol tunes - mEnvironment.mSoundManager->startRandomTitle(); + mEnvironment.mSoundManager->playPlaylist(std::string("Explore")); // scripts if (mCompileAll) diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index 3cdf63119..2dc861430 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -2,11 +2,16 @@ #include "armor.hpp" #include +#include +#include #include #include "../mwworld/ptr.hpp" #include "../mwworld/actiontake.hpp" +#include "../mwworld/inventorystore.hpp" +#include "../mwworld/environment.hpp" +#include "../mwworld/world.hpp" #include "../mwrender/objects.hpp" @@ -77,6 +82,79 @@ namespace MWClass return ref->base->script; } + std::pair, bool> Armor::getEquipmentSlots (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + std::vector slots; + + const int size = 11; + + static const int sMapping[size][2] = + { + { ESM::Armor::Helmet, MWWorld::InventoryStore::Slot_Helmet }, + { ESM::Armor::Cuirass, MWWorld::InventoryStore::Slot_Cuirass }, + { ESM::Armor::LPauldron, MWWorld::InventoryStore::Slot_LeftPauldron }, + { ESM::Armor::RPauldron, MWWorld::InventoryStore::Slot_RightPauldron }, + { ESM::Armor::Greaves, MWWorld::InventoryStore::Slot_Greaves }, + { ESM::Armor::Boots, MWWorld::InventoryStore::Slot_Boots }, + { ESM::Armor::LGauntlet, MWWorld::InventoryStore::Slot_LeftGauntlet }, + { ESM::Armor::RGauntlet, MWWorld::InventoryStore::Slot_RightGauntlet }, + { ESM::Armor::Shield, MWWorld::InventoryStore::Slot_CarriedLeft }, + { ESM::Armor::LBracer, MWWorld::InventoryStore::Slot_LeftGauntlet }, + { ESM::Armor::RBracer, MWWorld::InventoryStore::Slot_RightGauntlet } + }; + + for (int i=0; ibase->data.type) + { + slots.push_back (int (sMapping[i][1])); + break; + } + + return std::make_pair (slots, false); + } + + int Armor::getEuqipmentSkill (const MWWorld::Ptr& ptr, const MWWorld::Environment& environment) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + std::string typeGmst; + + switch (ref->base->data.type) + { + case ESM::Armor::Helmet: typeGmst = "iHelmWeight"; break; + case ESM::Armor::Cuirass: typeGmst = "iCuirassWeight"; break; + case ESM::Armor::LPauldron: + case ESM::Armor::RPauldron: typeGmst = "iPauldronWeight"; break; + case ESM::Armor::Greaves: typeGmst = "iGreavesWeight"; break; + case ESM::Armor::Boots: typeGmst = "iBootsWeight"; break; + case ESM::Armor::LGauntlet: + case ESM::Armor::RGauntlet: typeGmst = "iGauntletWeight"; break; +/// \todo how to determine if shield light, medium or heavy? +// case ESM::Armor::Shield: + case ESM::Armor::LBracer: + case ESM::Armor::RBracer: typeGmst = "iGauntletWeight"; break; + } + + if (typeGmst.empty()) + return -1; + + float iWeight = environment.mWorld->getStore().gameSettings.find (typeGmst)->f; + + if (iWeight * environment.mWorld->getStore().gameSettings.find ("fLightMaxMod")->f<= + ref->base->data.weight) + return ESM::Skill::LightArmor; + + if (iWeight * environment.mWorld->getStore().gameSettings.find ("fMedMaxMod")->f<= + ref->base->data.weight) + return ESM::Skill::MediumArmor; + + return ESM::Skill::HeavyArmor; + } + void Armor::registerSelf() { boost::shared_ptr instance (new Armor); diff --git a/apps/openmw/mwclass/armor.hpp b/apps/openmw/mwclass/armor.hpp index 060bc364e..6c78a535a 100644 --- a/apps/openmw/mwclass/armor.hpp +++ b/apps/openmw/mwclass/armor.hpp @@ -31,6 +31,15 @@ namespace MWClass virtual std::string getScript (const MWWorld::Ptr& ptr) const; ///< Return name of the script attached to ptr + virtual std::pair, bool> getEquipmentSlots (const MWWorld::Ptr& ptr) const; + ///< \return first: Return IDs of the slot this object can be equipped in; second: can object + /// stay stacked when equipped? + + virtual int getEuqipmentSkill (const MWWorld::Ptr& ptr, + const MWWorld::Environment& environment) const; + /// Return the index of the skill this item corresponds to when equiopped or -1, if there is + /// no such skill. + static void registerSelf(); }; } diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 88c43d82c..9af59937a 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -7,6 +7,7 @@ #include "../mwworld/ptr.hpp" #include "../mwworld/actiontake.hpp" +#include "../mwworld/inventorystore.hpp" #include "../mwrender/objects.hpp" @@ -65,6 +66,58 @@ namespace MWClass return ref->base->script; } + std::pair, bool> Clothing::getEquipmentSlots (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + std::vector slots; + + if (ref->base->data.type==ESM::Clothing::Ring) + { + slots.push_back (int (MWWorld::InventoryStore::Slot_LeftRing)); + slots.push_back (int (MWWorld::InventoryStore::Slot_RightRing)); + } + else + { + const int size = 9; + + static const int sMapping[size][2] = + { + { ESM::Clothing::Shirt, MWWorld::InventoryStore::Slot_Cuirass }, + { ESM::Clothing::Belt, MWWorld::InventoryStore::Slot_Belt }, + { ESM::Clothing::Robe, MWWorld::InventoryStore::Slot_Robe }, + { ESM::Clothing::Pants, MWWorld::InventoryStore::Slot_Pants }, + { ESM::Clothing::Shoes, MWWorld::InventoryStore::Slot_Boots }, + { ESM::Clothing::LGlove, MWWorld::InventoryStore::Slot_LeftGauntlet }, + { ESM::Clothing::RGlove, MWWorld::InventoryStore::Slot_RightGauntlet }, + { ESM::Clothing::Skirt, MWWorld::InventoryStore::Slot_Skirt }, + { ESM::Clothing::Amulet, MWWorld::InventoryStore::Slot_Amulet } + }; + + for (int i=0; ibase->data.type) + { + slots.push_back (int (sMapping[i][1])); + break; + } + } + + return std::make_pair (slots, false); + } + + int Clothing::getEuqipmentSkill (const MWWorld::Ptr& ptr, + const MWWorld::Environment& environment) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + if (ref->base->data.type==ESM::Clothing::Shoes) + return ESM::Skill::Unarmored; + + return -1; + } + void Clothing::registerSelf() { boost::shared_ptr instance (new Clothing); diff --git a/apps/openmw/mwclass/clothing.hpp b/apps/openmw/mwclass/clothing.hpp index 606aba9e0..448c73a73 100644 --- a/apps/openmw/mwclass/clothing.hpp +++ b/apps/openmw/mwclass/clothing.hpp @@ -25,6 +25,15 @@ namespace MWClass virtual std::string getScript (const MWWorld::Ptr& ptr) const; ///< Return name of the script attached to ptr + virtual std::pair, bool> getEquipmentSlots (const MWWorld::Ptr& ptr) const; + ///< \return first: Return IDs of the slot this object can be equipped in; second: can object + /// stay stacked when equipped? + + virtual int getEuqipmentSkill (const MWWorld::Ptr& ptr, + const MWWorld::Environment& environment) const; + /// Return the index of the skill this item corresponds to when equiopped or -1, if there is + /// no such skill. + static void registerSelf(); }; } diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index f9ec1c956..fc3c7a6a9 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -9,6 +9,7 @@ #include "../mwworld/actiontake.hpp" #include "../mwworld/nullaction.hpp" #include "../mwworld/environment.hpp" +#include "../mwworld/inventorystore.hpp" #include "../mwsound/soundmanager.hpp" @@ -94,6 +95,19 @@ namespace MWClass return ref->base->script; } + std::pair, bool> Light::getEquipmentSlots (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + std::vector slots; + + if (ref->base->data.flags & ESM::Light::Carry) + slots.push_back (int (MWWorld::InventoryStore::Slot_CarriedLeft)); + + return std::make_pair (slots, false); + } + void Light::registerSelf() { boost::shared_ptr instance (new Light); diff --git a/apps/openmw/mwclass/light.hpp b/apps/openmw/mwclass/light.hpp index c9940d0a5..179531808 100644 --- a/apps/openmw/mwclass/light.hpp +++ b/apps/openmw/mwclass/light.hpp @@ -30,6 +30,10 @@ namespace MWClass virtual std::string getScript (const MWWorld::Ptr& ptr) const; ///< Return name of the script attached to ptr + virtual std::pair, bool> getEquipmentSlots (const MWWorld::Ptr& ptr) const; + ///< \return first: Return IDs of the slot this object can be equipped in; second: can object + /// stay stacked when equipped? + static void registerSelf(); }; } diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 98c05a1b3..5fc6ea9c7 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -7,6 +7,7 @@ #include "../mwworld/ptr.hpp" #include "../mwworld/actiontake.hpp" +#include "../mwworld/inventorystore.hpp" #include "../mwrender/objects.hpp" @@ -66,6 +67,15 @@ namespace MWClass return ref->base->script; } + std::pair, bool> Lockpick::getEquipmentSlots (const MWWorld::Ptr& ptr) const + { + std::vector slots; + + slots.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight)); + + return std::make_pair (slots, false); + } + void Lockpick::registerSelf() { boost::shared_ptr instance (new Lockpick); diff --git a/apps/openmw/mwclass/lockpick.hpp b/apps/openmw/mwclass/lockpick.hpp index 9cbfa0d23..5465e5215 100644 --- a/apps/openmw/mwclass/lockpick.hpp +++ b/apps/openmw/mwclass/lockpick.hpp @@ -25,6 +25,10 @@ namespace MWClass virtual std::string getScript (const MWWorld::Ptr& ptr) const; ///< Return name of the script attached to ptr + virtual std::pair, bool> getEquipmentSlots (const MWWorld::Ptr& ptr) const; + ///< \return first: Return IDs of the slot this object can be equipped in; second: can object + /// stay stacked when equipped? + static void registerSelf(); }; } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index cc7daa83e..83a94d27d 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -16,7 +16,7 @@ #include "../mwworld/actiontalk.hpp" #include "../mwworld/environment.hpp" #include "../mwworld/world.hpp" -#include "../mwworld/containerstore.hpp" +#include "../mwworld/inventorystore.hpp" #include "../mwworld/customdata.hpp" namespace @@ -29,7 +29,7 @@ namespace MWMechanics::NpcStats mNpcStats; MWMechanics::CreatureStats mCreatureStats; MWMechanics::Movement mMovement; - MWWorld::ContainerStore mContainerStore; + MWWorld::InventoryStore mInventoryStore; virtual MWWorld::CustomData *clone() const; }; @@ -161,7 +161,15 @@ namespace MWClass { ensureCustomData (ptr); - return dynamic_cast (*ptr.getRefData().getCustomData()).mContainerStore; + return dynamic_cast (*ptr.getRefData().getCustomData()).mInventoryStore; + } + + MWWorld::InventoryStore& Npc::getInventoryStore (const MWWorld::Ptr& ptr) + const + { + ensureCustomData (ptr); + + return dynamic_cast (*ptr.getRefData().getCustomData()).mInventoryStore; } std::string Npc::getScript (const MWWorld::Ptr& ptr) const diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index bef417332..f210eda5f 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -38,6 +38,9 @@ namespace MWClass virtual MWWorld::ContainerStore& getContainerStore (const MWWorld::Ptr& ptr) const; ///< Return container store + virtual MWWorld::InventoryStore& getInventoryStore (const MWWorld::Ptr& ptr) const; + ///< Return inventory store + virtual boost::shared_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const; ///< Generate action for activation diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index de024e430..d70cda83d 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -7,6 +7,7 @@ #include "../mwworld/ptr.hpp" #include "../mwworld/actiontake.hpp" +#include "../mwworld/inventorystore.hpp" #include "../mwrender/objects.hpp" @@ -65,6 +66,15 @@ namespace MWClass return ref->base->script; } + std::pair, bool> Probe::getEquipmentSlots (const MWWorld::Ptr& ptr) const + { + std::vector slots; + + slots.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight)); + + return std::make_pair (slots, false); + } + void Probe::registerSelf() { boost::shared_ptr instance (new Probe); diff --git a/apps/openmw/mwclass/probe.hpp b/apps/openmw/mwclass/probe.hpp index 3f2bfed5b..2aadaa22e 100644 --- a/apps/openmw/mwclass/probe.hpp +++ b/apps/openmw/mwclass/probe.hpp @@ -25,6 +25,10 @@ namespace MWClass virtual std::string getScript (const MWWorld::Ptr& ptr) const; ///< Return name of the script attached to ptr + virtual std::pair, bool> getEquipmentSlots (const MWWorld::Ptr& ptr) const; + ///< \return first: Return IDs of the slot this object can be equipped in; second: can object + /// stay stacked when equipped? + static void registerSelf(); }; } diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 90fd3e33b..e4cb4582b 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -7,6 +7,7 @@ #include "../mwworld/ptr.hpp" #include "../mwworld/actiontake.hpp" +#include "../mwworld/inventorystore.hpp" #include "../mwrender/objects.hpp" @@ -78,6 +79,61 @@ namespace MWClass return ref->base->script; } + std::pair, bool> Weapon::getEquipmentSlots (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + std::vector slots; + bool stack = false; + + if (ref->base->data.type==ESM::Weapon::Arrow || ref->base->data.type==ESM::Weapon::Bolt) + { + slots.push_back (int (MWWorld::InventoryStore::Slot_Ammunition)); + stack = true; + } + else if (ref->base->data.type==ESM::Weapon::MarksmanThrown) + { + slots.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight)); + stack = true; + } + else + slots.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight)); + + return std::make_pair (slots, stack); + } + + int Weapon::getEuqipmentSkill (const MWWorld::Ptr& ptr, + const MWWorld::Environment& environment) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + const int size = 12; + + static const int sMapping[size][2] = + { + { ESM::Weapon::ShortBladeOneHand, ESM::Skill::ShortBlade }, + { ESM::Weapon::LongBladeOneHand, ESM::Skill::LongBlade }, + { ESM::Weapon::LongBladeTwoHand, ESM::Skill::LongBlade }, + { ESM::Weapon::BluntOneHand, ESM::Skill::BluntWeapon }, + { ESM::Weapon::BluntTwoClose, ESM::Skill::BluntWeapon }, + { ESM::Weapon::BluntTwoWide, ESM::Skill::BluntWeapon }, + { ESM::Weapon::SpearTwoWide, ESM::Skill::Spear }, + { ESM::Weapon::AxeOneHand, ESM::Skill::Axe }, + { ESM::Weapon::AxeTwoHand, ESM::Skill::Axe }, + { ESM::Weapon::MarksmanBow, ESM::Skill::Marksman }, + { ESM::Weapon::MarksmanCrossbow, ESM::Skill::Marksman }, + { ESM::Weapon::MarksmanThrown, ESM::Skill::Marksman } + }; + + for (int i=0; ibase->data.type) + return sMapping[i][1]; + + return -1; + } + void Weapon::registerSelf() { boost::shared_ptr instance (new Weapon); diff --git a/apps/openmw/mwclass/weapon.hpp b/apps/openmw/mwclass/weapon.hpp index b056249b9..2b6dfb5a8 100644 --- a/apps/openmw/mwclass/weapon.hpp +++ b/apps/openmw/mwclass/weapon.hpp @@ -31,6 +31,15 @@ namespace MWClass virtual std::string getScript (const MWWorld::Ptr& ptr) const; ///< Return name of the script attached to ptr + virtual std::pair, bool> getEquipmentSlots (const MWWorld::Ptr& ptr) const; + ///< \return first: Return IDs of the slot this object can be equipped in; second: can object + /// stay stacked when equipped? + + virtual int getEuqipmentSkill (const MWWorld::Ptr& ptr, + const MWWorld::Environment& environment) const; + /// Return the index of the skill this item corresponds to when equiopped or -1, if there is + /// no such skill. + static void registerSelf(); }; } diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 63855f3b8..9df987dc1 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -276,6 +276,7 @@ namespace MWRender{ rotmult = bonePtr->getOrientation(); scale = bonePtr->getScale().x; boneSequenceIter++; + for(; boneSequenceIter != boneSequence.end(); boneSequenceIter++) { if(creaturemodel->getSkeleton()->hasBone(*boneSequenceIter)){ @@ -330,7 +331,7 @@ namespace MWRender{ } } - bool Animation::timeIndex( float time, std::vector times, int & i, int & j, float & x ){ + bool Animation::timeIndex( float time, const std::vector & times, int & i, int & j, float & x ){ int count; if ( (count = times.size()) > 0 ) { @@ -388,6 +389,8 @@ namespace MWRender{ } void Animation::handleAnimationTransforms(){ + + Ogre::SkeletonInstance* skel = base->getSkeleton(); @@ -404,10 +407,10 @@ namespace MWRender{ for(unsigned int i = 0; i < entityparts.size(); i++){ //Ogre::SkeletonInstance* skel = entityparts[i]->getSkeleton(); - Ogre::Bone* b = skel->getRootBone(); - b->setOrientation(Ogre::Real(.3),Ogre::Real(.3),Ogre::Real(.3), Ogre::Real(.3));//This is a trick + //Ogre::Bone* b = skel->getRootBone(); + //b->setOrientation(Ogre::Real(.3),Ogre::Real(.3),Ogre::Real(.3), Ogre::Real(.3));//This is a trick - entityparts[i]->getAllAnimationStates()->_notifyDirty(); + //entityparts[i]->getAllAnimationStates()->_notifyDirty(); } @@ -424,18 +427,19 @@ namespace MWRender{ float x; float x2; - std::vector quats = iter->getQuat(); + const std::vector & quats = iter->getQuat(); - std::vector ttime = iter->gettTime(); - std::vector::iterator ttimeiter = ttime.begin(); + const std::vector & ttime = iter->gettTime(); + - std::vector rtime = iter->getrTime(); - int rindexJ = 0; + const std::vector & rtime = iter->getrTime(); + int rindexJ = rindexI[slot]; + timeIndex(time, rtime, rindexI[slot], rindexJ, x2); - int tindexJ = 0; + int tindexJ = tindexI[slot]; - std::vector translist1 = iter->getTranslist1(); + const std::vector & translist1 = iter->getTranslist1(); timeIndex(time, ttime, tindexI[slot], tindexJ, x); @@ -443,34 +447,35 @@ namespace MWRender{ Ogre::Quaternion r; bool bTrans = translist1.size() > 0; - if(bTrans){ - Ogre::Vector3 v1 = translist1[tindexI[slot]]; - Ogre::Vector3 v2 = translist1[tindexJ]; - t = (v1 + (v2 - v1) * x); - - } + bool bQuats = quats.size() > 0; - if(bQuats){ - r = Ogre::Quaternion::Slerp(x2, quats[rindexI[slot]], quats[rindexJ], true); - } - skel = base->getSkeleton(); + if(skel->hasBone(iter->getBonename())){ Ogre::Bone* bone = skel->getBone(iter->getBonename()); - if(bTrans) + if(bTrans){ + Ogre::Vector3 v1 = translist1[tindexI[slot]]; + Ogre::Vector3 v2 = translist1[tindexJ]; + t = (v1 + (v2 - v1) * x); bone->setPosition(t); - if(bQuats) + + } + if(bQuats){ + r = Ogre::Quaternion::Slerp(x2, quats[rindexI[slot]], quats[rindexJ], true); bone->setOrientation(r); + } - skel->_updateTransforms(); - base->getAllAnimationStates()->_notifyDirty(); + } + slot++; } + skel->_updateTransforms(); + base->getAllAnimationStates()->_notifyDirty(); } } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index bad7eca15..e08b86e8d 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -28,6 +28,7 @@ class Animation{ MWWorld::Environment& mEnvironment; std::map vecRotPos; static std::map mUniqueIDs; + std::vector* > shapeparts; //All the NiTriShape data that we need for animating an npc @@ -55,7 +56,7 @@ class Animation{ Ogre::Entity* base; void handleShapes(std::vector* allshapes, Ogre::Entity* creaturemodel, Ogre::SkeletonInstance *skel); void handleAnimationTransforms(); - bool timeIndex( float time, std::vector times, int & i, int & j, float & x ); + bool timeIndex( float time, const std::vector & times, int & i, int & j, float & x ); std::string getUniqueID(std::string mesh); public: diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 4c9c0e466..c6fe023d6 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -42,6 +42,7 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, MWWorld::Environment& _env,O std::string bodyRaceID = headID.substr(0, headID.find_last_of("head_") - 4); char secondtolast = bodyRaceID.at(bodyRaceID.length() - 2); bool female = tolower(secondtolast) == 'f'; + std::transform(bodyRaceID.begin(), bodyRaceID.end(), bodyRaceID.begin(), ::tolower); bool beast = bodyRaceID == "b_n_khajiit_m_" || bodyRaceID == "b_n_khajiit_f_" || bodyRaceID == "b_n_argonian_m_" || bodyRaceID == "b_n_argonian_f_"; /*std::cout << "Race: " << ref->base->race ; @@ -276,6 +277,7 @@ void NpcAnimation::runAnimation(float timepassed){ shapepartsiter++; entitypartsiter++; } + } } diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index 896d78385..717064ada 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -108,7 +108,7 @@ void Objects::insertMesh (const MWWorld::Ptr& ptr, const std::string& mesh) // - the culling will be more inefficient // If it is set too low: // - there will be too many batches. - sg->setRegionDimensions(Ogre::Vector3(1000,1000,1000)); + sg->setRegionDimensions(Ogre::Vector3(2500,2500,2500)); } else { diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index dda95c188..e8e7b01ae 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -659,9 +659,15 @@ void SkyManager::setWeather(const MWWorld::WeatherResult& weather) if (weather.mNight && mStarsOpacity != weather.mNightFade) { - for (int i=0; i<7; ++i) - mStarsMaterials[i]->getTechnique(0)->getPass(0)->setDiffuse(0.0, 0.0, 0.0, weather.mNightFade); - mStarsOpacity = weather.mNightFade; + if (weather.mNightFade == 0) + mAtmosphereNight->setVisible(false); + else + { + mAtmosphereNight->setVisible(true); + for (int i=0; i<7; ++i) + mStarsMaterials[i]->getTechnique(0)->getPass(0)->setDiffuse(0.0, 0.0, 0.0, weather.mNightFade); + mStarsOpacity = weather.mNightFade; + } } float strength; diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 76ef23bc2..2440eda23 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -10,10 +10,8 @@ #include #include -#include #include - #include "../mwworld/environment.hpp" #include "../mwworld/world.hpp" #include "../mwworld/player.hpp" @@ -45,7 +43,6 @@ using namespace Mangle::Sound; typedef OEngine::Sound::SoundManager OEManager; -typedef OEngine::Sound::SoundManagerPtr OEManagerPtr; // Set the position on a sound based on a Ptr. static void setPos(SoundPtr &snd, const MWWorld::Ptr ref) @@ -60,175 +57,65 @@ static void setPos(SoundPtr &snd, const MWWorld::Ptr ref) namespace MWSound { - struct SoundManager::SoundImpl - { - /* This is the sound manager. It loades, stores and deletes - sounds based on the sound factory it is given. - */ - OEManagerPtr mgr; - SoundPtr music; - /* This class calls update() on the sound manager each frame - using and Ogre::FrameListener - */ - Mangle::Sound::OgreOutputUpdater updater; - - /* This class tracks the movement of an Ogre::Camera and moves - a sound listener automatically to follow it. - */ - Mangle::Sound::OgreListenerMover cameraTracker; - - const ESMS::ESMStore &store; - - typedef std::map IDMap; - typedef std::map PtrMap; - PtrMap sounds; - - // This is used for case insensitive and slash-type agnostic file - // finding. It takes DOS paths (any case, \\ slashes or / slashes) - // relative to the sound dir, and translates them into full paths - // of existing files in the filesystem, if they exist. - bool FSstrict; - FileFinder::LessTreeFileFinder files; - FileFinder::StrictTreeFileFinder strict; - FileFinder::LessTreeFileFinder musicpath; - FileFinder::StrictTreeFileFinder musicpathStrict; - - SoundImpl(Ogre::Root *root, Ogre::Camera *camera, const ESMS::ESMStore &str, - const Files::PathContainer& soundDir, - const Files::PathContainer& musicDir, - bool fsstrict) - : mgr(new OEManager(SoundFactoryPtr(new SOUND_FACTORY))) - , updater(mgr) - , cameraTracker(mgr) - , store(str) - , FSstrict(fsstrict) - , files(soundDir) - , strict(soundDir) - , musicpath(musicDir) - , musicpathStrict(musicDir) + SoundManager::SoundManager(Ogre::Root *root, Ogre::Camera *camera, + const Files::PathContainer& dataDirs, + bool useSound, bool fsstrict, MWWorld::Environment& environment) + : mFSStrict(fsstrict) + , mEnvironment(environment) + , mgr(new OEManager(SoundFactoryPtr(new SOUND_FACTORY))) + , updater(mgr) + , cameraTracker(mgr) + , mCurrentPlaylist(NULL) { + if(useSound) + { + // The music library will accept these filetypes + // If none is given then it will accept all filetypes + std::vector acceptableExtensions; + acceptableExtensions.push_back(".mp3"); + acceptableExtensions.push_back(".wav"); + acceptableExtensions.push_back(".ogg"); + acceptableExtensions.push_back(".flac"); - std::cout << "Sound output: " << SOUND_OUT << std::endl; - std::cout << "Sound decoder: " << SOUND_IN << std::endl; - // Attach the camera to the camera tracker - cameraTracker.followCamera(camera); + // Makes a list of all sound files, searches in reverse for priority reasons + for (Files::PathContainer::const_reverse_iterator it = dataDirs.rbegin(); it != dataDirs.rend(); ++it) + { + Files::FileLister(*it / std::string("Sound"), mSoundFiles, true); + } - // Tell Ogre to update the sound system each frame - root->addFrameListener(&updater); - } + // Makes a FileLibrary of all music files, searches in reverse for priority reasons + for (Files::PathContainer::const_reverse_iterator it = dataDirs.rbegin(); it != dataDirs.rend(); ++it) + { + mMusicLibrary.add(*it / std::string("Music"), true, mFSStrict, acceptableExtensions); + } - ~SoundImpl() + std::string anything = "anything"; // anything is better that a segfault + mCurrentPlaylist = mMusicLibrary.section(anything, mFSStrict); // now points to an empty path + + std::cout << "Sound output: " << SOUND_OUT << std::endl; + std::cout << "Sound decoder: " << SOUND_IN << std::endl; + // Attach the camera to the camera tracker + cameraTracker.followCamera(camera); + + // Tell Ogre to update the sound system each frame + root->addFrameListener(&updater); + } + } + + SoundManager::~SoundManager() { Ogre::Root::getSingleton().removeFrameListener(&updater); cameraTracker.unfollowCamera(); } - - - static std::string toMp3(std::string str) - { - std::string::size_type i = str.rfind('.'); - if(str.find('/', i) == std::string::npos && - str.find('\\', i) == std::string::npos) - str = str.substr(0, i) + ".mp3"; - else - str += ".mp3"; - return str; - } - - bool hasFile(const std::string &str, bool music = false) - { - bool found = false; - if(!FSstrict) - { - if(music) - { - found = musicpath.has(str); - // Not found? Try with .mp3 - if (!found) - { - found = musicpath.has(toMp3(str)); - } - } - else - { - found = files.has(str); - if (!found) - { - found = files.has(toMp3(str)); - } - } - } - else - { - if(music) - { - found = musicpathStrict.has(str); - // Not found? Try with .mp3 - if (!found) - { - found = musicpathStrict.has(toMp3(str)); - } - } - else - { - found = strict.has(str); - if (!found) - { - found = strict.has(toMp3(str)); - } - } - } - - return found; - } - - // Convert a Morrowind sound path (eg. Fx\funny.wav) to full path - // with proper slash conversion (eg. datadir/Sound/Fx/funny.wav) - std::string convertPath(const std::string &str, bool music = false) - { - if(FSstrict == false) - { - // Search and return - if(music && musicpath.has(str)) - return musicpath.lookup(str); - else if(files.has(str)) - return files.lookup(str); - - // Try mp3 if the wav wasn't found - std::string mp3 = toMp3(str); - if(music && musicpath.has(mp3)) - return musicpath.lookup(mp3); - else if(files.has(mp3)) - return files.lookup(mp3); - } - else - { - if(music && musicpathStrict.has(str)) - return musicpathStrict.lookup(str); - else if(strict.has(str)) - return strict.lookup(str); - - // Try mp3 if the wav wasn't found - std::string mp3 = toMp3(str); - if(music && musicpathStrict.has(mp3)) - return musicpathStrict.lookup(mp3); - else if(strict.has(str)) - return strict.lookup(mp3); - } - - // Give up - return ""; - } - // Convert a soundId to file name, and modify the volume // according to the sounds local volume setting, minRange and // maxRange. - std::string lookup(const std::string &soundId, + std::string SoundManager::lookup(const std::string &soundId, float &volume, float &min, float &max) { - const ESM::Sound *snd = store.sounds.search(soundId); + const ESM::Sound *snd = mEnvironment.mWorld->getStore().sounds.search(soundId); if(snd == NULL) return ""; if(snd->data.volume == 0) @@ -249,11 +136,11 @@ namespace MWSound max = std::max(min, max); } - return convertPath(snd->sound); + return Files::FileListLocator(mSoundFiles, snd->sound, mFSStrict); } // Add a sound to the list and play it - void add(const std::string &file, + void SoundManager::add(const std::string &file, MWWorld::Ptr ptr, const std::string &id, float volume, float pitch, @@ -280,7 +167,7 @@ namespace MWSound // Clears all the sub-elements of a given iterator, and then // removes it from 'sounds'. - void clearAll(PtrMap::iterator& it) + void SoundManager::clearAll(PtrMap::iterator& it) { IDMap::iterator sit = it->second.begin(); @@ -301,7 +188,7 @@ namespace MWSound // Stop a sound and remove it from the list. If id="" then // remove the entire object and stop all its sounds. - void remove(MWWorld::Ptr ptr, const std::string &id = "") + void SoundManager::remove(MWWorld::Ptr ptr, const std::string &id) { PtrMap::iterator it = sounds.find(ptr); if(it != sounds.end()) @@ -324,7 +211,7 @@ namespace MWSound } } - bool isPlaying(MWWorld::Ptr ptr, const std::string &id) const + bool SoundManager::isPlaying(MWWorld::Ptr ptr, const std::string &id) const { PtrMap::const_iterator it = sounds.find(ptr); if(it != sounds.end()) @@ -348,7 +235,7 @@ namespace MWSound } // Remove all references to objects belonging to a given cell - void removeCell(const MWWorld::Ptr::CellStore *cell) + void SoundManager::removeCell(const MWWorld::Ptr::CellStore *cell) { PtrMap::iterator it2, it = sounds.begin(); while(it != sounds.end()) @@ -360,7 +247,7 @@ namespace MWSound } } - void updatePositions(MWWorld::Ptr ptr) + void SoundManager::updatePositions(MWWorld::Ptr ptr) { // Find the reference (if any) PtrMap::iterator it = sounds.find(ptr); @@ -378,80 +265,45 @@ namespace MWSound } } } - }; /* SoundImpl */ + + void SoundManager::stopMusic() + { + if (music) + music->stop(); + setPlaylist(); + } + void SoundManager::streamMusicFull(const std::string& filename) { - if(!mData) return; - // Play the sound and tell it to stream, if possible. TODO: // Store the reference, the jukebox will need to check status, // control volume etc. - if (mData->music) - mData->music->stop(); - mData->music = mData->mgr->load(filename); - mData->music->setStreaming(true); - mData->music->setVolume(0.4); - mData->music->play(); + if (music) + music->stop(); + music = mgr->load(filename); + music->setStreaming(true); + music->setVolume(0.4); + music->play(); } - SoundManager::SoundManager(Ogre::Root *root, Ogre::Camera *camera, - const ESMS::ESMStore &store, const Files::PathContainer& dataDirs, - bool useSound, bool fsstrict, MWWorld::Environment& environment) - : mData(NULL) - , fsStrict(fsstrict) - , mEnvironment(environment) - { - for (Files::PathContainer::const_iterator it = dataDirs.begin(); it != dataDirs.end(); ++it) - { - MP3Lookup((*it) / "Music/Explore/"); - } - - if(useSound) - { - mData = new SoundImpl(root, camera, store, dataDirs /* Sound */, dataDirs /* Music */, fsstrict); - } - - test.name = ""; - total = 0; - } - - SoundManager::~SoundManager() - { - if(mData) - delete mData; - } - void SoundManager::streamMusic(const std::string& filename) { - if(mData->hasFile(filename, true)) + std::string filePath = mMusicLibrary.locate(filename, mFSStrict).string(); + if(!filePath.empty()) { - streamMusicFull(mData->convertPath(filename, true)); + streamMusicFull(filePath); } } - void SoundManager::MP3Lookup(const boost::filesystem::path& dir) - { - boost::filesystem::directory_iterator dir_iter(dir), dir_end; - - std::string mp3extension = ".mp3"; - for(;dir_iter != dir_end; dir_iter++) - { - if(boost::filesystem::extension(*dir_iter) == mp3extension) - { - files.push_back(*dir_iter); - } - } - } - void SoundManager::startRandomTitle() { - if(!files.empty()) + if(mCurrentPlaylist && !mCurrentPlaylist->empty()) { - Files::PathContainer::iterator fileIter = files.begin(); + Files::PathContainer::const_iterator fileIter = mCurrentPlaylist->begin(); srand( time(NULL) ); - int r = rand() % files.size() + 1; //old random code + int r = rand() % mCurrentPlaylist->size() + 1; //old random code std::advance(fileIter, r - 1); std::string music = fileIter->string(); @@ -471,105 +323,157 @@ namespace MWSound bool SoundManager::isMusicPlaying() { bool test = false; - if(mData && mData->music) + if(music) { - test = mData->music->isPlaying(); + test = music->isPlaying(); } return test; } - SoundManager::SoundImpl SoundManager::getMData() - { - // bool test = mData->music->isPlaying(); - return *mData; - } + bool SoundManager::setPlaylist(std::string playlist) + { + const Files::PathContainer* previousPlaylist; + previousPlaylist = mCurrentPlaylist; + if (playlist == "") + { + mCurrentPlaylist = mMusicLibrary.section(playlist, mFSStrict); + } + else if(mMusicLibrary.containsSection(playlist, mFSStrict)) + { + mCurrentPlaylist = mMusicLibrary.section(playlist, mFSStrict); + } + else + { + std::cout << "Warning: playlist named " << playlist << " does not exist.\n"; + } + return previousPlaylist == mCurrentPlaylist; + } + + void SoundManager::playPlaylist(std::string playlist) + { + if (playlist == "") + { + if(!isMusicPlaying()) + { + startRandomTitle(); + } + return; + } + + if(!setPlaylist(playlist)) + { + startRandomTitle(); + } + else if (!isMusicPlaying()) + { + startRandomTitle(); + } + } void SoundManager::say (MWWorld::Ptr ptr, const std::string& filename) { // The range values are not tested - if(!mData) return; - if(mData->hasFile(filename)) - mData->add(mData->convertPath(filename), ptr, "_say_sound", 1, 1, 100, 20000, false); + std::string filePath = Files::FileListLocator(mSoundFiles, filename, mFSStrict); + if(!filePath.empty()) + add(filePath, ptr, "_say_sound", 1, 1, 100, 20000, false); else std::cout << "Sound file " << filename << " not found, skipping.\n"; } bool SoundManager::sayDone (MWWorld::Ptr ptr) const { - if(!mData) return false; - return !mData->isPlaying(ptr, "_say_sound"); + return !isPlaying(ptr, "_say_sound"); } - void SoundManager::playSound(const std::string& soundId, float volume, float pitch) + void SoundManager::playSound(const std::string& soundId, float volume, float pitch, bool loop) { - if(!mData) return; - // Play and forget float min, max; - const std::string &file = mData->lookup(soundId, volume, min, max); + const std::string &file = lookup(soundId, volume, min, max); if (file != "") { - SoundPtr snd = mData->mgr->load(file); + SoundPtr snd = mgr->load(file); + snd->setRepeat(loop); snd->setVolume(volume); snd->setRange(min,max); snd->setPitch(pitch); snd->play(); + + if (loop) + { + // Only add the looping sound once + IDMap::iterator it = mLoopedSounds.find(soundId); + if(it == mLoopedSounds.end()) + { + mLoopedSounds[soundId] = WSoundPtr(snd); + } + } } } void SoundManager::playSound3D (MWWorld::Ptr ptr, const std::string& soundId, float volume, float pitch, bool loop) { - if(!mData) return; - // Look up the sound in the ESM data float min, max; - const std::string &file = mData->lookup(soundId, volume, min, max); + const std::string &file = lookup(soundId, volume, min, max); if (file != "") - mData->add(file, ptr, soundId, volume, pitch, min, max, loop); + add(file, ptr, soundId, volume, pitch, min, max, loop); } void SoundManager::stopSound3D (MWWorld::Ptr ptr, const std::string& soundId) { - if(!mData) return; - mData->remove(ptr, soundId); + remove(ptr, soundId); } void SoundManager::stopSound (MWWorld::Ptr::CellStore *cell) { - if(!mData) return; - mData->removeCell(cell); + removeCell(cell); } + void SoundManager::stopSound(const std::string& soundId) + { + IDMap::iterator it = mLoopedSounds.find(soundId); + if(it != mLoopedSounds.end()) + { + SoundPtr snd = it->second.lock(); + if(snd) snd->stop(); + mLoopedSounds.erase(it); + } + } + bool SoundManager::getSoundPlaying (MWWorld::Ptr ptr, const std::string& soundId) const { // Mark all sounds as playing, otherwise the scripts will just // keep trying to play them every frame. - if(!mData) return true; - return mData->isPlaying(ptr, soundId); + return isPlaying(ptr, soundId); } void SoundManager::updateObject(MWWorld::Ptr ptr) { - if (mData != NULL) - { - mData->updatePositions(ptr); - } + updatePositions(ptr); } void SoundManager::update (float duration) { MWWorld::Ptr::CellStore *current = mEnvironment.mWorld->getPlayer().getPlayer().getCell(); + static int total = 0; + static std::string regionName = ""; + static float timePassed = 0.0; + timePassed += duration; //If the region has changed - if(!(current->cell->data.flags & current->cell->Interior) && timer.elapsed() >= 10) + if(!(current->cell->data.flags & current->cell->Interior) && timePassed >= 10) { - timer.restart(); - if (test.name != current->cell->region) + + ESM::Region test = (ESM::Region) *(mEnvironment.mWorld->getStore().regions.find(current->cell->region)); + + timePassed = 0; + if (regionName != current->cell->region) { + regionName = current->cell->region; total = 0; - test = (ESM::Region) *(mEnvironment.mWorld->getStore().regions.find(current->cell->region)); } if(test.soundList.size() > 0) @@ -593,15 +497,15 @@ namespace MWSound soundIter = test.soundList.begin(); while (soundIter != test.soundList.end()) { - const ESM::NAME32 go = soundIter->sound; + const std::string go = soundIter->sound.toString(); int chance = (int) soundIter->chance; //std::cout << "Sound: " << go.name <<" Chance:" << chance << "\n"; soundIter++; if( r - pos < chance) { //play sound - std::cout << "Sound: " << go.name <<" Chance:" << chance << "\n"; - mEnvironment.mSoundManager->playSound(go.name, 20.0, 1.0); + std::cout << "Sound: " << go <<" Chance:" << chance << "\n"; + mEnvironment.mSoundManager->playSound(go, 20.0, 1.0); break; } @@ -611,7 +515,7 @@ namespace MWSound } else if(current->cell->data.flags & current->cell->Interior) { - test.name = ""; + regionName = ""; } } diff --git a/apps/openmw/mwsound/soundmanager.hpp b/apps/openmw/mwsound/soundmanager.hpp index 5c3f473f2..03c19ce77 100644 --- a/apps/openmw/mwsound/soundmanager.hpp +++ b/apps/openmw/mwsound/soundmanager.hpp @@ -3,13 +3,14 @@ #include -#include -#include +#include +#include + +#include + +#include #include "../mwworld/ptr.hpp" -#include -#include - namespace Ogre @@ -18,11 +19,16 @@ namespace Ogre class Camera; } -namespace ESMS +namespace Mangle { - struct ESMStore; + namespace Sound + { + typedef boost::shared_ptr SoundPtr; + } } +typedef OEngine::Sound::SoundManagerPtr OEManagerPtr; + namespace MWWorld { struct Environment; @@ -30,43 +36,94 @@ namespace MWWorld namespace MWSound { - //SoundPtr *music; class SoundManager { - // Hide implementation details - engine.cpp is compiling - // enough as it is. - struct SoundImpl; - SoundImpl *mData; - Files::PathContainer files; - bool fsStrict; + // This is used for case insensitive and slash-type agnostic file + // finding. It takes DOS paths (any case, \\ slashes or / slashes) + // relative to the sound dir, and translates them into full paths + // of existing files in the filesystem, if they exist. + bool mFSStrict; + MWWorld::Environment& mEnvironment; - int total; - ESM::Region test; - boost::timer timer; - void streamMusicFull (const std::string& filename); ///< Play a soundifle /// \param absolute filename + /* This is the sound manager. It loades, stores and deletes + sounds based on the sound factory it is given. + */ + OEManagerPtr mgr; + Mangle::Sound::SoundPtr music; + + /* This class calls update() on the sound manager each frame + using and Ogre::FrameListener + */ + Mangle::Sound::OgreOutputUpdater updater; + + /* This class tracks the movement of an Ogre::Camera and moves + a sound listener automatically to follow it. + */ + Mangle::Sound::OgreListenerMover cameraTracker; + + typedef std::map IDMap; + typedef std::map PtrMap; + PtrMap sounds; + + // A list of all sound files used to lookup paths + Files::PathContainer mSoundFiles; + + // A library of all Music file paths stored by the folder they are contained in + Files::FileLibrary mMusicLibrary; + + // Points to the current playlist of music files stored in the music library + const Files::PathContainer* mCurrentPlaylist; + + IDMap mLoopedSounds; + + std::string lookup(const std::string &soundId, + float &volume, float &min, float &max); + void add(const std::string &file, + MWWorld::Ptr ptr, const std::string &id, + float volume, float pitch, float min, float max, + bool loop); + void clearAll(PtrMap::iterator& it); + void remove(MWWorld::Ptr ptr, const std::string &id = ""); + bool isPlaying(MWWorld::Ptr ptr, const std::string &id) const; + void removeCell(const MWWorld::Ptr::CellStore *cell); + void updatePositions(MWWorld::Ptr ptr); + public: - SoundManager(Ogre::Root*, Ogre::Camera*, const ESMS::ESMStore &store, + SoundManager(Ogre::Root*, Ogre::Camera*, const Files::PathContainer& dataDir, bool useSound, bool fsstrict, MWWorld::Environment& environment); ~SoundManager(); + void stopMusic(); + ///< Stops music if it's playing + void streamMusic(const std::string& filename); ///< Play a soundifle /// \param filename name of a sound file in "Music/" in the data directory. void startRandomTitle(); - void MP3Lookup(const boost::filesystem::path& dir); + ///< Starts a random track from the current playlist bool isMusicPlaying(); + ///< Returns true if music is playing - SoundImpl getMData(); + bool setPlaylist(std::string playlist=""); + ///< Set the playlist to an existing folder + /// \param name of the folder that contains the playlist + /// if none is set then it is set to an empty playlist + /// \return Return true if the previous playlist was the same + + void playPlaylist(std::string playlist=""); + ///< Start playing music from the selected folder + /// \param name of the folder that contains the playlist + /// if none is set then it plays from the current playlist void say (MWWorld::Ptr reference, const std::string& filename); ///< Make an actor say some text. @@ -75,7 +132,7 @@ namespace MWSound bool sayDone (MWWorld::Ptr reference) const; ///< Is actor not speaking? - void playSound (const std::string& soundId, float volume, float pitch); + void playSound (const std::string& soundId, float volume, float pitch, bool loop=false); ///< Play a sound, independently of 3D-position void playSound3D (MWWorld::Ptr reference, const std::string& soundId, @@ -89,6 +146,9 @@ namespace MWSound void stopSound (MWWorld::Ptr::CellStore *cell); ///< Stop all sounds for the given cell. + void stopSound(const std::string& soundId); + ///< Stop a non-3d looping sound + bool getSoundPlaying (MWWorld::Ptr reference, const std::string& soundId) const; ///< Is the given sound currently playing on the given object? diff --git a/apps/openmw/mwworld/cells.cpp b/apps/openmw/mwworld/cells.cpp index 079c888aa..8c657a5c8 100644 --- a/apps/openmw/mwworld/cells.cpp +++ b/apps/openmw/mwworld/cells.cpp @@ -5,6 +5,8 @@ #include #include "world.hpp" +#include "class.hpp" +#include "containerstore.hpp" MWWorld::Ptr::CellStore *MWWorld::Cells::getCellStore (const ESM::Cell *cell) { @@ -35,6 +37,39 @@ MWWorld::Ptr::CellStore *MWWorld::Cells::getCellStore (const ESM::Cell *cell) } } +void MWWorld::Cells::fillContainers (Ptr::CellStore& cellStore) +{ + for (ESMS::CellRefList::List::iterator iter ( + cellStore.containers.list.begin()); + iter!=cellStore.containers.list.end(); ++iter) + { + Ptr container (&*iter, &cellStore); + + Class::get (container).getContainerStore (container).fill ( + iter->base->inventory, mStore); + } + + for (ESMS::CellRefList::List::iterator iter ( + cellStore.creatures.list.begin()); + iter!=cellStore.creatures.list.end(); ++iter) + { + Ptr container (&*iter, &cellStore); + + Class::get (container).getContainerStore (container).fill ( + iter->base->inventory, mStore); + } + + for (ESMS::CellRefList::List::iterator iter ( + cellStore.npcs.list.begin()); + iter!=cellStore.npcs.list.end(); ++iter) + { + Ptr container (&*iter, &cellStore); + + Class::get (container).getContainerStore (container).fill ( + iter->base->inventory, mStore); + } +} + MWWorld::Cells::Cells (const ESMS::ESMStore& store, ESM::ESMReader& reader, MWWorld::World& world) : mStore (store), mReader (reader), mWorld (world) {} @@ -43,6 +78,8 @@ MWWorld::Ptr::CellStore *MWWorld::Cells::getExterior (int x, int y) std::map, Ptr::CellStore>::iterator result = mExteriors.find (std::make_pair (x, y)); + bool fill = false; + if (result==mExteriors.end()) { const ESM::Cell *cell = mStore.cells.searchExt (x, y); @@ -63,11 +100,16 @@ MWWorld::Ptr::CellStore *MWWorld::Cells::getExterior (int x, int y) result = mExteriors.insert (std::make_pair ( std::make_pair (x, y), Ptr::CellStore (cell))).first; + + fill = true; } if (result->second.mState!=Ptr::CellStore::State_Loaded) result->second.load (mStore, mReader); + if (fill) + fillContainers (result->second); + return &result->second; } @@ -75,16 +117,23 @@ MWWorld::Ptr::CellStore *MWWorld::Cells::getInterior (const std::string& name) { std::map::iterator result = mInteriors.find (name); + bool fill = false; + if (result==mInteriors.end()) { const ESM::Cell *cell = mStore.cells.findInt (name); result = mInteriors.insert (std::make_pair (name, Ptr::CellStore (cell))).first; + + fill = true; } if (result->second.mState!=Ptr::CellStore::State_Loaded) result->second.load (mStore, mReader); + if (fill) + fillContainers (result->second); + return &result->second; } diff --git a/apps/openmw/mwworld/cells.hpp b/apps/openmw/mwworld/cells.hpp index 0ecbd02d3..42aa1050c 100644 --- a/apps/openmw/mwworld/cells.hpp +++ b/apps/openmw/mwworld/cells.hpp @@ -34,6 +34,8 @@ namespace MWWorld Ptr::CellStore *getCellStore (const ESM::Cell *cell); + void fillContainers (Ptr::CellStore& cellStore); + public: Cells (const ESMS::ESMStore& store, ESM::ESMReader& reader, MWWorld::World& world); diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 641da73e1..96afc89a1 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -77,6 +77,11 @@ namespace MWWorld throw std::runtime_error ("class does not have a container store"); } + InventoryStore& Class::getInventoryStore (const Ptr& ptr) const + { + throw std::runtime_error ("class does not have an inventory store"); + } + void Class::lock (const Ptr& ptr, int lockLevel) const { throw std::runtime_error ("class does not support locking"); @@ -122,6 +127,16 @@ namespace MWWorld return Ogre::Vector3 (0, 0, 0); } + std::pair, bool> Class::getEquipmentSlots (const Ptr& ptr) const + { + return std::make_pair (std::vector(), false); + } + + int Class::getEuqipmentSkill (const Ptr& ptr, const Environment& environment) const + { + return -1; + } + const Class& Class::get (const std::string& key) { std::map >::const_iterator iter = sClasses.find (key); diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 9b6acb3ce..49c7bed0e 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -3,6 +3,7 @@ #include #include +#include #include @@ -34,6 +35,7 @@ namespace MWWorld class Ptr; class Environment; class ContainerStore; + class InventoryStore; /// \brief Base class for referenceable esm records class Class @@ -108,6 +110,10 @@ namespace MWWorld ///< Return container store or throw an exception, if class does not have a /// container store (default implementation: throw an exceoption) + virtual InventoryStore& getInventoryStore (const Ptr& ptr) const; + ///< Return inventory store or throw an exception, if class does not have a + /// inventory store (default implementation: throw an exceoption) + virtual void lock (const Ptr& ptr, int lockLevel) const; ///< Lock object (default implementation: throw an exception) @@ -137,6 +143,18 @@ namespace MWWorld ///< Return desired movement vector (determined based on movement settings, /// stance and stats). + virtual std::pair, bool> getEquipmentSlots (const Ptr& ptr) const; + ///< \return first: Return IDs of the slot this object can be equipped in; second: can object + /// stay stacked when equipped? + /// + /// Default implementation: return (empty vector, false). + + virtual int getEuqipmentSkill (const Ptr& ptr, const Environment& environment) + const; + /// Return the index of the skill this item corresponds to when equiopped or -1, if there is + /// no such skill. + /// (default implementation: return -1) + static const Class& get (const std::string& key); ///< If there is no class for this \a key, an exception is thrown. diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 54908deec..c498cabce 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -5,6 +5,12 @@ #include #include +#include + +#include "manualref.hpp" + +MWWorld::ContainerStore::~ContainerStore() {} + MWWorld::ContainerStoreIterator MWWorld::ContainerStore::begin (int mask) { return ContainerStoreIterator (mask, this); @@ -17,7 +23,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::end() void MWWorld::ContainerStore::add (const Ptr& ptr) { - /// \todo implement item stocking + /// \todo implement item stacking switch (getType (ptr)) { @@ -36,6 +42,40 @@ void MWWorld::ContainerStore::add (const Ptr& ptr) } } +void MWWorld::ContainerStore::fill (const ESM::InventoryList& items, const ESMS::ESMStore& store) +{ + for (std::vector::const_iterator iter (items.list.begin()); iter!=items.list.end(); + ++iter) + { + ManualRef ref (store, iter->item.toString()); + + if (ref.getPtr().getTypeName()==typeid (ESM::ItemLevList).name()) + { + /// \todo implement leveled item lists + continue; + } + + ref.getPtr().getRefData().setCount (iter->count); + add (ref.getPtr()); + } +} + +void MWWorld::ContainerStore::clear() +{ + potions.list.clear(); + appas.list.clear(); + armors.list.clear(); + books.list.clear(); + clothes.list.clear(); + ingreds.list.clear(); + lights.list.clear(); + lockpicks.list.clear(); + miscItems.list.clear(); + probes.list.clear(); + repairs.list.clear(); + weapons.list.clear(); +} + int MWWorld::ContainerStore::getType (const Ptr& ptr) { if (ptr.isEmpty()) @@ -331,6 +371,11 @@ int MWWorld::ContainerStoreIterator::getType() const return mType; } +const MWWorld::ContainerStore *MWWorld::ContainerStoreIterator::getContainerStore() const +{ + return mContainer; +} + bool MWWorld::operator== (const ContainerStoreIterator& left, const ContainerStoreIterator& right) { return left.isEqual (right); diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index 7263245f3..69e5ba446 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -1,11 +1,18 @@ #ifndef GAME_MWWORLD_CONTAINERSTORE_H #define GAME_MWWORLD_CONTAINERSTORE_H +#include + #include #include "refdata.hpp" #include "ptr.hpp" +namespace ESM +{ + struct InventoryList; +} + namespace MWWorld { class ContainerStoreIterator; @@ -48,6 +55,8 @@ namespace MWWorld public: + virtual ~ContainerStore(); + ContainerStoreIterator begin (int mask = Type_All); ContainerStoreIterator end(); @@ -60,6 +69,12 @@ namespace MWWorld /// \attention Do not add items to an existing stack by increasing the count instead of /// calling this function! + void fill (const ESM::InventoryList& items, const ESMS::ESMStore& store); + ///< Insert items into *this. + + void clear(); + ///< Empty container. + static int getType (const Ptr& ptr); ///< This function throws an exception, if ptr does not point to an object, that can be /// put into a container. @@ -71,6 +86,7 @@ namespace MWWorld /// /// \note The iterator will automatically skip over deleted objects. class ContainerStoreIterator + : public std::iterator { int mType; int mMask; @@ -126,6 +142,8 @@ namespace MWWorld int getType() const; + const ContainerStore *getContainerStore() const; + friend class ContainerStore; }; diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp new file mode 100644 index 000000000..264be7bb3 --- /dev/null +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -0,0 +1,86 @@ + +#include "inventorystore.hpp" + +#include +#include + +#include "class.hpp" + +void MWWorld::InventoryStore::copySlots (const InventoryStore& store) +{ + // some const-trickery, required because of a flaw in the handling of MW-references and the + // resulting workarounds + for (std::vector::const_iterator iter ( + const_cast (store).mSlots.begin()); + iter!=const_cast (store).mSlots.end(); ++iter) + { + std::size_t distance = std::distance (const_cast (store).begin(), *iter); + + ContainerStoreIterator slot = begin(); + + std::advance (slot, distance); + + mSlots.push_back (slot); + } +} + +MWWorld::InventoryStore::InventoryStore() +{ + for (int i=0; i=static_cast (mSlots.size())) + throw std::runtime_error ("slot number out of range"); + + if (iterator.getContainerStore()!=this) + throw std::runtime_error ("attempt to equip an item that is not in the inventory"); + + if (iterator!=end()) + { + std::pair, bool> slots = Class::get (*iterator).getEquipmentSlots (*iterator); + + if (std::find (slots.first.begin(), slots.first.end(), slot)==slots.first.end()) + throw std::runtime_error ("invalid slot"); + } + + /// \todo restack item previously in this slot (if required) + + /// \todo unstack item pointed to by iterator if required) + + mSlots[slot] = iterator; +} + +MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSlot (int slot) +{ + if (slot<0 || slot>=static_cast (mSlots.size())) + throw std::runtime_error ("slot number out of range"); + + if (mSlots[slot]==end()) + return end(); + + if (mSlots[slot]->getRefData().getCount()<1) + { + // object has been deleted + mSlots[slot] = end(); + return end(); + } + + return mSlots[slot]; +} diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp new file mode 100644 index 000000000..c41f9e8e3 --- /dev/null +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -0,0 +1,58 @@ +#ifndef GAME_MWWORLD_INVENTORYSTORE_H +#define GAME_MWWORLD_INVENTORYSTORE_H + +#include "containerstore.hpp" + +namespace MWWorld +{ + ///< \brief Variant of the ContainerStore for NPCs + class InventoryStore : public ContainerStore + { + public: + + static const int Slot_Helmet = 0; + static const int Slot_Cuirass = 1; + static const int Slot_Greaves = 2; + static const int Slot_LeftPauldron = 3; + static const int Slot_RightPauldron = 4; + static const int Slot_LeftGauntlet = 5; + static const int Slot_RightGauntlet = 6; + static const int Slot_Boots = 7; + static const int Slot_Shirt = 8; + static const int Slot_Pants = 9; + static const int Slot_Skirt = 10; + static const int Slot_Robe = 11; + static const int Slot_LeftRing = 12; + static const int Slot_RightRing = 13; + static const int Slot_Amulet = 14; + static const int Slot_Belt = 15; + static const int Slot_CarriedRight = 16; + static const int Slot_CarriedLeft = 17; + static const int Slot_Ammunition = 18; + + static const int Slots = 19; + + static const int Slot_NoSlot = -1; + + private: + + mutable std::vector mSlots; + + void copySlots (const InventoryStore& store); + + public: + + InventoryStore(); + + InventoryStore (const InventoryStore& store); + + InventoryStore& operator= (const InventoryStore& store); + + void equip (int slot, const ContainerStoreIterator& iterator); + ///< \note \a iteartor can be an end-iterator + + ContainerStoreIterator getSlot (int slot); + }; +} + +#endif diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index b0da4524e..bb2f9f8a9 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -120,7 +120,7 @@ namespace MWWorld void PhysicsSystem::addObject (const std::string& handle, const std::string& mesh, const Ogre::Quaternion& rotation, float scale, const Ogre::Vector3& position) { - OEngine::Physic::RigidBody* body = mEngine->createRigidBody(mesh,handle); + OEngine::Physic::RigidBody* body = mEngine->createRigidBody(mesh,handle,scale); mEngine->addRigidBody(body); btTransform tr; tr.setOrigin(btVector3(position.x,position.y,position.z)); diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 7cb9f3dfc..eadd4b353 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -722,6 +722,40 @@ void WeatherManager::update(float duration) mRendering->skyDisable(); mRendering->getSkyManager()->setThunder(0.f); } + + // play sounds + std::string ambientSnd = (mNextWeather == "" ? mWeatherSettings[mCurrentWeather].mAmbientLoopSoundID : ""); + if (ambientSnd != "") + { + if (std::find(mSoundsPlaying.begin(), mSoundsPlaying.end(), ambientSnd) == mSoundsPlaying.end()) + { + mSoundsPlaying.push_back(ambientSnd); + mEnvironment->mSoundManager->playSound(ambientSnd, 1.0, 1.0, true); + } + } + + std::string rainSnd = (mNextWeather == "" ? mWeatherSettings[mCurrentWeather].mRainLoopSoundID : ""); + if (rainSnd != "") + { + if (std::find(mSoundsPlaying.begin(), mSoundsPlaying.end(), rainSnd) == mSoundsPlaying.end()) + { + mSoundsPlaying.push_back(rainSnd); + mEnvironment->mSoundManager->playSound(rainSnd, 1.0, 1.0, true); + } + } + + // stop sounds + std::vector::iterator it=mSoundsPlaying.begin(); + while (it!=mSoundsPlaying.end()) + { + if ( *it != ambientSnd && *it != rainSnd) + { + mEnvironment->mSoundManager->stopSound(*it); + it = mSoundsPlaying.erase(it); + } + else + ++it; + } } void WeatherManager::setHour(const float hour) @@ -758,7 +792,7 @@ unsigned int WeatherManager::getWeatherID() const return 3; else if (mCurrentWeather == "rain") return 4; - else if (mCurrentWeather == "thunder") + else if (mCurrentWeather == "thunderstorm") return 5; else if (mCurrentWeather == "ashstorm") return 6; @@ -787,7 +821,7 @@ void WeatherManager::changeWeather(const std::string& region, const unsigned int else if (id==4) weather = "rain"; else if (id==5) - weather = "thunder"; + weather = "thunderstorm"; else if (id==6) weather = "ashstorm"; else if (id==7) diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index 7a719252b..1828a90c9 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -243,8 +243,10 @@ namespace MWWorld MWWorld::Environment* mEnvironment; std::map mWeatherSettings; - + std::map mRegionOverrides; + + std::vector mSoundsPlaying; Ogre::String mCurrentWeather; Ogre::String mNextWeather; diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 6bf7bacf4..c95efb37d 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -44,6 +44,7 @@ add_component_dir (misc add_component_dir (files linuxpath windowspath macospath fixedpath multidircollection collections fileops configurationmanager + filelibrary ) add_component_dir (compiler diff --git a/components/files/filelibrary.cpp b/components/files/filelibrary.cpp new file mode 100644 index 000000000..f1467166e --- /dev/null +++ b/components/files/filelibrary.cpp @@ -0,0 +1,120 @@ +#include "filelibrary.hpp" + +#include + +#include +#include + +namespace Files +{ + // Looks for a string in a vector of strings + bool containsVectorString(const StringVector& list, const std::string& str) + { + for (StringVector::const_iterator iter = list.begin(); + iter != list.end(); iter++) + { + if (*iter == str) + return true; + } + return false; + } + + // Searches a path and adds the results to the library + void FileLibrary::add(const boost::filesystem::path &root, bool recursive, bool strict, + const StringVector &acceptableExtensions) + { + if (!boost::filesystem::exists(root)) + { + std::cout << "Warning " << root.string() << " does not exist.\n"; + return; + } + + std::string fileExtension; + std::string type; + + // remember the last location of the priority list when listing new items + int length = mPriorityList.size(); + + // First makes a list of all candidate files + FileLister(root, mPriorityList, recursive); + + // Then sort these files into sections according to the folder they belong to + for (PathContainer::iterator listIter = mPriorityList.begin() + length; + listIter != mPriorityList.end(); ++listIter) + { + if( !acceptableExtensions.empty() ) + { + fileExtension = boost::filesystem::path (listIter->extension()).string(); + boost::algorithm::to_lower(fileExtension); + if(!containsVectorString(acceptableExtensions, fileExtension)) + continue; + } + + type = boost::filesystem::path (listIter->parent_path().leaf()).string(); + if (!strict) + boost::algorithm::to_lower(type); + + mMap[type].push_back(*listIter); + // std::cout << "Added path: " << listIter->string() << " in section "<< type <second); + } + } + + // Searches the library for an item and returns a boost path to it + boost::filesystem::path FileLibrary::locate(std::string item, bool strict, std::string sectionName) + { + boost::filesystem::path result(""); + if (sectionName == "") + { + return FileListLocator(mPriorityList, boost::filesystem::path(item), strict); + } + else + { + if (!containsSection(sectionName, strict)) + { + std::cout << "Warning: There is no section named " << sectionName << "\n"; + return result; + } + result = FileListLocator(mMap[sectionName], boost::filesystem::path(item), strict); + } + return result; + } + + // Prints all the available sections, used for debugging + void FileLibrary::printSections() + { + for(StringPathContMap::const_iterator mapIter = mMap.begin(); + mapIter != mMap.end(); mapIter++) + { + std::cout << mapIter->first < + +namespace Files +{ + typedef std::map StringPathContMap; + typedef std::vector StringVector; + + /// Looks for a string in a vector of strings + bool containsVectorString(const StringVector& list, const std::string& str); + + /// \brief Searches directories and makes lists of files according to folder name + class FileLibrary + { + private: + StringPathContMap mMap; + PathContainer mEmptyPath; + PathContainer mPriorityList; + + public: + /// Searches a path and adds the results to the library + /// Recursive search and fs strict options are available + /// Takes a vector of acceptable files extensions, if none is given it lists everything. + void add(const boost::filesystem::path &root, bool recursive, bool strict, + const StringVector &acceptableExtensions); + + /// Returns true if the named section exists + /// You can run this check before running section() + bool containsSection(std::string sectionName, bool strict); + + /// Returns a pointer to const for a section of the library + /// which is essentially a PathContainer. + /// If the section does not exists it returns a pointer to an empty path. + const PathContainer* section(std::string sectionName, bool strict); + + /// Searches the library for an item and returns a boost path to it + /// Optionally you can provide a specific section + /// The result is the first that comes up according to alphabetical + /// section naming + boost::filesystem::path locate(std::string item, bool strict, std::string sectionName=""); + + /// Prints all the available sections, used for debugging + void printSections(); + }; +} + +#endif diff --git a/components/files/fileops.cpp b/components/files/fileops.cpp index 329e4eb05..f57eaa546 100644 --- a/components/files/fileops.cpp +++ b/components/files/fileops.cpp @@ -1,5 +1,9 @@ #include "fileops.hpp" + +#include + #include +#include namespace Files { @@ -9,4 +13,90 @@ bool isFile(const char *name) return boost::filesystem::exists(boost::filesystem::path(name)); } + // Makes a list of files from a directory + void FileLister( boost::filesystem::path currentPath, Files::PathContainer& list, bool recursive) + { + if (!boost::filesystem::exists(currentPath)) + { + std::cout << "WARNING: " << currentPath.string() << " does not exist.\n"; + return ; + } + if (recursive) + { + for ( boost::filesystem::recursive_directory_iterator end, itr(currentPath.string()); + itr != end; ++itr ) + { + if ( boost::filesystem::is_regular_file(*itr)) + list.push_back(itr->path()); + } + } + else + { + for ( boost::filesystem::directory_iterator end, itr(currentPath.string()); + itr != end; ++itr ) + { + if ( boost::filesystem::is_regular_file(*itr)) + list.push_back(itr->path()); + } + } + } + + // Locates path in path container + boost::filesystem::path FileListLocator (const Files::PathContainer& list, const boost::filesystem::path& toFind, bool strict) + { + boost::filesystem::path result(""); + if (list.empty()) + return result; + + std::string toFindStr = toFind.string(); + + std::string fullPath; + + // The filesystems slash sets the default slash + std::string slash; + std::string wrongslash; + if(list[0].string().find("\\") != std::string::npos) + { + slash = "\\"; + wrongslash = "/"; + } + else + { + slash = "/"; + wrongslash = "\\"; + } + + // The file being looked for is converted to the new slash + if(toFindStr.find(wrongslash) != std::string::npos ) + { + boost::replace_all(toFindStr, wrongslash, slash); + } + + if (!strict) + { + boost::algorithm::to_lower(toFindStr); + } + + for (Files::PathContainer::const_iterator it = list.begin(); it != list.end(); ++it) + { + fullPath = it->string(); + if (!strict) + { + boost::algorithm::to_lower(fullPath); + } + if(fullPath.find(toFindStr) != std::string::npos) + { + result = *it; + break; + } + } + return result; + } + + // Overloaded form of the locator that takes a string and returns a string + std::string FileListLocator (const Files::PathContainer& list,const std::string& toFind, bool strict) + { + return FileListLocator(list, boost::filesystem::path(toFind), strict).string(); + } + } diff --git a/components/files/fileops.hpp b/components/files/fileops.hpp index a541beffc..49ef7b947 100644 --- a/components/files/fileops.hpp +++ b/components/files/fileops.hpp @@ -1,6 +1,12 @@ #ifndef COMPONENTS_FILES_FILEOPS_HPP #define COMPONENTS_FILES_FILEOPS_HPP +#include +#include +#include + +#include + namespace Files { @@ -8,6 +14,24 @@ namespace Files ///\param [in] name - filename bool isFile(const char *name); + /// A vector of Boost Paths, very handy + typedef std::vector PathContainer; + + /// Makes a list of files from a directory by taking a boost + /// path and a Path Container and adds to the Path container + /// all files in the path. It has a recursive option. + void FileLister( boost::filesystem::path currentPath, Files::PathContainer& list, bool recursive); + + /// Locates boost path in path container + /// returns the path from the container + /// that contains the searched path. + /// If it's not found it returns and empty path + /// Takes care of slashes, backslashes and it has a strict option. + boost::filesystem::path FileListLocator (const Files::PathContainer& list, const boost::filesystem::path& toFind, bool strict); + + /// Overloaded form of the locator that takes a string and returns a string + std::string FileListLocator (const Files::PathContainer& list,const std::string& toFind, bool strict); + } #endif /* COMPONENTS_FILES_FILEOPS_HPP */ diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index baf33d51f..2e68cfe90 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -243,6 +243,8 @@ void NIFLoader::createMaterial(const String &name, /*TextureUnitState *txt =*/ pass->createTextureUnitState(texName); + pass->setVertexColourTracking(TVC_DIFFUSE); + // As of yet UNTESTED code from Chris: /*pass->setTextureFiltering(Ogre::TFO_ANISOTROPIC); pass->setDepthFunction(Ogre::CMPF_LESS_EQUAL); @@ -1459,7 +1461,10 @@ void NIFLoader::loadResource(Resource *resource) (*iter)->addBoneAssignment(vba); } - mesh->_notifySkeleton(mSkel); + //Don't link on npc parts to eliminate redundant skeletons + //Will have to be changed later slightly for robes/skirts + if(triname == "") + mesh->_notifySkeleton(mSkel); } } diff --git a/libs/mangle b/libs/mangle deleted file mode 160000 index 14b2851e7..000000000 --- a/libs/mangle +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 14b2851e72f610ae81dd296598867e6fb0babd2a diff --git a/libs/mangle/.gitignore b/libs/mangle/.gitignore new file mode 100644 index 000000000..cd24d7897 --- /dev/null +++ b/libs/mangle/.gitignore @@ -0,0 +1,3 @@ +upload_docs.sh +docs +*~ diff --git a/libs/mangle/Doxyfile b/libs/mangle/Doxyfile new file mode 100644 index 000000000..f3e018002 --- /dev/null +++ b/libs/mangle/Doxyfile @@ -0,0 +1,1510 @@ +# Doxyfile 1.5.8 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = Mangle + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = 1 + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Farsi, Finnish, French, German, Greek, +# Hungarian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, Polish, +# Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, Slovene, +# Spanish, Swedish, and Ukrainian. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it parses. +# With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this tag. +# The format is ext=language, where ext is a file extension, and language is one of +# the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP, +# Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat +# .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C + +EXTENSION_MAPPING = + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = YES + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen to replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penality. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will rougly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols + +SYMBOL_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespace are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = NO + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. +# This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by +# doxygen. The layout file controls the global structure of the generated output files +# in an output format independent way. The create the layout file that represents +# doxygen's defaults, run doxygen with the -l option. You can optionally specify a +# file name after the option, if omitted DoxygenLayout.xml will be used as the name +# of the layout file. + +LAYOUT_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be abled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = sound stream vfs input + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx +# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 + +FILE_PATTERNS = *.h + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix filesystem feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = */tests/* + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. +# If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. +# Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. +# The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER +# is applied to all files. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. +# Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = docs + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +HTML_DYNAMIC_SECTIONS = NO + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER +# are set, an additional index file will be generated that can be used as input for +# Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated +# HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add. +# For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's +# filter section matches. +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to FRAME, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, +# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are +# probably better off using the HTML help feature. Other possible values +# for this tag are: HIERARCHIES, which will generate the Groups, Directories, +# and Class Hierarchy pages using a tree view instead of an ordered list; +# ALL, which combines the behavior of FRAME and HIERARCHIES; and NONE, which +# disables this behavior completely. For backwards compatibility with previous +# releases of Doxygen, the values YES and NO are equivalent to FRAME and NONE +# respectively. + +GENERATE_TREEVIEW = NONE + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. +# This is useful +# if you want to understand what is going on. +# On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse +# the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option is superseded by the HAVE_DOT option below. This is only a +# fallback. It is recommended to install and use dot, since it yields more +# powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = YES + +# By default doxygen will write a font called FreeSans.ttf to the output +# directory and reference it in all dot files that doxygen generates. This +# font does not include all possible unicode characters however, so when you need +# these (or just want a differently looking font) you can specify the font name +# using DOT_FONTNAME. You need need to make sure dot is able to find the font, +# which can be done by putting it in a standard location or by setting the +# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory +# containing the font. + +DOT_FONTNAME = FreeSans + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the output directory to look for the +# FreeSans.ttf font (which doxygen will put there itself). If you specify a +# different font using DOT_FONTNAME you can set the path where dot +# can find it using this tag. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES + +#--------------------------------------------------------------------------- +# Options related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = NO diff --git a/libs/mangle/LICENSE.txt b/libs/mangle/LICENSE.txt new file mode 100644 index 000000000..ccfcc9f22 --- /dev/null +++ b/libs/mangle/LICENSE.txt @@ -0,0 +1,26 @@ +Minimal Abstraction Game Layer (Mangle) is licensed under the +'zlib/libpng' license: + +---- + +Copyright (c) 2009 Nicolay Korslund + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. + diff --git a/libs/mangle/README.txt b/libs/mangle/README.txt new file mode 100644 index 000000000..f4849bebd --- /dev/null +++ b/libs/mangle/README.txt @@ -0,0 +1,129 @@ +Welcome to Mangle v0.1 +---------------------- + +Written by: Nicolay Korslund (korslund@gmail.com) +License: zlib/png (see LICENSE.txt) +WWW: http://asm-soft.com/mangle/ +Documentation: http://asm-soft.com/mangle/docs + + + +Mangle is the project name for a small set of generic interfaces for +various game middleware libraries, such as sound, input, graphics, and +so on. You can imagine that it stands for "Minimal Abstraction Game +Layer", if you like. It will consist of several more or less +independent modules, one for each of these areas. These may be used +together to build an entire game engine, or they can be used +individually as separate libraries. + +However, Mangle does NOT actually implement a game engine, or any new +fundamental functionality. More on that below. + +Currently there's modules for sound and streams / archives (virtual +file systems.) More will come in the future (including input, 2D/3D +graphics, GUI, physics, and more.) + + +Main idea +--------- + +The idea behind Mangle is to provide a uniform, consistent interface +to other game libraries. The library does not provide ANY +functionality on its own. Instead it connects to a backend +implementation of your choice (or of your making.) + +The Sound module, for example, currently has backends for OpenAL +(output only), FFmpeg (input only) and for Audiere. Hopefully we'll +add IrrKlang, FMod, DirectSound, Miles and more in the future. It can +combine libraries to get more complete functionality (like using +OpenAL for output and FFmpeg to decode sound files), and it's also +easy to write your own backend if you're using a different (or +home-brewed) sound system. + +Regardless of what backend you use, the front-end interfaces (found +eg. in sound/output.h) is identical, and as a library user you +shouldn't notice much difference at all if you swap one backend for +another at a later point. It should Just Work. + +The interfaces themselves are also quite simple. Setting up a sound +stream from FFmpeg or other decoder into OpenAL can be quite hairy - +but with Mangle the hairy parts have already been written for you. You +just plug the parts together. + +The goal in the long run is to support a wide variety of game-related +libraries, and as many backend libraries (free and commercial) as +possible, so that you the user will have to write as little code as +possible. + + + +What is it good for +------------------- + +The main point of Mangle, as we said above, is that it connects to any +library of your choice "behind the scenes" but provides the same, +super-simple interface front-end for all of them. There can benefit +you in many ways: + +- If you want to use a new library that Mangle support. You don't have + to scour the net for tutorials and usage examples, since much of the + common usage code is already included in the implementation classes. + +- If you don't want to pollute your code with library-specific code. + The Mangle interfaces can help you keep your code clean, and its + user interface is often simpler than the exteral library one. + +- If you want to quickly connect different libraries together, it + really helps if they speak a common language. The Mangle interfaces + are exactly that - a common language between libraries. Do you need + Audiere to load sounds from a weird archive format only implemented + for PhysFS, all channeled through the OGRE resource system? No + problem! + +- If you are creating a library that depends on a specific feature + (such as sound), but you don't want to lock your users into any + specific sound library. Mangle works as an abstraction that lets + your users select their own implementation. + +- If you want to support multiple backends for your game/app, or want + to make it possible to easily switch backends later. You can select + backends at compile time or even at runtime. For example you might + want to switch to to a commercial sound library at a later stage in + development, or you may want to use a different input library on + console platforms than on PC. + +The Mangle implementations are extremely light-weight - often just one +or two cpp/h pairs per module. You can plug them directly into your +program, there's no separate library building step required. + +Since the library aims to be very modularly put together, you can +also, in many cases, just copy-and-paste the parts you need and ignore +the rest. Or modify stuff without fearing that the whole 'system' will +come crashing down, because there is no big 'system' to speak of. + + +Past and future +--------------- + +Mangle started out as (and still is) a spin-off from OpenMW, another +project I am personally working on ( http://openmw.com/ ). OpenMW is +an attempt to recreate the engine behind the commercial game +Morrowind, using only open source software. + +The projects are still tightly interlinked, and they will continue to +be until OpenMW is finished. Most near-future work on Mangle will be +focused chiefly on OpenMW at the moment. However I will gladly include +external contributions and suggestions that are not OpenMW-related if +someone sends them to me. + + +Conclusion +---------- + +As you might have guessed, Mangle is more a concept in development +than a finished library right now. + +All feedback, ideas, concepts, questions and code are very +welcome. Send them to: korslund@gmail.com + +I will put up a forum later as well if there's enough interest. diff --git a/libs/mangle/input/clients/ogre_input_capture.hpp b/libs/mangle/input/clients/ogre_input_capture.hpp new file mode 100644 index 000000000..2e77dc10b --- /dev/null +++ b/libs/mangle/input/clients/ogre_input_capture.hpp @@ -0,0 +1,29 @@ +#ifndef MANGLE_INPUT_OGREINPUTFRAME_H +#define MANGLE_INPUT_OGREINPUTFRAME_H + +/* + This Ogre FrameListener calls capture() on an input driver every frame. + */ + +#include +#include "../driver.hpp" + +namespace Mangle { +namespace Input { + + struct OgreInputCapture : Ogre::FrameListener + { + Mangle::Input::Driver &driver; + + OgreInputCapture(Mangle::Input::Driver &drv) + : driver(drv) {} + + bool frameStarted(const Ogre::FrameEvent &evt) + { + driver.capture(); + return true; + } + }; +}} + +#endif diff --git a/libs/mangle/input/driver.hpp b/libs/mangle/input/driver.hpp new file mode 100644 index 000000000..f4ba159c5 --- /dev/null +++ b/libs/mangle/input/driver.hpp @@ -0,0 +1,69 @@ +#ifndef MANGLE_INPUT_DRIVER_H +#define MANGLE_INPUT_DRIVER_H + +#include "event.hpp" + +namespace Mangle +{ + namespace Input + { + /** Input::Driver is the main interface to any input system that + handles keyboard and/or mouse input, along with any other + input source like joysticks. + + It is really a generalized event system, and could also be + used for non-input related events. The definition of the event + codes and structures are entirely dependent on the + implementation. + + A system-independent key code list will be found in keys.hpp, + and input drivers should privide optional translations to/from + this list for full compatibility. + */ + struct Driver + { + Driver() {} + virtual ~Driver() {} + + /** Captures input and produces the relevant events from it. An + event callback must be set with setEvent(), or all events + will be ignored. + */ + virtual void capture() = 0; + + /** Check the state of a given key or button. The key/button + definitions depends on the driver. + */ + virtual bool isDown(int index) = 0; + + /** Show or hide system mouse cursor + */ + virtual void showMouse(bool show) = 0; + + /** Set the event handler for input events. The evt->event() + function is called for each event. The meaning of the index + and *p parameters will be specific to each driver and to + each input system. + */ + void setEvent(EventPtr evt) + { event = evt; } + + /** Instigate an event. Is used internally for all events, but + can also be called from the outside to "fake" events from + this driver. + */ + void makeEvent(Event::Type type, int index, const void *p=NULL) + { + if(event) + event->event(type,index,p); + } + + private: + /// Holds the event callback set byt setEvent() + EventPtr event; + }; + + typedef boost::shared_ptr DriverPtr; + } +} +#endif diff --git a/libs/mangle/input/event.hpp b/libs/mangle/input/event.hpp new file mode 100644 index 000000000..dc7b47088 --- /dev/null +++ b/libs/mangle/input/event.hpp @@ -0,0 +1,46 @@ +#ifndef MANGLE_INPUT_EVENT_H +#define MANGLE_INPUT_EVENT_H + +#include "../tools/shared_ptr.hpp" + +namespace Mangle +{ + namespace Input + { + /** Generic callback for input events. The meaning of the + parameters depend on the system producing the events. + */ + struct Event + { + /// Event types + enum Type + { + EV_Unknown = 1, // Unknown event type + EV_KeyDown = 2, // Keyboard button was pressed + EV_KeyUp = 4, // Keyboard button was released + EV_Keyboard = 6, // All keyboard events + + EV_MouseMove = 8, // Mouse movement + EV_MouseDown = 16, // Mouse button pressed + EV_MouseUp = 32, // Mouse button released + EV_Mouse = 56, // All mouse events + + EV_ALL = 63 // All events + }; + + /** + Called upon all events. The first parameter give the event + type, the second gives additional data (usually the local + keysym or button index as defined by the driver), and the + pointer points to the full custom event structure provided by + the driver (the type may vary depending on the EventType, + this is defined in the Driver documentation.) + */ + virtual void event(Type type, int index, const void *p) = 0; + virtual ~Event() {} + }; + + typedef boost::shared_ptr EventPtr; + } +} +#endif diff --git a/libs/mangle/input/filters/eventlist.hpp b/libs/mangle/input/filters/eventlist.hpp new file mode 100644 index 000000000..b3e2ff8f2 --- /dev/null +++ b/libs/mangle/input/filters/eventlist.hpp @@ -0,0 +1,47 @@ +#ifndef MANGLE_INPUT_EVENTLIST_H +#define MANGLE_INPUT_EVENTLIST_H + +#include "../event.hpp" +#include + +namespace Mangle +{ + namespace Input + { + /** And Event handler that distributes each event to a list of + other handlers. Supports filtering events by their Type + parameter. + */ + struct EventList : Event + { + struct Filter + { + EventPtr evt; + int flags; + }; + std::vector list; + + void add(EventPtr e, int flags = EV_ALL) + { + Filter f; + f.evt = e; + f.flags = flags; + list.push_back(f); + } + + virtual void event(Type type, int index, const void *p) + { + std::vector::iterator it; + + for(it=list.begin(); it!=list.end(); it++) + { + if(type & it->flags) + it->evt->event(type,index,p); + } + } + }; + + typedef boost::shared_ptr EventListPtr; + } +} +#endif diff --git a/libs/mangle/input/servers/ois_driver.cpp b/libs/mangle/input/servers/ois_driver.cpp new file mode 100644 index 000000000..2071b91ea --- /dev/null +++ b/libs/mangle/input/servers/ois_driver.cpp @@ -0,0 +1,148 @@ +#include "ois_driver.hpp" + +#include +#include +#include +#include + +#ifdef __APPLE_CC__ +#include +#endif + +using namespace Mangle::Input; +using namespace OIS; + +struct Mangle::Input::OISListener : OIS::KeyListener, OIS::MouseListener +{ + OISDriver &drv; + + OISListener(OISDriver &driver) + : drv(driver) {} + + bool keyPressed( const OIS::KeyEvent &arg ) + { + drv.makeEvent(Event::EV_KeyDown, arg.key, &arg); + return true; + } + + bool keyReleased( const OIS::KeyEvent &arg ) + { + drv.makeEvent(Event::EV_KeyUp, arg.key, &arg); + return true; + } + + bool mousePressed( const OIS::MouseEvent &arg, OIS::MouseButtonID id ) + { + // Mouse button events are handled as key events + // TODO: Translate mouse buttons into pseudo-keysyms + drv.makeEvent(Event::EV_MouseDown, id, &arg); + return true; + } + + bool mouseReleased( const OIS::MouseEvent &arg, OIS::MouseButtonID id ) + { + // TODO: ditto + drv.makeEvent(Event::EV_MouseUp, id, &arg); + return true; + } + + bool mouseMoved( const OIS::MouseEvent &arg ) + { + drv.makeEvent(Event::EV_MouseMove, -1, &arg); + return true; + } +}; + +OISDriver::OISDriver(Ogre::RenderWindow *window, bool exclusive) +{ + assert(window); + + size_t windowHnd; + + window->getCustomAttribute("WINDOW", &windowHnd); + + std::ostringstream windowHndStr; + ParamList pl; + + windowHndStr << windowHnd; + pl.insert(std::make_pair(std::string("WINDOW"), windowHndStr.str())); + + // Set non-exclusive mouse and keyboard input if the user requested + // it. + if(!exclusive) + { +#if defined OIS_WIN32_PLATFORM + pl.insert(std::make_pair(std::string("w32_mouse"), + std::string("DISCL_FOREGROUND" ))); + pl.insert(std::make_pair(std::string("w32_mouse"), + std::string("DISCL_NONEXCLUSIVE"))); + pl.insert(std::make_pair(std::string("w32_keyboard"), + std::string("DISCL_FOREGROUND"))); + pl.insert(std::make_pair(std::string("w32_keyboard"), + std::string("DISCL_NONEXCLUSIVE"))); +#elif defined OIS_LINUX_PLATFORM + pl.insert(std::make_pair(std::string("x11_mouse_grab"), + std::string("false"))); + pl.insert(std::make_pair(std::string("x11_mouse_hide"), + std::string("false"))); + pl.insert(std::make_pair(std::string("x11_keyboard_grab"), + std::string("false"))); + pl.insert(std::make_pair(std::string("XAutoRepeatOn"), + std::string("true"))); +#endif + } + +#ifdef __APPLE_CC__ + // Give the application window focus to receive input events + ProcessSerialNumber psn = { 0, kCurrentProcess }; + TransformProcessType(&psn, kProcessTransformToForegroundApplication); + SetFrontProcess(&psn); +#endif + + inputMgr = InputManager::createInputSystem( pl ); + + // Create all devices + keyboard = static_cast(inputMgr->createInputObject + ( OISKeyboard, true )); + mouse = static_cast(inputMgr->createInputObject + ( OISMouse, true )); + + // Set mouse region + const MouseState &ms = mouse->getMouseState(); + ms.width = window->getWidth(); + ms.height = window->getHeight(); + + // Set up the input listener + listener = new OISListener(*this); + keyboard-> setEventCallback(listener); + mouse-> setEventCallback(listener); +} + +OISDriver::~OISDriver() +{ + // Delete the listener object + if(listener) + delete listener; + + if(inputMgr == NULL) return; + + // Kill the input systems. This will reset input options such as key + // repeat rate. + inputMgr->destroyInputObject(keyboard); + inputMgr->destroyInputObject(mouse); + InputManager::destroyInputSystem(inputMgr); + inputMgr = NULL; +} + +void OISDriver::capture() +{ + // Capture keyboard and mouse events + keyboard->capture(); + mouse->capture(); +} + +bool OISDriver::isDown(int index) +{ + // TODO: Extend to mouse buttons as well + return keyboard->isKeyDown((OIS::KeyCode)index); +} diff --git a/libs/mangle/input/servers/ois_driver.hpp b/libs/mangle/input/servers/ois_driver.hpp new file mode 100644 index 000000000..ba780c39e --- /dev/null +++ b/libs/mangle/input/servers/ois_driver.hpp @@ -0,0 +1,48 @@ +#ifndef MANGLE_INPUT_OIS_DRIVER_H +#define MANGLE_INPUT_OIS_DRIVER_H + +#include "../driver.hpp" + +namespace OIS +{ + class InputManager; + class Mouse; + class Keyboard; +} + +namespace Ogre +{ + class RenderWindow; +} + +namespace Mangle +{ + namespace Input + { + struct OISListener; + + /** Input driver for OIS, the input manager typically used with + Ogre. + */ + struct OISDriver : Driver + { + /// If exclusive=true, then we capture mouse and keyboard from + /// the OS. + OISDriver(Ogre::RenderWindow *window, bool exclusive=true); + ~OISDriver(); + + void capture(); + bool isDown(int index); + /// Not currently supported. + void showMouse(bool) {} + + private: + OIS::InputManager *inputMgr; + OIS::Mouse *mouse; + OIS::Keyboard *keyboard; + + OISListener *listener; + }; + } +} +#endif diff --git a/libs/mangle/input/servers/sdl_driver.cpp b/libs/mangle/input/servers/sdl_driver.cpp new file mode 100644 index 000000000..93884a6e6 --- /dev/null +++ b/libs/mangle/input/servers/sdl_driver.cpp @@ -0,0 +1,54 @@ +#include "sdl_driver.hpp" + +#include + +using namespace Mangle::Input; + +void SDLDriver::capture() +{ + // Poll for events + SDL_Event evt; + while(SDL_PollEvent(&evt)) + { + Event::Type type = Event::EV_Unknown; + int index = -1; + + switch(evt.type) + { + // For key events, send the keysym as the index. + case SDL_KEYDOWN: + type = Event::EV_KeyDown; + index = evt.key.keysym.sym; + break; + case SDL_KEYUP: + type = Event::EV_KeyUp; + index = evt.key.keysym.sym; + break; + case SDL_MOUSEMOTION: + type = Event::EV_MouseMove; + break; + // Add more event types later + } + + // Pass the event along, using -1 as index for unidentified + // event types. + makeEvent(type, index, &evt); + } +} + +bool SDLDriver::isDown(int index) +{ + int num; + Uint8 *keys = SDL_GetKeyState(&num); + assert(index >= 0 && index < num); + + // The returned array from GetKeyState is indexed by the + // SDLK_KEYNAME enums and is just a list of bools. If the indexed + // value is true, the button is down. + return keys[index]; +} + +void SDLDriver::showMouse(bool show) +{ + SDL_ShowCursor(show?SDL_ENABLE:SDL_DISABLE); +} diff --git a/libs/mangle/input/servers/sdl_driver.hpp b/libs/mangle/input/servers/sdl_driver.hpp new file mode 100644 index 000000000..b71346cba --- /dev/null +++ b/libs/mangle/input/servers/sdl_driver.hpp @@ -0,0 +1,27 @@ +#ifndef MANGLE_INPUT_SDL_DRIVER_H +#define MANGLE_INPUT_SDL_DRIVER_H + +#include "../driver.hpp" + +namespace Mangle +{ + namespace Input + { + /** Input driver for SDL. As the input system of SDL is seldomly + used alone (most often along with the video system), it is + assumed that you do your own initialization and cleanup of SDL + before and after using this driver. + + The Event.event() calls will be given the proper EV_ type, the + key index (for key up/down events), and a pointer to the full + SDL_Event structure. + */ + struct SDLDriver : Driver + { + void capture(); + bool isDown(int index); + void showMouse(bool); + }; + } +} +#endif diff --git a/libs/mangle/input/tests/.gitignore b/libs/mangle/input/tests/.gitignore new file mode 100644 index 000000000..460c76f00 --- /dev/null +++ b/libs/mangle/input/tests/.gitignore @@ -0,0 +1,2 @@ +*_test +ogre.cfg diff --git a/libs/mangle/input/tests/Makefile b/libs/mangle/input/tests/Makefile new file mode 100644 index 000000000..8760adfe7 --- /dev/null +++ b/libs/mangle/input/tests/Makefile @@ -0,0 +1,15 @@ +GCC=g++ -Wall + +all: sdl_driver_test ois_driver_test evtlist_test + +sdl_driver_test: sdl_driver_test.cpp + $(GCC) $< ../servers/sdl_driver.cpp -o $@ -I/usr/include/SDL/ -lSDL + +ois_driver_test: ois_driver_test.cpp + $(GCC) $< ../servers/ois_driver.cpp -o $@ -I/usr/local/include/OGRE/ -lOgreMain -lOIS -lboost_filesystem + +evtlist_test: evtlist_test.cpp ../filters/eventlist.hpp ../event.hpp + $(GCC) $< -o $@ + +clean: + rm *_test diff --git a/libs/mangle/input/tests/common.cpp b/libs/mangle/input/tests/common.cpp new file mode 100644 index 000000000..0c7c76466 --- /dev/null +++ b/libs/mangle/input/tests/common.cpp @@ -0,0 +1,35 @@ +#include +#include "../driver.hpp" +#include +using namespace std; +using namespace Mangle::Input; + +Driver *input; + +struct MyCB : Event +{ + void event(Event::Type type, int i, const void *p) + { + cout << "got event: type=" << type << " index=" << i << endl; + } +}; + +void mainLoop(int argc, int quitKey) +{ + cout << "Hold the Q key to quit:\n"; + input->setEvent(EventPtr(new MyCB)); + while(!input->isDown(quitKey)) + { + input->capture(); + usleep(20000); + + if(argc == 1) + { + cout << "You are running in script mode, aborting. Run this test with a parameter (any at all) to test the input loop properly\n"; + break; + } + } + + delete input; + cout << "\nBye bye!\n"; +} diff --git a/libs/mangle/input/tests/evtlist_test.cpp b/libs/mangle/input/tests/evtlist_test.cpp new file mode 100644 index 000000000..fbd980cbd --- /dev/null +++ b/libs/mangle/input/tests/evtlist_test.cpp @@ -0,0 +1,45 @@ +#include +#include "../filters/eventlist.hpp" + +using namespace std; +using namespace Mangle::Input; + +struct MyEvent : Event +{ + int ii; + MyEvent(int i) : ii(i) {} + + void event(Event::Type type, int i, const void *p) + { + cout << " #" << ii << " got event: type=" << type << " index=" << i << endl; + } +}; + +EventList lst; + +int iii=1; +void make(int flags) +{ + lst.add(EventPtr(new MyEvent(iii++)), flags); +} + +void send(Event::Type type) +{ + cout << "Sending type " << type << endl; + lst.event(type,0,NULL); +} + +int main() +{ + make(Event::EV_ALL); + make(Event::EV_KeyDown); + make(Event::EV_KeyUp | Event::EV_MouseDown); + + send(Event::EV_Unknown); + send(Event::EV_KeyDown); + send(Event::EV_KeyUp); + send(Event::EV_MouseDown); + + cout << "Enough of that\n"; + return 0; +} diff --git a/libs/mangle/input/tests/ois_driver_test.cpp b/libs/mangle/input/tests/ois_driver_test.cpp new file mode 100644 index 000000000..386f24055 --- /dev/null +++ b/libs/mangle/input/tests/ois_driver_test.cpp @@ -0,0 +1,51 @@ +#include "common.cpp" + +#include "../servers/ois_driver.hpp" +#include +#include +#include + +bool isFile(const char *name) +{ + boost::filesystem::path cfg_file_path(name); + return boost::filesystem::exists(cfg_file_path); +} + +using namespace Ogre; +using namespace OIS; + +Root *root; +RenderWindow *window; + +void setupOgre() +{ + // Disable logging + new LogManager; + Log *log = LogManager::getSingleton().createLog(""); + log->setDebugOutputEnabled(false); + + bool useConfig = isFile("ogre.cfg"); + + // Set up Root + root = new Root("plugins.cfg", "ogre.cfg", ""); + + // Configure + if(!useConfig) + root->showConfigDialog(); + else + root->restoreConfig(); + + // Initialize OGRE window + window = root->initialise(true, "test", ""); +} + +int main(int argc, char** argv) +{ + setupOgre(); + input = new OISDriver(window); + + mainLoop(argc, KC_Q); + + delete root; + return 0; +} diff --git a/libs/mangle/input/tests/output/evtlist_test.out b/libs/mangle/input/tests/output/evtlist_test.out new file mode 100644 index 000000000..180dcc58a --- /dev/null +++ b/libs/mangle/input/tests/output/evtlist_test.out @@ -0,0 +1,12 @@ +Sending type 1 + #1 got event: type=1 index=0 +Sending type 2 + #1 got event: type=2 index=0 + #2 got event: type=2 index=0 +Sending type 4 + #1 got event: type=4 index=0 + #3 got event: type=4 index=0 +Sending type 16 + #1 got event: type=16 index=0 + #3 got event: type=16 index=0 +Enough of that diff --git a/libs/mangle/input/tests/output/ois_driver_test.out b/libs/mangle/input/tests/output/ois_driver_test.out new file mode 100644 index 000000000..7d273fd46 --- /dev/null +++ b/libs/mangle/input/tests/output/ois_driver_test.out @@ -0,0 +1,5 @@ +Hold the Q key to quit: +got event: type=8 index=-1 +You are running in script mode, aborting. Run this test with a parameter (any at all) to test the input loop properly + +Bye bye! diff --git a/libs/mangle/input/tests/output/sdl_driver_test.out b/libs/mangle/input/tests/output/sdl_driver_test.out new file mode 100644 index 000000000..2df2e4014 --- /dev/null +++ b/libs/mangle/input/tests/output/sdl_driver_test.out @@ -0,0 +1,5 @@ +Hold the Q key to quit: +got event: type=1 index=-1 +You are running in script mode, aborting. Run this test with a parameter (any at all) to test the input loop properly + +Bye bye! diff --git a/libs/mangle/input/tests/plugins.cfg b/libs/mangle/input/tests/plugins.cfg new file mode 100644 index 000000000..57ec54e1a --- /dev/null +++ b/libs/mangle/input/tests/plugins.cfg @@ -0,0 +1,12 @@ +# Defines plugins to load + +# Define plugin folder +PluginFolder=/usr/local/lib/OGRE/ + +# Define plugins +Plugin=RenderSystem_GL +Plugin=Plugin_ParticleFX +Plugin=Plugin_OctreeSceneManager +# Plugin=Plugin_CgProgramManager + + diff --git a/libs/mangle/input/tests/sdl_driver_test.cpp b/libs/mangle/input/tests/sdl_driver_test.cpp new file mode 100644 index 000000000..5db6dbba8 --- /dev/null +++ b/libs/mangle/input/tests/sdl_driver_test.cpp @@ -0,0 +1,16 @@ +#include "common.cpp" + +#include "../servers/sdl_driver.hpp" +#include + +int main(int argc, char** argv) +{ + SDL_Init(SDL_INIT_VIDEO); + SDL_SetVideoMode(640, 480, 0, SDL_SWSURFACE); + input = new SDLDriver(); + + mainLoop(argc, SDLK_q); + + SDL_Quit(); + return 0; +} diff --git a/libs/mangle/input/tests/test.sh b/libs/mangle/input/tests/test.sh new file mode 100755 index 000000000..2d07708ad --- /dev/null +++ b/libs/mangle/input/tests/test.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +make || exit + +mkdir -p output + +PROGS=*_test + +for a in $PROGS; do + if [ -f "output/$a.out" ]; then + echo "Running $a:" + ./$a | diff output/$a.out - + else + echo "Creating $a.out" + ./$a > "output/$a.out" + git add "output/$a.out" + fi +done diff --git a/libs/mangle/rend2d/driver.hpp b/libs/mangle/rend2d/driver.hpp new file mode 100644 index 000000000..08a15b0ae --- /dev/null +++ b/libs/mangle/rend2d/driver.hpp @@ -0,0 +1,63 @@ +#ifndef MANGLE_REND2D_DRIVER_H +#define MANGLE_REND2D_DRIVER_H + +#include +#include "sprite.hpp" + +namespace Mangle +{ + namespace Rend2D + { + /** + The driver is the connection to the backend system that powers + 2D sprite rendering. For example the backend could be SDL or + any other 2D-capable graphics library. + */ + struct Driver + { + /// Get the screen sprite + virtual Sprite *getScreen() = 0; + + /// Sets the video mode. + virtual void setVideoMode(int width, int height, int bpp=32, bool fullscreen=false) = 0; + + /** Update the screen. Until this function is called, none of + the changes written to the screen sprite will be visible. + */ + virtual void update() = 0; + + /// Set the window title, as well as the title of the window + /// when "iconified" + virtual void setWindowTitle(const std::string &title, + const std::string &icon) = 0; + + /// Set the window title + void setWindowTitle(const std::string &title) { setWindowTitle(title,title); } + + /// Load sprite from an image file. Thows an exception on + /// failure. + virtual Sprite* loadImage(const std::string &file) = 0; + + /// Load a sprite from an image file stored in memory. Throws + /// exception on failure. + virtual Sprite* loadImage(const void* data, size_t size) = 0; + + /** @brief Set gamma value for all colors. + + Note: Setting this in windowed mode will affect the ENTIRE + SCREEN! + */ + virtual void setGamma(float gamma) = 0; + + /// Set gamma individually for red, green, blue + virtual void setGamma(float red, float green, float blue) = 0; + + /// Get screen width + virtual int width() = 0; + + /// Get screen height + virtual int height() = 0; + }; + } +} +#endif diff --git a/libs/mangle/rend2d/servers/sdl_driver.cpp b/libs/mangle/rend2d/servers/sdl_driver.cpp new file mode 100644 index 000000000..aa1ff6c6d --- /dev/null +++ b/libs/mangle/rend2d/servers/sdl_driver.cpp @@ -0,0 +1,259 @@ +#include "sdl_driver.hpp" + +#include +#include +#include +#include + +using namespace Mangle::Rend2D; + +const SpriteData *SDL_Sprite::lock() +{ + // Make sure we aren't already locked + assert(!data.pixels); + + // Lock the surface and set up the data structure + SDL_LockSurface(surface); + + data.pixels = surface->pixels; + data.w = surface->w; + data.h = surface->h; + data.pitch = surface->pitch; + data.bypp = surface->format->BytesPerPixel; + + return &data; +} + +void SDL_Sprite::unlock() +{ + if(data.pixels) + { + SDL_UnlockSurface(surface); + data.pixels = NULL; + } +} + +// This is a really crappy and slow implementation, only intended for +// testing purposes. Use lock/unlock for faster pixel drawing. +void SDL_Sprite::pixel(int x, int y, int color) +{ + SDL_LockSurface(surface); + + int bpp = surface->format->BytesPerPixel; + char *p = (char*)surface->pixels + y*surface->pitch + x*bpp; + + switch(bpp) + { + case 1: *p = color; break; + case 3: + if(SDL_BYTEORDER == SDL_BIG_ENDIAN) + { + p[0] = (color >> 16) & 0xff; + p[1] = (color >> 8) & 0xff; + p[2] = color & 0xff; + } + else + { + p[0] = color & 0xff; + p[1] = (color >> 8) & 0xff; + p[2] = (color >> 16) & 0xff; + } + break; + case 4: + *(int*)p = color; + break; + } + SDL_UnlockSurface(surface); +} + +void SDL_Sprite::draw(Sprite *s, // Must be SDL_Sprite + int x, int y, // Destination position + int sx, int sy, // Source position + int w, int h // Amount to draw. -1 means remainder. + ) +{ + // Get source surface + SDL_Sprite *other = dynamic_cast(s); + assert(other != NULL); + SDL_Surface *img = other->getSurface(); + + // Check coordinate validity + assert(sx <= img->w && sy <= img->h); + assert(x <= surface->w && y <= surface->h); + assert(sx >= 0 && sy >= 0); + + // Compute width and height if necessary + if(w == -1) w = img->w - sx; + if(h == -1) h = img->h - sy; + + // Check them if they're valid + assert(w >= 0 && w <= img->w); + assert(h >= 0 && h <= img->h); + + SDL_Rect dest; + dest.x = x; + dest.y = y; + dest.w = w; + dest.h = h; + + SDL_Rect src; + src.x = sx; + src.y = sy; + src.w = w; + src.h = h; + + // Do the Blitman + SDL_BlitSurface(img, &src, surface, &dest); +} + +SDL_Sprite::SDL_Sprite(SDL_Surface *s, bool autoDelete) + : surface(s), autoDel(autoDelete) +{ + assert(surface != NULL); + data.pixels = NULL; +} + +SDL_Sprite::~SDL_Sprite() +{ + if(autoDel) + SDL_FreeSurface(surface); +} + +void SDL_Sprite::fill(int value) +{ + SDL_FillRect(surface, NULL, value); +} + +int SDL_Sprite::width() { return surface->w; } +int SDL_Sprite::height() { return surface->h; } + +SDLDriver::SDLDriver() : display(NULL), realDisp(NULL), softDouble(false) +{ + if (SDL_InitSubSystem( SDL_INIT_VIDEO ) == -1) + throw std::runtime_error("Error initializing SDL video"); +} +SDLDriver::~SDLDriver() +{ + if(display) delete display; + SDL_Quit(); +} + +void SDLDriver::setVideoMode(int width, int height, int bpp, bool fullscreen) +{ + unsigned int flags; + + if(display) delete display; + + if (fullscreen) + // Assume fullscreen mode allows a double-bufferd hardware + // mode. We need more test code for this to be safe though. + flags = SDL_FULLSCREEN | SDL_HWSURFACE | SDL_DOUBLEBUF; + else + flags = SDL_SWSURFACE; + + // Create the surface and check it + realDisp = SDL_SetVideoMode(width, height, bpp, flags); + if(realDisp == NULL) + throw std::runtime_error("Failed setting SDL video mode"); + + // Code for software double buffering. I haven't found this to be + // any speed advantage at all in windowed mode (it's slower, as one + // would expect.) Not properly tested in fullscreen mode with + // hardware buffers, but it will probably only be an improvement if + // we do excessive writing (ie. write each pixel on average more + // than once) or try to read from the display buffer. + if(softDouble) + { + // Make a new surface with the same attributes as the real + // display surface. + SDL_Surface *back = SDL_DisplayFormat(realDisp); + assert(back != NULL); + + // Create a sprite representing the double buffer + display = new SDL_Sprite(back); + } + else + { + // Create a sprite directly representing the display surface. + // The 'false' parameter means do not autodelete the screen + // surface upon exit (since SDL manages it) + display = new SDL_Sprite(realDisp, false); + } +} + +/// Update the screen +void SDLDriver::update() +{ + // Blit the soft double buffer onto the real display buffer + if(softDouble) + SDL_BlitSurface(display->getSurface(), NULL, realDisp, NULL ); + + if(realDisp) + SDL_Flip(realDisp); +} + +/// Set the window title, as well as the title of the window when +/// "iconified" +void SDLDriver::setWindowTitle(const std::string &title, + const std::string &icon) +{ + SDL_WM_SetCaption( title.c_str(), icon.c_str() ); +} + +// Convert the given surface to display format. +static SDL_Surface* convertImage(SDL_Surface* surf) +{ + if(surf != NULL) + { + // Convert the image to the display buffer format, for faster + // blitting + SDL_Surface *surf2 = SDL_DisplayFormat(surf); + SDL_FreeSurface(surf); + surf = surf2; + } + return surf; +} + +/// Load sprite from an image file, using SDL_image. +Sprite* SDLDriver::loadImage(const std::string &file) +{ + SDL_Surface *surf = IMG_Load(file.c_str()); + surf = convertImage(surf); + if(surf == NULL) + throw std::runtime_error("SDL failed to load image file '" + file + "'"); + return spriteFromSDL(surf); +} + +/// Load sprite from an SDL_RWops structure. autoFree determines +/// whether the RWops struct should be closed/freed after use. +Sprite* SDLDriver::loadImage(SDL_RWops *src, bool autoFree) +{ + SDL_Surface *surf = IMG_Load_RW(src, autoFree); + surf = convertImage(surf); + if(surf == NULL) + throw std::runtime_error("SDL failed to load image"); + return spriteFromSDL(surf); +} + +/// Load a sprite from an image file stored in memory. Uses +/// SDL_image. +Sprite* SDLDriver::loadImage(const void* data, size_t size) +{ + SDL_RWops *rw = SDL_RWFromConstMem(data, size); + return loadImage(rw, true); +} + +void SDLDriver::setGamma(float red, float green, float blue) +{ + SDL_SetGamma(red,green,blue); +} + +/// Convert an existing SDL surface into a sprite +Sprite* SDLDriver::spriteFromSDL(SDL_Surface *surf, bool autoFree) +{ + assert(surf); + return new SDL_Sprite(surf, autoFree); +} + +void SDLDriver::sleep(int ms) { SDL_Delay(ms); } +unsigned int SDLDriver::ticks() { return SDL_GetTicks(); } diff --git a/libs/mangle/rend2d/servers/sdl_driver.hpp b/libs/mangle/rend2d/servers/sdl_driver.hpp new file mode 100644 index 000000000..0f205ba34 --- /dev/null +++ b/libs/mangle/rend2d/servers/sdl_driver.hpp @@ -0,0 +1,125 @@ +#ifndef MANGLE_DRAW2D_SDL_H +#define MANGLE_DRAW2D_SDL_H + +#include "../driver.hpp" + +// Predeclarations keep the streets safe at night +struct SDL_Surface; +struct SDL_RWops; + +namespace Mangle +{ + namespace Rend2D + { + /// SDL-implementation of Sprite + struct SDL_Sprite : Sprite + { + /** Draw a sprite in the given position. Can only draw other SDL + sprites. + */ + void draw(Sprite *s, // Must be SDL_Sprite + int x, int y, // Destination position + int sx=0, int sy=0, // Source position + int w=-1, int h=-1 // Amount to draw. -1 means remainder. + ); + + SDL_Sprite(SDL_Surface *s, bool autoDelete=true); + ~SDL_Sprite(); + + // Information retrieval + int width(); + int height(); + SDL_Surface *getSurface() { return surface; } + + // Fill with a given pixel value + void fill(int value); + + // Set one pixel + void pixel(int x, int y, int value); + + const SpriteData *lock(); + void unlock(); + + private: + // The SDL surface + SDL_Surface* surface; + + // Used for locking + SpriteData data; + + // If true, delete this surface when the canvas is destructed + bool autoDel; + }; + + class SDLDriver : public Driver + { + // The main display surface + SDL_Sprite *display; + + // The actual display surface. May or may not be the same + // surface pointed to by 'display' above, depending on the + // softDouble flag. + SDL_Surface *realDisp; + + // If true, we do software double buffering. + bool softDouble; + + public: + SDLDriver(); + ~SDLDriver(); + + /// Sets the video mode. Will create the window if it is not + /// already set up. Note that for SDL, bpp=0 means use current + /// bpp. + void setVideoMode(int width, int height, int bpp=0, bool fullscreen=false); + + /// Update the screen + void update(); + + /// Set the window title, as well as the title of the window + /// when "iconified" + void setWindowTitle(const std::string &title, + const std::string &icon); + + // Include overloads from our Glorious parent + using Driver::setWindowTitle; + + /// Load sprite from an image file, using SDL_image. + Sprite* loadImage(const std::string &file); + + /// Load sprite from an SDL_RWops structure. autoFree determines + /// whether the RWops struct should be closed/freed after use. + Sprite* loadImage(SDL_RWops *src, bool autoFree=false); + + /// Load a sprite from an image file stored in memory. Uses + /// SDL_image. + Sprite* loadImage(const void* data, size_t size); + + /// Set gamma value + void setGamma(float gamma) { setGamma(gamma,gamma,gamma); } + + /// Set gamma individually for red, green, blue + void setGamma(float red, float green, float blue); + + /// Convert an existing SDL surface into a sprite + Sprite* spriteFromSDL(SDL_Surface *surf, bool autoFree = true); + + // Get width and height + int width() { return display ? display->width() : 0; } + int height() { return display ? display->height() : 0; } + + /// Get the screen sprite + Sprite *getScreen() { return display; } + + /// Not really a graphic-related function, but very + /// handly. Sleeps the given number of milliseconds using + /// SDL_Delay(). + void sleep(int ms); + + /// Get the number of ticks since SDL initialization, using + /// SDL_GetTicks(). + unsigned int ticks(); + }; + } +} +#endif diff --git a/libs/mangle/rend2d/servers/sdl_gl_driver.cpp b/libs/mangle/rend2d/servers/sdl_gl_driver.cpp new file mode 100644 index 000000000..2bcb1d677 --- /dev/null +++ b/libs/mangle/rend2d/servers/sdl_gl_driver.cpp @@ -0,0 +1,311 @@ +#include "sdl_gl_driver.hpp" + +#include +#include +#include +#include +#include + +using namespace Mangle::Rend2D; + +void SDLGL_Sprite::draw(Sprite *s, // Must be SDLGL_Sprite + int x, int y, // Destination position + int sx, int sy, // Source position + int w, int h // Amount to draw. -1 means remainder. + ) +{ + // Get source surface + SDLGL_Sprite *other = dynamic_cast(s); + assert(other != NULL); + SDL_Surface *img = other->getSurface(); + + // Check coordinate validity + assert(sx <= img->w && sy <= img->h); + assert(x <= surface->w && y <= surface->h); + assert(sx >= 0 && sy >= 0); + + // Compute width and height if necessary + if(w == -1) w = img->w - sx; + if(h == -1) h = img->h - sy; + + // Check them if they're valid + assert(w >= 0 && w <= img->w); + assert(h >= 0 && h <= img->h); + + SDL_Rect dest; + dest.x = x; + dest.y = y; + dest.w = w; + dest.h = h; + + SDL_Rect src; + src.x = sx; + src.y = sy; + src.w = w; + src.h = h; + + // Do the Blitman + SDL_BlitSurface(img, &src, surface, &dest); +} + +SDLGL_Sprite::SDLGL_Sprite(SDL_Surface *s, bool autoDelete) + : surface(s), autoDel(autoDelete) +{ + assert(surface != NULL); +} + +SDLGL_Sprite::~SDLGL_Sprite() +{ + if(autoDel) + SDL_FreeSurface(surface); +} + +void SDLGL_Sprite::fill(int value) +{ + SDL_FillRect(surface, NULL, value); +} + +int SDLGL_Sprite::width() { return surface->w; } +int SDLGL_Sprite::height() { return surface->h; } + +SDLGLDriver::SDLGLDriver() : display(NULL), realDisp(NULL) +{ + if (SDL_InitSubSystem( SDL_INIT_VIDEO ) == -1) + throw std::runtime_error("Error initializing SDL video"); +} +SDLGLDriver::~SDLGLDriver() +{ + if(display) delete display; + SDL_Quit(); +} + +// Surface used for the screen. Since OpenGL surfaces must have sizes +// that are powers of 2, we have to "fake" the returned display size +// to match the screen, not the surface itself. If we don't use this, +// the client program will get confused about the actual size of our +// screen, thinking it is bigger than it is. +struct FakeSizeSprite : SDLGL_Sprite +{ + int fakeW, fakeH; + + FakeSizeSprite(SDL_Surface *s, int fw, int fh) + : SDLGL_Sprite(s), fakeW(fw), fakeH(fh) + {} + + int width() { return fakeW; } + int height() { return fakeH; } +}; + +static int makePow2(int num) +{ + assert(num); + if((num & (num-1)) != 0) + { + int cnt = 0; + while(num) + { + num >>= 1; + cnt++; + } + num = 1 << cnt; + } + return num; +} + +void SDLGLDriver::setVideoMode(int width, int height, int bpp, bool fullscreen) +{ + unsigned int flags; + + if(display) delete display; + + flags = SDL_OPENGL; + + if (fullscreen) + flags |= SDL_FULLSCREEN; + + SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 ); + SDL_GL_SetAttribute( SDL_GL_SWAP_CONTROL, 1 ); + + // Create the surface and check it + screen = SDL_SetVideoMode(width, height, bpp, flags); + if(screen == NULL) + throw std::runtime_error("Failed setting SDL video mode"); + + // Expand width and height to be powers of 2 + int width2 = makePow2(width); + int height2 = makePow2(height); + + // Create a new SDL surface of this size + const SDL_PixelFormat& fmt = *(screen->format); + realDisp = SDL_CreateRGBSurface(SDL_SWSURFACE,width2,height2, + fmt.BitsPerPixel, + fmt.Rmask,fmt.Gmask,fmt.Bmask,fmt.Amask); + + // Create a sprite directly representing the display surface. This + // allows the user to blit to it directly. + display = new FakeSizeSprite(realDisp, width, height); + + // Set up the OpenGL format + nOfColors = fmt.BytesPerPixel; + + if(nOfColors == 4) + { + if (fmt.Rmask == 0x000000ff) + texture_format = GL_RGBA; + else + texture_format = GL_BGRA; + } + else if(nOfColors == 3) + { + if (fmt.Rmask == 0x000000ff) + texture_format = GL_RGB; + else + texture_format = GL_BGR; + } + else + assert(0 && "unsupported screen format"); + + glEnable(GL_TEXTURE_2D); + + // Have OpenGL generate a texture object handle for us + glGenTextures( 1, &texture ); + + // Bind the texture object + glBindTexture( GL_TEXTURE_2D, texture ); + + // Set the texture's stretching properties + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); +} + +void SDLGLDriver::updateNoSwap() +{ + if(!realDisp) return; + + // Fist, set up the screen texture: + + // Bind the texture object + glBindTexture( GL_TEXTURE_2D, texture ); + + // Edit the texture object's image data + glTexImage2D( GL_TEXTURE_2D, 0, nOfColors, realDisp->w, realDisp->h, 0, + texture_format, GL_UNSIGNED_BYTE, realDisp->pixels ); + + glClear(GL_COLOR_BUFFER_BIT| GL_DEPTH_BUFFER_BIT); + glLoadIdentity(); + + // OpenGL barf. Set up the projection to match our screen + int vPort[4]; + glGetIntegerv(GL_VIEWPORT, vPort); + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + glOrtho(0, vPort[2], 0, vPort[3], -1, 1); + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + + glBegin( GL_QUADS ); + + // Needed to move the screen into the right place + int diff = screen->h - realDisp->h; + + // Bottom-left vertex (corner) + glTexCoord2i( 0, 1 ); + glVertex3f(0,diff,0); + + // Bottom-right vertex (corner) + glTexCoord2i( 1, 1 ); + glVertex3f( realDisp->w, diff, 0.f ); + + // Top-right vertex (corner) + glTexCoord2i( 1, 0 ); + glVertex3f( realDisp->w, screen->h, 0.f ); + + // Top-left vertex (corner) + glTexCoord2i( 0, 0 ); + glVertex3f( 0, screen->h, 0.f ); + glEnd(); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); +} + +void SDLGLDriver::swap() +{ + SDL_GL_SwapBuffers(); +} + +void SDLGLDriver::update() +{ + updateNoSwap(); + swap(); +} + +/// Set the window title, as well as the title of the window when +/// "iconified" +void SDLGLDriver::setWindowTitle(const std::string &title, + const std::string &icon) +{ + SDL_WM_SetCaption( title.c_str(), icon.c_str() ); +} + +// Convert the given surface to display format. +static SDL_Surface* convertImage(SDL_Surface* surf) +{ + if(surf != NULL) + { + // Convert the image to the display buffer format, for faster + // blitting + SDL_Surface *surf2 = SDL_DisplayFormat(surf); + SDL_FreeSurface(surf); + surf = surf2; + } + return surf; +} + +/// Load sprite from an image file, using SDL_image. +Sprite* SDLGLDriver::loadImage(const std::string &file) +{ + SDL_Surface *surf = IMG_Load(file.c_str()); + surf = convertImage(surf); + if(surf == NULL) + throw std::runtime_error("SDL failed to load image file '" + file + "'"); + return spriteFromSDL(surf); +} + +/// Load sprite from an SDL_RWops structure. autoFree determines +/// whether the RWops struct should be closed/freed after use. +Sprite* SDLGLDriver::loadImage(SDL_RWops *src, bool autoFree) +{ + SDL_Surface *surf = IMG_Load_RW(src, autoFree); + surf = convertImage(surf); + if(surf == NULL) + throw std::runtime_error("SDL failed to load image"); + return spriteFromSDL(surf); +} + +/// Load a sprite from an image file stored in memory. Uses +/// SDL_image. +Sprite* SDLGLDriver::loadImage(const void* data, size_t size) +{ + SDL_RWops *rw = SDL_RWFromConstMem(data, size); + return loadImage(rw, true); +} + +void SDLGLDriver::setGamma(float red, float green, float blue) +{ + SDL_SetGamma(red,green,blue); +} + +/// Convert an existing SDL surface into a sprite +Sprite* SDLGLDriver::spriteFromSDL(SDL_Surface *surf, bool autoFree) +{ + assert(surf); + return new SDLGL_Sprite(surf, autoFree); +} + +void SDLGLDriver::sleep(int ms) { SDL_Delay(ms); } +unsigned int SDLGLDriver::ticks() { return SDL_GetTicks(); } diff --git a/libs/mangle/rend2d/servers/sdl_gl_driver.hpp b/libs/mangle/rend2d/servers/sdl_gl_driver.hpp new file mode 100644 index 000000000..d116e3659 --- /dev/null +++ b/libs/mangle/rend2d/servers/sdl_gl_driver.hpp @@ -0,0 +1,132 @@ +#ifndef MANGLE_DRAW2D_SDLGL_H +#define MANGLE_DRAW2D_SDLGL_H + +/** This driver is similar to SDLDriver, except that it uses SDL on + top of OpenGL. + + I've decided to make it a separate file instead of just adding + optional OpenGL support to the original, so that pure SDL users + don't have to add OpenGL as a dependency. + */ + +#include "../driver.hpp" + +// Predeclarations keep the streets safe at night +struct SDL_Surface; +struct SDL_RWops; + +namespace Mangle +{ + namespace Rend2D + { + /// SDL-implementation of Sprite + struct SDLGL_Sprite : Sprite + { + /** Draw a sprite in the given position. Can only draw other SDL + sprites. + */ + void draw(Sprite *s, // Must be SDLGL_Sprite + int x, int y, // Destination position + int sx=0, int sy=0, // Source position + int w=-1, int h=-1 // Amount to draw. -1 means remainder. + ); + + SDLGL_Sprite(SDL_Surface *s, bool autoDelete=true); + ~SDLGL_Sprite(); + + // Information retrieval + virtual int width(); + virtual int height(); + SDL_Surface *getSurface() { return surface; } + + // Fill with a given pixel value + void fill(int value); + + private: + // The SDL surface + SDL_Surface* surface; + + // If true, delete this surface when the canvas is destructed + bool autoDel; + }; + + class SDLGLDriver : public Driver + { + // The main display surface + SDLGL_Sprite *display; + + // The screen surface. This is completely unused. + SDL_Surface *screen; + + // The display surface and main GL texture. These are used when + // drawing the entire screen as one surface, as a drop-in + // replacement for SDLDriver. + SDL_Surface *realDisp; + unsigned int texture; + int nOfColors, texture_format; + + public: + SDLGLDriver(); + ~SDLGLDriver(); + + /// Sets the video mode. Will create the window if it is not + /// already set up. Note that for SDL, bpp=0 means use current + /// bpp. + void setVideoMode(int width, int height, int bpp=0, bool fullscreen=false); + + /// Update the screen + void update(); + + /// Calls SDL_GL_SwapBuffers + void swap(); + + /// Draw surface to screen but do not call SDL_GL_SwapBuffers() + void updateNoSwap(); + + /// Set the window title, as well as the title of the window + /// when "iconified" + void setWindowTitle(const std::string &title, + const std::string &icon); + + // Include overloads from our Glorious parent + using Driver::setWindowTitle; + + /// Load sprite from an image file, using SDL_image. + Sprite* loadImage(const std::string &file); + + /// Load sprite from an SDL_RWops structure. autoFree determines + /// whether the RWops struct should be closed/freed after use. + Sprite* loadImage(SDL_RWops *src, bool autoFree=false); + + /// Load a sprite from an image file stored in memory. Uses + /// SDL_image. + Sprite* loadImage(const void* data, size_t size); + + /// Set gamma value + void setGamma(float gamma) { setGamma(gamma,gamma,gamma); } + + /// Set gamma individually for red, green, blue + void setGamma(float red, float green, float blue); + + /// Convert an existing SDL surface into a sprite + Sprite* spriteFromSDL(SDL_Surface *surf, bool autoFree = true); + + // Get width and height + int width() { return display ? display->width() : 0; } + int height() { return display ? display->height() : 0; } + + /// Get the screen sprite + Sprite *getScreen() { return display; } + + /// Not really a graphic-related function, but very + /// handly. Sleeps the given number of milliseconds using + /// SDL_Delay(). + void sleep(int ms); + + /// Get the number of ticks since SDL initialization, using + /// SDL_GetTicks(). + unsigned int ticks(); + }; + } +} +#endif diff --git a/libs/mangle/rend2d/sprite.hpp b/libs/mangle/rend2d/sprite.hpp new file mode 100644 index 000000000..f49da6cb6 --- /dev/null +++ b/libs/mangle/rend2d/sprite.hpp @@ -0,0 +1,57 @@ +#ifndef MANGLE_REND2D_SPRITE_H +#define MANGLE_REND2D_SPRITE_H + +namespace Mangle +{ + namespace Rend2D + { + /** + A pointer to sprite data for direct drawing. Only to be used + while the corresponding sprite is locked. + */ + struct SpriteData + { + void *pixels; // Pixel data + int w, h; // Width and height + int pitch, bypp; // Pitch (bytes) and bytes per pixel + }; + + /** + A Sprite is either a bitmap to be drawn or an output of area + for blitting other bitmaps, or both. They are created by the + Driver. + */ + struct Sprite + { + /// Draw a sprite in the given position + virtual void draw(Sprite *s, // The sprite to draw + int x, int y, // Destination position + int sx=0, int sy=0, // Source position + int w=-1, int h=-1 // Amount to draw. -1 means remainder. + ) = 0; + + virtual ~Sprite() {} + + // Information retrieval + virtual int width() = 0; + virtual int height() = 0; + + /// Fill the sprite with the given pixel value. The pixel format + /// depends on the format of the sprite. + virtual void fill(int value) = 0; + + /// Set one pixel value. The pixel format depends on the sprite + /// format. This is not expected to be fast, and in some + /// implementations may not work at all. + virtual void pixel(int x, int y, int value) {} + + /// Lock sprite for direct drawing, and return a struct + /// containing the necessary pointer. When finished, unlock the + /// sprite with unlock(). May return NULL, if so then direct + /// drawing is not possible. + virtual const SpriteData *lock() { return NULL; } + virtual void unlock() {} + }; + } +} +#endif diff --git a/libs/mangle/rend2d/tests/.gitignore b/libs/mangle/rend2d/tests/.gitignore new file mode 100644 index 000000000..814490404 --- /dev/null +++ b/libs/mangle/rend2d/tests/.gitignore @@ -0,0 +1 @@ +*_test diff --git a/libs/mangle/rend2d/tests/Makefile b/libs/mangle/rend2d/tests/Makefile new file mode 100644 index 000000000..d430f60a9 --- /dev/null +++ b/libs/mangle/rend2d/tests/Makefile @@ -0,0 +1,15 @@ +GCC=g++ -Wall -Werror + +all: sdl_test sdl_move_test sdlgl_move_test + +sdl_test: sdl_test.cpp + $(GCC) $< ../servers/sdl_driver.cpp -o $@ -I/usr/include/SDL/ -lSDL -lSDL_image + +sdl_move_test: sdl_move_test.cpp ../servers/sdl_driver.cpp + $(GCC) $^ -o $@ -I/usr/include/SDL/ -lSDL -lSDL_image + +sdlgl_move_test: sdlgl_move_test.cpp ../servers/sdl_gl_driver.cpp + $(GCC) $^ -o $@ -I/usr/include/SDL/ -lSDL -lSDL_image -lGL + +clean: + rm *_test diff --git a/libs/mangle/rend2d/tests/output/sdl_move_test.out b/libs/mangle/rend2d/tests/output/sdl_move_test.out new file mode 100644 index 000000000..e69de29bb diff --git a/libs/mangle/rend2d/tests/output/sdl_test.out b/libs/mangle/rend2d/tests/output/sdl_test.out new file mode 100644 index 000000000..4528e1a98 --- /dev/null +++ b/libs/mangle/rend2d/tests/output/sdl_test.out @@ -0,0 +1,11 @@ +Loading SDL driver. +Creating window. +Current mode: 640x480 +Setting fancy title, cause we like fancy titles. +Loading tile1-blue.png from file. +Loading tile1-yellow.png from memory. +Going bananas. +Taking a breather. +WOW DID YOU SEE THAT!? +Mucking about with the gamma settings +Done. diff --git a/libs/mangle/rend2d/tests/output/sdlgl_move_test.out b/libs/mangle/rend2d/tests/output/sdlgl_move_test.out new file mode 100644 index 000000000..e69de29bb diff --git a/libs/mangle/rend2d/tests/sdl_move_test.cpp b/libs/mangle/rend2d/tests/sdl_move_test.cpp new file mode 100644 index 000000000..bfbca98fa --- /dev/null +++ b/libs/mangle/rend2d/tests/sdl_move_test.cpp @@ -0,0 +1,30 @@ +#include +#include + +using namespace std; + +#include "../servers/sdl_driver.hpp" + +using namespace Mangle::Rend2D; + +int main() +{ + SDLDriver sdl; + + sdl.setVideoMode(640,480,0,false); + sdl.setWindowTitle("Testing 123"); + Sprite *screen = sdl.getScreen(); + const char* imgName = "tile1-blue.png"; + Sprite *image = sdl.loadImage(imgName); + + for(int frames=0; frames<170; frames++) + { + screen->fill(0); + for(int j=0; j<10; j++) + for(int i=0; i<25; i++) + screen->draw(image, 2*frames+30*j, 20*i); + sdl.update(); + sdl.sleep(10); + } + return 0; +} diff --git a/libs/mangle/rend2d/tests/sdl_test.cpp b/libs/mangle/rend2d/tests/sdl_test.cpp new file mode 100644 index 000000000..0355112e6 --- /dev/null +++ b/libs/mangle/rend2d/tests/sdl_test.cpp @@ -0,0 +1,65 @@ +#include +#include + +using namespace std; + +#include "../servers/sdl_driver.hpp" + +using namespace Mangle::Rend2D; + +int main() +{ + cout << "Loading SDL driver.\n"; + SDLDriver sdl; + + cout << "Creating window.\n"; + sdl.setVideoMode(640,480); + cout << "Current mode: " << sdl.width() << "x" << sdl.height() << endl; + + cout << "Setting fancy title, cause we like fancy titles.\n"; + sdl.setWindowTitle("Chief executive window"); + + // Display surface + Sprite *screen = sdl.getScreen(); + + const char* imgName = "tile1-blue.png"; + cout << "Loading " << imgName << " from file.\n"; + Sprite *image = sdl.loadImage(imgName); + + const char* imgName2 = "tile1-yellow.png"; + cout << "Loading " << imgName2 << " from memory.\n"; + Sprite *image2; + { + // This is hard-coded for file sizes below 500 bytes, so obviously + // you shouldn't mess with the image files. + ifstream file(imgName2, ios::binary); + char buf[500]; + file.read(buf, 500); + int size = file.gcount(); + image2 = sdl.loadImage(buf, size); + } + + cout << "Going bananas.\n"; + for(int i=1; i<20; i++) + screen->draw(image, 30*i, 20*i); + + cout << "Taking a breather.\n"; + sdl.update(); + for(int i=1; i<20; i++) + screen->draw(image2, 30*(20-i), 20*i); + sdl.sleep(800); + sdl.update(); + cout << "WOW DID YOU SEE THAT!?\n"; + sdl.sleep(800); + + cout << "Mucking about with the gamma settings\n"; + sdl.setGamma(2.0, 0.1, 0.8); + sdl.sleep(100); + sdl.setGamma(0.6, 2.1, 2.1); + sdl.sleep(100); + sdl.setGamma(1.6); + sdl.sleep(100); + + cout << "Done.\n"; + return 0; +} diff --git a/libs/mangle/rend2d/tests/sdlgl_move_test.cpp b/libs/mangle/rend2d/tests/sdlgl_move_test.cpp new file mode 100644 index 000000000..b769ee837 --- /dev/null +++ b/libs/mangle/rend2d/tests/sdlgl_move_test.cpp @@ -0,0 +1,31 @@ +#include +#include + +using namespace std; + +#include "../servers/sdl_gl_driver.hpp" + +using namespace Mangle::Rend2D; + +int main() +{ + SDLGLDriver sdl; + + sdl.setVideoMode(640,480,0,false); + sdl.setWindowTitle("Testing 123"); + Sprite *screen = sdl.getScreen(); + const char* imgName = "tile1-blue.png"; + Sprite *image = sdl.loadImage(imgName); + + for(int frames=0; frames<170; frames++) + { + screen->fill(0); + for(int j=0; j<10; j++) + for(int i=0; i<25; i++) + screen->draw(image, 2*frames+30*j, 20*i); + sdl.update(); + sdl.sleep(5); + } + + return 0; +} diff --git a/libs/mangle/rend2d/tests/test.sh b/libs/mangle/rend2d/tests/test.sh new file mode 100755 index 000000000..2d07708ad --- /dev/null +++ b/libs/mangle/rend2d/tests/test.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +make || exit + +mkdir -p output + +PROGS=*_test + +for a in $PROGS; do + if [ -f "output/$a.out" ]; then + echo "Running $a:" + ./$a | diff output/$a.out - + else + echo "Creating $a.out" + ./$a > "output/$a.out" + git add "output/$a.out" + fi +done diff --git a/libs/mangle/rend2d/tests/tile1-blue.png b/libs/mangle/rend2d/tests/tile1-blue.png new file mode 100644 index 000000000..066e6f8eb Binary files /dev/null and b/libs/mangle/rend2d/tests/tile1-blue.png differ diff --git a/libs/mangle/rend2d/tests/tile1-yellow.png b/libs/mangle/rend2d/tests/tile1-yellow.png new file mode 100644 index 000000000..2aaf9015d Binary files /dev/null and b/libs/mangle/rend2d/tests/tile1-yellow.png differ diff --git a/libs/mangle/sound/.gitignore b/libs/mangle/sound/.gitignore new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/libs/mangle/sound/.gitignore @@ -0,0 +1 @@ + diff --git a/libs/mangle/sound/clients/ogre_listener_mover.hpp b/libs/mangle/sound/clients/ogre_listener_mover.hpp new file mode 100644 index 000000000..739c08a13 --- /dev/null +++ b/libs/mangle/sound/clients/ogre_listener_mover.hpp @@ -0,0 +1,79 @@ +#ifndef MANGLE_SOUND_OGRELISTENERMOVER_H +#define MANGLE_SOUND_OGRELISTENERMOVER_H + +#include +#include +#include "../output.hpp" + +namespace Mangle { +namespace Sound { + + /** This class lets a sound listener (ie. the SoundFactory) track a + given camera in Ogre3D. The position and orientation of the + listener will be updated to match the camera whenever the camera + is moved. + */ + struct OgreListenerMover : Ogre::Camera::Listener + { + OgreListenerMover(Mangle::Sound::SoundFactoryPtr snd) + : soundFact(snd), camera(NULL) + {} + + /// Follow a camera. WARNING: This will OVERRIDE any other + /// MovableObject::Listener you may have attached to the camera. + void followCamera(Ogre::Camera *cam) + { + camera = cam; + camera->addListener(this); + } + + void unfollowCamera() + { + // If the camera is null, this object wasn't following a camera. + // It doesn't make sense to call unfollow + assert(camera != NULL); + + camera->removeListener(this); + camera = NULL; + } + + private: + Mangle::Sound::SoundFactoryPtr soundFact; + Ogre::Camera *camera; + Ogre::Vector3 pos, dir, up; + + /// From Camera::Listener. This is called once per + /// frame. Unfortunately, Ogre doesn't allow us to be notified + /// only when the camera itself has moved, so we must poll every + /// frame. + void cameraPreRenderScene(Ogre::Camera *cam) + { + assert(cam == camera); + + Ogre::Vector3 nPos, nDir, nUp; + + nPos = camera->getPosition(); + nDir = camera->getDirection(); + nUp = camera->getUp(); + + // Don't bother the sound system needlessly + if(nDir != dir || nPos != pos || nUp != up) + { + pos = nPos; + dir = nDir; + up = nUp; + + soundFact->setListenerPos(pos.x, pos.y, pos.z, + dir.x, dir.y, dir.z, + up.x, up.y, up.z); + } + } + + void cameraDestroyed(Ogre::Camera *cam) + { + assert(cam == camera); + camera = NULL; + } + }; +}} +#endif diff --git a/libs/mangle/sound/clients/ogre_output_updater.hpp b/libs/mangle/sound/clients/ogre_output_updater.hpp new file mode 100644 index 000000000..b73168c75 --- /dev/null +++ b/libs/mangle/sound/clients/ogre_output_updater.hpp @@ -0,0 +1,31 @@ +#ifndef MANGLE_SOUND_OGREUPDATER_H +#define MANGLE_SOUND_OGREUPDATER_H + +/* + This Ogre FrameListener calls update on a SoundFactory + */ + +#include +#include "../output.hpp" +#include + +namespace Mangle { +namespace Sound { + + struct OgreOutputUpdater : Ogre::FrameListener + { + Mangle::Sound::SoundFactoryPtr driver; + + OgreOutputUpdater(Mangle::Sound::SoundFactoryPtr drv) + : driver(drv) + { assert(drv->needsUpdate); } + + bool frameStarted(const Ogre::FrameEvent &evt) + { + driver->update(); + return true; + } + }; +}} + +#endif diff --git a/libs/mangle/sound/filters/input_filter.hpp b/libs/mangle/sound/filters/input_filter.hpp new file mode 100644 index 000000000..00ee18766 --- /dev/null +++ b/libs/mangle/sound/filters/input_filter.hpp @@ -0,0 +1,68 @@ +#ifndef MANGLE_INPUT_FILTER_H +#define MANGLE_INPUT_FILTER_H + +#include "../output.hpp" + +#include + +namespace Mangle { +namespace Sound { + +/** + @brief This filter class adds file loading capabilities to a + Sound::SoundFactory class, by associating a SampleSourceLoader with + it. + + The class takes an existing SoundFactory able to load streams, and + associates a SampleSourceLoader with it. The combined class is able + to load files directly. */ +class InputFilter : public SoundFactory +{ + protected: + SoundFactoryPtr snd; + SampleSourceLoaderPtr inp; + + public: + /// Empty constructor + InputFilter() {} + + /// Assign an input manager and a sound manager to this object + InputFilter(SoundFactoryPtr _snd, SampleSourceLoaderPtr _inp) + { set(_snd, _inp); } + + /// Assign an input manager and a sound manager to this object + void set(SoundFactoryPtr _snd, SampleSourceLoaderPtr _inp) + { + inp = _inp; + snd = _snd; + + // Set capabilities + needsUpdate = snd->needsUpdate; + has3D = snd->has3D; + canLoadStream = inp->canLoadStream; + + // Both these should be true, or the use of this class is pretty + // pointless + canLoadSource = snd->canLoadSource; + canLoadFile = inp->canLoadFile; + assert(canLoadSource && canLoadFile); + } + + virtual SoundPtr load(const std::string &file) + { return loadRaw(inp->load(file)); } + + virtual SoundPtr load(Stream::StreamPtr input) + { return loadRaw(inp->load(input)); } + + virtual SoundPtr loadRaw(SampleSourcePtr input) + { return snd->loadRaw(input); } + + virtual void update() { snd->update(); } + virtual void setListenerPos(float x, float y, float z, + float fx, float fy, float fz, + float ux, float uy, float uz) + { snd->setListenerPos(x,y,z,fx,fy,fz,ux,uy,uz); } +}; + +}} +#endif diff --git a/libs/mangle/sound/filters/openal_audiere.hpp b/libs/mangle/sound/filters/openal_audiere.hpp new file mode 100644 index 000000000..5b9b51824 --- /dev/null +++ b/libs/mangle/sound/filters/openal_audiere.hpp @@ -0,0 +1,24 @@ +#ifndef MANGLE_AUDIERE_OPENAL_H +#define MANGLE_AUDIERE_OPENAL_H + +#include "input_filter.hpp" +#include "../sources/audiere_source.hpp" +#include "../outputs/openal_out.hpp" + +namespace Mangle { +namespace Sound { + +/// A InputFilter that adds audiere decoding to OpenAL. Audiere has +/// it's own output, but OpenAL sports 3D and other advanced features. +class OpenAL_Audiere_Factory : public InputFilter +{ + public: + OpenAL_Audiere_Factory() + { + set(SoundFactoryPtr(new OpenAL_Factory), + SampleSourceLoaderPtr(new AudiereLoader)); + } +}; + +}} +#endif diff --git a/libs/mangle/sound/filters/openal_ffmpeg.hpp b/libs/mangle/sound/filters/openal_ffmpeg.hpp new file mode 100644 index 000000000..42c76af0c --- /dev/null +++ b/libs/mangle/sound/filters/openal_ffmpeg.hpp @@ -0,0 +1,23 @@ +#ifndef MANGLE_FFMPEG_OPENAL_H +#define MANGLE_FFMPEG_OPENAL_H + +#include "input_filter.hpp" +#include "../sources/ffmpeg_source.hpp" +#include "../outputs/openal_out.hpp" + +namespace Mangle { +namespace Sound { + +/// A InputFilter that adds ffmpeg decoding to OpenAL. +class OpenAL_FFMpeg_Factory : public InputFilter +{ + public: + OpenAL_FFMpeg_Factory() + { + set(SoundFactoryPtr(new OpenAL_Factory), + SampleSourceLoaderPtr(new FFMpegLoader)); + } +}; + +}} +#endif diff --git a/libs/mangle/sound/filters/openal_mpg123.hpp b/libs/mangle/sound/filters/openal_mpg123.hpp new file mode 100644 index 000000000..bfd926c0b --- /dev/null +++ b/libs/mangle/sound/filters/openal_mpg123.hpp @@ -0,0 +1,24 @@ +#ifndef MANGLE_MPG123_OPENAL_H +#define MANGLE_MPG123_OPENAL_H + +#include "input_filter.hpp" +#include "../sources/mpg123_source.hpp" +#include "../outputs/openal_out.hpp" + +namespace Mangle { +namespace Sound { + +/// A InputFilter that adds mpg123 decoding to OpenAL. Only supports +/// MP3 files. +class OpenAL_Mpg123_Factory : public InputFilter +{ + public: + OpenAL_Mpg123_Factory() + { + set(SoundFactoryPtr(new OpenAL_Factory), + SampleSourceLoaderPtr(new Mpg123Loader)); + } +}; + +}} +#endif diff --git a/libs/mangle/sound/filters/openal_sndfile.hpp b/libs/mangle/sound/filters/openal_sndfile.hpp new file mode 100644 index 000000000..fd7e78025 --- /dev/null +++ b/libs/mangle/sound/filters/openal_sndfile.hpp @@ -0,0 +1,24 @@ +#ifndef MANGLE_SNDFILE_OPENAL_H +#define MANGLE_SNDFILE_OPENAL_H + +#include "input_filter.hpp" +#include "../sources/libsndfile.hpp" +#include "../outputs/openal_out.hpp" + +namespace Mangle { +namespace Sound { + +/// A InputFilter that adds libsnd decoding to OpenAL. libsndfile +/// supports most formats except MP3. +class OpenAL_SndFile_Factory : public InputFilter +{ + public: + OpenAL_SndFile_Factory() + { + set(SoundFactoryPtr(new OpenAL_Factory), + SampleSourceLoaderPtr(new SndFileLoader)); + } +}; + +}} +#endif diff --git a/libs/mangle/sound/filters/openal_sndfile_mpg123.hpp b/libs/mangle/sound/filters/openal_sndfile_mpg123.hpp new file mode 100644 index 000000000..6e5db4d0e --- /dev/null +++ b/libs/mangle/sound/filters/openal_sndfile_mpg123.hpp @@ -0,0 +1,33 @@ +#ifndef MANGLE_SNDFILE_MPG123_OPENAL_H +#define MANGLE_SNDFILE_MPG123_OPENAL_H + +#include "input_filter.hpp" +#include "source_splicer.hpp" +#include "../sources/mpg123_source.hpp" +#include "../sources/libsndfile.hpp" +#include "../outputs/openal_out.hpp" + +namespace Mangle { +namespace Sound { + +/// A InputFilter that uses OpenAL for output, and mpg123 (for MP3) + +/// libsndfile (for everything else) to decode files. Can only load +/// from the file system, and uses the file name to differentiate +/// between mp3 and non-mp3 types. +class OpenAL_SndFile_Mpg123_Factory : public InputFilter +{ + public: + OpenAL_SndFile_Mpg123_Factory() + { + SourceSplicer *splice = new SourceSplicer; + + splice->add("mp3", SampleSourceLoaderPtr(new Mpg123Loader)); + splice->setDefault(SampleSourceLoaderPtr(new SndFileLoader)); + + set(SoundFactoryPtr(new OpenAL_Factory), + SampleSourceLoaderPtr(splice)); + } +}; + +}} +#endif diff --git a/libs/mangle/sound/filters/openal_various.hpp b/libs/mangle/sound/filters/openal_various.hpp new file mode 100644 index 000000000..945b3dabd --- /dev/null +++ b/libs/mangle/sound/filters/openal_various.hpp @@ -0,0 +1,39 @@ +#ifndef MANGLE_VARIOUS_OPENAL_H +#define MANGLE_VARIOUS_OPENAL_H + +#include "input_filter.hpp" +#include "source_splicer.hpp" +#include "../sources/mpg123_source.hpp" +#include "../sources/wav_source.hpp" +#include "../outputs/openal_out.hpp" + +namespace Mangle { +namespace Sound { + +/** A InputFilter that uses OpenAL for output, and load input from + various individual sources, depending on file extension. Currently + supports: + + MP3: mpg123 + WAV: custom wav loader (PCM only) + + This could be an alternative to using eg. 3rd party decoder + libraries like libsndfile. + */ +class OpenAL_Various_Factory : public InputFilter +{ + public: + OpenAL_Various_Factory() + { + SourceSplicer *splice = new SourceSplicer; + + splice->add("mp3", SampleSourceLoaderPtr(new Mpg123Loader)); + splice->add("wav", SampleSourceLoaderPtr(new WavLoader)); + + set(SoundFactoryPtr(new OpenAL_Factory), + SampleSourceLoaderPtr(splice)); + } +}; + +}} +#endif diff --git a/libs/mangle/sound/filters/pure_filter.hpp b/libs/mangle/sound/filters/pure_filter.hpp new file mode 100644 index 000000000..1e8b9f92c --- /dev/null +++ b/libs/mangle/sound/filters/pure_filter.hpp @@ -0,0 +1,72 @@ +#ifndef MANGLE_SOUND_OUTPUT_PUREFILTER_H +#define MANGLE_SOUND_OUTPUT_PUREFILTER_H + +#include "../output.hpp" + +namespace Mangle +{ + namespace Sound + { + // For use in writing other filters + class SoundFilter : public Sound + { + protected: + SoundPtr client; + + public: + SoundFilter(SoundPtr c) : client(c) {} + void play() { client->play(); } + void stop() { client->stop(); } + void pause() { client->pause(); } + bool isPlaying() const { return client->isPlaying(); } + void setVolume(float f) { client->setVolume(f); } + void setPan(float f) { client->setPan(f); } + void setPos(float x, float y, float z) + { client->setPos(x,y,z); } + void setPitch(float p) { client->setPitch(p); } + void setRepeat(bool b) { client->setRepeat(b); } + void setRange(float a, float b=0, float c=0) + { client->setRange(a,b,c); } + void setStreaming(bool b) { client->setStreaming(b); } + + // The clone() function is not implemented here, as you will + // almost certainly want to override it yourself + }; + + class FactoryFilter : public SoundFactory + { + protected: + SoundFactoryPtr client; + + public: + FactoryFilter(SoundFactoryPtr c) : client(c) + { + needsUpdate = client->needsUpdate; + has3D = client->has3D; + canLoadFile = client->canLoadFile; + canLoadStream = client->canLoadStream; + canLoadSource = client->canLoadSource; + } + + SoundPtr loadRaw(SampleSourcePtr input) + { return client->loadRaw(input); } + + SoundPtr load(Stream::StreamPtr input) + { return client->load(input); } + + SoundPtr load(const std::string &file) + { return client->load(file); } + + void update() + { client->update(); } + + void setListenerPos(float x, float y, float z, + float fx, float fy, float fz, + float ux, float uy, float uz) + { + client->setListenerPos(x,y,z,fx,fy,fz,ux,uy,uz); + } + }; + } +} +#endif diff --git a/libs/mangle/sound/filters/source_splicer.hpp b/libs/mangle/sound/filters/source_splicer.hpp new file mode 100644 index 000000000..9c7623086 --- /dev/null +++ b/libs/mangle/sound/filters/source_splicer.hpp @@ -0,0 +1,90 @@ +#ifndef MANGLE_SOUND_SOURCE_SPLICE_H +#define MANGLE_SOUND_SOURCE_SPLICE_H + +#include "../source.hpp" +#include +#include +#include +#include + +namespace Mangle +{ + namespace Sound + { + class SourceSplicer : public SampleSourceLoader + { + struct SourceType + { + std::string type; + SampleSourceLoaderPtr loader; + }; + + typedef std::list TypeList; + TypeList list; + SampleSourceLoaderPtr catchAll; + + static bool isMatch(char a, char b) + { + if(a >= 'A' && a <= 'Z') + a += 'a' - 'A'; + if(b >= 'A' && b <= 'Z') + b += 'a' - 'A'; + return a == b; + } + + public: + SourceSplicer() + { + canLoadStream = false; + canLoadFile = true; + } + + void add(const std::string &type, SampleSourceLoaderPtr fact) + { + SourceType tp; + tp.type = type; + tp.loader = fact; + list.push_back(tp); + } + + void setDefault(SampleSourceLoaderPtr def) + { + catchAll = def; + } + + SampleSourcePtr load(const std::string &file) + { + // Search the list for this file type. + for(TypeList::iterator it = list.begin(); + it != list.end(); it++) + { + const std::string &t = it->type; + + int diff = file.size() - t.size(); + if(diff < 0) continue; + + bool match = true; + for(unsigned i=0; iloader->load(file); + } + // If not found, use the catch-all + if(catchAll) + return catchAll->load(file); + + throw std::runtime_error("No handler for sound file " + file); + } + + SampleSourcePtr load(Stream::StreamPtr input) { assert(0); } + }; + } +} + +#endif diff --git a/libs/mangle/sound/output.hpp b/libs/mangle/sound/output.hpp new file mode 100644 index 000000000..9bbdebb2c --- /dev/null +++ b/libs/mangle/sound/output.hpp @@ -0,0 +1,180 @@ +#ifndef MANGLE_SOUND_OUTPUT_H +#define MANGLE_SOUND_OUTPUT_H + +#include +#include + +#include "source.hpp" +#include "../stream/stream.hpp" + +namespace Mangle { +namespace Sound { + +/// Abstract interface for a single playable sound +/** This class represents one sound outlet, which may be played, + stopped, paused and so on. + + Sound instances are created from the SoundFactory class. Sounds + may be connected to a SampleSource or read directly from a file, + and they may support 3d sounds, looping and other features + depending on the capabilities of the backend system. + + To create multiple instances of one sound, it is recommended to + 'clone' an existing instance instead of reloading it from + file. Cloned sounds will often (depending on the back-end) use + less memory due to shared buffers. +*/ +class Sound; +typedef boost::shared_ptr SoundPtr; +typedef boost::weak_ptr WSoundPtr; + +class Sound +{ + public: + /// Play or resume the sound + virtual void play() = 0; + + /// Stop the sound + virtual void stop() = 0; + + /// Pause the sound, may be resumed later + virtual void pause() = 0; + + /// Check if the sound is still playing + virtual bool isPlaying() const = 0; + + /// Set the volume. The parameter must be between 0.0 and 1.0. + virtual void setVolume(float) = 0; + + /// Set left/right pan. -1.0 is left, 0.0 is center and 1.0 is right. + virtual void setPan(float) = 0; + + /// Set pitch (1.0 is normal speed) + virtual void setPitch(float) = 0; + + /// Set range factors for 3D sounds. The meaning of the fields + /// depend on implementation. + virtual void setRange(float a, float b=0.0, float c=0.0) = 0; + + /// Set the position. May not work with all backends. + virtual void setPos(float x, float y, float z) = 0; + + /// Set loop mode + virtual void setRepeat(bool) = 0; + + /// Set streaming mode. + /** This may be used by implementations to optimize for very large + files. If streaming mode is off (default), most implementations + will load the entire file into memory before starting playback. + */ + virtual void setStreaming(bool) = 0; + + /// Create a new instance of this sound. + /** Playback status is not cloned, only the sound data + itself. Back-ends can use this as a means of sharing data and + saving memory. */ + virtual SoundPtr clone() = 0; + + /// Virtual destructor + virtual ~Sound() {} +}; + +/// Factory interface for creating Sound objects +/** The SoundFactory is the main entry point to a given sound output + system. It is used to create Sound objects, which may be connected + to a sound file or stream, and which may be individually played, + paused, and so on. + + The class also contains a set of public bools which describe the + capabilities the particular system. These should be set by + implementations (base classes) in their respective constructors. + */ +class SoundFactory +{ + public: + /// Virtual destructor + virtual ~SoundFactory() {} + + /** @brief If set to true, you should call update() regularly (every frame + or so) on this sound manager. If false, update() should not be + called. + */ + bool needsUpdate; + + /** @brief true if 3D functions are available. If false, all use of + 3D sounds and calls to setPos / setListenerPos will result in + undefined behavior. + */ + bool has3D; + + /// true if we can load sounds directly from file (containing encoded data) + bool canLoadFile; + + /// If true, we can lound sound files from a Stream (containing encoded data) + bool canLoadStream; + + /// true if we can load sounds from a SampleSource (containing raw data) + bool canLoadSource; + + /** + @brief Load a sound from a sample source. Only valid if + canLoadSource is true. + + This function loads a sound from a given stream as defined by + SampleSource. + + @param input the input source + @param stream true if the file should be streamed. + Implementations may use this for optimizing playback of + large files, but they are not required to. + @return a new Sound object + */ + virtual SoundPtr loadRaw(SampleSourcePtr input) = 0; + + /** + @brief Load a sound file from stream. Only valid if canLoadStream + is true. + + @param input audio file stream + @param stream true if the file should be streamed + @see load(InputSource*,bool) + */ + virtual SoundPtr load(Stream::StreamPtr input) = 0; + + /** + @brief Load a sound directly from file. Only valid if canLoadFile + is true. + + @param file filename + @param stream true if the file should be streamed + @see load(InputSource*,bool) + */ + virtual SoundPtr load(const std::string &file) = 0; + + /// Call this every frame if needsUpdate is true + /** + This should be called regularly (about every frame in a normal + game setting.) Implementions may use this for filling streaming + buffers and similar tasks. Implementations that do not need this + should set needsUpdate to false. + */ + virtual void update() { assert(0); } + + /// Set listener position (coordinates, front and up vectors) + /** + Only valid if has3D is true. + + @param x,y,z listener position + @param fx,fy,fz listener's looking direction + @param ux,uy,uz listener's up direction + */ + virtual void setListenerPos(float x, float y, float z, + float fx, float fy, float fz, + float ux, float uy, float uz) = 0; +}; + +typedef boost::shared_ptr SoundFactoryPtr; + +}} // Namespaces + +#endif diff --git a/libs/mangle/sound/outputs/openal_out.cpp b/libs/mangle/sound/outputs/openal_out.cpp new file mode 100644 index 000000000..f1cf39838 --- /dev/null +++ b/libs/mangle/sound/outputs/openal_out.cpp @@ -0,0 +1,492 @@ +#include "openal_out.hpp" +#include +#include + +#include "../../stream/filters/buffer_stream.hpp" + +#ifdef _WIN32 +#include +#include +#elif defined(__APPLE__) +#include +#include +#else +#include +#include +#endif + +using namespace Mangle::Sound; + +// ---- Helper functions and classes ---- + +// Static buffer used to shuffle sound data from the input into +// OpenAL. The data is only stored temporarily and then immediately +// shuffled off to the library. This is not thread safe, but it works +// fine with multiple sounds in one thread. It could be made thread +// safe simply by using thread local storage. +const size_t BSIZE = 32*1024; +static char tmp_buffer[BSIZE]; + +// Number of buffers used (per sound) for streaming sounds. Each +// buffer is of size BSIZE. Increasing this will make streaming sounds +// more fault tolerant against temporary lapses in call to update(), +// but will also increase memory usage. +// This was changed from 4 to 150 for an estimated 30 seconds tolerance. +// At some point we should replace it with a more multithreading-ish +// solution. +const int STREAM_BUF_NUM = 150; + +static void fail(const std::string &msg) +{ throw std::runtime_error("OpenAL exception: " + msg); } + +/* + Check for AL error. Since we're always calling this with string + literals, and it only makes sense to optimize for the non-error + case, the parameter is const char* rather than std::string. + + This way we don't force the compiler to create a string object each + time we're called (since the string is never used unless there's an + error), although a good compiler might have optimized that away in + any case. + */ +static void checkALError(const char *where) +{ + ALenum err = alGetError(); + if(err != AL_NO_ERROR) + { + std::string msg = where; + + const ALchar* errmsg = alGetString(err); + if(errmsg) + fail("\"" + std::string(alGetString(err)) + "\" while " + msg); + else + fail("non-specified error while " + msg + " (did you forget to initialize OpenAL?)"); + } +} + +static void getALFormat(SampleSourcePtr inp, int &fmt, int &rate) +{ + boost::int32_t rate_, ch, bits; + inp->getInfo(&rate_, &ch, &bits); + rate = rate_; + + fmt = 0; + + if(bits == 8) + { + if(ch == 1) fmt = AL_FORMAT_MONO8; + if(ch == 2) fmt = AL_FORMAT_STEREO8; + if(alIsExtensionPresent("AL_EXT_MCFORMATS")) + { + if(ch == 4) fmt = alGetEnumValue("AL_FORMAT_QUAD8"); + if(ch == 6) fmt = alGetEnumValue("AL_FORMAT_51CHN8"); + } + } + if(bits == 16) + { + if(ch == 1) fmt = AL_FORMAT_MONO16; + if(ch == 2) fmt = AL_FORMAT_STEREO16; + if(ch == 4) fmt = alGetEnumValue("AL_FORMAT_QUAD16"); + if(alIsExtensionPresent("AL_EXT_MCFORMATS")) + { + if(ch == 4) fmt = alGetEnumValue("AL_FORMAT_QUAD16"); + if(ch == 6) fmt = alGetEnumValue("AL_FORMAT_51CHN16"); + } + } + + if(fmt == 0) + fail("Unsupported input format"); +} + +/// OpenAL sound output +class Mangle::Sound::OpenAL_Sound : public Sound +{ + ALuint inst; + + // Buffers. Only the first is used for non-streaming sounds. + ALuint bufferID[STREAM_BUF_NUM]; + + // Number of buffers used + int bufNum; + + // Parameters used for filling buffers + int fmt, rate; + + // Poor mans reference counting. Might improve this later. When + // NULL, the buffer has not been set up yet. + int *refCnt; + + bool streaming; + + // Input stream + SampleSourcePtr input; + + OpenAL_Factory *owner; + bool ownerAlive; + + // Used for streamed sound list + OpenAL_Sound *next, *prev; + + void setupBuffer(); + + // Fill data into the given buffer and queue it, if there is any + // data left to queue. Assumes the buffer is already unqueued, if + // necessary. + void queueBuffer(ALuint buf) + { + // If there is no more data, do nothing + if(!input) return; + if(input->eof()) + { + input.reset(); + return; + } + + // Get some new data + size_t bytes = input->read(tmp_buffer, BSIZE); + if(bytes == 0) + { + input.reset(); + return; + } + + // Move data into the OpenAL buffer + alBufferData(buf, fmt, tmp_buffer, bytes, rate); + // Queue it + alSourceQueueBuffers(inst, 1, &buf); + checkALError("Queueing buffer data"); + } + + public: + /// Read samples from the given input buffer + OpenAL_Sound(SampleSourcePtr input, OpenAL_Factory *fact); + + /// Play an existing buffer, with a given ref counter. Used + /// internally for cloning. + OpenAL_Sound(ALuint buf, int *ref, OpenAL_Factory *fact); + + ~OpenAL_Sound(); + + // Must be called regularly on streamed sounds + void update() + { + if(!streaming) return; + if(!input) return; + + // Get the number of processed buffers + ALint count; + alGetSourcei(inst, AL_BUFFERS_PROCESSED, &count); + checkALError("getting number of unprocessed buffers"); + + for(int i=0; iupdate(); +} + +void OpenAL_Factory::notifyStreaming(OpenAL_Sound *snd) +{ + // Add the sound to the streaming list + streaming.push_back(snd); +} + +void OpenAL_Factory::notifyDelete(OpenAL_Sound *snd) +{ + // Remove the sound from the stream list + streaming.remove(snd); +} + +OpenAL_Factory::~OpenAL_Factory() +{ + // Notify remaining streamed sounds that we're dying + StreamList::iterator it = streaming.begin(); + for(;it != streaming.end(); it++) + (*it)->notifyOwnerDeath(); + + // Deinitialize sound system + if(didSetup) + { + alcMakeContextCurrent(NULL); + if(context) alcDestroyContext((ALCcontext*)context); + if(device) alcCloseDevice((ALCdevice*)device); + } +} + +// ---- OpenAL_Sound ---- + +void OpenAL_Sound::play() +{ + setupBuffer(); + alSourcePlay(inst); + checkALError("starting playback"); +} + +void OpenAL_Sound::stop() +{ + alSourceStop(inst); + checkALError("stopping"); +} + +void OpenAL_Sound::pause() +{ + alSourcePause(inst); + checkALError("pausing"); +} + +bool OpenAL_Sound::isPlaying() const +{ + ALint state; + alGetSourcei(inst, AL_SOURCE_STATE, &state); + + return state == AL_PLAYING; +} + +void OpenAL_Sound::setVolume(float volume) +{ + if(volume > 1.0) volume = 1.0; + if(volume < 0.0) volume = 0.0; + alSourcef(inst, AL_GAIN, volume); + checkALError("setting volume"); +} + +void OpenAL_Sound::setRange(float a, float b, float) +{ + alSourcef(inst, AL_REFERENCE_DISTANCE, a); + alSourcef(inst, AL_MAX_DISTANCE, b); + checkALError("setting sound ranges"); +} + +void OpenAL_Sound::setPos(float x, float y, float z) +{ + alSource3f(inst, AL_POSITION, x, y, z); + checkALError("setting position"); +} + +void OpenAL_Sound::setPitch(float pitch) +{ + alSourcef(inst, AL_PITCH, pitch); + checkALError("setting pitch"); +} + +void OpenAL_Sound::setRepeat(bool rep) +{ + alSourcei(inst, AL_LOOPING, rep?AL_TRUE:AL_FALSE); +} + +SoundPtr OpenAL_Sound::clone() +{ + setupBuffer(); + assert(!streaming && "cloning streamed sounds not supported"); + return SoundPtr(new OpenAL_Sound(bufferID[0], refCnt, owner)); +} + +// Constructor used for cloned sounds +OpenAL_Sound::OpenAL_Sound(ALuint buf, int *ref, OpenAL_Factory *fact) + : refCnt(ref), streaming(false), owner(fact), ownerAlive(false) +{ + // Increase the reference count + assert(ref != NULL); + (*refCnt)++; + + // Set up buffer + bufferID[0] = buf; + bufNum = 1; + + // Create a source + alGenSources(1, &inst); + checkALError("creating instance (clone)"); + alSourcei(inst, AL_BUFFER, bufferID[0]); + checkALError("assigning buffer (clone)"); +} + +// Constructor used for original (non-cloned) sounds +OpenAL_Sound::OpenAL_Sound(SampleSourcePtr _input, OpenAL_Factory *fact) + : refCnt(NULL), streaming(false), input(_input), owner(fact), ownerAlive(false) +{ + // Create a source + alGenSources(1, &inst); + checkALError("creating source"); + + // By default, the sound starts out in a buffer-less mode. We don't + // create a buffer until the sound is played. This gives the user + // the chance to call setStreaming(true) first. +} + +void OpenAL_Sound::setupBuffer() +{ + if(refCnt != NULL) return; + + assert(input); + + // Get the format + getALFormat(input, fmt, rate); + + // Create a cheap reference counter for the buffer + refCnt = new int; + *refCnt = 1; + + if(streaming) bufNum = STREAM_BUF_NUM; + else bufNum = 1; + + // Set up the OpenAL buffer(s) + alGenBuffers(bufNum, bufferID); + checkALError("generating buffer(s)"); + assert(bufferID[0] != 0); + + // STREAMING. + if(streaming) + { + // Just queue all the buffers with data and exit. queueBuffer() + // will work correctly also in the case where there is not + // enough data to fill all the buffers. + for(int i=0; inotifyStreaming(this); + ownerAlive = true; + + return; + } + + // NON-STREAMING. We have to load all the data and shove it into the + // buffer. + + // Does the stream support pointer operations? + if(input->hasPtr) + { + // If so, we can read the data directly from the stream + alBufferData(bufferID[0], fmt, input->getPtr(), input->size(), rate); + } + else + { + // Read the entire stream into a temporary buffer first + Mangle::Stream::BufferStream buf(input, 128*1024); + + // Then copy that into OpenAL + alBufferData(bufferID[0], fmt, buf.getPtr(), buf.size(), rate); + } + checkALError("loading sound data"); + + // We're done with the input stream, release the pointer + input.reset(); + + alSourcei(inst, AL_BUFFER, bufferID[0]); + checkALError("assigning buffer"); +} + +OpenAL_Sound::~OpenAL_Sound() +{ + // Stop + alSourceStop(inst); + + // Return sound + alDeleteSources(1, &inst); + + // Notify the factory that we quit. You will hear from our union + // rep. The bool check is to handle cases where the manager goes out + // of scope before the sounds do. In that case, don't try to contact + // the factory. + if(ownerAlive) + owner->notifyDelete(this); + + // Decrease the reference counter + if((-- (*refCnt)) == 0) + { + // We're the last owner. Delete the buffer(s) and the counter + // itself. + alDeleteBuffers(bufNum, bufferID); + checkALError("deleting buffer"); + delete refCnt; + } +} diff --git a/libs/mangle/sound/outputs/openal_out.hpp b/libs/mangle/sound/outputs/openal_out.hpp new file mode 100644 index 000000000..44d03ecf8 --- /dev/null +++ b/libs/mangle/sound/outputs/openal_out.hpp @@ -0,0 +1,44 @@ +#ifndef MANGLE_SOUND_OPENAL_OUT_H +#define MANGLE_SOUND_OPENAL_OUT_H + +#include "../output.hpp" +#include + +namespace Mangle { +namespace Sound { + +class OpenAL_Sound; + +class OpenAL_Factory : public SoundFactory +{ + void *device; + void *context; + bool didSetup; + + // List of streaming sounds that need to be updated every frame. + typedef std::list StreamList; + StreamList streaming; + + friend class OpenAL_Sound; + void notifyStreaming(OpenAL_Sound*); + void notifyDelete(OpenAL_Sound*); + + public: + /// Initialize object. Pass true (default) if you want the + /// constructor to set up the current ALCdevice and ALCcontext for + /// you. + OpenAL_Factory(bool doSetup = true); + ~OpenAL_Factory(); + + SoundPtr load(const std::string &file) { assert(0); return SoundPtr(); } + SoundPtr load(Stream::StreamPtr input) { assert(0); return SoundPtr(); } + SoundPtr loadRaw(SampleSourcePtr input); + + void update(); + void setListenerPos(float x, float y, float z, + float fx, float fy, float fz, + float ux, float uy, float uz); +}; + +}} // namespaces +#endif diff --git a/libs/mangle/sound/source.hpp b/libs/mangle/sound/source.hpp new file mode 100644 index 000000000..fbe7cf958 --- /dev/null +++ b/libs/mangle/sound/source.hpp @@ -0,0 +1,62 @@ +#ifndef MANGLE_SOUND_SOURCE_H +#define MANGLE_SOUND_SOURCE_H + +#include +#include +#include + +#include "../stream/stream.hpp" + +namespace Mangle { +namespace Sound { + +typedef boost::int32_t int32_t; + +/// A stream containing raw sound data and information about the format +class SampleSource : public Stream::Stream +{ + protected: + bool isEof; + + public: + SampleSource() : isEof(false) {} + + /// Get the sample rate, number of channels, and bits per + /// sample. NULL parameters are ignored. + virtual void getInfo(int32_t *rate, int32_t *channels, int32_t *bits) = 0; + + bool eof() const { return isEof; } + + // Disabled functions by default. You can still override them in + // subclasses. + void seek(size_t pos) { assert(0); } + size_t tell() const { assert(0); return 0; } + size_t size() const { assert(0); return 0; } +}; + +typedef boost::shared_ptr SampleSourcePtr; + +/// A factory interface for loading SampleSources from file or stream +class SampleSourceLoader +{ + public: + /// If true, the stream version of load() works + bool canLoadStream; + + /// If true, the file version of load() works + bool canLoadFile; + + /// Load a sound input source from file (if canLoadFile is true) + virtual SampleSourcePtr load(const std::string &file) = 0; + + /// Load a sound input source from stream (if canLoadStream is true) + virtual SampleSourcePtr load(Stream::StreamPtr input) = 0; + + /// Virtual destructor + virtual ~SampleSourceLoader() {} +}; + +typedef boost::shared_ptr SampleSourceLoaderPtr; + +}} // namespaces +#endif diff --git a/libs/mangle/sound/sources/audiere_source.cpp b/libs/mangle/sound/sources/audiere_source.cpp new file mode 100644 index 000000000..faaa3c8c5 --- /dev/null +++ b/libs/mangle/sound/sources/audiere_source.cpp @@ -0,0 +1,77 @@ +#include "audiere_source.hpp" + +#include "../../stream/clients/audiere_file.hpp" + +#include + +using namespace Mangle::Stream; + +static void fail(const std::string &msg) +{ throw std::runtime_error("Audiere exception: " + msg); } + +using namespace audiere; +using namespace Mangle::Sound; + +// --- SampleSource --- + +void AudiereSource::getInfo(Mangle::Sound::int32_t *rate, + Mangle::Sound::int32_t *channels, Mangle::Sound::int32_t *bits) +{ + SampleFormat fmt; + int channels_, rate_; + sample->getFormat(channels_, rate_, fmt); + *channels = channels_; + *rate = rate_; + if(bits) + { + if(fmt == SF_U8) + *bits = 8; + else if(fmt == SF_S16) + *bits = 16; + else assert(0); + } +} + +// --- Constructors --- + +AudiereSource::AudiereSource(const std::string &file) +{ + sample = OpenSampleSource(file.c_str()); + + if(!sample) + fail("Couldn't load file " + file); + + doSetup(); +} + +AudiereSource::AudiereSource(StreamPtr input) +{ + // Use our Stream::AudiereFile implementation to convert a Mangle + // 'Stream' to an Audiere 'File' + sample = OpenSampleSource(new AudiereFile(input)); + if(!sample) + fail("Couldn't load stream"); + + doSetup(); +} + +AudiereSource::AudiereSource(audiere::SampleSourcePtr src) + : sample(src) +{ assert(sample); doSetup(); } + +// Common function called from all constructors +void AudiereSource::doSetup() +{ + assert(sample); + + SampleFormat fmt; + int channels, rate; + sample->getFormat(channels, rate, fmt); + + // Calculate the size of one frame, and pass it to SampleReader. + setup(GetSampleSize(fmt) * channels); + + isSeekable = sample->isSeekable(); + hasPosition = true; + hasSize = true; +} diff --git a/libs/mangle/sound/sources/audiere_source.hpp b/libs/mangle/sound/sources/audiere_source.hpp new file mode 100644 index 000000000..d797c55c8 --- /dev/null +++ b/libs/mangle/sound/sources/audiere_source.hpp @@ -0,0 +1,48 @@ +#ifndef MANGLE_SOUND_AUDIERE_SOURCE_H +#define MANGLE_SOUND_AUDIERE_SOURCE_H + +#include "sample_reader.hpp" + +// audiere.h from 1.9.4 (latest) release uses +// cstring routines like strchr() and strlen() without +// including cstring itself. +#include +#include + +namespace Mangle { +namespace Sound { + +/// A sample source that decodes files using Audiere +class AudiereSource : public SampleReader +{ + audiere::SampleSourcePtr sample; + + size_t readSamples(void *data, size_t length) + { return sample->read(length, data); } + + void doSetup(); + + public: + /// Decode the given sound file + AudiereSource(const std::string &file); + + /// Decode the given sound stream + AudiereSource(Mangle::Stream::StreamPtr src); + + /// Read directly from an existing audiere::SampleSource + AudiereSource(audiere::SampleSourcePtr src); + + void getInfo(int32_t *rate, int32_t *channels, int32_t *bits); + + void seek(size_t pos) { sample->setPosition(pos/frameSize); } + size_t tell() const { return sample->getPosition()*frameSize; } + size_t size() const { return sample->getLength()*frameSize; } +}; + +#include "loadertemplate.hpp" + +/// A factory that loads AudiereSources from file and stream +typedef SSL_Template AudiereLoader; + +}} // Namespace +#endif diff --git a/libs/mangle/sound/sources/ffmpeg_source.cpp b/libs/mangle/sound/sources/ffmpeg_source.cpp new file mode 100644 index 000000000..6349be691 --- /dev/null +++ b/libs/mangle/sound/sources/ffmpeg_source.cpp @@ -0,0 +1,189 @@ +#include "ffmpeg_source.hpp" + +#include + +using namespace Mangle::Sound; + +// Static output buffer. Not thread safe, but supports multiple +// streams operated from the same thread. +static uint8_t outBuf[AVCODEC_MAX_AUDIO_FRAME_SIZE]; + +static void fail(const std::string &msg) +{ throw std::runtime_error("FFMpeg exception: " + msg); } + +// --- Loader --- + +static bool init = false; + +FFMpegLoader::FFMpegLoader(bool setup) +{ + if(setup && !init) + { + av_register_all(); + av_log_set_level(AV_LOG_ERROR); + init = true; + } +} + +// --- Source --- + +FFMpegSource::FFMpegSource(const std::string &file) +{ + std::string msg; + AVCodec *codec; + + if(av_open_input_file(&FmtCtx, file.c_str(), NULL, 0, NULL) != 0) + fail("Error loading audio file " + file); + + if(av_find_stream_info(FmtCtx) < 0) + { + msg = "Error in file stream " + file; + goto err; + } + + // Pick the first audio stream, if any + for(StreamNum = 0; StreamNum < FmtCtx->nb_streams; StreamNum++) + { + // Pick the first audio stream + if(FmtCtx->streams[StreamNum]->codec->codec_type == CODEC_TYPE_AUDIO) + break; + } + + if(StreamNum == FmtCtx->nb_streams) + fail("File '" + file + "' didn't contain any audio streams"); + + // Open the decoder + CodecCtx = FmtCtx->streams[StreamNum]->codec; + codec = avcodec_find_decoder(CodecCtx->codec_id); + + if(!codec || avcodec_open(CodecCtx, codec) < 0) + { + msg = "Error loading '" + file + "': "; + if(codec) + msg += "coded error"; + else + msg += "no codec found"; + goto err; + } + + // No errors, we're done + return; + + // Handle errors + err: + av_close_input_file(FmtCtx); + fail(msg); +} + +FFMpegSource::~FFMpegSource() +{ + avcodec_close(CodecCtx); + av_close_input_file(FmtCtx); +} + +void FFMpegSource::getInfo(int32_t *rate, int32_t *channels, int32_t *bits) +{ + if(rate) *rate = CodecCtx->sample_rate; + if(channels) *channels = CodecCtx->channels; + if(bits) *bits = 16; +} + +size_t FFMpegSource::read(void *data, size_t length) +{ + if(isEof) return 0; + + size_t left = length; + uint8_t *outPtr = (uint8_t*)data; + + // First, copy over any stored data we might be sitting on + { + size_t s = storage.size(); + size_t copy = s; + if(s) + { + // Make sure there's room + if(copy > left) + copy = left; + + // Copy + memcpy(outPtr, &storage[0], copy); + outPtr += copy; + left -= copy; + + // Is there anything left in the storage? + assert(s>= copy); + s -= copy; + if(s) + { + assert(left == 0); + + // Move it to the start and resize + memmove(&storage[0], &storage[copy], s); + storage.resize(s); + } + } + } + + // Next, get more input data from stream, and decode it + while(left) + { + AVPacket packet; + + // Get the next packet, if any + if(av_read_frame(FmtCtx, &packet) < 0) + break; + + // We only allow one stream per file at the moment + assert((int)StreamNum == packet.stream_index); + + // Decode the packet + int len = AVCODEC_MAX_AUDIO_FRAME_SIZE; + int tmp = avcodec_decode_audio2(CodecCtx, (int16_t*)outBuf, + &len, packet.data, packet.size); + assert(tmp < 0 || tmp == packet.size); + + // We don't need the input packet any longer + av_free_packet(&packet); + + if(tmp < 0) + fail("Error decoding audio stream"); + + // Copy whatever data we got, and advance the pointer + if(len > 0) + { + // copy = how many bytes do we copy now + size_t copy = len; + if(copy > left) + copy = left; + + // len = how many bytes are left uncopied + len -= copy; + + // copy data + memcpy(outPtr, outBuf, copy); + + // left = how much space is left in the caller output + // buffer. This loop repeats as long left is > 0 + left -= copy; + outPtr += copy; + assert(left >= 0); + + if(len > 0) + { + // There were uncopied bytes. Store them for later. + assert(left == 0); + storage.resize(len); + memcpy(&storage[0], outBuf, len); + } + } + } + + // End of loop. Return the number of bytes copied. + assert(left <= length); + + // If we're returning less than asked for, then we're done + if(left > 0) + isEof = true; + + return length - left; +} diff --git a/libs/mangle/sound/sources/ffmpeg_source.hpp b/libs/mangle/sound/sources/ffmpeg_source.hpp new file mode 100644 index 000000000..d422b9809 --- /dev/null +++ b/libs/mangle/sound/sources/ffmpeg_source.hpp @@ -0,0 +1,52 @@ +#ifndef MANGLE_SOUND_FFMPEG_H +#define MANGLE_SOUND_FFMPEG_H + +#include "../source.hpp" +#include +#include + +extern "C" +{ +#include +#include +} + +namespace Mangle { +namespace Sound { + +class FFMpegSource : public SampleSource +{ + AVFormatContext *FmtCtx; + AVCodecContext *CodecCtx; + unsigned int StreamNum; + + std::vector storage; + + public: + /// Decode the given sound file + FFMpegSource(const std::string &file); + + /// Decode the given sound stream (not supported by FFmpeg) + FFMpegSource(Mangle::Stream::StreamPtr src) { assert(0); } + + ~FFMpegSource(); + + // Overrides + void getInfo(int32_t *rate, int32_t *channels, int32_t *bits); + size_t read(void *data, size_t length); +}; + +#include "loadertemplate.hpp" + +/// A factory that loads FFMpegSources from file +class FFMpegLoader : public SSL_Template +{ + public: + + /// Sets up the libavcodec library. If you want to do your own + /// setup, send a setup=false parameter. + FFMpegLoader(bool setup=true); +}; + +}} // namespaces +#endif diff --git a/libs/mangle/sound/sources/libsndfile.cpp b/libs/mangle/sound/sources/libsndfile.cpp new file mode 100644 index 000000000..b69a2d436 --- /dev/null +++ b/libs/mangle/sound/sources/libsndfile.cpp @@ -0,0 +1,48 @@ +#include "libsndfile.hpp" + +#include +#include + +using namespace Mangle::Stream; + +static void fail(const std::string &msg) +{ throw std::runtime_error("Mangle::libsndfile: " + msg); } + +using namespace Mangle::Sound; + +void SndFileSource::getInfo(int32_t *_rate, int32_t *_channels, int32_t *_bits) +{ + *_rate = rate; + *_channels = channels; + *_bits = bits; +} + +size_t SndFileSource::readSamples(void *data, size_t length) +{ + // readf_* reads entire frames, including channels + return sf_readf_short((SNDFILE*)handle, (short*)data, length); +} + +SndFileSource::SndFileSource(const std::string &file) +{ + SF_INFO info; + info.format = 0; + handle = sf_open(file.c_str(), SFM_READ, &info); + if(handle == NULL) + fail("Failed to open " + file); + + // I THINK that using sf_read_short forces the library to convert to + // 16 bits no matter what, but the libsndfile docs aren't exactly + // very clear on this point. + channels = info.channels; + rate = info.samplerate; + bits = 16; + + // 16 bits per sample times number of channels + setup(2*channels); +} + +SndFileSource::~SndFileSource() +{ + sf_close((SNDFILE*)handle); +} diff --git a/libs/mangle/sound/sources/libsndfile.hpp b/libs/mangle/sound/sources/libsndfile.hpp new file mode 100644 index 000000000..7286cf0fe --- /dev/null +++ b/libs/mangle/sound/sources/libsndfile.hpp @@ -0,0 +1,36 @@ +#ifndef MANGLE_SOUND_SNDFILE_SOURCE_H +#define MANGLE_SOUND_SNDFILE_SOURCE_H + +#include "sample_reader.hpp" + +namespace Mangle { +namespace Sound { + +/// A sample source that decodes files using libsndfile. Supports most +/// formats except mp3. +class SndFileSource : public SampleReader +{ + void *handle; + int channels, rate, bits; + + size_t readSamples(void *data, size_t length); + + public: + /// Decode the given sound file + SndFileSource(const std::string &file); + + /// Decode the given sound stream (not supported) + SndFileSource(Mangle::Stream::StreamPtr src) { assert(0); } + + ~SndFileSource(); + + void getInfo(int32_t *rate, int32_t *channels, int32_t *bits); +}; + +#include "loadertemplate.hpp" + +/// A factory that loads SndFileSources from file and stream +typedef SSL_Template SndFileLoader; + +}} // Namespace +#endif diff --git a/libs/mangle/sound/sources/loadertemplate.hpp b/libs/mangle/sound/sources/loadertemplate.hpp new file mode 100644 index 000000000..a27a77d10 --- /dev/null +++ b/libs/mangle/sound/sources/loadertemplate.hpp @@ -0,0 +1,28 @@ +#ifndef SSL_TEMPL_H +#define SSL_TEMPL_H + +template +class SSL_Template : public SampleSourceLoader +{ + public: + + SSL_Template() + { + canLoadStream = stream; + canLoadFile = file; + } + + SampleSourcePtr load(const std::string &filename) + { + assert(canLoadFile); + return SampleSourcePtr(new SourceT(filename)); + } + + SampleSourcePtr load(Stream::StreamPtr input) + { + assert(canLoadStream); + return SampleSourcePtr(new SourceT(input)); + } +}; + +#endif diff --git a/libs/mangle/sound/sources/mpg123_source.cpp b/libs/mangle/sound/sources/mpg123_source.cpp new file mode 100644 index 000000000..24d6ecce1 --- /dev/null +++ b/libs/mangle/sound/sources/mpg123_source.cpp @@ -0,0 +1,115 @@ +#include "mpg123_source.hpp" + +#include + +#include + +using namespace Mangle::Stream; + +/* + TODOs: + + - mpg123 impressively enough supports custom stream reading. Which + means we could (and SHOULD!) support reading from Mangle::Streams + as well. But I'll save it til I need it. + + An alternative way to do this is through feeding (which they also + support), but that's more messy. + + - the library also supports output, via various other sources, + including ALSA, OSS, PortAudio, PulseAudio and SDL. Using this + library as a pure output library (if that is possible) would be a + nice shortcut over using those libraries - OTOH it's another + dependency. + + - we could implement seek(), tell() and size(), but they aren't + really necessary. Furthermore, since the returned size is only a + guess, it is not safe to rely on it. + */ + +static void fail(const std::string &msg) +{ throw std::runtime_error("Mangle::Mpg123 exception: " + msg); } + +static void checkError(int err, void *mh = NULL) +{ + if(err != MPG123_OK) + { + std::string msg; + if(mh) msg = mpg123_strerror((mpg123_handle*)mh); + else msg = mpg123_plain_strerror(err); + fail(msg); + } +} + +using namespace Mangle::Sound; + +void Mpg123Source::getInfo(int32_t *pRate, int32_t *pChannels, int32_t *pBits) +{ + // Use the values we found in the constructor + *pRate = rate; + *pChannels = channels; + *pBits = bits; +} + +size_t Mpg123Source::read(void *data, size_t length) +{ + size_t done; + // This is extraordinarily nice. I like this library. + int err = mpg123_read((mpg123_handle*)mh, (unsigned char*)data, length, &done); + assert(done <= length); + if(err == MPG123_DONE) + isEof = true; + else + checkError(err, mh); + return done; +} + +Mpg123Loader::Mpg123Loader(bool setup) +{ + // Do as we're told + if(setup) + { + int err = mpg123_init(); + checkError(err); + } + didSetup = setup; +} + +Mpg123Loader::~Mpg123Loader() +{ + // Deinitialize the library on exit + if(didSetup) + mpg123_exit(); +} + +Mpg123Source::Mpg123Source(const std::string &file) +{ + int err; + + // Create a new handle + mh = mpg123_new(NULL, &err); + if(mh == NULL) + checkError(err, mh); + + mpg123_handle *mhh = (mpg123_handle*)mh; + + // Open the file (hack around constness) + err = mpg123_open(mhh, (char*)file.c_str()); + checkError(err, mh); + + // Get the format + int encoding; + err = mpg123_getformat(mhh, &rate, &channels, &encoding); + checkError(err, mh); + if(encoding != MPG123_ENC_SIGNED_16) + fail("Unsupported encoding in " + file); + + // This is the only bit size we support. + bits = 16; +} + +Mpg123Source::~Mpg123Source() +{ + mpg123_close((mpg123_handle*)mh); + mpg123_delete((mpg123_handle*)mh); +} diff --git a/libs/mangle/sound/sources/mpg123_source.hpp b/libs/mangle/sound/sources/mpg123_source.hpp new file mode 100644 index 000000000..1ac16b530 --- /dev/null +++ b/libs/mangle/sound/sources/mpg123_source.hpp @@ -0,0 +1,47 @@ +#ifndef MANGLE_SOUND_MPG123_SOURCE_H +#define MANGLE_SOUND_MPG123_SOURCE_H + +#include "../source.hpp" +#include + +namespace Mangle { +namespace Sound { + +/// A sample source that decodes files using libmpg123. Only supports +/// MP3 files. +class Mpg123Source : public SampleSource +{ + void *mh; + long int rate; + int channels, bits; + + public: + /// Decode the given sound file + Mpg123Source(const std::string &file); + + /// Needed by SSL_Template but not yet supported + Mpg123Source(Mangle::Stream::StreamPtr data) + { assert(0); } + + ~Mpg123Source(); + + void getInfo(int32_t *rate, int32_t *channels, int32_t *bits); + size_t read(void *data, size_t length); +}; + +#include "loadertemplate.hpp" + +/// A factory that loads Mpg123Sources from file and stream +struct Mpg123Loader : SSL_Template +{ + /** Sets up libmpg123 for you, and closes it on destruction. If you + want to do this yourself, send setup=false. + */ + Mpg123Loader(bool setup=true); + ~Mpg123Loader(); +private: + bool didSetup; +}; + +}} // Namespace +#endif diff --git a/libs/mangle/sound/sources/sample_reader.cpp b/libs/mangle/sound/sources/sample_reader.cpp new file mode 100644 index 000000000..c30de654a --- /dev/null +++ b/libs/mangle/sound/sources/sample_reader.cpp @@ -0,0 +1,99 @@ +#include "sample_reader.hpp" + +#include + +using namespace Mangle::Sound; + +void SampleReader::setup(int size) +{ + pullSize = 0; + frameSize = size; + pullOver = new char[size]; +} + +SampleReader::~SampleReader() +{ + if(pullOver) + delete[] pullOver; +} + +size_t SampleReader::read(void *_data, size_t length) +{ + if(isEof) return 0; + char *data = (char*)_data; + + // Pullsize holds the number of bytes that were copied "extra" at + // the end of LAST round. If non-zero, it also means there is data + // left in the pullOver buffer. + if(pullSize) + { + // Amount of data left + size_t doRead = frameSize - pullSize; + assert(doRead > 0); + + // Make sure we don't read more than we're supposed to + if(doRead > length) doRead = length; + + memcpy(data, pullOver+pullSize, doRead); + + // Update the number of bytes now copied + pullSize += doRead; + assert(pullSize <= frameSize); + + if(pullSize < frameSize) + { + // There is STILL data left in the pull buffer, and we've + // done everything we were supposed to. Leave it and return. + assert(doRead == length); + return doRead; + } + + // Set up variables for further reading below. No need to update + // pullSize, it is overwritten anyway. + length -= doRead; + data += doRead; + } + + // Number of whole frames + size_t frames = length / frameSize; + + // Read the data + size_t res = readSamples(data, frames); + assert(res <= frames); + + // Total bytes read + size_t num = res*frameSize; + data += num; + + if(res < frames) + { + // End of stream. + isEof = true; + // Determine how much we read + return data-(char*)_data; + } + + // Determine the overshoot + pullSize = length - num; + assert(pullSize < frameSize && pullSize >= 0); + + // Are we missing data? + if(pullSize) + { + // Fill in one sample + res = readSamples(pullOver,1); + assert(res == 1 || res == 0); + if(res) + { + // Move as much as we can into the output buffer + memcpy(data, pullOver, pullSize); + data += pullSize; + } + else + // Failed reading, we're out of data + isEof = true; + } + + // Return the total number of bytes stored + return data-(char*)_data; +} diff --git a/libs/mangle/sound/sources/sample_reader.hpp b/libs/mangle/sound/sources/sample_reader.hpp new file mode 100644 index 000000000..89ddf1f65 --- /dev/null +++ b/libs/mangle/sound/sources/sample_reader.hpp @@ -0,0 +1,48 @@ +#ifndef MANGLE_SOUND_SAMPLE_READER_H +#define MANGLE_SOUND_SAMPLE_READER_H + +#include "../source.hpp" + +namespace Mangle { +namespace Sound { + + /* This is a helper base class for other SampleSource + implementations. Certain sources (like Audiere and libsndfile) + insist on reading whole samples rather than bytes. This class + compensates for that, and allows you to read bytes rather than + samples. + + There are two ways for subclasses to use this class. EITHER call + setup() with the size of frameSize. This will allocate a buffer, + which the destructor frees. OR set frameSize manually and + manipulate the pullOver pointer yourself. In that case you MUST + reset it to NULL if you don't want the destructor to call + delete[] on it. + */ +class SampleReader : public SampleSource +{ + // How much of the above buffer is in use. + int pullSize; + +protected: + // Pullover buffer + char* pullOver; + + // Size of one frame, in bytes. This is also the size of the + // pullOver buffer. + int frameSize; + + // The parameter gives the size of one sample/frame, in bytes. + void setup(int); + + // Read the given number of samples, in multiples of frameSize. Does + // not have to set or respect isEof. + virtual size_t readSamples(void *data, size_t num) = 0; + + public: + SampleReader() : pullSize(0), pullOver(NULL) {} + ~SampleReader(); + size_t read(void *data, size_t length); +}; +}} // Namespace +#endif diff --git a/libs/mangle/sound/sources/stream_source.hpp b/libs/mangle/sound/sources/stream_source.hpp new file mode 100644 index 000000000..43c605a00 --- /dev/null +++ b/libs/mangle/sound/sources/stream_source.hpp @@ -0,0 +1,47 @@ +#ifndef MANGLE_SOUND_STREAMSOURCE_H +#define MANGLE_SOUND_STREAMSOURCE_H + +#include "../source.hpp" + +namespace Mangle { +namespace Sound { + +/// A class for reading raw samples directly from a stream. +class Stream2Samples : public SampleSource +{ + Mangle::Stream::StreamPtr inp; + int32_t rate, channels, bits; + + public: + Stream2Samples(Mangle::Stream::StreamPtr _inp, int32_t _rate, int32_t _channels, int32_t _bits) + : inp(_inp), rate(_rate), channels(_channels), bits(_bits) + { + isSeekable = inp->isSeekable; + hasPosition = inp->hasPosition; + hasSize = inp->hasSize; + hasPtr = inp->hasPtr; + } + + /// Get the sample rate, number of channels, and bits per + /// sample. NULL parameters are ignored. + void getInfo(int32_t *_rate, int32_t *_channels, int32_t *_bits) + { + if(_rate) *_rate = rate; + if(_channels) *_channels = channels; + if(_bits) *_bits = bits; + } + + size_t read(void *out, size_t count) + { return inp->read(out, count); } + + void seek(size_t pos) { inp->seek(pos); } + size_t tell() const { return inp->tell(); } + size_t size() const { return inp->size(); } + bool eof() const { return inp->eof(); } + const void *getPtr() { return inp->getPtr(); } + const void *getPtr(size_t size) { return inp->getPtr(size); } + const void *getPtr(size_t pos, size_t size) { return inp->getPtr(pos, size); } +}; + +}} // namespaces +#endif diff --git a/libs/mangle/sound/sources/wav_source.cpp b/libs/mangle/sound/sources/wav_source.cpp new file mode 100644 index 000000000..a46b3d27e --- /dev/null +++ b/libs/mangle/sound/sources/wav_source.cpp @@ -0,0 +1,99 @@ +#include "wav_source.hpp" + +#include "../../stream/servers/file_stream.hpp" + +#include + +using namespace Mangle::Stream; +using namespace Mangle::Sound; + +static void fail(const std::string &msg) +{ throw std::runtime_error("Mangle::Wav exception: " + msg); } + +void WavSource::getInfo(int32_t *pRate, int32_t *pChannels, int32_t *pBits) +{ + // Use the values we found in the constructor + *pRate = rate; + *pChannels = channels; + *pBits = bits; +} + +void WavSource::seek(size_t pos) +{ + // Seek the stream and set 'left' + assert(isSeekable); + if(pos > total) pos = total; + input->seek(dataOffset + pos); + left = total-pos; +} + +size_t WavSource::read(void *data, size_t length) +{ + if(length > left) + length = left; + size_t read = input->read(data, length); + if(read < length) + // Something went wrong + fail("WAV read error"); + return length; +} + +void WavSource::open(Mangle::Stream::StreamPtr data) +{ + input = data; + + hasPosition = true; + hasSize = true; + // If we can check position and seek in the input stream, then we + // can seek the wav data too. + isSeekable = input->isSeekable && input->hasPosition; + + // Read header + unsigned int val; + + input->read(&val,4); // header + if(val != 0x46464952) // "RIFF" + fail("Not a WAV file"); + + input->read(&val,4); // size (ignored) + input->read(&val,4); // file format + if(val != 0x45564157) // "WAVE" + fail("Not a valid WAV file"); + + input->read(&val,4); // "fmt " + input->read(&val,4); // chunk size (must be 16) + if(val != 16) + fail("Unsupported WAV format"); + + input->read(&val,2); + if(val != 1) + fail("Non-PCM (compressed) WAV files not supported"); + + // Sound data specification + channels = 0; + input->read(&channels,2); + input->read(&rate, 4); + + // Skip next 6 bytes + input->read(&val, 4); + input->read(&val, 2); + + // Bits per sample + bits = 0; + input->read(&bits,2); + + input->read(&val,4); // Data header + if(val != 0x61746164) // "data" + fail("Expected data block"); + + // Finally, read the data size + input->read(&total,4); + left = total; + + // Store the beginning of the data block for later + if(input->hasPosition) + dataOffset = input->tell(); +} + +WavSource::WavSource(const std::string &file) +{ open(StreamPtr(new FileStream(file))); } diff --git a/libs/mangle/sound/sources/wav_source.hpp b/libs/mangle/sound/sources/wav_source.hpp new file mode 100644 index 000000000..227f4da73 --- /dev/null +++ b/libs/mangle/sound/sources/wav_source.hpp @@ -0,0 +1,49 @@ +#ifndef MANGLE_SOUND_WAV_SOURCE_H +#define MANGLE_SOUND_WAV_SOURCE_H + +#include "../source.hpp" +#include + +namespace Mangle { +namespace Sound { + +/// WAV file decoder. Has no external library dependencies. +class WavSource : public SampleSource +{ + // Sound info + uint32_t rate, channels, bits; + + // Total size (of output) and bytes left + uint32_t total, left; + + // Offset in input of the beginning of the data block + size_t dataOffset; + + Mangle::Stream::StreamPtr input; + + void open(Mangle::Stream::StreamPtr); + + public: + /// Decode the given sound file + WavSource(const std::string&); + + /// Decode from stream + WavSource(Mangle::Stream::StreamPtr s) + { open(s); } + + void getInfo(int32_t *rate, int32_t *channels, int32_t *bits); + size_t read(void *data, size_t length); + + void seek(size_t); + size_t tell() const { return total-left; } + size_t size() const { return total; } + bool eof() const { return left > 0; } +}; + +#include "loadertemplate.hpp" + +/// A factory that loads WavSources from file and stream +typedef SSL_Template WavLoader; + +}} // Namespace +#endif diff --git a/libs/mangle/sound/tests/.gitignore b/libs/mangle/sound/tests/.gitignore new file mode 100644 index 000000000..814490404 --- /dev/null +++ b/libs/mangle/sound/tests/.gitignore @@ -0,0 +1 @@ +*_test diff --git a/libs/mangle/sound/tests/Makefile b/libs/mangle/sound/tests/Makefile new file mode 100644 index 000000000..6fcac72da --- /dev/null +++ b/libs/mangle/sound/tests/Makefile @@ -0,0 +1,38 @@ +GCC=g++ -I../ -Wall + +all: audiere_source_test ffmpeg_source_test openal_output_test openal_audiere_test openal_ffmpeg_test openal_mpg123_test openal_sndfile_test wav_source_test openal_various_test + +L_FFMPEG=$(shell pkg-config --libs libavcodec libavformat) +I_FFMPEG=-I/usr/include/libavcodec -I/usr/include/libavformat +L_OPENAL=$(shell pkg-config --libs openal) +L_AUDIERE=-laudiere + +wav_source_test: wav_source_test.cpp ../sources/wav_source.cpp + $(GCC) $^ -o $@ + +openal_various_test: openal_various_test.cpp ../sources/mpg123_source.cpp ../sources/wav_source.cpp ../outputs/openal_out.cpp + $(GCC) $^ -o $@ -lmpg123 ${L_OPENAL} + +openal_audiere_test: openal_audiere_test.cpp ../sources/audiere_source.cpp ../sources/sample_reader.cpp ../outputs/openal_out.cpp ../../stream/clients/audiere_file.cpp + $(GCC) $^ -o $@ $(L_AUDIERE) $(L_OPENAL) + +openal_ffmpeg_test: openal_ffmpeg_test.cpp ../sources/ffmpeg_source.cpp ../outputs/openal_out.cpp + $(GCC) $^ -o $@ $(L_FFMPEG) $(L_OPENAL) $(I_FFMPEG) + +openal_mpg123_test: openal_mpg123_test.cpp ../sources/mpg123_source.cpp ../outputs/openal_out.cpp + $(GCC) $^ -o $@ -lmpg123 ${L_OPENAL} + +openal_sndfile_test: openal_sndfile_test.cpp ../sources/libsndfile.cpp ../sources/sample_reader.cpp ../outputs/openal_out.cpp + $(GCC) $^ -o $@ -lsndfile ${L_OPENAL} + +openal_output_test: openal_output_test.cpp ../outputs/openal_out.cpp + $(GCC) $^ -o $@ $(L_OPENAL) + +audiere_source_test: audiere_source_test.cpp ../sources/audiere_source.cpp ../../stream/clients/audiere_file.cpp ../sources/sample_reader.cpp + $(GCC) $^ -o $@ $(L_AUDIERE) + +ffmpeg_source_test: ffmpeg_source_test.cpp ../sources/ffmpeg_source.cpp + $(GCC) $^ -o $@ $(L_FFMPEG) $(I_FFMPEG) + +clean: + rm *_test diff --git a/libs/mangle/sound/tests/audiere_source_test.cpp b/libs/mangle/sound/tests/audiere_source_test.cpp new file mode 100644 index 000000000..637d743b2 --- /dev/null +++ b/libs/mangle/sound/tests/audiere_source_test.cpp @@ -0,0 +1,68 @@ +#include + +#include "../../stream/servers/file_stream.hpp" +#include "../sources/audiere_source.hpp" + +#include +#include + +using namespace std; +using namespace Mangle::Stream; +using namespace Mangle::Sound; + +// Contents and size of cow.raw +void *orig; +size_t orig_size; + +void run(SampleSourcePtr &src) +{ + size_t ss = src->size(); + assert(ss == orig_size); + + cout << "Source size: " << ss << endl; + int rate, channels, bits; + src->getInfo(&rate, &channels, &bits); + cout << "rate=" << rate << "\nchannels=" << channels + << "\nbits=" << bits << endl; + + cout << "Reading entire buffer into memory\n"; + void *buf = malloc(ss); + src->read(buf, ss); + + cout << "Comparing...\n"; + if(memcmp(buf, orig, ss) != 0) + { + cout << "Oops!\n"; + assert(0); + } + + cout << "Done\n"; +} + +int main() +{ + { + cout << "Reading cow.raw first\n"; + FileStream tmp("cow.raw"); + orig_size = tmp.size(); + cout << "Size: " << orig_size << endl; + orig = malloc(orig_size); + tmp.read(orig, orig_size); + cout << "Done\n"; + } + + { + cout << "\nLoading cow.wav by filename:\n"; + SampleSourcePtr cow_file( new AudiereSource("cow.wav") ); + run(cow_file); + } + + { + cout << "\nLoading cow.wav by stream:\n"; + StreamPtr inp( new FileStream("cow.wav") ); + SampleSourcePtr cow_stream( new AudiereSource(inp) ); + run(cow_stream); + } + + return 0; +} diff --git a/libs/mangle/sound/tests/cow.raw b/libs/mangle/sound/tests/cow.raw new file mode 100644 index 000000000..c4d155bbf Binary files /dev/null and b/libs/mangle/sound/tests/cow.raw differ diff --git a/libs/mangle/sound/tests/cow.wav b/libs/mangle/sound/tests/cow.wav new file mode 100644 index 000000000..494e6c4ac Binary files /dev/null and b/libs/mangle/sound/tests/cow.wav differ diff --git a/libs/mangle/sound/tests/ffmpeg_source_test.cpp b/libs/mangle/sound/tests/ffmpeg_source_test.cpp new file mode 100644 index 000000000..f03b15b99 --- /dev/null +++ b/libs/mangle/sound/tests/ffmpeg_source_test.cpp @@ -0,0 +1,62 @@ +#include + +#include "../../stream/servers/file_stream.hpp" +#include "../sources/ffmpeg_source.hpp" + +#include +#include + +using namespace std; +using namespace Mangle::Stream; +using namespace Mangle::Sound; + +// Contents and size of cow.raw +void *orig; +size_t orig_size; + +void run(SampleSourcePtr &src) +{ + int rate, channels, bits; + src->getInfo(&rate, &channels, &bits); + cout << "rate=" << rate << "\nchannels=" << channels + << "\nbits=" << bits << endl; + + cout << "Reading entire buffer into memory\n"; + void *buf = malloc(orig_size); + size_t ss = src->read(buf, orig_size); + cout << "Actually read: " << ss << endl; + assert(ss == orig_size); + + cout << "Comparing...\n"; + if(memcmp(buf, orig, ss) != 0) + { + cout << "Oops!\n"; + assert(0); + } + + cout << "Done\n"; +} + +int main() +{ + { + cout << "Reading cow.raw first\n"; + FileStream tmp("cow.raw"); + orig_size = tmp.size(); + cout << "Size: " << orig_size << endl; + orig = malloc(orig_size); + tmp.read(orig, orig_size); + cout << "Done\n"; + } + + // Initializes the library, not used for anything else. + FFMpegLoader fm; + + { + cout << "\nLoading cow.wav by filename:\n"; + SampleSourcePtr cow_file( new FFMpegSource("cow.wav") ); + run(cow_file); + } + + return 0; +} diff --git a/libs/mangle/sound/tests/openal_audiere_test.cpp b/libs/mangle/sound/tests/openal_audiere_test.cpp new file mode 100644 index 000000000..ced7fe5d2 --- /dev/null +++ b/libs/mangle/sound/tests/openal_audiere_test.cpp @@ -0,0 +1,52 @@ +#include +#include + +#include "../../stream/servers/file_stream.hpp" +#include "../filters/openal_audiere.hpp" + +using namespace std; +using namespace Mangle::Stream; +using namespace Mangle::Sound; + +OpenAL_Audiere_Factory mg; + +void play(const char* name, bool stream=false) +{ + // Only load streams if the backend supports it + if(stream && !mg.canLoadStream) + return; + + cout << "Playing " << name; + if(stream) cout << " (from stream)"; + cout << "\n"; + + SoundPtr snd; + + try + { + if(stream) + snd = mg.load(StreamPtr(new FileStream(name))); + else + snd = mg.load(name); + + snd->play(); + + while(snd->isPlaying()) + { + usleep(10000); + if(mg.needsUpdate) mg.update(); + } + } + catch(exception &e) + { + cout << " ERROR: " << e.what() << "\n"; + } +} + +int main() +{ + play("cow.wav"); + play("owl.ogg"); + play("cow.wav", true); + return 0; +} diff --git a/libs/mangle/sound/tests/openal_ffmpeg_test.cpp b/libs/mangle/sound/tests/openal_ffmpeg_test.cpp new file mode 100644 index 000000000..d4b8e9300 --- /dev/null +++ b/libs/mangle/sound/tests/openal_ffmpeg_test.cpp @@ -0,0 +1,52 @@ +#include +#include + +#include "../../stream/servers/file_stream.hpp" +#include "../filters/openal_ffmpeg.hpp" + +using namespace std; +using namespace Mangle::Stream; +using namespace Mangle::Sound; + +OpenAL_FFMpeg_Factory mg; + +void play(const char* name, bool stream=false) +{ + // Only load streams if the backend supports it + if(stream && !mg.canLoadStream) + return; + + cout << "Playing " << name; + if(stream) cout << " (from stream)"; + cout << "\n"; + + SoundPtr snd; + + try + { + if(stream) + snd = mg.load(StreamPtr(new FileStream(name))); + else + snd = mg.load(name); + + snd->play(); + + while(snd->isPlaying()) + { + usleep(10000); + if(mg.needsUpdate) mg.update(); + } + } + catch(exception &e) + { + cout << " ERROR: " << e.what() << "\n"; + } +} + +int main() +{ + play("cow.wav"); + play("owl.ogg"); + play("cow.wav", true); + return 0; +} diff --git a/libs/mangle/sound/tests/openal_mpg123_test.cpp b/libs/mangle/sound/tests/openal_mpg123_test.cpp new file mode 100644 index 000000000..fef1a5605 --- /dev/null +++ b/libs/mangle/sound/tests/openal_mpg123_test.cpp @@ -0,0 +1,54 @@ +#include +#include + +#include "../../stream/servers/file_stream.hpp" +#include "../filters/openal_mpg123.hpp" + +using namespace std; +using namespace Mangle::Stream; +using namespace Mangle::Sound; + +OpenAL_Mpg123_Factory mg; + +void play(const char* name, bool stream=false) +{ + // Only load streams if the backend supports it + if(stream && !mg.canLoadStream) + return; + + cout << "Playing " << name; + if(stream) cout << " (from stream)"; + cout << "\n"; + + SoundPtr snd; + + try + { + if(stream) + snd = mg.load(StreamPtr(new FileStream(name))); + else + snd = mg.load(name); + + snd->setStreaming(true); + snd->play(); + + while(snd->isPlaying()) + { + usleep(10000); + if(mg.needsUpdate) mg.update(); + } + } + catch(exception &e) + { + cout << " ERROR: " << e.what() << "\n"; + } +} + +int main(int argc, char**argv) +{ + if(argc != 2) + cout << "Please specify an MP3 file\n"; + else + play(argv[1]); + return 0; +} diff --git a/libs/mangle/sound/tests/openal_output_test.cpp b/libs/mangle/sound/tests/openal_output_test.cpp new file mode 100644 index 000000000..a8059ec65 --- /dev/null +++ b/libs/mangle/sound/tests/openal_output_test.cpp @@ -0,0 +1,59 @@ +#include +#include + +#include "../../stream/servers/file_stream.hpp" +#include "../sources/stream_source.hpp" +#include "../outputs/openal_out.hpp" + +using namespace std; +using namespace Mangle::Stream; +using namespace Mangle::Sound; + +int main() +{ + cout << "Loading cow.raw\n"; + + int rate = 11025; + int chan = 1; + int bits = 16; + + cout << " rate=" << rate << "\n channels=" << chan + << "\n bits=" << bits << endl; + + StreamPtr file( new FileStream("cow.raw") ); + SampleSourcePtr source( new Stream2Samples( file, rate, chan, bits)); + + cout << "Playing\n"; + + OpenAL_Factory mg; + + SoundPtr snd = mg.loadRaw(source); + + try + { + // Try setting all kinds of stuff before playing. OpenAL_Sound + // uses delayed buffer loading, but these should still work + // without a buffer. + snd->stop(); + snd->pause(); + snd->setVolume(0.8); + snd->setPitch(0.9); + + // Also test streaming, since all the other examples test + // non-streaming sounds. + snd->setStreaming(true); + + snd->play(); + + while(snd->isPlaying()) + { + usleep(10000); + mg.update(); + } + } + catch(exception &e) + { + cout << " ERROR: " << e.what() << "\n"; + } + return 0; +} diff --git a/libs/mangle/sound/tests/openal_sndfile_test.cpp b/libs/mangle/sound/tests/openal_sndfile_test.cpp new file mode 100644 index 000000000..bd5f117a5 --- /dev/null +++ b/libs/mangle/sound/tests/openal_sndfile_test.cpp @@ -0,0 +1,52 @@ +#include +#include + +#include "../../stream/servers/file_stream.hpp" +#include "../filters/openal_sndfile.hpp" + +using namespace std; +using namespace Mangle::Stream; +using namespace Mangle::Sound; + +OpenAL_SndFile_Factory mg; + +void play(const char* name, bool stream=false) +{ + // Only load streams if the backend supports it + if(stream && !mg.canLoadStream) + return; + + cout << "Playing " << name; + if(stream) cout << " (from stream)"; + cout << "\n"; + + SoundPtr snd; + + try + { + if(stream) + snd = mg.load(StreamPtr(new FileStream(name))); + else + snd = mg.load(name); + + snd->play(); + + while(snd->isPlaying()) + { + usleep(10000); + if(mg.needsUpdate) mg.update(); + } + } + catch(exception &e) + { + cout << " ERROR: " << e.what() << "\n"; + } +} + +int main() +{ + play("cow.wav"); + play("owl.ogg"); + play("cow.wav", true); + return 0; +} diff --git a/libs/mangle/sound/tests/openal_various_test.cpp b/libs/mangle/sound/tests/openal_various_test.cpp new file mode 100644 index 000000000..9426a672e --- /dev/null +++ b/libs/mangle/sound/tests/openal_various_test.cpp @@ -0,0 +1,51 @@ +#include +#include + +#include "../../stream/servers/file_stream.hpp" +#include "../filters/openal_various.hpp" + +using namespace std; +using namespace Mangle::Stream; +using namespace Mangle::Sound; + +OpenAL_Various_Factory mg; + +void play(const char* name, bool stream=false) +{ + // Only load streams if the backend supports it + if(stream && !mg.canLoadStream) + return; + + cout << "Playing " << name; + if(stream) cout << " (from stream)"; + cout << "\n"; + + SoundPtr snd; + + try + { + if(stream) + snd = mg.load(StreamPtr(new FileStream(name))); + else + snd = mg.load(name); + + snd->play(); + + while(snd->isPlaying()) + { + usleep(10000); + if(mg.needsUpdate) mg.update(); + } + } + catch(exception &e) + { + cout << " ERROR: " << e.what() << "\n"; + } +} + +int main() +{ + play("cow.wav"); + play("cow.wav", true); + return 0; +} diff --git a/libs/mangle/sound/tests/output/audiere_source_test.out b/libs/mangle/sound/tests/output/audiere_source_test.out new file mode 100644 index 000000000..47a5a9e41 --- /dev/null +++ b/libs/mangle/sound/tests/output/audiere_source_test.out @@ -0,0 +1,21 @@ +Reading cow.raw first +Size: 37502 +Done + +Loading cow.wav by filename: +Source size: 37502 +rate=11025 +channels=1 +bits=16 +Reading entire buffer into memory +Comparing... +Done + +Loading cow.wav by stream: +Source size: 37502 +rate=11025 +channels=1 +bits=16 +Reading entire buffer into memory +Comparing... +Done diff --git a/libs/mangle/sound/tests/output/ffmpeg_source_test.out b/libs/mangle/sound/tests/output/ffmpeg_source_test.out new file mode 100644 index 000000000..1c7d49113 --- /dev/null +++ b/libs/mangle/sound/tests/output/ffmpeg_source_test.out @@ -0,0 +1,12 @@ +Reading cow.raw first +Size: 37502 +Done + +Loading cow.wav by filename: +rate=11025 +channels=1 +bits=16 +Reading entire buffer into memory +Actually read: 37502 +Comparing... +Done diff --git a/libs/mangle/sound/tests/output/openal_audiere_test.out b/libs/mangle/sound/tests/output/openal_audiere_test.out new file mode 100644 index 000000000..4fe01eac2 --- /dev/null +++ b/libs/mangle/sound/tests/output/openal_audiere_test.out @@ -0,0 +1,3 @@ +Playing cow.wav +Playing owl.ogg +Playing cow.wav (from stream) diff --git a/libs/mangle/sound/tests/output/openal_ffmpeg_test.out b/libs/mangle/sound/tests/output/openal_ffmpeg_test.out new file mode 100644 index 000000000..96e1db0f9 --- /dev/null +++ b/libs/mangle/sound/tests/output/openal_ffmpeg_test.out @@ -0,0 +1,2 @@ +Playing cow.wav +Playing owl.ogg diff --git a/libs/mangle/sound/tests/output/openal_mpg123_test.out b/libs/mangle/sound/tests/output/openal_mpg123_test.out new file mode 100644 index 000000000..e55dabbb1 --- /dev/null +++ b/libs/mangle/sound/tests/output/openal_mpg123_test.out @@ -0,0 +1 @@ +Please specify an MP3 file diff --git a/libs/mangle/sound/tests/output/openal_output_test.out b/libs/mangle/sound/tests/output/openal_output_test.out new file mode 100644 index 000000000..04392a72e --- /dev/null +++ b/libs/mangle/sound/tests/output/openal_output_test.out @@ -0,0 +1,5 @@ +Loading cow.raw + rate=11025 + channels=1 + bits=16 +Playing diff --git a/libs/mangle/sound/tests/output/openal_sndfile_test.out b/libs/mangle/sound/tests/output/openal_sndfile_test.out new file mode 100644 index 000000000..96e1db0f9 --- /dev/null +++ b/libs/mangle/sound/tests/output/openal_sndfile_test.out @@ -0,0 +1,2 @@ +Playing cow.wav +Playing owl.ogg diff --git a/libs/mangle/sound/tests/output/openal_various_test.out b/libs/mangle/sound/tests/output/openal_various_test.out new file mode 100644 index 000000000..f25a55513 --- /dev/null +++ b/libs/mangle/sound/tests/output/openal_various_test.out @@ -0,0 +1 @@ +Playing cow.wav diff --git a/libs/mangle/sound/tests/output/wav_source_test.out b/libs/mangle/sound/tests/output/wav_source_test.out new file mode 100644 index 000000000..b6fc8e6fc --- /dev/null +++ b/libs/mangle/sound/tests/output/wav_source_test.out @@ -0,0 +1,12 @@ +Source size: 37502 +rate=11025 +channels=1 +bits=16 +Reading entire buffer into memory + +Reading cow.raw +Size: 37502 + +Comparing... + +Done diff --git a/libs/mangle/sound/tests/owl.ogg b/libs/mangle/sound/tests/owl.ogg new file mode 100644 index 000000000..e992f24d4 Binary files /dev/null and b/libs/mangle/sound/tests/owl.ogg differ diff --git a/libs/mangle/sound/tests/test.sh b/libs/mangle/sound/tests/test.sh new file mode 100755 index 000000000..2d07708ad --- /dev/null +++ b/libs/mangle/sound/tests/test.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +make || exit + +mkdir -p output + +PROGS=*_test + +for a in $PROGS; do + if [ -f "output/$a.out" ]; then + echo "Running $a:" + ./$a | diff output/$a.out - + else + echo "Creating $a.out" + ./$a > "output/$a.out" + git add "output/$a.out" + fi +done diff --git a/libs/mangle/sound/tests/wav_source_test.cpp b/libs/mangle/sound/tests/wav_source_test.cpp new file mode 100644 index 000000000..749af1849 --- /dev/null +++ b/libs/mangle/sound/tests/wav_source_test.cpp @@ -0,0 +1,48 @@ +#include + +#include "../sources/wav_source.hpp" +#include "../../stream/servers/file_stream.hpp" + +#include +#include + +using namespace std; +using namespace Mangle::Sound; +using namespace Mangle::Stream; + +int main() +{ + WavSource wav("cow.wav"); + + cout << "Source size: " << wav.size() << endl; + int rate, channels, bits; + wav.getInfo(&rate, &channels, &bits); + cout << "rate=" << rate << "\nchannels=" << channels + << "\nbits=" << bits << endl; + + cout << "Reading entire buffer into memory\n"; + void *buf = malloc(wav.size()); + wav.read(buf, wav.size()); + + cout << "\nReading cow.raw\n"; + FileStream tmp("cow.raw"); + cout << "Size: " << tmp.size() << endl; + void *buf2 = malloc(tmp.size()); + tmp.read(buf2, tmp.size()); + + cout << "\nComparing...\n"; + if(tmp.size() != wav.size()) + { + cout << "SIZE MISMATCH!\n"; + assert(0); + } + + if(memcmp(buf, buf2, wav.size()) != 0) + { + cout << "CONTENT MISMATCH!\n"; + assert(0); + } + + cout << "\nDone\n"; + return 0; +} diff --git a/libs/mangle/stream/clients/audiere_file.cpp b/libs/mangle/stream/clients/audiere_file.cpp new file mode 100644 index 000000000..16bc7891a --- /dev/null +++ b/libs/mangle/stream/clients/audiere_file.cpp @@ -0,0 +1,32 @@ +#include "audiere_file.hpp" + +using namespace audiere; +using namespace Mangle::Stream; + +bool AudiereFile::seek(int pos, SeekMode mode) +{ + assert(inp->isSeekable); + assert(inp->hasPosition); + + size_t newPos; + + switch(mode) + { + case BEGIN: newPos = pos; break; + case CURRENT: newPos = pos+tell(); break; + case END: + // Seeking from the end. This requires that we're able to get + // the entire size of the stream. The pos also has to be + // non-positive. + assert(inp->hasSize); + assert(pos <= 0); + newPos = inp->size() + pos; + break; + default: + assert(0 && "invalid seek mode"); + } + + inp->seek(newPos); + return inp->tell() == newPos; + +} diff --git a/libs/mangle/stream/clients/audiere_file.hpp b/libs/mangle/stream/clients/audiere_file.hpp new file mode 100644 index 000000000..61e26f21b --- /dev/null +++ b/libs/mangle/stream/clients/audiere_file.hpp @@ -0,0 +1,38 @@ +#ifndef MANGLE_STREAM_AUDIERECLIENT_H +#define MANGLE_STREAM_AUDIERECLIENT_H + +#include +#include + +#include "../stream.hpp" + +namespace Mangle { +namespace Stream { + +/** @brief An Audiere::File that wraps a Mangle::Stream input. + + This lets Audiere read sound files from any generic archive or + file manager that supports Mangle streams. + */ +class AudiereFile : public audiere::RefImplementation +{ + StreamPtr inp; + + public: + AudiereFile(StreamPtr _inp) + : inp(_inp) {} + + /// Read 'count' bytes, return bytes successfully read + int ADR_CALL read(void *buf, int count) + { return inp->read(buf,count); } + + /// Seek, relative to specified seek mode. Returns true if successful. + bool ADR_CALL seek(int pos, audiere::File::SeekMode mode); + + /// Get current position + int ADR_CALL tell() + { assert(inp->hasPosition); return inp->tell(); } +}; + +}} // namespaces +#endif diff --git a/libs/mangle/stream/clients/io_stream.cpp b/libs/mangle/stream/clients/io_stream.cpp new file mode 100644 index 000000000..5f1edc221 --- /dev/null +++ b/libs/mangle/stream/clients/io_stream.cpp @@ -0,0 +1,221 @@ +#include "io_stream.hpp" + +// This seems to work +#ifndef EOF +#define EOF -1 +#endif + +using namespace Mangle::Stream; + +#define BSIZE 1024 + +// Streambuf for normal stream reading +class _istreambuf : public std::streambuf +{ + StreamPtr client; + char buf[BSIZE]; + +public: + _istreambuf(StreamPtr strm) : client(strm) + { + // Make sure we picked the right class + assert(client->isReadable); + assert(!client->hasPtr); + + // Tell streambuf to delegate reading operations to underflow() + setg(NULL,NULL,NULL); + + // Disallow writing + setp(NULL,NULL); + } + + /* Underflow is called when there is no more info to read in the + input buffer. We need to refill buf with new data (if any), and + set up the internal pointers with setg() to reflect the new + state. + */ + int underflow() + { + // Read some more data + size_t read = client->read(buf, BSIZE); + assert(read <= BSIZE); + + // If we're out of data, then EOF + if(read == 0) + return EOF; + + // Otherwise, set up input buffer + setg(buf, buf, buf+read); + + // Return the first char + return *((unsigned char*)buf); + } + + // Seek stream, if the source supports it. Ignores the second + // parameter as Mangle doesn't separate input and output pointers. + std::streampos seekpos(std::streampos pos, std::ios_base::openmode = std::ios_base::in) + { + // Does this stream know how to seek? + if(!client->isSeekable || !client->hasPosition) + // If not, signal an error. + return -1; + + // Set stream position and reset the buffer. + client->seek(pos); + setg(NULL,NULL,NULL); + + return client->tell(); + } +}; + +// Streambuf optimized for pointer-based input streams +class _ptrstreambuf : public std::streambuf +{ + StreamPtr client; + +public: + _ptrstreambuf(StreamPtr strm) : client(strm) + { + // Make sure we picked the right class + assert(client->isReadable); + assert(client->hasPtr); + + // seekpos() does all the work + seekpos(0); + } + + // Underflow is only called when we're at the end of the file + int underflow() { return EOF; } + + // Seek to a new position within the memory stream. This bypasses + // client->seek() entirely so isSeekable doesn't have to be set. + std::streampos seekpos(std::streampos pos, std::ios_base::openmode = std::ios_base::in) + { + // All pointer streams need a size + assert(client->hasSize); + + // Figure out how much will be left of the stream after seeking + size_t size = client->size() - pos; + + // Get a pointer + char* ptr = (char*)client->getPtr(pos,size); + + // And use it + setg(ptr,ptr,ptr+size); + + return pos; + } +}; + +// Streambuf for stream writing +class _ostreambuf : public std::streambuf +{ + StreamPtr client; + char buf[BSIZE]; + +public: + _ostreambuf(StreamPtr strm) : client(strm) + { + // Make sure we picked the right class + assert(client->isWritable); + + // Inform streambuf about our nice buffer + setp(buf, buf+BSIZE); + + // Disallow reading + setg(NULL,NULL,NULL); + } + + /* Sync means to flush (write) all current data to the output + stream. It will also set up the entire output buffer to be usable + again. + */ + int sync() + { + // Get the number of bytes that streambuf wants us to write + int num = pptr() - pbase(); + assert(num >= 0); + + // Is there any work to do? + if(num == 0) return 0; + + if((int)client->write(pbase(), num) != num) + // Inform caller that writing failed + return -1; + + // Reset output buffer pointers + setp(buf, buf+BSIZE); + + // No error + return 0; + } + + /* Called whenever the output buffer is full. + */ + int overflow(int c) + { + // First, write all existing data + if(sync()) return EOF; + + // Put the requested character in the next round of output + if(c != EOF) + { + *pptr() = c; + pbump(1); + } + + // No error + return 0; + } + + // Seek stream, if the source supports it. + std::streampos seekpos(std::streampos pos, std::ios_base::openmode = std::ios_base::out) + { + if(!client->isSeekable || !client->hasPosition) + return -1; + + // Flush data and reset buffers + sync(); + + // Set stream position + client->seek(pos); + + return client->tell(); + } +}; + +MangleIStream::MangleIStream(StreamPtr inp) + : std::istream(NULL) +{ + assert(inp->isReadable); + + // Pick the right streambuf implementation based on whether the + // input supports pointers or not. + if(inp->hasPtr) + buf = new _ptrstreambuf(inp); + else + buf = new _istreambuf(inp); + + rdbuf(buf); +} + +MangleIStream::~MangleIStream() +{ + delete buf; +} + +MangleOStream::MangleOStream(StreamPtr out) + : std::ostream(NULL) +{ + assert(out->isWritable); + buf = new _ostreambuf(out); + + rdbuf(buf); +} + +MangleOStream::~MangleOStream() +{ + // Make sure we don't have lingering data on exit + flush(); + delete buf; +} diff --git a/libs/mangle/stream/clients/io_stream.hpp b/libs/mangle/stream/clients/io_stream.hpp new file mode 100644 index 000000000..98c6252ed --- /dev/null +++ b/libs/mangle/stream/clients/io_stream.hpp @@ -0,0 +1,43 @@ +#ifndef MANGLE_STREAM_IOSTREAM_H +#define MANGLE_STREAM_IOSTREAM_H + +#include +#include "../stream.hpp" +#include + +namespace Mangle { +namespace Stream { + + /** This file contains classes for wrapping an std::istream or + std::ostream around a Mangle::Stream. + + This allows you to use Mangle streams in places that require std + streams. + + This is much easier than trying to make your own custom streams + into iostreams. The std::iostream interface is horrible and NOT + designed for easy subclassing. Create a Mangle::Stream instead, + and use this wrapper. + */ + + // An istream wrapping a readable Mangle::Stream. Has extra + // optimizations for pointer-based streams. + class MangleIStream : public std::istream + { + std::streambuf *buf; + public: + MangleIStream(StreamPtr inp); + ~MangleIStream(); + }; + + // An ostream wrapping a writable Mangle::Stream. + class MangleOStream : public std::ostream + { + std::streambuf *buf; + public: + MangleOStream(StreamPtr inp); + ~MangleOStream(); + }; + +}} // namespaces +#endif diff --git a/libs/mangle/stream/clients/ogre_datastream.hpp b/libs/mangle/stream/clients/ogre_datastream.hpp new file mode 100644 index 000000000..76a6f20cf --- /dev/null +++ b/libs/mangle/stream/clients/ogre_datastream.hpp @@ -0,0 +1,68 @@ +#ifndef MANGLE_STREAM_OGRECLIENT_H +#define MANGLE_STREAM_OGRECLIENT_H + +#include +#include +#include "../stream.hpp" + +namespace Mangle { +namespace Stream { + +/** An OGRE DataStream that wraps a Mangle::Stream input. + + This has been built and tested against OGRE 1.6.2. You might have + to make your own modifications if you're working with newer (or + older) versions. + */ +class Mangle2OgreStream : public Ogre::DataStream +{ + StreamPtr inp; + + void init() + { + // Get the size, if possible + if(inp->hasSize) + mSize = inp->size(); + + // Allow writing if inp supports it + if(inp->isWritable) + mAccess |= Ogre::DataStream::WRITE; + } + + public: + /// Constructor without name + Mangle2OgreStream(StreamPtr _inp) + : inp(_inp) { init(); } + + /// Constructor for a named data stream + Mangle2OgreStream(const Ogre::String &name, StreamPtr _inp) + : Ogre::DataStream(name), inp(_inp) { init(); } + + // Only implement the DataStream functions we have to implement + + size_t read(void *buf, size_t count) + { return inp->read(buf,count); } + + size_t write(const void *buf, size_t count) + { assert(inp->isWritable); return inp->write(buf,count); } + + void skip(long count) + { + assert(inp->isSeekable && inp->hasPosition); + inp->seek(inp->tell() + count); + } + + void seek(size_t pos) + { assert(inp->isSeekable); inp->seek(pos); } + + size_t tell() const + { assert(inp->hasPosition); return inp->tell(); } + + bool eof() const { return inp->eof(); } + + /// Does nothing + void close() {} +}; + +}} // namespaces +#endif diff --git a/libs/mangle/stream/filters/buffer_stream.hpp b/libs/mangle/stream/filters/buffer_stream.hpp new file mode 100644 index 000000000..f037212a3 --- /dev/null +++ b/libs/mangle/stream/filters/buffer_stream.hpp @@ -0,0 +1,74 @@ +#ifndef MANGLE_STREAM_BUFFER_H +#define MANGLE_STREAM_BUFFER_H + +#include "../servers/memory_stream.hpp" +#include + +namespace Mangle { +namespace Stream { + +/** A Stream that reads another Stream into a buffer, and serves it as + a MemoryStream. Might be expanded with other capabilities later. + */ + +class BufferStream : public MemoryStream +{ + std::vector buffer; + + public: + /* + input = stream to copy + ADD = each read increment (for streams without size()) + */ + BufferStream(StreamPtr input, size_t ADD = 32*1024) + { + assert(input); + + // Allocate memory, read the stream into it. Then call set() + if(input->hasSize) + { + // We assume that we can get the position as well + assert(input->hasPosition); + + // Calculate how much is left of the stream + size_t left = input->size() - input->tell(); + + // Allocate the buffer and fill it + buffer.resize(left); + input->read(&buffer[0], left); + } + else + { + // We DON'T know how big the stream is. We'll have to read + // it in increments. + size_t len=0, newlen; + + while(!input->eof()) + { + // Read one block + newlen = len + ADD; + buffer.resize(newlen); + size_t read = input->read(&buffer[len], ADD); + + // Increase the total length + len += read; + + // If we read less than expected, we should be at the + // end of the stream + assert(read == ADD || (read < ADD && input->eof())); + } + + // Downsize to match the real length + buffer.resize(len); + } + + // After the buffer has been filled, set up our MemoryStream + // ancestor to reference it. + set(&buffer[0], buffer.size()); + } +}; + +typedef boost::shared_ptr BufferStreamPtr; + +}} // namespaces +#endif diff --git a/libs/mangle/stream/filters/pure_filter.hpp b/libs/mangle/stream/filters/pure_filter.hpp new file mode 100644 index 000000000..f0ce91f87 --- /dev/null +++ b/libs/mangle/stream/filters/pure_filter.hpp @@ -0,0 +1,46 @@ +#ifndef MANGLE_STREAM_FILTER_H +#define MANGLE_STREAM_FILTER_H + +#include "../stream.hpp" + +namespace Mangle { +namespace Stream { + +/** A stream that filters another stream with no changes. Intended as + a base class for other filters. + */ +class PureFilter : public Stream +{ + protected: + StreamPtr src; + + public: + PureFilter() {} + PureFilter(StreamPtr _src) + { setStream(_src); } + + void setStream(StreamPtr _src) + { + src = _src; + isSeekable = src->isSeekable; + isWritable = src->isWritable; + hasPosition = src->hasPosition; + hasSize = src->hasSize; + hasPtr = src->hasPtr; + } + + size_t read(void *buf, size_t count) { return src->read(buf, count); } + size_t write(const void *buf, size_t count) { return src->write(buf,count); } + void flush() { src->flush(); } + void seek(size_t pos) { src->seek(pos); } + size_t tell() const { return src->tell(); } + size_t size() const { return src->size(); } + bool eof() const { return src->eof(); } + const void *getPtr() { return src->getPtr(); } + const void *getPtr(size_t size) { return src->getPtr(size); } + const void *getPtr(size_t pos, size_t size) + { return src->getPtr(pos, size); } +}; + +}} // namespaces +#endif diff --git a/libs/mangle/stream/filters/slice_stream.hpp b/libs/mangle/stream/filters/slice_stream.hpp new file mode 100644 index 000000000..6337b9d57 --- /dev/null +++ b/libs/mangle/stream/filters/slice_stream.hpp @@ -0,0 +1,101 @@ +#ifndef MANGLE_STREAM_SLICE_H +#define MANGLE_STREAM_SLICE_H + +#include "../stream.hpp" + +namespace Mangle { +namespace Stream { + +/** A Stream that represents a subset (called a slice) of another stream. + */ +class SliceStream : public Stream +{ + StreamPtr src; + size_t offset, length, pos; + + public: + SliceStream(StreamPtr _src, size_t _offset, size_t _length) + : src(_src), offset(_offset), length(_length), pos(0) + { + assert(src->hasSize); + assert(src->isSeekable); + + // Make sure we can actually fit inside the source stream + assert(src->size() >= offset+length); + + isSeekable = true; + hasPosition = true; + hasSize = true; + hasPtr = src->hasPtr; + isWritable = src->isWritable; + } + + size_t read(void *buf, size_t count) + { + // Check that we're not reading past our slice + if(count > length-pos) + count = length-pos; + + // Seek into place and start reading + src->seek(offset+pos); + count = src->read(buf, count); + + pos += count; + assert(pos <= length); + return count; + } + + // Note that writing to a slice does not allow you to append data, + // you may only overwrite existing data. + size_t write(const void *buf, size_t count) + { + assert(isWritable); + // Check that we're not reading past our slice + if(count > length-pos) + count = length-pos; + + // Seek into place and action + src->seek(offset+pos); + count = src->write(buf, count); + + pos += count; + assert(pos <= length); + return count; + } + + void seek(size_t _pos) + { + pos = _pos; + if(pos > length) pos = length; + } + + bool eof() const { return pos == length; } + size_t tell() const { return pos; } + size_t size() const { return length; } + void flush() { src->flush(); } + + const void *getPtr() { return getPtr(0, length); } + const void *getPtr(size_t size) + { + const void *ptr = getPtr(pos, size); + seek(pos+size); + return ptr; + } + const void *getPtr(size_t pos, size_t size) + { + // Boundry checks on pos and size. Bounding the size is + // important even when getting pointers, as the source stream + // may use the size parameter for something (such as memory + // mapping or buffering.) + if(pos > length) pos = length; + if(pos+size > length) size = length-pos; + + // Ask the source to kindly give us a pointer + return src->getPtr(offset+pos, size); + } +}; + +typedef boost::shared_ptr SliceStreamPtr; + +}} // namespaces +#endif diff --git a/libs/mangle/stream/servers/file_stream.hpp b/libs/mangle/stream/servers/file_stream.hpp new file mode 100644 index 000000000..314a49642 --- /dev/null +++ b/libs/mangle/stream/servers/file_stream.hpp @@ -0,0 +1,32 @@ +#ifndef MANGLE_STREAM_FILESERVER_H +#define MANGLE_STREAM_FILESERVER_H + +#include "std_stream.hpp" +#include +#include + +namespace Mangle { +namespace Stream { + +/** Very simple file input stream, based on std::ifstream + */ +class FileStream : public StdStream +{ + std::ifstream file; + + public: + FileStream(const std::string &name) + : StdStream(&file) + { + file.open(name.c_str(), std::ios::binary); + + if(file.fail()) + throw std::runtime_error("FileStream: failed to open file " + name); + } + ~FileStream() { file.close(); } +}; + +typedef boost::shared_ptr FileStreamPtr; + +}} // namespaces +#endif diff --git a/libs/mangle/stream/servers/memory_stream.hpp b/libs/mangle/stream/servers/memory_stream.hpp new file mode 100644 index 000000000..0849e4a3c --- /dev/null +++ b/libs/mangle/stream/servers/memory_stream.hpp @@ -0,0 +1,116 @@ +#ifndef MANGLE_STREAM_MEMSERVER_H +#define MANGLE_STREAM_MEMSERVER_H + +#include +#include "../stream.hpp" +#include + +namespace Mangle { +namespace Stream { + +// Do this before the class declaration, since the class itself +// uses it. +class MemoryStream; +typedef boost::shared_ptr MemoryStreamPtr; + +/** A Stream wrapping a memory buffer + + This will create a fully seekable stream out of any pointer/length + pair you give it. + */ +class MemoryStream : public Stream +{ + const void *data; + size_t length, pos; + + public: + MemoryStream(const void *ptr, size_t len) + : data(ptr), length(len), pos(0) + { + isSeekable = true; + hasPosition = true; + hasSize = true; + hasPtr = true; + } + + MemoryStream() + : data(NULL), length(0), pos(0) + { + isSeekable = true; + hasPosition = true; + hasSize = true; + hasPtr = true; + } + + size_t read(void *buf, size_t count) + { + assert(data != NULL); + assert(pos <= length); + + // Don't read more than we have + if(count > (length - pos)) + count = length - pos; + + // Copy data + if(count) + memcpy(buf, ((char*)data)+pos, count); + + // aaand remember to increase the count + pos += count; + + return count; + } + + void seek(size_t _pos) + { + pos = _pos; + if(pos > length) + pos = length; + } + + size_t tell() const { return pos; } + size_t size() const { return length; } + bool eof() const { return pos == length; } + + const void *getPtr() { return data; } + const void *getPtr(size_t size) + { + // This variant of getPtr must move the position pointer + size_t opos = pos; + pos += size; + if(pos > length) pos = length; + return ((char*)data)+opos; + } + const void *getPtr(size_t pos, size_t size) + { + if(pos > length) pos = length; + return ((char*)data)+pos; + } + + // New members in MemoryStream: + + /// Set a new buffer and length. This will rewind the position to zero. + void set(const void* _data, size_t _length) + { + data = _data; + length = _length; + pos = 0; + } + + /// Clone this memory stream + /** Make a new stream of the same buffer. If setPos is true, we also + set the clone's position to be the same as ours. + + No memory is copied during this operation, the new stream is + just another 'view' into the same shared memory buffer. + */ + MemoryStreamPtr clone(bool setPos=false) const + { + MemoryStreamPtr res(new MemoryStream(data, length)); + if(setPos) res->seek(pos); + return res; + } +}; + +}} // namespaces +#endif diff --git a/libs/mangle/stream/servers/ogre_datastream.hpp b/libs/mangle/stream/servers/ogre_datastream.hpp new file mode 100644 index 000000000..a5be98c84 --- /dev/null +++ b/libs/mangle/stream/servers/ogre_datastream.hpp @@ -0,0 +1,37 @@ +#ifndef MANGLE_STREAM_OGRESERVER_H +#define MANGLE_STREAM_OGRESERVER_H + +#include + +namespace Mangle { +namespace Stream { + +/** A Stream wrapping an OGRE DataStream. + + This has been built and tested against OGRE 1.6.2. You might have + to make your own modifications if you're working with newer (or + older) versions. + */ +class OgreStream : public Stream +{ + Ogre::DataStreamPtr inp; + + public: + OgreStream(Ogre::DataStreamPtr _inp) : inp(_inp) + { + isSeekable = true; + hasPosition = true; + hasSize = true; + } + + size_t read(void *buf, size_t count) { return inp->read(buf,count); } + void seek(size_t pos) { inp->seek(pos); } + size_t tell() const { return inp->tell(); } + size_t size() const { return inp->size(); } + bool eof() const { return inp->eof(); } +}; + +typedef boost::shared_ptr OgreStreamPtr; + +}} // namespaces +#endif diff --git a/libs/mangle/stream/servers/outfile_stream.hpp b/libs/mangle/stream/servers/outfile_stream.hpp new file mode 100644 index 000000000..8d953d904 --- /dev/null +++ b/libs/mangle/stream/servers/outfile_stream.hpp @@ -0,0 +1,41 @@ +#ifndef MANGLE_OSTREAM_FILESERVER_H +#define MANGLE_OSTREAM_FILESERVER_H + +#include "std_ostream.hpp" +#include + +namespace Mangle { +namespace Stream { + +/** File stream based on std::ofstream, only supports writing. + */ +class OutFileStream : public StdOStream +{ + std::ofstream file; + + public: + /** + By default we overwrite the file. If append=true, then we will + open an existing file and seek to the end instead. + */ + OutFileStream(const std::string &name, bool append=false) + : StdOStream(&file) + { + std::ios::openmode mode = std::ios::binary; + if(append) + mode |= std::ios::app; + else + mode |= std::ios::trunc; + + file.open(name.c_str(), mode); + + if(file.fail()) + throw std::runtime_error("OutFileStream: failed to open file " + name); + } + ~OutFileStream() { file.close(); } +}; + +typedef boost::shared_ptr OutFileStreamPtr; + +}} // namespaces +#endif diff --git a/libs/mangle/stream/servers/phys_stream.hpp b/libs/mangle/stream/servers/phys_stream.hpp new file mode 100644 index 000000000..4312ac041 --- /dev/null +++ b/libs/mangle/stream/servers/phys_stream.hpp @@ -0,0 +1,36 @@ +#ifndef MANGLE_STREAM_OGRESERVER_H +#define MANGLE_STREAM_OGRESERVER_H + +#include + +namespace Mangle { +namespace Stream { + +/// A Stream wrapping a PHYSFS_file stream from the PhysFS library. +class PhysFile : public Stream +{ + PHYSFS_file *file; + + public: + PhysFile(PHYSFS_file *inp) : file(inp) + { + isSeekable = true; + hasPosition = true; + hasSize = true; + } + + ~PhysFile() { PHYSFS_close(file); } + + size_t read(void *buf, size_t count) + { return PHYSFS_read(file, buf, 1, count); } + + void seek(size_t pos) { PHYSFS_seek(file, pos); } + size_t tell() const { return PHYSFS_tell(file); } + size_t size() const { return PHYSFS_fileLength(file); } + bool eof() const { return PHYSFS_eof(file); } +}; + +typedef boost::shared_ptr PhysFilePtr; + +}} // namespaces +#endif diff --git a/libs/mangle/stream/servers/std_ostream.hpp b/libs/mangle/stream/servers/std_ostream.hpp new file mode 100644 index 000000000..f406e1a93 --- /dev/null +++ b/libs/mangle/stream/servers/std_ostream.hpp @@ -0,0 +1,78 @@ +#ifndef MANGLE_OSTREAM_STDIOSERVER_H +#define MANGLE_OSTREAM_STDIOSERVER_H + +#include "../stream.hpp" +#include +#include + +namespace Mangle { +namespace Stream { + +/** Simple wrapper for std::ostream, only supports output. + */ +class StdOStream : public Stream +{ + std::ostream *inf; + + static void fail(const std::string &msg) + { throw std::runtime_error("StdOStream: " + msg); } + + public: + StdOStream(std::ostream *_inf) + : inf(_inf) + { + isSeekable = true; + hasPosition = true; + hasSize = true; + isWritable = true; + isReadable = false; + } + + size_t write(const void* buf, size_t len) + { + inf->write((const char*)buf, len); + if(inf->fail()) + fail("error writing to stream"); + // Just return len, but that is ok. The only cases where we would + // return less than len is when an error occured. + return len; + } + + void flush() + { + inf->flush(); + } + + void seek(size_t pos) + { + inf->seekp(pos); + if(inf->fail()) + fail("seek error"); + } + + size_t tell() const + // Hack around the fact that ifstream->tellp() isn't const + { return ((StdOStream*)this)->inf->tellp(); } + + size_t size() const + { + // Use the standard iostream size hack, terrible as it is. + std::streampos pos = inf->tellp(); + inf->seekp(0, std::ios::end); + size_t res = inf->tellp(); + inf->seekp(pos); + + if(inf->fail()) + fail("could not get stream size"); + + return res; + } + + bool eof() const + { return inf->eof(); } +}; + +typedef boost::shared_ptr StdOStreamPtr; + +}} // namespaces +#endif diff --git a/libs/mangle/stream/servers/std_stream.hpp b/libs/mangle/stream/servers/std_stream.hpp new file mode 100644 index 000000000..163f023f6 --- /dev/null +++ b/libs/mangle/stream/servers/std_stream.hpp @@ -0,0 +1,70 @@ +#ifndef MANGLE_STREAM_STDIOSERVER_H +#define MANGLE_STREAM_STDIOSERVER_H + +#include "../stream.hpp" +#include +#include + +namespace Mangle { +namespace Stream { + +/** Simple wrapper for std::istream. + */ +class StdStream : public Stream +{ + std::istream *inf; + + static void fail(const std::string &msg) + { throw std::runtime_error("StdStream: " + msg); } + + public: + StdStream(std::istream *_inf) + : inf(_inf) + { + isSeekable = true; + hasPosition = true; + hasSize = true; + } + + size_t read(void* buf, size_t len) + { + inf->read((char*)buf, len); + if(inf->bad()) + fail("error reading from stream"); + return inf->gcount(); + } + + void seek(size_t pos) + { + inf->clear(); + inf->seekg(pos); + if(inf->fail()) + fail("seek error"); + } + + size_t tell() const + // Hack around the fact that ifstream->tellg() isn't const + { return ((StdStream*)this)->inf->tellg(); } + + size_t size() const + { + // Use the standard iostream size hack, terrible as it is. + std::streampos pos = inf->tellg(); + inf->seekg(0, std::ios::end); + size_t res = inf->tellg(); + inf->seekg(pos); + + if(inf->fail()) + fail("could not get stream size"); + + return res; + } + + bool eof() const + { return inf->eof(); } +}; + +typedef boost::shared_ptr StdStreamPtr; + +}} // namespaces +#endif diff --git a/libs/mangle/stream/stream.hpp b/libs/mangle/stream/stream.hpp new file mode 100644 index 000000000..2ee4fcbd8 --- /dev/null +++ b/libs/mangle/stream/stream.hpp @@ -0,0 +1,103 @@ +#ifndef MANGLE_STREAM_INPUT_H +#define MANGLE_STREAM_INPUT_H + +#include +#include "../tools/shared_ptr.hpp" +#include + +namespace Mangle { +namespace Stream { + +/// An abstract interface for a stream data. +class Stream +{ + public: + // Feature options. These should be set in the constructor. + + /// If true, seek() works + bool isSeekable; + + /// If true, tell() works + bool hasPosition; + + /// If true, size() works + bool hasSize; + + /// If true, write() works. Writing through pointer operations is + /// not (yet) supported. + bool isWritable; + + /// If true, read() and eof() works. + bool isReadable; + + /// If true, the getPtr() functions work + bool hasPtr; + + /// Initialize all bools to false by default, except isReadable. + Stream() : + isSeekable(false), hasPosition(false), hasSize(false), + isWritable(false), isReadable(true), hasPtr(false) {} + + /// Virtual destructor + virtual ~Stream() {} + + /** Read a given number of bytes from the stream. Returns the actual + number read. If the return value is less than count, then the + stream is empty or an error occured. Only required for readable + streams. + */ + virtual size_t read(void* buf, size_t count) { assert(0); return 0; } + + /** Write a given number of bytes from the stream. Semantics is + similar to read(). Only valid if isWritable is true. + + The returned value is the number of bytes written. However in + most cases, unlike for read(), a write-count less than requested + usually indicates an error. The implementation should throw such + errors as exceptions rather than expect the caller to handle + them. + + Since most implementations do NOT support writing we default to + an assert(0) here. + */ + virtual size_t write(const void *buf, size_t count) { assert(0); return 0; } + + /// Flush an output stream. Does nothing for non-writing streams. + virtual void flush() {} + + /// Seek to an absolute position in this stream. Not all streams are + /// seekable. + virtual void seek(size_t pos) { assert(0); } + + /// Get the current position in the stream. Non-seekable streams are + /// not required to keep track of this. + virtual size_t tell() const { assert(0); return 0; } + + /// Return the total size of the stream. For streams hasSize is + /// false, size() should fail in some way, since it is an error to + /// call it in those cases. + virtual size_t size() const { assert(0); return 0; } + + /// Returns true if the stream is empty. Required for readable + /// streams. + virtual bool eof() const { assert(0); return 0; } + + /// Return a pointer to the entire stream. This function (and the + /// other getPtr() variants below) should only be implemented for + /// memory-based streams where using them would be an optimization. + virtual const void *getPtr() { assert(0); return NULL; } + + /// Get a pointer to a memory region of 'size' bytes starting from + /// position 'pos' + virtual const void *getPtr(size_t pos, size_t size) { assert(0); return NULL; } + + /// Get a pointer to a memory region of 'size' bytes from the + /// current position. Unlike the two other getPtr variants, this + /// will advance the position past the returned area. + virtual const void *getPtr(size_t size) { assert(0); return NULL; } +}; + +typedef boost::shared_ptr StreamPtr; + +}} // namespaces +#endif diff --git a/libs/mangle/stream/tests/.gitignore b/libs/mangle/stream/tests/.gitignore new file mode 100644 index 000000000..9dfd618e2 --- /dev/null +++ b/libs/mangle/stream/tests/.gitignore @@ -0,0 +1,2 @@ +*_test +test.file diff --git a/libs/mangle/stream/tests/Makefile b/libs/mangle/stream/tests/Makefile new file mode 100644 index 000000000..5f4397819 --- /dev/null +++ b/libs/mangle/stream/tests/Makefile @@ -0,0 +1,34 @@ +GCC=g++ -I../ -Wall -Werror + +all: ogre_client_test audiere_client_test memory_server_test buffer_filter_test file_server_test slice_filter_test file_write_test iostream_test + +I_OGRE=$(shell pkg-config --cflags OGRE) +L_OGRE=$(shell pkg-config --libs OGRE) +L_AUDIERE=-laudiere + +ogre_client_test: ogre_client_test.cpp ../stream.hpp ../clients/ogre_datastream.hpp + $(GCC) $< -o $@ $(I_OGRE) $(L_OGRE) + +audiere_client_test: audiere_client_test.cpp ../stream.hpp ../clients/audiere_file.hpp ../clients/audiere_file.cpp + $(GCC) $< -o $@ ../clients/audiere_file.cpp $(L_AUDIERE) + +iostream_test: iostream_test.cpp ../clients/io_stream.cpp + $(GCC) $^ -o $@ + +file_server_test: file_server_test.cpp ../stream.hpp ../servers/file_stream.hpp ../servers/std_stream.hpp + $(GCC) $< -o $@ + +file_write_test: file_write_test.cpp ../stream.hpp ../servers/outfile_stream.hpp ../servers/std_ostream.hpp + $(GCC) $< -o $@ + +memory_server_test: memory_server_test.cpp ../stream.hpp ../servers/memory_stream.hpp + $(GCC) $< -o $@ + +buffer_filter_test: buffer_filter_test.cpp ../stream.hpp ../servers/memory_stream.hpp ../filters/buffer_stream.hpp + $(GCC) $< -o $@ + +slice_filter_test: slice_filter_test.cpp ../stream.hpp ../servers/memory_stream.hpp ../filters/slice_stream.hpp + $(GCC) $< -o $@ + +clean: + rm *_test diff --git a/libs/mangle/stream/tests/audiere_client_test.cpp b/libs/mangle/stream/tests/audiere_client_test.cpp new file mode 100644 index 000000000..82be569cc --- /dev/null +++ b/libs/mangle/stream/tests/audiere_client_test.cpp @@ -0,0 +1,34 @@ +#include "../servers/memory_stream.hpp" +#include "../clients/audiere_file.hpp" +#include +#include + +using namespace Mangle::Stream; +using namespace audiere; +using namespace std; + +int main() +{ + char str[12]; + memset(str, 0, 12); + StreamPtr inp(new MemoryStream("hello world", 11)); + FilePtr p(new AudiereFile(inp)); + cout << "pos=" << p->tell() << endl; + p->read(str, 2); + cout << "2 bytes: " << str << endl; + cout << "pos=" << p->tell() << endl; + p->seek(4, File::BEGIN); + cout << "pos=" << p->tell() << endl; + p->read(str, 3); + cout << "3 bytes: " << str << endl; + p->seek(-1, File::CURRENT); + cout << "pos=" << p->tell() << endl; + p->seek(-4, File::END); + cout << "pos=" << p->tell() << endl; + p->read(str, 4); + cout << "last 4 bytes: " << str << endl; + p->seek(0, File::BEGIN); + p->read(str, 11); + cout << "entire stream: " << str << endl; + return 0; +} diff --git a/libs/mangle/stream/tests/buffer_filter_test.cpp b/libs/mangle/stream/tests/buffer_filter_test.cpp new file mode 100644 index 000000000..e53bda651 --- /dev/null +++ b/libs/mangle/stream/tests/buffer_filter_test.cpp @@ -0,0 +1,41 @@ +#include +#include + +#include "../filters/buffer_stream.hpp" + +using namespace Mangle::Stream; +using namespace std; + +int main() +{ + StreamPtr orig (new MemoryStream("hello world", 11)); + StreamPtr inp (new BufferStream(orig)); + + cout << "Size: " << inp->size() << endl; + cout << "Pos: " << inp->tell() << "\nSeeking...\n"; + inp->seek(3); + cout << "Pos: " << inp->tell() << endl; + char data[12]; + memset(data, 0, 12); + cout << "Reading: " << inp->read(data, 4) << endl; + cout << "Four bytes: " << data << endl; + cout << "Eof: " << inp->eof() << endl; + cout << "Pos: " << inp->tell() << "\nSeeking again...\n"; + inp->seek(33); + cout << "Pos: " << inp->tell() << endl; + cout << "Eof: " << inp->eof() << "\nSeek to 6\n"; + inp->seek(6); + cout << "Eof: " << inp->eof() << endl; + cout << "Pos: " << inp->tell() << endl; + cout << "Over-reading: " << inp->read(data, 200) << endl; + cout << "Result: " << data << endl; + cout << "Eof: " << inp->eof() << endl; + cout << "Pos: " << inp->tell() << endl; + inp->seek(0); + cout << "Finally, reading the entire string: " << inp->read(data,11) << endl; + cout << "Result: " << data << endl; + cout << "Eof: " << inp->eof() << endl; + cout << "Pos: " << inp->tell() << endl; + + return 0; +} diff --git a/libs/mangle/stream/tests/file_server_test.cpp b/libs/mangle/stream/tests/file_server_test.cpp new file mode 100644 index 000000000..3e1e3cfa5 --- /dev/null +++ b/libs/mangle/stream/tests/file_server_test.cpp @@ -0,0 +1,18 @@ +#include "../servers/file_stream.hpp" +#include + +using namespace Mangle::Stream; +using namespace std; + +int main() +{ + StreamPtr inp(new FileStream("file_server_test.cpp")); + + char buf[21]; + buf[20] = 0; + cout << "pos=" << inp->tell() << " eof=" << inp->eof() << endl; + inp->read(buf, 20); + cout << "First 20 bytes: " << buf << endl; + cout << "pos=" << inp->tell() << " eof=" << inp->eof() << endl; + return 0; +} diff --git a/libs/mangle/stream/tests/file_write_test.cpp b/libs/mangle/stream/tests/file_write_test.cpp new file mode 100644 index 000000000..c6c61fcca --- /dev/null +++ b/libs/mangle/stream/tests/file_write_test.cpp @@ -0,0 +1,41 @@ +#include "../servers/outfile_stream.hpp" +#include + +using namespace Mangle::Stream; +using namespace std; + +void print(Stream &str) +{ + cout << "size=" << str.size() + << " pos=" << str.tell() + << " eof=" << str.eof() + << endl; +} + +int main() +{ + { + cout << "\nCreating file\n"; + OutFileStream out("test.file"); + print(out); + out.write("hello",5); + print(out); + } + + { + cout << "\nAppending to file\n"; + OutFileStream out("test.file", true); + print(out); + out.write(" again\n",7); + print(out); + } + + { + cout << "\nOverwriting file\n"; + OutFileStream out("test.file"); + print(out); + out.write("overwrite!\n",11); + print(out); + } + return 0; +} diff --git a/libs/mangle/stream/tests/iostream_test.cpp b/libs/mangle/stream/tests/iostream_test.cpp new file mode 100644 index 000000000..60648c6c5 --- /dev/null +++ b/libs/mangle/stream/tests/iostream_test.cpp @@ -0,0 +1,176 @@ +#include +#include "../clients/io_stream.hpp" +#include "../servers/memory_stream.hpp" + +using namespace Mangle::Stream; +using namespace std; + +void test1() +{ + cout << "Testing ASCII reading from memory:\n"; + StreamPtr input(new MemoryStream("hello you world you", 19)); + MangleIStream inp(input); + + string str; + while(!inp.eof()) + { + inp >> str; + cout << "Got: " << str << endl; + } +} + +class Dummy : public Stream +{ + int count; + +public: + + Dummy() : count(0) + { + } + + size_t read(void *ptr, size_t num) + { + char *p = (char*)ptr; + char *start = p; + for(; (count < 2560) && (p-start < (int)num); count++) + { + *p = count / 10; + p++; + } + return p-start; + } + + bool eof() const { return count == 2560; } +}; + +void test2() +{ + cout << "\nTesting binary reading from non-memory:\n"; + + StreamPtr input(new Dummy); + MangleIStream inp(input); + + int x = 0; + while(!inp.eof()) + { + unsigned char buf[5]; + inp.read((char*)buf,5); + + // istream doesn't set eof() until we read _beyond_ the end of + // the stream, so we need an extra check. + if(inp.gcount() == 0) break; + + /* + for(int i=0;i<5;i++) + cout << (int)buf[i] << " "; + cout << endl; + */ + + assert(buf[4] == buf[0]); + assert(buf[0] == x/2); + x++; + } + cout << " Done\n"; +} + +struct Dummy2 : Stream +{ + Dummy2() + { + isWritable = true; + isReadable = false; + } + + size_t write(const void *ptr, size_t num) + { + const char *p = (const char*)ptr; + cout << " Got: "; + for(unsigned i=0;iwrite("testing", 7); + + cout << " Running through MangleOStream:\n"; + MangleOStream out(output); + out << "hello"; + out << " - are you ok?"; + cout << " Flushing:\n"; + out.flush(); + + cout << " Writing a hell of a lot of characters:\n"; + for(int i=0; i<127; i++) + out << "xxxxxxxx"; // 127 * 8 = 1016 + out << "fffffff"; // +7 = 1023 + cout << " Just one more:\n"; + out << "y"; + cout << " And oooone more:\n"; + out << "z"; + + cout << " Flushing again:\n"; + out.flush(); + cout << " Writing some more and exiting:\n"; + out << "blah bleh blob"; +} + +struct Dummy3 : Stream +{ + int pos; + + Dummy3() : pos(0) + { + hasPosition = true; + isSeekable = true; + } + + size_t read(void*, size_t num) + { + cout << " Reading " << num << " bytes from " << pos << endl; + pos += num; + return num; + } + + void seek(size_t npos) { pos = npos; } + size_t tell() const { return pos; } +}; + +void test4() +{ + cout << "\nTesting seeking;\n"; + StreamPtr input(new Dummy3); + + cout << " Direct reading:\n"; + input->read(0,10); + input->read(0,5); + + MangleIStream inp(input); + + cout << " Reading from istream:\n"; + char buf[20]; + inp.read(buf, 20); + inp.read(buf, 20); + inp.read(buf, 20); + + cout << " Seeking to 30 and reading again:\n"; + inp.seekg(30); + inp.read(buf, 20); +} + +int main() +{ + test1(); + test2(); + test3(); + test4(); + return 0; +} diff --git a/libs/mangle/stream/tests/memory_server_test.cpp b/libs/mangle/stream/tests/memory_server_test.cpp new file mode 100644 index 000000000..24d3bb17e --- /dev/null +++ b/libs/mangle/stream/tests/memory_server_test.cpp @@ -0,0 +1,42 @@ +#include +#include + +#include "../servers/memory_stream.hpp" + +using namespace Mangle::Stream; +using namespace std; + +int main() +{ + Stream* inp = new MemoryStream("hello world\0", 12); + + cout << "Size: " << inp->size() << endl; + cout << "Pos: " << inp->tell() << "\nSeeking...\n"; + inp->seek(3); + cout << "Pos: " << inp->tell() << endl; + char data[12]; + memset(data, 0, 12); + cout << "Reading: " << inp->read(data, 4) << endl; + cout << "Four bytes: " << data << endl; + cout << "Eof: " << inp->eof() << endl; + cout << "Pos: " << inp->tell() << "\nSeeking again...\n"; + inp->seek(33); + cout << "Pos: " << inp->tell() << endl; + cout << "Eof: " << inp->eof() << "\nSeek to 6\n"; + inp->seek(6); + cout << "Eof: " << inp->eof() << endl; + cout << "Pos: " << inp->tell() << endl; + cout << "Over-reading: " << inp->read(data, 200) << endl; + cout << "Result: " << data << endl; + cout << "Eof: " << inp->eof() << endl; + cout << "Pos: " << inp->tell() << endl; + inp->seek(0); + cout << "Finally, reading the entire string: " << inp->read(data,11) << endl; + cout << "Result: " << data << endl; + cout << "Eof: " << inp->eof() << endl; + cout << "Pos: " << inp->tell() << endl; + + cout << "Entire stream from pointer: " << (char*)inp->getPtr() << endl; + + return 0; +} diff --git a/libs/mangle/stream/tests/ogre_client_test.cpp b/libs/mangle/stream/tests/ogre_client_test.cpp new file mode 100644 index 000000000..c8d0442c0 --- /dev/null +++ b/libs/mangle/stream/tests/ogre_client_test.cpp @@ -0,0 +1,22 @@ +#include "../servers/memory_stream.hpp" +#include "../clients/ogre_datastream.hpp" +#include + +using namespace Mangle::Stream; +using namespace Ogre; +using namespace std; + +int main() +{ + StreamPtr inp(new MemoryStream("hello world", 11)); + DataStreamPtr p(new Mangle2OgreStream("hello", inp)); + cout << "Name: " << p->getName() << endl; + cout << "As string: " << p->getAsString() << endl; + cout << "pos=" << p->tell() << " eof=" << p->eof() << endl; + p->seek(0); + cout << "pos=" << p->tell() << " eof=" << p->eof() << endl; + p->skip(5); + p->skip(-2); + cout << "pos=" << p->tell() << " eof=" << p->eof() << endl; + return 0; +} diff --git a/libs/mangle/stream/tests/output/audiere_client_test.out b/libs/mangle/stream/tests/output/audiere_client_test.out new file mode 100644 index 000000000..2130db9fd --- /dev/null +++ b/libs/mangle/stream/tests/output/audiere_client_test.out @@ -0,0 +1,9 @@ +pos=0 +2 bytes: he +pos=2 +pos=4 +3 bytes: o w +pos=6 +pos=7 +last 4 bytes: orld +entire stream: hello world diff --git a/libs/mangle/stream/tests/output/buffer_filter_test.out b/libs/mangle/stream/tests/output/buffer_filter_test.out new file mode 100644 index 000000000..0ca252f25 --- /dev/null +++ b/libs/mangle/stream/tests/output/buffer_filter_test.out @@ -0,0 +1,22 @@ +Size: 11 +Pos: 0 +Seeking... +Pos: 3 +Reading: 4 +Four bytes: lo w +Eof: 0 +Pos: 7 +Seeking again... +Pos: 11 +Eof: 1 +Seek to 6 +Eof: 0 +Pos: 6 +Over-reading: 5 +Result: world +Eof: 1 +Pos: 11 +Finally, reading the entire string: 11 +Result: hello world +Eof: 1 +Pos: 11 diff --git a/libs/mangle/stream/tests/output/file_server_test.out b/libs/mangle/stream/tests/output/file_server_test.out new file mode 100644 index 000000000..240f066a3 --- /dev/null +++ b/libs/mangle/stream/tests/output/file_server_test.out @@ -0,0 +1,3 @@ +pos=0 eof=0 +First 20 bytes: #include "../servers +pos=20 eof=0 diff --git a/libs/mangle/stream/tests/output/file_write_test.out b/libs/mangle/stream/tests/output/file_write_test.out new file mode 100644 index 000000000..34b07c49f --- /dev/null +++ b/libs/mangle/stream/tests/output/file_write_test.out @@ -0,0 +1,12 @@ + +Creating file +size=0 pos=0 eof=0 +size=5 pos=5 eof=0 + +Appending to file +size=5 pos=5 eof=0 +size=12 pos=12 eof=0 + +Overwriting file +size=0 pos=0 eof=0 +size=11 pos=11 eof=0 diff --git a/libs/mangle/stream/tests/output/iostream_test.out b/libs/mangle/stream/tests/output/iostream_test.out new file mode 100644 index 000000000..b6da80c80 --- /dev/null +++ b/libs/mangle/stream/tests/output/iostream_test.out @@ -0,0 +1,32 @@ +Testing ASCII reading from memory: +Got: hello +Got: you +Got: world +Got: you + +Testing binary reading from non-memory: + Done + +Writing to dummy stream: + Pure dummy test: + Got: t e s t i n g + Running through MangleOStream: + Flushing: + Got: h e l l o - a r e y o u o k ? + Writing a hell of a lot of characters: + Just one more: + And oooone more: + Got: x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x f f f f f f f y + Flushing again: + Got: z + Writing some more and exiting: + Got: b l a h b l e h b l o b + +Testing seeking; + Direct reading: + Reading 10 bytes from 0 + Reading 5 bytes from 10 + Reading from istream: + Reading 1024 bytes from 15 + Seeking to 30 and reading again: + Reading 1024 bytes from 30 diff --git a/libs/mangle/stream/tests/output/memory_server_test.out b/libs/mangle/stream/tests/output/memory_server_test.out new file mode 100644 index 000000000..7cd9533e7 --- /dev/null +++ b/libs/mangle/stream/tests/output/memory_server_test.out @@ -0,0 +1,23 @@ +Size: 12 +Pos: 0 +Seeking... +Pos: 3 +Reading: 4 +Four bytes: lo w +Eof: 0 +Pos: 7 +Seeking again... +Pos: 12 +Eof: 1 +Seek to 6 +Eof: 0 +Pos: 6 +Over-reading: 6 +Result: world +Eof: 1 +Pos: 12 +Finally, reading the entire string: 11 +Result: hello world +Eof: 0 +Pos: 11 +Entire stream from pointer: hello world diff --git a/libs/mangle/stream/tests/output/ogre_client_test.out b/libs/mangle/stream/tests/output/ogre_client_test.out new file mode 100644 index 000000000..62cd14604 --- /dev/null +++ b/libs/mangle/stream/tests/output/ogre_client_test.out @@ -0,0 +1,5 @@ +Name: hello +As string: hello world +pos=11 eof=1 +pos=0 eof=0 +pos=3 eof=0 diff --git a/libs/mangle/stream/tests/output/slice_filter_test.out b/libs/mangle/stream/tests/output/slice_filter_test.out new file mode 100644 index 000000000..6d84704a7 --- /dev/null +++ b/libs/mangle/stream/tests/output/slice_filter_test.out @@ -0,0 +1,36 @@ + +Slice 1: +-------- +Size: 6 +Pos: 0 +Seeking... +Reading 6 bytes +Result: hello +Pos: 6 +Eof: 1 +Seeking: +Pos: 2 +Eof: 0 +Reading 4 bytes +Result: llo +Pos: 6 +Eof: 1 +Entire stream as pointer: hello + +Slice 2: +-------- +Size: 6 +Pos: 0 +Seeking... +Reading 6 bytes +Result: world +Pos: 6 +Eof: 1 +Seeking: +Pos: 2 +Eof: 0 +Reading 4 bytes +Result: rld +Pos: 6 +Eof: 1 +Entire stream as pointer: world diff --git a/libs/mangle/stream/tests/slice_filter_test.cpp b/libs/mangle/stream/tests/slice_filter_test.cpp new file mode 100644 index 000000000..da90e24ad --- /dev/null +++ b/libs/mangle/stream/tests/slice_filter_test.cpp @@ -0,0 +1,42 @@ +#include +#include + +#include "../filters/slice_stream.hpp" +#include "../servers/memory_stream.hpp" + +using namespace Mangle::Stream; +using namespace std; + +void test(StreamPtr inp) +{ + cout << "Size: " << inp->size() << endl; + cout << "Pos: " << inp->tell() << "\nSeeking...\n"; + char data[6]; + memset(data, 0, 6); + cout << "Reading " << inp->read(data, 6) << " bytes\n"; + cout << "Result: " << data << endl; + cout << "Pos: " << inp->tell() << endl; + cout << "Eof: " << inp->eof() << endl; + inp->seek(2); + cout << "Seeking:\nPos: " << inp->tell() << endl; + cout << "Eof: " << inp->eof() << endl; + cout << "Reading " << inp->read(data, 6) << " bytes\n"; + cout << "Result: " << data << endl; + cout << "Pos: " << inp->tell() << endl; + cout << "Eof: " << inp->eof() << endl; + cout << "Entire stream as pointer: " << (char*)inp->getPtr() << endl; +} + +int main() +{ + StreamPtr orig (new MemoryStream("hello\0world\0", 12)); + StreamPtr slice1 (new SliceStream(orig,0,6)); + StreamPtr slice2 (new SliceStream(orig,6,6)); + + cout << "\nSlice 1:\n--------\n"; + test(slice1); + cout << "\nSlice 2:\n--------\n"; + test(slice2); + + return 0; +} diff --git a/libs/mangle/stream/tests/test.sh b/libs/mangle/stream/tests/test.sh new file mode 100755 index 000000000..2d07708ad --- /dev/null +++ b/libs/mangle/stream/tests/test.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +make || exit + +mkdir -p output + +PROGS=*_test + +for a in $PROGS; do + if [ -f "output/$a.out" ]; then + echo "Running $a:" + ./$a | diff output/$a.out - + else + echo "Creating $a.out" + ./$a > "output/$a.out" + git add "output/$a.out" + fi +done diff --git a/libs/mangle/testall.sh b/libs/mangle/testall.sh new file mode 100755 index 000000000..b93fee215 --- /dev/null +++ b/libs/mangle/testall.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +function run() +{ + echo "TESTING $1" + cd "$1/tests/" + ./test.sh + cd ../../ +} + +run stream +run vfs +run sound +run input +run rend2d +run . diff --git a/libs/mangle/tests/.gitignore b/libs/mangle/tests/.gitignore new file mode 100644 index 000000000..814490404 --- /dev/null +++ b/libs/mangle/tests/.gitignore @@ -0,0 +1 @@ +*_test diff --git a/libs/mangle/tests/Makefile b/libs/mangle/tests/Makefile new file mode 100644 index 000000000..d912c0784 --- /dev/null +++ b/libs/mangle/tests/Makefile @@ -0,0 +1,14 @@ +GCC=g++ -I../ + +all: ogrevfs_audiere_openal_test + +I_OGRE=$(shell pkg-config --cflags OGRE) +L_OGRE=$(shell pkg-config --libs OGRE) +L_OPENAL=$(shell pkg-config --libs openal) +L_AUDIERE=-laudiere + +ogrevfs_audiere_openal_test: ogrevfs_audiere_openal_test.cpp ../vfs/servers/ogre_vfs.cpp ../sound/sources/audiere_source.cpp ../sound/outputs/openal_out.cpp ../stream/clients/audiere_file.cpp + $(GCC) $^ -o $@ $(I_OGRE) $(L_OGRE) $(L_OPENAL) $(L_AUDIERE) + +clean: + rm *_test diff --git a/libs/mangle/tests/ogrevfs_audiere_openal_test.cpp b/libs/mangle/tests/ogrevfs_audiere_openal_test.cpp new file mode 100644 index 000000000..4936538c5 --- /dev/null +++ b/libs/mangle/tests/ogrevfs_audiere_openal_test.cpp @@ -0,0 +1,49 @@ +/* + This example combines: + + - the OGRE VFS system (to read from zip) + - Audiere (for decoding sound data) + - OpenAL (for sound playback) + + */ + +#include "sound/filters/openal_audiere.hpp" +#include "vfs/servers/ogre_vfs.hpp" +#include +#include + +using namespace Ogre; +using namespace Mangle; +using namespace std; + +int main() +{ + // Disable Ogre logging + new LogManager; + Log *log = LogManager::getSingleton().createLog(""); + log->setDebugOutputEnabled(false); + + // Set up Root + Root *root = new Root("","",""); + + // Add zip file with a sound in it + root->addResourceLocation("sound.zip", "Zip", "General"); + + // Ogre file system + VFS::OgreVFS vfs; + + // The main sound system + Sound::OpenAL_Audiere_Factory mg; + Sound::SoundPtr snd = mg.load(vfs.open("owl.ogg")); + + cout << "Playing 'owl.ogg' from 'sound.zip'\n"; + snd->play(); + + while(snd->isPlaying()) + { + usleep(10000); + if(mg.needsUpdate) mg.update(); + } + + return 0; +} diff --git a/libs/mangle/tests/output/ogrevfs_audiere_openal_test.out b/libs/mangle/tests/output/ogrevfs_audiere_openal_test.out new file mode 100644 index 000000000..28ea8a71b --- /dev/null +++ b/libs/mangle/tests/output/ogrevfs_audiere_openal_test.out @@ -0,0 +1 @@ +Playing 'owl.ogg' from 'sound.zip' diff --git a/libs/mangle/tests/sound.zip b/libs/mangle/tests/sound.zip new file mode 100644 index 000000000..fd32b3529 Binary files /dev/null and b/libs/mangle/tests/sound.zip differ diff --git a/libs/mangle/tests/test.sh b/libs/mangle/tests/test.sh new file mode 100755 index 000000000..2d07708ad --- /dev/null +++ b/libs/mangle/tests/test.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +make || exit + +mkdir -p output + +PROGS=*_test + +for a in $PROGS; do + if [ -f "output/$a.out" ]; then + echo "Running $a:" + ./$a | diff output/$a.out - + else + echo "Creating $a.out" + ./$a > "output/$a.out" + git add "output/$a.out" + fi +done diff --git a/libs/mangle/tools/shared_ptr.hpp b/libs/mangle/tools/shared_ptr.hpp new file mode 100644 index 000000000..3d073fc24 --- /dev/null +++ b/libs/mangle/tools/shared_ptr.hpp @@ -0,0 +1,3 @@ +// This file should include whatever it needs to define the boost/tr1 +// shared_ptr<> and weak_ptr<> templates. +#include diff --git a/libs/mangle/vfs/clients/ogre_archive.cpp b/libs/mangle/vfs/clients/ogre_archive.cpp new file mode 100644 index 000000000..2d3f7c520 --- /dev/null +++ b/libs/mangle/vfs/clients/ogre_archive.cpp @@ -0,0 +1,85 @@ +#include "ogre_archive.hpp" + +#include "../../stream/clients/ogre_datastream.hpp" + +using namespace Mangle::VFS; +using namespace Mangle::Stream; + +Ogre::DataStreamPtr MangleArchive::open(const Ogre::String& filename) const +{ + return Ogre::DataStreamPtr(new Mangle2OgreStream + (filename, vfs->open(filename))); +} + +static void fill(Ogre::FileInfoList &out, FileInfoList &in) +{ + int size = in.size(); + out.resize(size); + + for(int i=0; ihasList); + FileInfoListPtr lst = vfs->list("", recursive, dirs); + Ogre::StringVector *res = new Ogre::StringVector; + + fill(*res, *lst); + + return Ogre::StringVectorPtr(res); +} + +Ogre::FileInfoListPtr MangleArchive::listFileInfo(bool recursive, bool dirs) +{ + assert(vfs->hasList); + FileInfoListPtr lst = vfs->list("", recursive, dirs); + Ogre::FileInfoList *res = new Ogre::FileInfoList; + + fill(*res, *lst); + + return Ogre::FileInfoListPtr(res); +} + +// Find functions will only work if vfs->hasFind is set. +Ogre::StringVectorPtr MangleArchive::find(const Ogre::String& pattern, + bool recursive, + bool dirs) +{ + assert(vfs->hasFind); + FileInfoListPtr lst = vfs->find(pattern, recursive, dirs); + Ogre::StringVector *res = new Ogre::StringVector; + + fill(*res, *lst); + + return Ogre::StringVectorPtr(res); +} + +Ogre::FileInfoListPtr MangleArchive::findFileInfo(const Ogre::String& pattern, + bool recursive, + bool dirs) +{ + assert(vfs->hasFind); + FileInfoListPtr lst = vfs->find(pattern, recursive, dirs); + Ogre::FileInfoList *res = new Ogre::FileInfoList; + + fill(*res, *lst); + + return Ogre::FileInfoListPtr(res); +} diff --git a/libs/mangle/vfs/clients/ogre_archive.hpp b/libs/mangle/vfs/clients/ogre_archive.hpp new file mode 100644 index 000000000..7b371c6ef --- /dev/null +++ b/libs/mangle/vfs/clients/ogre_archive.hpp @@ -0,0 +1,59 @@ +#ifndef MANGLE_VFS_OGRECLIENT_H +#define MANGLE_VFS_OGRECLIENT_H + +#include +#include +#include "../vfs.hpp" + +namespace Mangle { +namespace VFS { + +/** An OGRE Archive implementation that wraps a Mangle::VFS + filesystem. + + This has been built and tested against OGRE 1.6.2, and has been + extended for OGRE 1.7. You might have to make your own + modifications if you're working with newer (or older) versions. + */ +class MangleArchive : public Ogre::Archive +{ + VFSPtr vfs; + + public: + MangleArchive(VFSPtr _vfs, const std::string &name, + const std::string &archType = "Mangle") + : Ogre::Archive(name, archType) + , vfs(_vfs) + {} + + bool isCaseSensitive() const { return vfs->isCaseSensitive; } + + // These do nothing. You have to load / unload the archive in the + // constructor/destructor. + void load() {} + void unload() {} + + bool exists(const Ogre::String& filename) + { return vfs->isFile(filename); } + + time_t getModifiedTime(const Ogre::String& filename) + { return vfs->stat(filename)->time; } + + // With both these we support both OGRE 1.6 and 1.7 + Ogre::DataStreamPtr open(const Ogre::String& filename) const; + Ogre::DataStreamPtr open(const Ogre::String& filename, bool readOnly) const + { return open(filename); } + + Ogre::StringVectorPtr list(bool recursive = true, bool dirs = false); + Ogre::FileInfoListPtr listFileInfo(bool recursive = true, bool dirs = false); + + // Find functions will only work if vfs->hasFind is set. + Ogre::StringVectorPtr find(const Ogre::String& pattern, bool recursive = true, + bool dirs = false); + Ogre::FileInfoListPtr findFileInfo(const Ogre::String& pattern, + bool recursive = true, + bool dirs = false); +}; + +}} // namespaces +#endif diff --git a/libs/mangle/vfs/servers/ogre_vfs.cpp b/libs/mangle/vfs/servers/ogre_vfs.cpp new file mode 100644 index 000000000..7f728c1b0 --- /dev/null +++ b/libs/mangle/vfs/servers/ogre_vfs.cpp @@ -0,0 +1,61 @@ +#include "ogre_vfs.hpp" +#include "../../stream/servers/ogre_datastream.hpp" + +using namespace Mangle::VFS; +using namespace Mangle::Stream; + +OgreVFS::OgreVFS(const std::string &_group) + : group(_group) +{ + hasList = true; + hasFind = true; + isCaseSensitive = true; + + // Get the group manager once + gm = Ogre::ResourceGroupManager::getSingletonPtr(); + + // Use the default group if none was specified + if(group.empty()) + group = gm->getWorldResourceGroupName(); +} + +StreamPtr OgreVFS::open(const std::string &name) +{ + Ogre::DataStreamPtr data = gm->openResource(name, group); + return StreamPtr(new OgreStream(data)); +} + +static void fill(FileInfoList &out, Ogre::FileInfoList &in, bool dirs) +{ + int size = in.size(); + out.resize(size); + + for(int i=0; ilistResourceFileInfo(group, dirs); + FileInfoListPtr res(new FileInfoList); + fill(*res, *olist, dirs); + return res; +} + +FileInfoListPtr OgreVFS::find(const std::string& pattern, + bool recursive, + bool dirs) const +{ + Ogre::FileInfoListPtr olist = gm->findResourceFileInfo(group, pattern, dirs); + FileInfoListPtr res(new FileInfoList); + fill(*res, *olist, dirs); + return res; +} diff --git a/libs/mangle/vfs/servers/ogre_vfs.hpp b/libs/mangle/vfs/servers/ogre_vfs.hpp new file mode 100644 index 000000000..7ab64169d --- /dev/null +++ b/libs/mangle/vfs/servers/ogre_vfs.hpp @@ -0,0 +1,70 @@ +#ifndef MANGLE_VFS_OGRESERVER_H +#define MANGLE_VFS_OGRESERVER_H + +#include "../vfs.hpp" +#include + +namespace Mangle { +namespace VFS { + +/** @brief An interface into the OGRE VFS system. + + This class does NOT wrap a single Ogre::Archive, but rather an + entire resource group in Ogre. You can use this class to tap into + all paths, Zip files, custom archives on so on that have been + inserted into Ogre as resource locations. + + This has been built and tested against OGRE 1.6.2. You might have + to make your own modifications if you're working with newer (or + older) versions. + */ +class OgreVFS : public VFS +{ + std::string group; + Ogre::ResourceGroupManager *gm; + + public: + /** @brief Constructor + + OGRE must be initialized (ie. you must have created an + Ogre::Root object somewhere) before calling this. + + @param group Optional resource group name. If none is given, + OGRE's default (or 'World') resource group is used. + */ + OgreVFS(const std::string &_group = ""); + + /// Open a new data stream. Deleting the object should be enough to + /// close it. + virtual Stream::StreamPtr open(const std::string &name); + + /// Check for the existence of a file + virtual bool isFile(const std::string &name) const + { return gm->resourceExists(group, name); } + + /// This doesn't work, always returns false. + virtual bool isDir(const std::string &name) const + { return false; } + + /// This doesn't work. + virtual FileInfoPtr stat(const std::string &name) const + { return FileInfoPtr(); } + + /// List all entries in a given directory. A blank dir should be + /// interpreted as a the root/current directory of the archive. If + /// dirs is true, list directories instead of files. OGRE note: The + /// ogre resource systemd does not support recursive listing of + /// files. We might make a separate filter for this later. + virtual FileInfoListPtr list(const std::string& dir = "", + bool recurse=true, + bool dirs=false) const; + + /// Find files after a given pattern. Wildcards (*) are + /// supported. + virtual FileInfoListPtr find(const std::string& pattern, + bool recursive=true, + bool dirs=false) const; +}; + +}} // namespaces +#endif diff --git a/libs/mangle/vfs/servers/physfs_vfs.hpp b/libs/mangle/vfs/servers/physfs_vfs.hpp new file mode 100644 index 000000000..8535088e0 --- /dev/null +++ b/libs/mangle/vfs/servers/physfs_vfs.hpp @@ -0,0 +1,71 @@ +#ifndef MANGLE_VFS_PHYSFS_SERVER_H +#define MANGLE_VFS_PHYSFS_SERVER_H + +#include "../vfs.hpp" +#include "../../stream/servers/phys_stream.hpp" + +#include +#include + +namespace Mangle { +namespace VFS { + +/** @brief An interface into the PhysFS virtual file system library + + You have to set up PhysFS on your own before using this class. + */ +class PhysVFS : public VFS +{ + public: + PhysVFS() + { + hasList = true; + hasFind = false; + isCaseSensitive = true; + } + + /// Open a new data stream. Deleting the object should be enough to + /// close it. + virtual Stream::StreamPtr open(const std::string &name) + { return Stream::StreamPtr(new Stream::PhysFile(PHYSFS_openRead(name.c_str()))); } + + /// Check for the existence of a file + virtual bool isFile(const std::string &name) const + { return PHYSFS_exists(name.c_str()); } + + /// Checks for a directory + virtual bool isDir(const std::string &name) const + { return PHYSFS_isDirectory(name.c_str()); } + + /// This doesn't work + virtual FileInfoPtr stat(const std::string &name) const + { assert(0); return FileInfoPtr(); } + + virtual FileInfoListPtr list(const std::string& dir = "", + bool recurse=true, + bool dirs=false) const + { + char **files = PHYSFS_enumerateFiles(dir.c_str()); + FileInfoListPtr lst(new FileInfoList); + + // Add all teh files + int i = 0; + while(files[i] != NULL) + { + FileInfo fi; + fi.name = files[i]; + fi.isDir = false; + + lst->push_back(fi); + } + return lst; + } + + virtual FileInfoListPtr find(const std::string& pattern, + bool recursive=true, + bool dirs=false) const + { assert(0); } +}; + +}} // namespaces +#endif diff --git a/libs/mangle/vfs/tests/.gitignore b/libs/mangle/vfs/tests/.gitignore new file mode 100644 index 000000000..814490404 --- /dev/null +++ b/libs/mangle/vfs/tests/.gitignore @@ -0,0 +1 @@ +*_test diff --git a/libs/mangle/vfs/tests/Makefile b/libs/mangle/vfs/tests/Makefile new file mode 100644 index 000000000..a5d1d1712 --- /dev/null +++ b/libs/mangle/vfs/tests/Makefile @@ -0,0 +1,25 @@ +GCC=g++ -I../ -Wall + +all: dummy_test ogre_client_test ogre_resource_test ogre_server_test physfs_server_test + +I_OGRE=$(shell pkg-config --cflags OGRE) +L_OGRE=$(shell pkg-config --libs OGRE) +L_PHYSFS=-lphysfs + +ogre_client_test: ogre_client_test.cpp dummy_vfs.cpp ../vfs.hpp ../clients/ogre_archive.hpp ../clients/ogre_archive.cpp + $(GCC) $< ../clients/ogre_archive.cpp -o $@ $(I_OGRE) $(L_OGRE) + +ogre_resource_test: ogre_resource_test.cpp + $(GCC) $< -o $@ $(I_OGRE) $(L_OGRE) + +ogre_server_test: ogre_server_test.cpp ../vfs.hpp ../servers/ogre_vfs.hpp ../servers/ogre_vfs.cpp + $(GCC) $< -o $@ $(I_OGRE) $(L_OGRE) ../servers/ogre_vfs.cpp + +physfs_server_test: physfs_server_test.cpp ../vfs.hpp ../servers/physfs_vfs.hpp + $(GCC) $< -o $@ $(L_PHYSFS) + +dummy_test: dummy_test.cpp dummy_vfs.cpp ../vfs.hpp + $(GCC) $< -o $@ + +clean: + rm *_test diff --git a/libs/mangle/vfs/tests/dummy_test.cpp b/libs/mangle/vfs/tests/dummy_test.cpp new file mode 100644 index 000000000..541010c6a --- /dev/null +++ b/libs/mangle/vfs/tests/dummy_test.cpp @@ -0,0 +1,42 @@ +#include "dummy_vfs.cpp" + +#include +#include + +using namespace std; + +void print(FileInfo &inf) +{ + cout << "name: " << inf.name << endl; + cout << "basename: " << inf.basename << endl; + cout << "isDir: " << inf.isDir << endl; + cout << "size: " << inf.size << endl; + cout << "time: " << inf.time << endl; +} +void print(FileInfoPtr inf) { print(*inf); } + +void print(FileInfoList &lst) +{ + for(unsigned i=0; isize() << endl; + + return 0; +} diff --git a/libs/mangle/vfs/tests/dummy_vfs.cpp b/libs/mangle/vfs/tests/dummy_vfs.cpp new file mode 100644 index 000000000..0448e96a5 --- /dev/null +++ b/libs/mangle/vfs/tests/dummy_vfs.cpp @@ -0,0 +1,117 @@ +// This file is shared between several test programs +#include "vfs.hpp" +#include +#include + +#include "../../stream/servers/memory_stream.hpp" + +using namespace Mangle::VFS; +using namespace Mangle::Stream; + +class DummyVFS : public VFS +{ +public: + DummyVFS() + { + hasFind = false; + hasList = true; + isCaseSensitive = true; + } + + // We only support opening 'file1' at the moment. + StreamPtr open(const std::string &name) + { + assert(name == "file1"); + return StreamPtr(new MemoryStream("hello world", 11)); + } + + bool isFile(const std::string &name) const + { + return (name == "file1" || + name == "dir/file2"); + } + + bool isDir(const std::string &name) const + { + return name == "dir"; + } + + /// Get info about a single file + FileInfoPtr stat(const std::string &name) const + { + FileInfoPtr fi(new FileInfo); + fi->name = name; + fi->time = 0; + + if(isFile(name)) + { + if(name == "dir/file2") + { + fi->basename = "file2"; + fi->size = 2; + } + else + { + fi->basename = "file1"; + fi->size = 1; + } + fi->isDir = false; + } + else if(isDir(name)) + { + fi->basename = "dir"; + fi->isDir = true; + fi->size = 0; + } + else assert(0); + + return fi; + } + + /// List all entries in a given directory. A blank dir should be + /// interpreted as a the root/current directory of the archive. If + /// dirs is true, list directories instead of files. + virtual FileInfoListPtr list(const std::string& dir = "", + bool recurse=true, + bool dirs=false) const + { + assert(dir == ""); + + FileInfoListPtr fl(new FileInfoList); + + FileInfo fi; + + if(!dirs) + { + fi.name = "file1"; + fi.basename = "file1"; + fi.isDir = false; + fi.size = 1; + fi.time = 0; + fl->push_back(fi); + + if(recurse) + { + fi.name = "dir/file2"; + fi.basename = "file2"; + fi.size = 2; + fl->push_back(fi); + } + } + else + { + fi.name = "dir"; + fi.basename = "dir"; + fi.isDir = true; + fi.size = 0; + fi.time = 0; + fl->push_back(fi); + } + return fl; + } + + FileInfoListPtr find(const std::string& pattern, + bool recursive=true, + bool dirs=false) const + { assert(0); } +}; diff --git a/libs/mangle/vfs/tests/ogre_client_test.cpp b/libs/mangle/vfs/tests/ogre_client_test.cpp new file mode 100644 index 000000000..8e1269b56 --- /dev/null +++ b/libs/mangle/vfs/tests/ogre_client_test.cpp @@ -0,0 +1,39 @@ +#include "dummy_vfs.cpp" +#include "../clients/ogre_archive.hpp" +#include + +using namespace Ogre; +using namespace std; + +void print(StringVectorPtr lst) +{ + int s = lst->size(); + + for(int i=0; isize() << endl; + cout << "contents: " << file->getAsString() << endl; + + return 0; +} diff --git a/libs/mangle/vfs/tests/ogre_resource_test.cpp b/libs/mangle/vfs/tests/ogre_resource_test.cpp new file mode 100644 index 000000000..8965adaa1 --- /dev/null +++ b/libs/mangle/vfs/tests/ogre_resource_test.cpp @@ -0,0 +1,61 @@ +#include +#include +#include + +/* + This isn't really a test of our implementation, but a test of using + the Ogre resource system to find files. If the Ogre interface + changes and you have to change this test, you will have to change + the servers/ogre_vfs.cpp implementation equivalently. + + */ + +using namespace std; +using namespace Ogre; + +ResourceGroupManager *gm; +String group; + +void find(const std::string &fileName) +{ + cout << "\nFile: " << fileName << endl; + + if(!gm->resourceExists(group, fileName)) + { + cout << "Does not exist\n"; + return; + } + + DataStreamPtr data = gm->openResource(fileName, group); + + cout << "Size: " << data->size() << endl; + cout << "First line: " << data->getLine() << "\n"; +} + +int main() +{ + // Disable logging + new LogManager; + Log *log = LogManager::getSingleton().createLog(""); + log->setDebugOutputEnabled(false); + + // Set up Ogre + Root root("","",""); + + root.addResourceLocation("./", "FileSystem", "General"); + + gm = ResourceGroupManager::getSingletonPtr(); + group = gm->getWorldResourceGroupName(); + + find("Makefile"); + find("ogre_resource_test.cpp"); + find("bleh"); + + cout << "\nAll source files:\n"; + FileInfoListPtr list = gm->findResourceFileInfo(group, "*.cpp"); + FileInfoList::iterator it, end; + it = list->begin(); + end = list->end(); + for(; it != end; it++) + cout << " " << it->filename << endl; +} diff --git a/libs/mangle/vfs/tests/ogre_server_test.cpp b/libs/mangle/vfs/tests/ogre_server_test.cpp new file mode 100644 index 000000000..b846eef96 --- /dev/null +++ b/libs/mangle/vfs/tests/ogre_server_test.cpp @@ -0,0 +1,38 @@ +#include "../servers/ogre_vfs.hpp" + +#include + +#include "server_common.cpp" + +Ogre::Root *root; + +void setupOgre() +{ + using namespace Ogre; + + // Disable logging + new LogManager; + Log *log = LogManager::getSingleton().createLog(""); + log->setDebugOutputEnabled(false); + + // Set up Root + root = new Root("","",""); + + // Add a zip file and the current directory + root->addResourceLocation("test.zip", "Zip", "General"); + root->addResourceLocation("./", "FileSystem", "General"); +} + +int main() +{ + // Set up the engine + setupOgre(); + + // This is our entry point into the resource file system + OgreVFS vfs("General"); + + // Run the test + testAll(vfs); + + return 0; +} diff --git a/libs/mangle/vfs/tests/output/dummy_test.out b/libs/mangle/vfs/tests/output/dummy_test.out new file mode 100644 index 000000000..61f1fda80 --- /dev/null +++ b/libs/mangle/vfs/tests/output/dummy_test.out @@ -0,0 +1,31 @@ +Listing all files: +name: file1 +basename: file1 +isDir: 0 +size: 1 +time: 0 +name: dir/file2 +basename: file2 +isDir: 0 +size: 2 +time: 0 + +Stat for single files: +name: file1 +basename: file1 +isDir: 0 +size: 1 +time: 0 + +name: dir/file2 +basename: file2 +isDir: 0 +size: 2 +time: 0 + +name: dir +basename: dir +isDir: 1 +size: 0 +time: 0 +filesize: 11 diff --git a/libs/mangle/vfs/tests/output/ogre_client_test.out b/libs/mangle/vfs/tests/output/ogre_client_test.out new file mode 100644 index 000000000..bc10b1e5c --- /dev/null +++ b/libs/mangle/vfs/tests/output/ogre_client_test.out @@ -0,0 +1,12 @@ +Case: 1 +Name: dummy +Type: Mangle +All files: + file1 + dir/file2 +Non-recursive: + file1 +Dirs: + dir +filesize: 11 +contents: hello world diff --git a/libs/mangle/vfs/tests/output/ogre_resource_test.out b/libs/mangle/vfs/tests/output/ogre_resource_test.out new file mode 100644 index 000000000..9bfc4ff5b --- /dev/null +++ b/libs/mangle/vfs/tests/output/ogre_resource_test.out @@ -0,0 +1,20 @@ + +File: Makefile +Size: 828 +First line: GCC=g++ -I../ + +File: ogre_resource_test.cpp +Size: 1437 +First line: #include + +File: bleh +Does not exist + +All source files: + physfs_server_test.cpp + dummy_test.cpp + ogre_resource_test.cpp + server_common.cpp + dummy_vfs.cpp + ogre_client_test.cpp + ogre_server_test.cpp diff --git a/libs/mangle/vfs/tests/output/ogre_server_test.out b/libs/mangle/vfs/tests/output/ogre_server_test.out new file mode 100644 index 000000000..74f350844 --- /dev/null +++ b/libs/mangle/vfs/tests/output/ogre_server_test.out @@ -0,0 +1,11 @@ + +File: Makefile +Size: 828 +First 12 bytes: GCC=g++ -I.. + +File: testfile.txt +Size: 13 +First 12 bytes: hello world! + +File: blah_bleh +File doesn't exist diff --git a/libs/mangle/vfs/tests/output/physfs_server_test.out b/libs/mangle/vfs/tests/output/physfs_server_test.out new file mode 100644 index 000000000..74f350844 --- /dev/null +++ b/libs/mangle/vfs/tests/output/physfs_server_test.out @@ -0,0 +1,11 @@ + +File: Makefile +Size: 828 +First 12 bytes: GCC=g++ -I.. + +File: testfile.txt +Size: 13 +First 12 bytes: hello world! + +File: blah_bleh +File doesn't exist diff --git a/libs/mangle/vfs/tests/physfs_server_test.cpp b/libs/mangle/vfs/tests/physfs_server_test.cpp new file mode 100644 index 000000000..9e9d088cd --- /dev/null +++ b/libs/mangle/vfs/tests/physfs_server_test.cpp @@ -0,0 +1,21 @@ +#include "../servers/physfs_vfs.hpp" + +#include "server_common.cpp" + +#include + +int main() +{ + // Set up the library and paths + PHYSFS_init("blah"); + PHYSFS_addToSearchPath("test.zip", 1); + PHYSFS_addToSearchPath("./", 1); + + // Create our interface + PhysVFS vfs; + + // Run the test + testAll(vfs); + + return 0; +} diff --git a/libs/mangle/vfs/tests/server_common.cpp b/libs/mangle/vfs/tests/server_common.cpp new file mode 100644 index 000000000..1834bc25a --- /dev/null +++ b/libs/mangle/vfs/tests/server_common.cpp @@ -0,0 +1,33 @@ +#include + +using namespace Mangle::VFS; +using namespace Mangle::Stream; +using namespace std; + +void find(VFS &vfs, const std::string &file) +{ + cout << "\nFile: " << file << endl; + + if(!vfs.isFile(file)) + { + cout << "File doesn't exist\n"; + return; + } + + StreamPtr data = vfs.open(file); + + cout << "Size: " << data->size() << endl; + + char buf[13]; + buf[12] = 0; + data->read(buf, 12); + + cout << "First 12 bytes: " << buf << "\n"; +} + +void testAll(VFS &vfs) +{ + find(vfs, "Makefile"); // From the file system + find(vfs, "testfile.txt"); // From the zip + find(vfs, "blah_bleh"); // Doesn't exist +} diff --git a/libs/mangle/vfs/tests/test.sh b/libs/mangle/vfs/tests/test.sh new file mode 100755 index 000000000..2d07708ad --- /dev/null +++ b/libs/mangle/vfs/tests/test.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +make || exit + +mkdir -p output + +PROGS=*_test + +for a in $PROGS; do + if [ -f "output/$a.out" ]; then + echo "Running $a:" + ./$a | diff output/$a.out - + else + echo "Creating $a.out" + ./$a > "output/$a.out" + git add "output/$a.out" + fi +done diff --git a/libs/mangle/vfs/tests/test.zip b/libs/mangle/vfs/tests/test.zip new file mode 100644 index 000000000..ec82f8bc6 Binary files /dev/null and b/libs/mangle/vfs/tests/test.zip differ diff --git a/libs/mangle/vfs/vfs.hpp b/libs/mangle/vfs/vfs.hpp new file mode 100644 index 000000000..258a6b0a0 --- /dev/null +++ b/libs/mangle/vfs/vfs.hpp @@ -0,0 +1,87 @@ +#ifndef MANGLE_VFS_H +#define MANGLE_VFS_H + +#include "../stream/stream.hpp" +#include +#include + +namespace Mangle { +namespace VFS { + +/// Generic file info structure +struct FileInfo +{ + /// Full name, including path + std::string name; + + /// Base name, not including path + std::string basename; + + /// Is this a directory? + bool isDir; + + /// File size + size_t size; + + /// Last modification date + time_t time; +}; + +typedef std::vector FileInfoList; + +typedef boost::shared_ptr FileInfoPtr; +typedef boost::shared_ptr FileInfoListPtr; + +/** An interface to any file system or other provider of named data + streams +*/ +class VFS +{ + public: + // Feature options. These should be set in the constructor. + + /// If true, the list() function work + bool hasList; + + /// If true, the find() function work + bool hasFind; + + /// If true, the file system is case sensitive + bool isCaseSensitive; + + /// Virtual destructor + virtual ~VFS() {} + + /// Open a new data stream. Deleting the object (letting all the + /// pointers to it go out of scope) should be enough to close it. + virtual Stream::StreamPtr open(const std::string &name) = 0; + + /// Check for the existence of a file + virtual bool isFile(const std::string &name) const = 0; + + /// Check for the existence of a directory + virtual bool isDir(const std::string &name) const = 0; + + /// Get info about a single file + virtual FileInfoPtr stat(const std::string &name) const = 0; + + /// List all entries in a given directory. A blank dir should be + /// interpreted as a the root/current directory of the archive. If + /// dirs is true, list directories instead of files. + virtual FileInfoListPtr list(const std::string& dir = "", + bool recurse=true, + bool dirs=false) const = 0; + + /// Find files after a given pattern. Wildcards (*) are + /// supported. Only valid if 'hasFind' is true. Don't implement your + /// own pattern matching here if the backend doesn't support it + /// natively; use a filter instead. + virtual FileInfoListPtr find(const std::string& pattern, + bool recursive=true, + bool dirs=false) const = 0; +}; + +typedef boost::shared_ptr VFSPtr; + +}} // namespaces +#endif diff --git a/libs/openengine b/libs/openengine deleted file mode 160000 index 21b845645..000000000 --- a/libs/openengine +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 21b8456453242e132c85f92047cf9bce535c1b22 diff --git a/libs/openengine/.gitignore b/libs/openengine/.gitignore new file mode 100644 index 000000000..23a5e931b --- /dev/null +++ b/libs/openengine/.gitignore @@ -0,0 +1,3 @@ +*~ +*.o +*_test diff --git a/libs/openengine/README b/libs/openengine/README new file mode 100644 index 000000000..621fe8d60 --- /dev/null +++ b/libs/openengine/README @@ -0,0 +1,12 @@ +OpenEngine README +================= + +OpenEngine is a bunch of stand-alone game engine modules collected from the OpenMW project (see http://github.com/korslund/openmw or http://openmw.com ) and from certain other projects. + +It is currently a very early work in progress, and development will follow OpenMW closely for a while forward. + +OpenEngine will depend heavily on Mangle ( http://github.com/korslund/mangle/ ) and will thus aim to be backend agnostic. When finished it should work with a variety for free and commercial middleware libraries as backends for graphics, sound, physics, input and so on. + +All questions can be directed to Nicolay Korslund at korslund@gmail.com + +- Nicolay diff --git a/libs/openengine/bullet/BtOgre.cpp b/libs/openengine/bullet/BtOgre.cpp new file mode 100644 index 000000000..618739083 --- /dev/null +++ b/libs/openengine/bullet/BtOgre.cpp @@ -0,0 +1,1090 @@ +/* + * ============================================================================================= + * + * Filename: BtOgre.cpp + * + * Description: BtOgre implementation. + * + * Version: 1.0 + * Created: 27/12/2008 01:47:56 PM + * + * Author: Nikhilesh (nikki) + * + * ============================================================================================= + */ + +#include "BtOgrePG.h" +#include "BtOgreGP.h" +#include "BtOgreExtras.h" + +using namespace Ogre; + +namespace BtOgre { + +/* + * ============================================================================================= + * BtOgre::VertexIndexToShape + * ============================================================================================= + */ + + void VertexIndexToShape::addStaticVertexData(const VertexData *vertex_data) + { + if (!vertex_data) + return; + + const VertexData *data = vertex_data; + + const unsigned int prev_size = mVertexCount; + mVertexCount += (unsigned int)data->vertexCount; + + Ogre::Vector3* tmp_vert = new Ogre::Vector3[mVertexCount]; + if (mVertexBuffer) + { + memcpy(tmp_vert, mVertexBuffer, sizeof(Vector3) * prev_size); + delete[] mVertexBuffer; + } + mVertexBuffer = tmp_vert; + + // Get the positional buffer element + { + const Ogre::VertexElement* posElem = data->vertexDeclaration->findElementBySemantic(Ogre::VES_POSITION); + Ogre::HardwareVertexBufferSharedPtr vbuf = data->vertexBufferBinding->getBuffer(posElem->getSource()); + const unsigned int vSize = (unsigned int)vbuf->getVertexSize(); + + unsigned char* vertex = static_cast(vbuf->lock(Ogre::HardwareBuffer::HBL_READ_ONLY)); + float* pReal; + Ogre::Vector3 * curVertices = &mVertexBuffer[prev_size]; + const unsigned int vertexCount = (unsigned int)data->vertexCount; + for(unsigned int j = 0; j < vertexCount; ++j) + { + posElem->baseVertexPointerToElement(vertex, &pReal); + vertex += vSize; + + curVertices->x = (*pReal++); + curVertices->y = (*pReal++); + curVertices->z = (*pReal++); + + *curVertices = mTransform * (*curVertices); + + curVertices++; + } + vbuf->unlock(); + } + } + + //------------------------------------------------------------------------------------------------ + void VertexIndexToShape::addAnimatedVertexData(const Ogre::VertexData *vertex_data, + const Ogre::VertexData *blend_data, + const Ogre::Mesh::IndexMap *indexMap) + { + // Get the bone index element + assert(vertex_data); + + const VertexData *data = blend_data; + const unsigned int prev_size = mVertexCount; + mVertexCount += (unsigned int)data->vertexCount; + Ogre::Vector3* tmp_vert = new Ogre::Vector3[mVertexCount]; + if (mVertexBuffer) + { + memcpy(tmp_vert, mVertexBuffer, sizeof(Vector3) * prev_size); + delete[] mVertexBuffer; + } + mVertexBuffer = tmp_vert; + + // Get the positional buffer element + { + const Ogre::VertexElement* posElem = data->vertexDeclaration->findElementBySemantic(Ogre::VES_POSITION); + assert (posElem); + Ogre::HardwareVertexBufferSharedPtr vbuf = data->vertexBufferBinding->getBuffer(posElem->getSource()); + const unsigned int vSize = (unsigned int)vbuf->getVertexSize(); + + unsigned char* vertex = static_cast(vbuf->lock(Ogre::HardwareBuffer::HBL_READ_ONLY)); + float* pReal; + Ogre::Vector3 * curVertices = &mVertexBuffer[prev_size]; + const unsigned int vertexCount = (unsigned int)data->vertexCount; + for(unsigned int j = 0; j < vertexCount; ++j) + { + posElem->baseVertexPointerToElement(vertex, &pReal); + vertex += vSize; + + curVertices->x = (*pReal++); + curVertices->y = (*pReal++); + curVertices->z = (*pReal++); + + *curVertices = mTransform * (*curVertices); + + curVertices++; + } + vbuf->unlock(); + } + + { + const Ogre::VertexElement* bneElem = vertex_data->vertexDeclaration->findElementBySemantic(Ogre::VES_BLEND_INDICES); + assert (bneElem); + + Ogre::HardwareVertexBufferSharedPtr vbuf = vertex_data->vertexBufferBinding->getBuffer(bneElem->getSource()); + const unsigned int vSize = (unsigned int)vbuf->getVertexSize(); + unsigned char* vertex = static_cast(vbuf->lock(Ogre::HardwareBuffer::HBL_READ_ONLY)); + + unsigned char* pBone; + + if (!mBoneIndex) + mBoneIndex = new BoneIndex(); + BoneIndex::iterator i; + + Ogre::Vector3 * curVertices = &mVertexBuffer[prev_size]; + + const unsigned int vertexCount = (unsigned int)vertex_data->vertexCount; + for(unsigned int j = 0; j < vertexCount; ++j) + { + bneElem->baseVertexPointerToElement(vertex, &pBone); + vertex += vSize; + + const unsigned char currBone = (indexMap) ? (*indexMap)[*pBone] : *pBone; + i = mBoneIndex->find (currBone); + Vector3Array* l = 0; + if (i == mBoneIndex->end()) + { + l = new Vector3Array; + mBoneIndex->insert(BoneKeyIndex(currBone, l)); + } + else + { + l = i->second; + } + + l->push_back(*curVertices); + + curVertices++; + } + vbuf->unlock(); + } + } + + //------------------------------------------------------------------------------------------------ + void VertexIndexToShape::addIndexData(IndexData *data, const unsigned int offset) + { + const unsigned int prev_size = mIndexCount; + mIndexCount += (unsigned int)data->indexCount; + + unsigned int* tmp_ind = new unsigned int[mIndexCount]; + if (mIndexBuffer) + { + memcpy (tmp_ind, mIndexBuffer, sizeof(unsigned int) * prev_size); + delete[] mIndexBuffer; + } + mIndexBuffer = tmp_ind; + + const unsigned int numTris = (unsigned int) data->indexCount / 3; + HardwareIndexBufferSharedPtr ibuf = data->indexBuffer; + const bool use32bitindexes = (ibuf->getType() == HardwareIndexBuffer::IT_32BIT); + unsigned int index_offset = prev_size; + + if (use32bitindexes) + { + const unsigned int* pInt = static_cast(ibuf->lock(HardwareBuffer::HBL_READ_ONLY)); + for(unsigned int k = 0; k < numTris; ++k) + { + mIndexBuffer[index_offset ++] = offset + *pInt++; + mIndexBuffer[index_offset ++] = offset + *pInt++; + mIndexBuffer[index_offset ++] = offset + *pInt++; + } + ibuf->unlock(); + } + else + { + const unsigned short* pShort = static_cast(ibuf->lock(HardwareBuffer::HBL_READ_ONLY)); + for(unsigned int k = 0; k < numTris; ++k) + { + mIndexBuffer[index_offset ++] = offset + static_cast (*pShort++); + mIndexBuffer[index_offset ++] = offset + static_cast (*pShort++); + mIndexBuffer[index_offset ++] = offset + static_cast (*pShort++); + } + ibuf->unlock(); + } + + } + + //------------------------------------------------------------------------------------------------ + Real VertexIndexToShape::getRadius() + { + if (mBoundRadius == (-1)) + { + getSize(); + mBoundRadius = (std::max(mBounds.x,std::max(mBounds.y,mBounds.z)) * 0.5); + } + return mBoundRadius; + } + + //------------------------------------------------------------------------------------------------ + Vector3 VertexIndexToShape::getSize() + { + const unsigned int vCount = getVertexCount(); + if (mBounds == Ogre::Vector3(-1,-1,-1) && vCount > 0) + { + + const Ogre::Vector3 * const v = getVertices(); + + Ogre::Vector3 vmin(v[0]); + Ogre::Vector3 vmax(v[0]); + + for(unsigned int j = 1; j < vCount; j++) + { + vmin.x = std::min(vmin.x, v[j].x); + vmin.y = std::min(vmin.y, v[j].y); + vmin.z = std::min(vmin.z, v[j].z); + + vmax.x = std::max(vmax.x, v[j].x); + vmax.y = std::max(vmax.y, v[j].y); + vmax.z = std::max(vmax.z, v[j].z); + } + + mBounds.x = vmax.x - vmin.x; + mBounds.y = vmax.y - vmin.y; + mBounds.z = vmax.z - vmin.z; + } + + return mBounds; + } + + //------------------------------------------------------------------------------------------------ + const Ogre::Vector3* VertexIndexToShape::getVertices() + { + return mVertexBuffer; + } + + //------------------------------------------------------------------------------------------------ + unsigned int VertexIndexToShape::getVertexCount() + { + return mVertexCount; + } + + //------------------------------------------------------------------------------------------------ + const unsigned int* VertexIndexToShape::getIndices() + { + return mIndexBuffer; + } + + //------------------------------------------------------------------------------------------------ + unsigned int VertexIndexToShape::getIndexCount() + { + return mIndexCount; + } + + //------------------------------------------------------------------------------------------------ + btSphereShape* VertexIndexToShape::createSphere() + { + const Ogre::Real rad = getRadius(); + assert((rad > 0.0) && + ("Sphere radius must be greater than zero")); + btSphereShape* shape = new btSphereShape(rad); + + shape->setLocalScaling(Convert::toBullet(mScale)); + + return shape; + } + + //------------------------------------------------------------------------------------------------ + btBoxShape* VertexIndexToShape::createBox() + { + const Ogre::Vector3 sz = getSize(); + + assert((sz.x > 0.0) && (sz.y > 0.0) && (sz.y > 0.0) && + ("Size of box must be greater than zero on all axes")); + + btBoxShape* shape = new btBoxShape(Convert::toBullet(sz * 0.5)); + + shape->setLocalScaling(Convert::toBullet(mScale)); + + return shape; + } + + //------------------------------------------------------------------------------------------------ + btCylinderShape* VertexIndexToShape::createCylinder() + { + const Ogre::Vector3 sz = getSize(); + + assert((sz.x > 0.0) && (sz.y > 0.0) && (sz.y > 0.0) && + ("Size of Cylinder must be greater than zero on all axes")); + + btCylinderShape* shape = new btCylinderShapeX(Convert::toBullet(sz * 0.5)); + + shape->setLocalScaling(Convert::toBullet(mScale)); + + return shape; + } + + //------------------------------------------------------------------------------------------------ + btConvexHullShape* VertexIndexToShape::createConvex() + { + assert(mVertexCount && (mIndexCount >= 6) && + ("Mesh must have some vertices and at least 6 indices (2 triangles)")); + + return new btConvexHullShape((btScalar*) &mVertexBuffer[0].x, mVertexCount, sizeof(Vector3)); + } + + //------------------------------------------------------------------------------------------------ + btBvhTriangleMeshShape* VertexIndexToShape::createTrimesh() + { + assert(mVertexCount && (mIndexCount >= 6) && + ("Mesh must have some vertices and at least 6 indices (2 triangles)")); + + unsigned int numFaces = mIndexCount / 3; + + btTriangleMesh *trimesh = new btTriangleMesh(); + unsigned int *indices = mIndexBuffer; + Vector3 *vertices = mVertexBuffer; + + btVector3 vertexPos[3]; + for (unsigned int n = 0; n < numFaces; ++n) + { + { + const Vector3 &vec = vertices[*indices]; + vertexPos[0][0] = vec.x; + vertexPos[0][1] = vec.y; + vertexPos[0][2] = vec.z; + } + { + const Vector3 &vec = vertices[*(indices + 1)]; + vertexPos[1][0] = vec.x; + vertexPos[1][1] = vec.y; + vertexPos[1][2] = vec.z; + } + { + const Vector3 &vec = vertices[*(indices + 2)]; + vertexPos[2][0] = vec.x; + vertexPos[2][1] = vec.y; + vertexPos[2][2] = vec.z; + } + + indices += 3; + + trimesh->addTriangle(vertexPos[0], vertexPos[1], vertexPos[2]); + } + + const bool useQuantizedAABB = true; + btBvhTriangleMeshShape *shape = new btBvhTriangleMeshShape(trimesh, useQuantizedAABB); + + shape->setLocalScaling(Convert::toBullet(mScale)); + + return shape; + } + + //------------------------------------------------------------------------------------------------ + VertexIndexToShape::~VertexIndexToShape() + { + delete[] mVertexBuffer; + delete[] mIndexBuffer; + + if (mBoneIndex) + { + for(BoneIndex::iterator i = mBoneIndex->begin(); + i != mBoneIndex->end(); + ++i) + { + delete i->second; + } + delete mBoneIndex; + } + } + + //------------------------------------------------------------------------------------------------ + VertexIndexToShape::VertexIndexToShape(const Matrix4 &transform) : + mVertexBuffer (0), + mIndexBuffer (0), + mVertexCount (0), + mIndexCount (0), + mTransform (transform), + mBoundRadius (-1), + mBounds (Vector3(-1,-1,-1)), + mBoneIndex (0), + mScale(1) + { + } + +/* + * ============================================================================================= + * BtOgre::StaticMeshToShapeConverter + * ============================================================================================= + */ + + StaticMeshToShapeConverter::StaticMeshToShapeConverter() : + VertexIndexToShape(), + mEntity (0), + mNode (0) + { + } + + //------------------------------------------------------------------------------------------------ + StaticMeshToShapeConverter::~StaticMeshToShapeConverter() + { + } + + //------------------------------------------------------------------------------------------------ + StaticMeshToShapeConverter::StaticMeshToShapeConverter(Entity *entity, const Matrix4 &transform) : + VertexIndexToShape(transform), + mEntity (0), + mNode (0) + { + addEntity(entity, transform); + } + + //------------------------------------------------------------------------------------------------ + StaticMeshToShapeConverter::StaticMeshToShapeConverter(Renderable *rend, const Matrix4 &transform) : + VertexIndexToShape(transform), + mEntity (0), + mNode (0) + { + RenderOperation op; + rend->getRenderOperation(op); + VertexIndexToShape::addStaticVertexData(op.vertexData); + if(op.useIndexes) + VertexIndexToShape::addIndexData(op.indexData); + + } + + //------------------------------------------------------------------------------------------------ + void StaticMeshToShapeConverter::addEntity(Entity *entity,const Matrix4 &transform) + { + // Each entity added need to reset size and radius + // next time getRadius and getSize are asked, they're computed. + mBounds = Ogre::Vector3(-1,-1,-1); + mBoundRadius = -1; + + mEntity = entity; + mNode = (SceneNode*)(mEntity->getParentNode()); + mTransform = transform; + mScale = mNode->getScale(); + + if (mEntity->getMesh()->sharedVertexData) + { + VertexIndexToShape::addStaticVertexData (mEntity->getMesh()->sharedVertexData); + } + + for (unsigned int i = 0;i < mEntity->getNumSubEntities();++i) + { + SubMesh *sub_mesh = mEntity->getSubEntity(i)->getSubMesh(); + + if (!sub_mesh->useSharedVertices) + { + VertexIndexToShape::addIndexData(sub_mesh->indexData, mVertexCount); + VertexIndexToShape::addStaticVertexData (sub_mesh->vertexData); + } + else + { + VertexIndexToShape::addIndexData (sub_mesh->indexData); + } + + } + } + + //------------------------------------------------------------------------------------------------ + void StaticMeshToShapeConverter::addMesh(const MeshPtr &mesh, const Matrix4 &transform) + { + // Each entity added need to reset size and radius + // next time getRadius and getSize are asked, they're computed. + mBounds = Ogre::Vector3(-1,-1,-1); + mBoundRadius = -1; + + //_entity = entity; + //_node = (SceneNode*)(_entity->getParentNode()); + mTransform = transform; + + if (mesh->hasSkeleton ()) + Ogre::LogManager::getSingleton().logMessage("MeshToShapeConverter::addMesh : Mesh " + mesh->getName () + " as skeleton but added to trimesh non animated"); + + if (mesh->sharedVertexData) + { + VertexIndexToShape::addStaticVertexData (mesh->sharedVertexData); + } + + for(unsigned int i = 0;i < mesh->getNumSubMeshes();++i) + { + SubMesh *sub_mesh = mesh->getSubMesh(i); + + if (!sub_mesh->useSharedVertices) + { + VertexIndexToShape::addIndexData(sub_mesh->indexData, mVertexCount); + VertexIndexToShape::addStaticVertexData (sub_mesh->vertexData); + } + else + { + VertexIndexToShape::addIndexData (sub_mesh->indexData); + } + + } + } + +/* + * ============================================================================================= + * BtOgre::AnimatedMeshToShapeConverter + * ============================================================================================= + */ + + AnimatedMeshToShapeConverter::AnimatedMeshToShapeConverter(Entity *entity,const Matrix4 &transform) : + VertexIndexToShape(transform), + mEntity (0), + mNode (0), + mTransformedVerticesTemp(0), + mTransformedVerticesTempSize(0) + { + addEntity(entity, transform); + } + + //------------------------------------------------------------------------------------------------ + AnimatedMeshToShapeConverter::AnimatedMeshToShapeConverter() : + VertexIndexToShape(), + mEntity (0), + mNode (0), + mTransformedVerticesTemp(0), + mTransformedVerticesTempSize(0) + { + } + + //------------------------------------------------------------------------------------------------ + AnimatedMeshToShapeConverter::~AnimatedMeshToShapeConverter() + { + delete[] mTransformedVerticesTemp; + } + + //------------------------------------------------------------------------------------------------ + void AnimatedMeshToShapeConverter::addEntity(Entity *entity,const Matrix4 &transform) + { + // Each entity added need to reset size and radius + // next time getRadius and getSize are asked, they're computed. + mBounds = Ogre::Vector3(-1,-1,-1); + mBoundRadius = -1; + + mEntity = entity; + mNode = (SceneNode*)(mEntity->getParentNode()); + mTransform = transform; + + assert (entity->getMesh()->hasSkeleton ()); + + mEntity->addSoftwareAnimationRequest(false); + mEntity->_updateAnimation(); + + if (mEntity->getMesh()->sharedVertexData) + { + VertexIndexToShape::addAnimatedVertexData (mEntity->getMesh()->sharedVertexData, + mEntity->_getSkelAnimVertexData(), + &mEntity->getMesh()->sharedBlendIndexToBoneIndexMap); + } + + for (unsigned int i = 0;i < mEntity->getNumSubEntities();++i) + { + SubMesh *sub_mesh = mEntity->getSubEntity(i)->getSubMesh(); + + if (!sub_mesh->useSharedVertices) + { + VertexIndexToShape::addIndexData(sub_mesh->indexData, mVertexCount); + + VertexIndexToShape::addAnimatedVertexData (sub_mesh->vertexData, + mEntity->getSubEntity(i)->_getSkelAnimVertexData(), + &sub_mesh->blendIndexToBoneIndexMap); + } + else + { + VertexIndexToShape::addIndexData (sub_mesh->indexData); + } + + } + + mEntity->removeSoftwareAnimationRequest(false); + } + + //------------------------------------------------------------------------------------------------ + void AnimatedMeshToShapeConverter::addMesh(const MeshPtr &mesh, const Matrix4 &transform) + { + // Each entity added need to reset size and radius + // next time getRadius and getSize are asked, they're computed. + mBounds = Ogre::Vector3(-1,-1,-1); + mBoundRadius = -1; + + //_entity = entity; + //_node = (SceneNode*)(_entity->getParentNode()); + mTransform = transform; + + assert (mesh->hasSkeleton ()); + + if (mesh->sharedVertexData) + { + VertexIndexToShape::addAnimatedVertexData (mesh->sharedVertexData, + 0, + &mesh->sharedBlendIndexToBoneIndexMap); + } + + for(unsigned int i = 0;i < mesh->getNumSubMeshes();++i) + { + SubMesh *sub_mesh = mesh->getSubMesh(i); + + if (!sub_mesh->useSharedVertices) + { + VertexIndexToShape::addIndexData(sub_mesh->indexData, mVertexCount); + + VertexIndexToShape::addAnimatedVertexData (sub_mesh->vertexData, + 0, + &sub_mesh->blendIndexToBoneIndexMap); + } + else + { + VertexIndexToShape::addIndexData (sub_mesh->indexData); + } + + } + } + + //------------------------------------------------------------------------------------------------ + bool AnimatedMeshToShapeConverter::getBoneVertices(unsigned char bone, + unsigned int &vertex_count, + Ogre::Vector3* &vertices, + const Vector3 &bonePosition) + { + BoneIndex::iterator i = mBoneIndex->find(bone); + + if (i == mBoneIndex->end()) + return false; + + if (i->second->empty()) + return false; + + vertex_count = (unsigned int) i->second->size() + 1; + if (vertex_count > mTransformedVerticesTempSize) + { + if (mTransformedVerticesTemp) + delete[] mTransformedVerticesTemp; + + mTransformedVerticesTemp = new Ogre::Vector3[vertex_count]; + + } + + vertices = mTransformedVerticesTemp; + vertices[0] = bonePosition; + //mEntity->_getParentNodeFullTransform() * + //mEntity->getSkeleton()->getBone(bone)->_getDerivedPosition(); + + //mEntity->getSkeleton()->getBone(bone)->_getDerivedOrientation() + unsigned int currBoneVertex = 1; + Vector3Array::iterator j = i->second->begin(); + while(j != i->second->end()) + { + vertices[currBoneVertex] = (*j); + ++j; + ++currBoneVertex; + } + return true; + } + + //------------------------------------------------------------------------------------------------ + btBoxShape* AnimatedMeshToShapeConverter::createAlignedBox(unsigned char bone, + const Vector3 &bonePosition, + const Quaternion &boneOrientation) + { + unsigned int vertex_count; + Vector3* vertices; + + if (!getBoneVertices(bone, vertex_count, vertices, bonePosition)) + return 0; + + Vector3 min_vec(vertices[0]); + Vector3 max_vec(vertices[0]); + + for(unsigned int j = 1; j < vertex_count ;j++) + { + min_vec.x = std::min(min_vec.x,vertices[j].x); + min_vec.y = std::min(min_vec.y,vertices[j].y); + min_vec.z = std::min(min_vec.z,vertices[j].z); + + max_vec.x = std::max(max_vec.x,vertices[j].x); + max_vec.y = std::max(max_vec.y,vertices[j].y); + max_vec.z = std::max(max_vec.z,vertices[j].z); + } + const Ogre::Vector3 maxMinusMin(max_vec - min_vec); + btBoxShape* box = new btBoxShape(Convert::toBullet(maxMinusMin)); + + /*const Ogre::Vector3 pos + (min_vec.x + (maxMinusMin.x * 0.5), + min_vec.y + (maxMinusMin.y * 0.5), + min_vec.z + (maxMinusMin.z * 0.5));*/ + + //box->setPosition(pos); + + return box; + } + + //------------------------------------------------------------------------------------------------ + bool AnimatedMeshToShapeConverter::getOrientedBox(unsigned char bone, + const Vector3 &bonePosition, + const Quaternion &boneOrientation, + Vector3 &box_afExtent, + Vector3 *box_akAxis, + Vector3 &box_kCenter) + { + unsigned int vertex_count; + Vector3* vertices; + + if (!getBoneVertices(bone, vertex_count, vertices, bonePosition)) + return false; + + box_kCenter = Vector3::ZERO; + + { + for(unsigned int c = 0 ;c < vertex_count;c++) + { + box_kCenter += vertices[c]; + } + const Ogre::Real invVertexCount = 1.0 / vertex_count; + box_kCenter *= invVertexCount; + } + Quaternion orient = boneOrientation; + orient.ToAxes(box_akAxis); + + // Let C be the box center and let U0, U1, and U2 be the box axes. Each + // input point is of the form X = C + y0*U0 + y1*U1 + y2*U2. The + // following code computes min(y0), max(y0), min(y1), max(y1), min(y2), + // and max(y2). The box center is then adjusted to be + // C' = C + 0.5*(min(y0)+max(y0))*U0 + 0.5*(min(y1)+max(y1))*U1 + + // 0.5*(min(y2)+max(y2))*U2 + + Ogre::Vector3 kDiff (vertices[1] - box_kCenter); + Ogre::Real fY0Min = kDiff.dotProduct(box_akAxis[0]), fY0Max = fY0Min; + Ogre::Real fY1Min = kDiff.dotProduct(box_akAxis[1]), fY1Max = fY1Min; + Ogre::Real fY2Min = kDiff.dotProduct(box_akAxis[2]), fY2Max = fY2Min; + + for (unsigned int i = 2; i < vertex_count; i++) + { + kDiff = vertices[i] - box_kCenter; + + const Ogre::Real fY0 = kDiff.dotProduct(box_akAxis[0]); + if ( fY0 < fY0Min ) + fY0Min = fY0; + else if ( fY0 > fY0Max ) + fY0Max = fY0; + + const Ogre::Real fY1 = kDiff.dotProduct(box_akAxis[1]); + if ( fY1 < fY1Min ) + fY1Min = fY1; + else if ( fY1 > fY1Max ) + fY1Max = fY1; + + const Ogre::Real fY2 = kDiff.dotProduct(box_akAxis[2]); + if ( fY2 < fY2Min ) + fY2Min = fY2; + else if ( fY2 > fY2Max ) + fY2Max = fY2; + } + + box_afExtent.x = ((Real)0.5)*(fY0Max - fY0Min); + box_afExtent.y = ((Real)0.5)*(fY1Max - fY1Min); + box_afExtent.z = ((Real)0.5)*(fY2Max - fY2Min); + + box_kCenter += (0.5*(fY0Max+fY0Min))*box_akAxis[0] + + (0.5*(fY1Max+fY1Min))*box_akAxis[1] + + (0.5*(fY2Max+fY2Min))*box_akAxis[2]; + + box_afExtent *= 2.0; + + return true; + } + + //------------------------------------------------------------------------------------------------ + btBoxShape *AnimatedMeshToShapeConverter::createOrientedBox(unsigned char bone, + const Vector3 &bonePosition, + const Quaternion &boneOrientation) + { + Ogre::Vector3 box_akAxis[3]; + Ogre::Vector3 box_afExtent; + Ogre::Vector3 box_afCenter; + + if (!getOrientedBox(bone, bonePosition, boneOrientation, + box_afExtent, + box_akAxis, + box_afCenter)) + return 0; + + btBoxShape *geom = new btBoxShape(Convert::toBullet(box_afExtent)); + //geom->setOrientation(Quaternion(box_akAxis[0],box_akAxis[1],box_akAxis[2])); + //geom->setPosition(box_afCenter); + return geom; + } + +/* + * ============================================================================================= + * BtOgre::DynamicRenderable + * ============================================================================================= + */ + + DynamicRenderable::DynamicRenderable() + { + } + + //------------------------------------------------------------------------------------------------ + DynamicRenderable::~DynamicRenderable() + { + delete mRenderOp.vertexData; + delete mRenderOp.indexData; + } + + //------------------------------------------------------------------------------------------------ + void DynamicRenderable::initialize(RenderOperation::OperationType operationType, + bool useIndices) + { + // Initialize render operation + mRenderOp.operationType = operationType; + mRenderOp.useIndexes = useIndices; + mRenderOp.vertexData = new VertexData; + if (mRenderOp.useIndexes) + mRenderOp.indexData = new IndexData; + + // Reset buffer capacities + mVertexBufferCapacity = 0; + mIndexBufferCapacity = 0; + + // Create vertex declaration + createVertexDeclaration(); + } + + //------------------------------------------------------------------------------------------------ + void DynamicRenderable::prepareHardwareBuffers(size_t vertexCount, + size_t indexCount) + { + // Prepare vertex buffer + size_t newVertCapacity = mVertexBufferCapacity; + if ((vertexCount > mVertexBufferCapacity) || + (!mVertexBufferCapacity)) + { + // vertexCount exceeds current capacity! + // It is necessary to reallocate the buffer. + + // Check if this is the first call + if (!newVertCapacity) + newVertCapacity = 1; + + // Make capacity the next power of two + while (newVertCapacity < vertexCount) + newVertCapacity <<= 1; + } + else if (vertexCount < mVertexBufferCapacity>>1) { + // Make capacity the previous power of two + while (vertexCount < newVertCapacity>>1) + newVertCapacity >>= 1; + } + if (newVertCapacity != mVertexBufferCapacity) + { + mVertexBufferCapacity = newVertCapacity; + // Create new vertex buffer + HardwareVertexBufferSharedPtr vbuf = + HardwareBufferManager::getSingleton().createVertexBuffer( + mRenderOp.vertexData->vertexDeclaration->getVertexSize(0), + mVertexBufferCapacity, + HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY); // TODO: Custom HBU_? + + // Bind buffer + mRenderOp.vertexData->vertexBufferBinding->setBinding(0, vbuf); + } + // Update vertex count in the render operation + mRenderOp.vertexData->vertexCount = vertexCount; + + if (mRenderOp.useIndexes) + { + OgreAssert(indexCount <= std::numeric_limits::max(), "indexCount exceeds 16 bit"); + + size_t newIndexCapacity = mIndexBufferCapacity; + // Prepare index buffer + if ((indexCount > newIndexCapacity) || + (!newIndexCapacity)) + { + // indexCount exceeds current capacity! + // It is necessary to reallocate the buffer. + + // Check if this is the first call + if (!newIndexCapacity) + newIndexCapacity = 1; + + // Make capacity the next power of two + while (newIndexCapacity < indexCount) + newIndexCapacity <<= 1; + + } + else if (indexCount < newIndexCapacity>>1) + { + // Make capacity the previous power of two + while (indexCount < newIndexCapacity>>1) + newIndexCapacity >>= 1; + } + + if (newIndexCapacity != mIndexBufferCapacity) + { + mIndexBufferCapacity = newIndexCapacity; + // Create new index buffer + mRenderOp.indexData->indexBuffer = + HardwareBufferManager::getSingleton().createIndexBuffer( + HardwareIndexBuffer::IT_16BIT, + mIndexBufferCapacity, + HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY); // TODO: Custom HBU_? + } + + // Update index count in the render operation + mRenderOp.indexData->indexCount = indexCount; + } + } + + //------------------------------------------------------------------------------------------------ + Real DynamicRenderable::getBoundingRadius(void) const + { + return Math::Sqrt(std::max(mBox.getMaximum().squaredLength(), mBox.getMinimum().squaredLength())); + } + + //------------------------------------------------------------------------------------------------ + Real DynamicRenderable::getSquaredViewDepth(const Camera* cam) const + { + Vector3 vMin, vMax, vMid, vDist; + vMin = mBox.getMinimum(); + vMax = mBox.getMaximum(); + vMid = ((vMax - vMin) * 0.5) + vMin; + vDist = cam->getDerivedPosition() - vMid; + + return vDist.squaredLength(); + } + +/* + * ============================================================================================= + * BtOgre::DynamicLines + * ============================================================================================= + */ + + enum { + POSITION_BINDING, + TEXCOORD_BINDING + }; + + //------------------------------------------------------------------------------------------------ + DynamicLines::DynamicLines(OperationType opType) + { + initialize(opType,false); + setMaterial("BaseWhiteNoLighting"); + mDirty = true; + } + + //------------------------------------------------------------------------------------------------ + DynamicLines::~DynamicLines() + { + } + + //------------------------------------------------------------------------------------------------ + void DynamicLines::setOperationType(OperationType opType) + { + mRenderOp.operationType = opType; + } + + //------------------------------------------------------------------------------------------------ + RenderOperation::OperationType DynamicLines::getOperationType() const + { + return mRenderOp.operationType; + } + + //------------------------------------------------------------------------------------------------ + void DynamicLines::addPoint(const Vector3 &p) + { + mPoints.push_back(p); + mDirty = true; + } + + //------------------------------------------------------------------------------------------------ + void DynamicLines::addPoint(Real x, Real y, Real z) + { + mPoints.push_back(Vector3(x,y,z)); + mDirty = true; + } + + //------------------------------------------------------------------------------------------------ + const Vector3& DynamicLines::getPoint(unsigned short index) const + { + assert(index < mPoints.size() && "Point index is out of bounds!!"); + return mPoints[index]; + } + + //------------------------------------------------------------------------------------------------ + unsigned short DynamicLines::getNumPoints(void) const + { + return (unsigned short)mPoints.size(); + } + + //------------------------------------------------------------------------------------------------ + void DynamicLines::setPoint(unsigned short index, const Vector3 &value) + { + assert(index < mPoints.size() && "Point index is out of bounds!!"); + + mPoints[index] = value; + mDirty = true; + } + + //------------------------------------------------------------------------------------------------ + void DynamicLines::clear() + { + mPoints.clear(); + mDirty = true; + } + + //------------------------------------------------------------------------------------------------ + void DynamicLines::update() + { + if (mDirty) fillHardwareBuffers(); + } + + //------------------------------------------------------------------------------------------------ + void DynamicLines::createVertexDeclaration() + { + VertexDeclaration *decl = mRenderOp.vertexData->vertexDeclaration; + decl->addElement(POSITION_BINDING, 0, VET_FLOAT3, VES_POSITION); + } + + //------------------------------------------------------------------------------------------------ + void DynamicLines::fillHardwareBuffers() + { + int size = mPoints.size(); + + prepareHardwareBuffers(size,0); + + if (!size) { + mBox.setExtents(Vector3::ZERO,Vector3::ZERO); + mDirty=false; + return; + } + + Vector3 vaabMin = mPoints[0]; + Vector3 vaabMax = mPoints[0]; + + HardwareVertexBufferSharedPtr vbuf = + mRenderOp.vertexData->vertexBufferBinding->getBuffer(0); + + Real *prPos = static_cast(vbuf->lock(HardwareBuffer::HBL_DISCARD)); + { + for(int i = 0; i < size; i++) + { + *prPos++ = mPoints[i].x; + *prPos++ = mPoints[i].y; + *prPos++ = mPoints[i].z; + + if(mPoints[i].x < vaabMin.x) + vaabMin.x = mPoints[i].x; + if(mPoints[i].y < vaabMin.y) + vaabMin.y = mPoints[i].y; + if(mPoints[i].z < vaabMin.z) + vaabMin.z = mPoints[i].z; + + if(mPoints[i].x > vaabMax.x) + vaabMax.x = mPoints[i].x; + if(mPoints[i].y > vaabMax.y) + vaabMax.y = mPoints[i].y; + if(mPoints[i].z > vaabMax.z) + vaabMax.z = mPoints[i].z; + } + } + vbuf->unlock(); + + mBox.setExtents(vaabMin, vaabMax); + + mDirty = false; + } +} diff --git a/libs/openengine/bullet/BtOgreExtras.h b/libs/openengine/bullet/BtOgreExtras.h new file mode 100644 index 000000000..f3e1aa87a --- /dev/null +++ b/libs/openengine/bullet/BtOgreExtras.h @@ -0,0 +1,280 @@ +/* + * ===================================================================================== + * + * Filename: BtOgreExtras.h + * + * Description: Contains the Ogre Mesh to Bullet Shape converters. + * + * Version: 1.0 + * Created: 27/12/2008 01:45:56 PM + * + * Author: Nikhilesh (nikki) + * + * ===================================================================================== + */ + +#ifndef _BtOgreShapes_H_ +#define _BtOgreShapes_H_ + +#include "btBulletDynamicsCommon.h" +#include "OgreSimpleRenderable.h" +#include "OgreCamera.h" +#include "OgreHardwareBufferManager.h" +#include "OgreMaterialManager.h" +#include "OgreTechnique.h" +#include "OgrePass.h" + +#include "OgreLogManager.h" + +namespace BtOgre +{ + +typedef std::vector Vector3Array; + +//Converts from and to Bullet and Ogre stuff. Pretty self-explanatory. +class Convert +{ +public: + Convert() {}; + ~Convert() {}; + + static btQuaternion toBullet(const Ogre::Quaternion &q) + { + return btQuaternion(q.x, q.y, q.z, q.w); + } + static btVector3 toBullet(const Ogre::Vector3 &v) + { + return btVector3(v.x, v.y, v.z); + } + + static Ogre::Quaternion toOgre(const btQuaternion &q) + { + return Ogre::Quaternion(q.w(), q.x(), q.y(), q.z()); + } + static Ogre::Vector3 toOgre(const btVector3 &v) + { + return Ogre::Vector3(v.x(), v.y(), v.z()); + } +}; + +//From here on its debug-drawing stuff. ------------------------------------------------------------------ + +class DynamicRenderable : public Ogre::SimpleRenderable +{ +public: + /// Constructor + DynamicRenderable(); + /// Virtual destructor + virtual ~DynamicRenderable(); + + /** Initializes the dynamic renderable. + @remarks + This function should only be called once. It initializes the + render operation, and calls the abstract function + createVertexDeclaration(). + @param operationType The type of render operation to perform. + @param useIndices Specifies whether to use indices to determine the + vertices to use as input. */ + void initialize(Ogre::RenderOperation::OperationType operationType, + bool useIndices); + + /// Implementation of Ogre::SimpleRenderable + virtual Ogre::Real getBoundingRadius(void) const; + /// Implementation of Ogre::SimpleRenderable + virtual Ogre::Real getSquaredViewDepth(const Ogre::Camera* cam) const; + +protected: + /// Maximum capacity of the currently allocated vertex buffer. + size_t mVertexBufferCapacity; + /// Maximum capacity of the currently allocated index buffer. + size_t mIndexBufferCapacity; + + /** Creates the vertex declaration. + @remarks + Override and set mRenderOp.vertexData->vertexDeclaration here. + mRenderOp.vertexData will be created for you before this method + is called. */ + virtual void createVertexDeclaration() = 0; + + /** Prepares the hardware buffers for the requested vertex and index counts. + @remarks + This function must be called before locking the buffers in + fillHardwareBuffers(). It guarantees that the hardware buffers + are large enough to hold at least the requested number of + vertices and indices (if using indices). The buffers are + possibly reallocated to achieve this. + @par + The vertex and index count in the render operation are set to + the values of vertexCount and indexCount respectively. + @param vertexCount The number of vertices the buffer must hold. + + @param indexCount The number of indices the buffer must hold. This + parameter is ignored if not using indices. */ + void prepareHardwareBuffers(size_t vertexCount, size_t indexCount); + + /** Fills the hardware vertex and index buffers with data. + @remarks + This function must call prepareHardwareBuffers() before locking + the buffers to ensure the they are large enough for the data to + be written. Afterwards the vertex and index buffers (if using + indices) can be locked, and data can be written to them. */ + virtual void fillHardwareBuffers() = 0; +}; + +class DynamicLines : public DynamicRenderable +{ + typedef Ogre::Vector3 Vector3; + typedef Ogre::Quaternion Quaternion; + typedef Ogre::Camera Camera; + typedef Ogre::Real Real; + typedef Ogre::RenderOperation::OperationType OperationType; + +public: + /// Constructor - see setOperationType() for description of argument. + DynamicLines(OperationType opType=Ogre::RenderOperation::OT_LINE_STRIP); + virtual ~DynamicLines(); + + /// Add a point to the point list + void addPoint(const Ogre::Vector3 &p); + /// Add a point to the point list + void addPoint(Real x, Real y, Real z); + + /// Change the location of an existing point in the point list + void setPoint(unsigned short index, const Vector3 &value); + + /// Return the location of an existing point in the point list + const Vector3& getPoint(unsigned short index) const; + + /// Return the total number of points in the point list + unsigned short getNumPoints(void) const; + + /// Remove all points from the point list + void clear(); + + /// Call this to update the hardware buffer after making changes. + void update(); + + /** Set the type of operation to draw with. + * @param opType Can be one of + * - RenderOperation::OT_LINE_STRIP + * - RenderOperation::OT_LINE_LIST + * - RenderOperation::OT_POINT_LIST + * - RenderOperation::OT_TRIANGLE_LIST + * - RenderOperation::OT_TRIANGLE_STRIP + * - RenderOperation::OT_TRIANGLE_FAN + * The default is OT_LINE_STRIP. + */ + void setOperationType(OperationType opType); + OperationType getOperationType() const; + +protected: + /// Implementation DynamicRenderable, creates a simple vertex-only decl + virtual void createVertexDeclaration(); + /// Implementation DynamicRenderable, pushes point list out to hardware memory + virtual void fillHardwareBuffers(); + +private: + std::vector mPoints; + bool mDirty; +}; + +class DebugDrawer : public btIDebugDraw +{ +protected: + Ogre::SceneNode *mNode; + btDynamicsWorld *mWorld; + DynamicLines *mLineDrawer; + bool mDebugOn; + +public: + + DebugDrawer(Ogre::SceneNode *node, btDynamicsWorld *world) + : mNode(node), + mWorld(world), + mDebugOn(true) + { + mLineDrawer = new DynamicLines(Ogre::RenderOperation::OT_LINE_LIST); + mNode->attachObject(mLineDrawer); + + if (!Ogre::ResourceGroupManager::getSingleton().resourceGroupExists("BtOgre")) + Ogre::ResourceGroupManager::getSingleton().createResourceGroup("BtOgre"); + if (!Ogre::MaterialManager::getSingleton().resourceExists("BtOgre/DebugLines")) + { + Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().create("BtOgre/DebugLines", "BtOgre"); + mat->setReceiveShadows(false); + mat->setSelfIllumination(1,1,1); + } + + mLineDrawer->setMaterial("BtOgre/DebugLines"); + } + + ~DebugDrawer() + { + Ogre::MaterialManager::getSingleton().remove("BtOgre/DebugLines"); + Ogre::ResourceGroupManager::getSingleton().destroyResourceGroup("BtOgre"); + delete mLineDrawer; + } + + void step() + { + if (mDebugOn) + { + mWorld->debugDrawWorld(); + mLineDrawer->update(); + mNode->needUpdate(); + mLineDrawer->clear(); + } + else + { + mLineDrawer->clear(); + mLineDrawer->update(); + mNode->needUpdate(); + } + } + + void drawLine(const btVector3& from,const btVector3& to,const btVector3& color) + { + mLineDrawer->addPoint(Convert::toOgre(from)); + mLineDrawer->addPoint(Convert::toOgre(to)); + } + + void drawContactPoint(const btVector3& PointOnB,const btVector3& normalOnB,btScalar distance,int lifeTime,const btVector3& color) + { + mLineDrawer->addPoint(Convert::toOgre(PointOnB)); + mLineDrawer->addPoint(Convert::toOgre(PointOnB) + (Convert::toOgre(normalOnB) * distance * 20)); + } + + void reportErrorWarning(const char* warningString) + { + Ogre::LogManager::getSingleton().logMessage(warningString); + } + + void draw3dText(const btVector3& location,const char* textString) + { + } + + //0 for off, anything else for on. + void setDebugMode(int isOn) + { + mDebugOn = (isOn == 0) ? false : true; + + if (!mDebugOn) + mLineDrawer->clear(); + } + + //0 for off, anything else for on. + int getDebugMode() const + { + return mDebugOn; + } + +}; + +} + +#endif + + + + + diff --git a/libs/openengine/bullet/BtOgreGP.h b/libs/openengine/bullet/BtOgreGP.h new file mode 100644 index 000000000..f0534de4b --- /dev/null +++ b/libs/openengine/bullet/BtOgreGP.h @@ -0,0 +1,142 @@ +/* + * ===================================================================================== + * + * Filename: BtOgreGP.h + * + * Description: The part of BtOgre that handles information transfer from Ogre to + * Bullet (like mesh data for making trimeshes). + * + * Version: 1.0 + * Created: 27/12/2008 03:29:56 AM + * + * Author: Nikhilesh (nikki) + * + * ===================================================================================== + */ + +#ifndef _BtOgrePG_H_ +#define _BtOgrePG_H_ + +#include "btBulletDynamicsCommon.h" +#include "BtOgreExtras.h" +#include "Ogre.h" + +namespace BtOgre { + +typedef std::map BoneIndex; +typedef std::pair BoneKeyIndex; + +class VertexIndexToShape +{ +public: + VertexIndexToShape(const Ogre::Matrix4 &transform = Ogre::Matrix4::IDENTITY); + ~VertexIndexToShape(); + + Ogre::Real getRadius(); + Ogre::Vector3 getSize(); + + + btSphereShape* createSphere(); + btBoxShape* createBox(); + btBvhTriangleMeshShape* createTrimesh(); + btCylinderShape* createCylinder(); + btConvexHullShape* createConvex(); + + const Ogre::Vector3* getVertices(); + unsigned int getVertexCount(); + const unsigned int* getIndices(); + unsigned int getIndexCount(); + +protected: + + void addStaticVertexData(const Ogre::VertexData *vertex_data); + + void addAnimatedVertexData(const Ogre::VertexData *vertex_data, + const Ogre::VertexData *blended_data, + const Ogre::Mesh::IndexMap *indexMap); + + void addIndexData(Ogre::IndexData *data, const unsigned int offset = 0); + + +protected: + Ogre::Vector3* mVertexBuffer; + unsigned int* mIndexBuffer; + unsigned int mVertexCount; + unsigned int mIndexCount; + + Ogre::Matrix4 mTransform; + + Ogre::Real mBoundRadius; + Ogre::Vector3 mBounds; + + BoneIndex *mBoneIndex; + + Ogre::Vector3 mScale; +}; + +//For static (non-animated) meshes. +class StaticMeshToShapeConverter : public VertexIndexToShape +{ +public: + + StaticMeshToShapeConverter(Ogre::Renderable *rend, const Ogre::Matrix4 &transform = Ogre::Matrix4::IDENTITY); + StaticMeshToShapeConverter(Ogre::Entity *entity, const Ogre::Matrix4 &transform = Ogre::Matrix4::IDENTITY); + StaticMeshToShapeConverter(); + + ~StaticMeshToShapeConverter(); + + void addEntity(Ogre::Entity *entity,const Ogre::Matrix4 &transform = Ogre::Matrix4::IDENTITY); + + void addMesh(const Ogre::MeshPtr &mesh, const Ogre::Matrix4 &transform = Ogre::Matrix4::IDENTITY); + + +protected: + + Ogre::Entity* mEntity; + Ogre::SceneNode* mNode; +}; + +//For animated meshes. +class AnimatedMeshToShapeConverter : public VertexIndexToShape +{ +public: + + AnimatedMeshToShapeConverter(Ogre::Entity *entity, const Ogre::Matrix4 &transform = Ogre::Matrix4::IDENTITY); + AnimatedMeshToShapeConverter(); + ~AnimatedMeshToShapeConverter(); + + void addEntity(Ogre::Entity *entity,const Ogre::Matrix4 &transform = Ogre::Matrix4::IDENTITY); + void addMesh(const Ogre::MeshPtr &mesh, const Ogre::Matrix4 &transform); + + btBoxShape* createAlignedBox(unsigned char bone, + const Ogre::Vector3 &bonePosition, + const Ogre::Quaternion &boneOrientation); + + btBoxShape* createOrientedBox(unsigned char bone, + const Ogre::Vector3 &bonePosition, + const Ogre::Quaternion &boneOrientation); + +protected: + + bool getBoneVertices(unsigned char bone, + unsigned int &vertex_count, + Ogre::Vector3* &vertices, + const Ogre::Vector3 &bonePosition); + + bool getOrientedBox(unsigned char bone, + const Ogre::Vector3 &bonePosition, + const Ogre::Quaternion &boneOrientation, + Ogre::Vector3 &extents, + Ogre::Vector3 *axis, + Ogre::Vector3 ¢er); + + Ogre::Entity* mEntity; + Ogre::SceneNode* mNode; + + Ogre::Vector3 *mTransformedVerticesTemp; + size_t mTransformedVerticesTempSize; +}; + +} + +#endif diff --git a/libs/openengine/bullet/BtOgrePG.h b/libs/openengine/bullet/BtOgrePG.h new file mode 100644 index 000000000..9ff069a8f --- /dev/null +++ b/libs/openengine/bullet/BtOgrePG.h @@ -0,0 +1,81 @@ +/* + * ===================================================================================== + * + * Filename: BtOgrePG.h + * + * Description: The part of BtOgre that handles information transfer from Bullet to + * Ogre (like updating graphics object positions). + * + * Version: 1.0 + * Created: 27/12/2008 03:40:56 AM + * + * Author: Nikhilesh (nikki) + * + * ===================================================================================== + */ + +#ifndef _BtOgreGP_H_ +#define _BtOgreGP_H_ + +#include "btBulletDynamicsCommon.h" +#include "OgreSceneNode.h" +#include "BtOgreExtras.h" + +namespace BtOgre { + +//A MotionState is Bullet's way of informing you about updates to an object. +//Pass this MotionState to a btRigidBody to have your SceneNode updated automaticaly. +class RigidBodyState : public btMotionState +{ + protected: + btTransform mTransform; + btTransform mCenterOfMassOffset; + + Ogre::SceneNode *mNode; + + public: + RigidBodyState(Ogre::SceneNode *node, const btTransform &transform, const btTransform &offset = btTransform::getIdentity()) + : mTransform(transform), + mCenterOfMassOffset(offset), + mNode(node) + { + } + + RigidBodyState(Ogre::SceneNode *node) + : mTransform(((node != NULL) ? BtOgre::Convert::toBullet(node->getOrientation()) : btQuaternion(0,0,0,1)), + ((node != NULL) ? BtOgre::Convert::toBullet(node->getPosition()) : btVector3(0,0,0))), + mCenterOfMassOffset(btTransform::getIdentity()), + mNode(node) + { + } + + virtual void getWorldTransform(btTransform &ret) const + { + ret = mCenterOfMassOffset.inverse() * mTransform; + } + + virtual void setWorldTransform(const btTransform &in) + { + if (mNode == NULL) + return; + + mTransform = in; + btTransform transform = in * mCenterOfMassOffset; + + btQuaternion rot = transform.getRotation(); + btVector3 pos = transform.getOrigin(); + mNode->setOrientation(rot.w(), rot.x(), rot.y(), rot.z()); + mNode->setPosition(pos.x(), pos.y(), pos.z()); + } + + void setNode(Ogre::SceneNode *node) + { + mNode = node; + } +}; + +//Softbody-Ogre connection goes here! + +} + +#endif diff --git a/libs/openengine/bullet/BulletShapeLoader.cpp b/libs/openengine/bullet/BulletShapeLoader.cpp new file mode 100644 index 000000000..48b87e1e8 --- /dev/null +++ b/libs/openengine/bullet/BulletShapeLoader.cpp @@ -0,0 +1,124 @@ +#include "BulletShapeLoader.h" + + + +BulletShape::BulletShape(Ogre::ResourceManager* creator, const Ogre::String &name, + Ogre::ResourceHandle handle, const Ogre::String &group, bool isManual, + Ogre::ManualResourceLoader *loader) : +Ogre::Resource(creator, name, handle, group, isManual, loader) +{ + /* If you were storing a pointer to an object, then you would set that pointer to NULL here. + */ + + /* For consistency with StringInterface, but we don't add any parameters here + That's because the Resource implementation of StringInterface is to + list all the options that need to be set before loading, of which + we have none as such. Full details can be set through scripts. + */ + Shape = NULL; + collide = true; + createParamDictionary("BulletShape"); +} + +BulletShape::~BulletShape() +{ +} + +// farm out to BulletShapeLoader +void BulletShape::loadImpl() +{ + mLoader->loadResource(this); +} + +void BulletShape::deleteShape(btCollisionShape* mShape) +{ + if(mShape!=NULL) + { + if(mShape->isCompound()) + { + btCompoundShape* ms = static_cast(Shape); + int a = ms->getNumChildShapes(); + for(int i=0; i getChildShape(i)); + } + } + delete mShape; + } + mShape = NULL; +} + +void BulletShape::unloadImpl() +{ + deleteShape(Shape); +} + +//TODO:change this? +size_t BulletShape::calculateSize() const +{ + return 1; +} + + + +//============================================================================================================= +template<> BulletShapeManager *Ogre::Singleton::ms_Singleton = 0; + +BulletShapeManager *BulletShapeManager::getSingletonPtr() +{ + return ms_Singleton; +} + +BulletShapeManager &BulletShapeManager::getSingleton() +{ + assert(ms_Singleton); + return(*ms_Singleton); +} + +BulletShapeManager::BulletShapeManager() +{ + mResourceType = "BulletShape"; + + // low, because it will likely reference other resources + mLoadOrder = 30.0f; + + // this is how we register the ResourceManager with OGRE + Ogre::ResourceGroupManager::getSingleton()._registerResourceManager(mResourceType, this); +} + +BulletShapeManager::~BulletShapeManager() +{ + // and this is how we unregister it + Ogre::ResourceGroupManager::getSingleton()._unregisterResourceManager(mResourceType); +} + +BulletShapePtr BulletShapeManager::load(const Ogre::String &name, const Ogre::String &group) +{ + BulletShapePtr textf = getByName(name); + + if (textf.isNull()) + textf = create(name, group); + + textf->load(); + return textf; +} + +Ogre::Resource *BulletShapeManager::createImpl(const Ogre::String &name, Ogre::ResourceHandle handle, + const Ogre::String &group, bool isManual, Ogre::ManualResourceLoader *loader, + const Ogre::NameValuePairList *createParams) +{ + BulletShape* res = new BulletShape(this, name, handle, group, isManual, loader); + //if(isManual) + //{ + //loader->loadResource(res); + //} + return res; +} + + +//==================================================================== +void BulletShapeLoader::loadResource(Ogre::Resource *resource) +{} + +void BulletShapeLoader::load(const std::string &name,const std::string &group) +{} diff --git a/libs/openengine/bullet/BulletShapeLoader.h b/libs/openengine/bullet/BulletShapeLoader.h new file mode 100644 index 000000000..316ee523c --- /dev/null +++ b/libs/openengine/bullet/BulletShapeLoader.h @@ -0,0 +1,138 @@ +#ifndef _BULLET_SHAPE_LOADER_H_ +#define _BULLET_SHAPE_LOADER_H_ + +#include +#include +#include + +//For some reason, Ogre Singleton cannot be used in another namespace, that's why there is no namespace here. +//But the risk of name collision seems pretty low here. + +/** +*Define a new resource which describe a Shape usable by bullet.See BulletShapeManager for how to get/use them. +*/ +class BulletShape : public Ogre::Resource +{ + Ogre::String mString; + +protected: + void loadImpl(); + void unloadImpl(); + size_t calculateSize() const; + + void deleteShape(btCollisionShape* mShape); + +public: + + BulletShape(Ogre::ResourceManager *creator, const Ogre::String &name, + Ogre::ResourceHandle handle, const Ogre::String &group, bool isManual = false, + Ogre::ManualResourceLoader *loader = 0); + + virtual ~BulletShape(); + + btCollisionShape* Shape; + //this flag indicate if the shape is used for collision or if it's for raycasting only. + bool collide; +}; + +/** +* +*/ +class BulletShapePtr : public Ogre::SharedPtr +{ +public: + BulletShapePtr() : Ogre::SharedPtr() {} + explicit BulletShapePtr(BulletShape *rep) : Ogre::SharedPtr(rep) {} + BulletShapePtr(const BulletShapePtr &r) : Ogre::SharedPtr(r) {} + BulletShapePtr(const Ogre::ResourcePtr &r) : Ogre::SharedPtr() + { + if( r.isNull() ) + return; + // lock & copy other mutex pointer + OGRE_LOCK_MUTEX(*r.OGRE_AUTO_MUTEX_NAME) + OGRE_COPY_AUTO_SHARED_MUTEX(r.OGRE_AUTO_MUTEX_NAME) + pRep = static_cast(r.getPointer()); + pUseCount = r.useCountPointer(); + useFreeMethod = r.freeMethod(); + if (pUseCount) + { + ++(*pUseCount); + } + } + + /// Operator used to convert a ResourcePtr to a BulletShapePtr + BulletShapePtr& operator=(const Ogre::ResourcePtr& r) + { + if(pRep == static_cast(r.getPointer())) + return *this; + release(); + if( r.isNull() ) + return *this; // resource ptr is null, so the call to release above has done all we need to do. + // lock & copy other mutex pointer + OGRE_LOCK_MUTEX(*r.OGRE_AUTO_MUTEX_NAME) + OGRE_COPY_AUTO_SHARED_MUTEX(r.OGRE_AUTO_MUTEX_NAME) + pRep = static_cast(r.getPointer()); + pUseCount = r.useCountPointer(); + useFreeMethod = r.freeMethod(); + if (pUseCount) + { + ++(*pUseCount); + } + return *this; + } +}; + + + + +/** +*Hold any BulletShape that was created by the ManualBulletShapeLoader. +* +*To get a bulletShape, you must load it first. +*First, create a manualBulletShapeLoader. Then call ManualBulletShapeManager->load(). This create an "empty" resource. +*Then use BulletShapeManager->load(). This will fill the resource with the required info. +*To get the resource,use BulletShapeManager::getByName. +*When you use the resource no more, just use BulletShapeManager->unload(). It won't completly delete the resource, but it will +*"empty" it.This allow a better management of memory: when you are leaving a cell, just unload every useless shape. +* +*Alternatively, you can call BulletShape->load() in order to actually load the resource. +*When you are finished with it, just call BulletShape->unload(). +* +*IMO: prefere the first methode, i am not completly sure about the 2nd. +* +*Important Note: i have no idea of what happen if you try to load two time the same resource without unloading. +*It won't crash, but it might lead to memory leaks(I don't know how Ogre handle this). So don't do it! +*/ +class BulletShapeManager : public Ogre::ResourceManager, public Ogre::Singleton +{ +protected: + + // must implement this from ResourceManager's interface + Ogre::Resource *createImpl(const Ogre::String &name, Ogre::ResourceHandle handle, + const Ogre::String &group, bool isManual, Ogre::ManualResourceLoader *loader, + const Ogre::NameValuePairList *createParams); + +public: + + BulletShapeManager(); + virtual ~BulletShapeManager(); + + virtual BulletShapePtr load(const Ogre::String &name, const Ogre::String &group); + + static BulletShapeManager &getSingleton(); + static BulletShapeManager *getSingletonPtr(); +}; + +class BulletShapeLoader : public Ogre::ManualResourceLoader +{ +public: + + BulletShapeLoader(){}; + virtual ~BulletShapeLoader() {} + + virtual void loadResource(Ogre::Resource *resource); + + virtual void load(const std::string &name,const std::string &group); +}; + +#endif diff --git a/libs/openengine/bullet/CMotionState.cpp b/libs/openengine/bullet/CMotionState.cpp new file mode 100644 index 000000000..5ddef5175 --- /dev/null +++ b/libs/openengine/bullet/CMotionState.cpp @@ -0,0 +1,45 @@ +#include "CMotionState.h" +#include "physic.hpp" + +#include +#include +#include +//#include + +namespace OEngine { +namespace Physic +{ + + CMotionState::CMotionState(PhysicEngine* eng,std::string name) + { + pEng = eng; + tr.setIdentity(); + pName = name; + }; + + void CMotionState::getWorldTransform(btTransform &worldTrans) const + { + worldTrans = tr; + } + + void CMotionState::setWorldTransform(const btTransform &worldTrans) + { + tr = worldTrans; + + PhysicEvent evt; + evt.isNPC = isNPC; + evt.isPC = isPC; + evt.newTransform = tr; + evt.RigidBodyName = pName; + + if(isPC) + { + pEng->PEventList.push_back(evt); + } + else + { + pEng->NPEventList.push_back(evt); + } + } + +}} diff --git a/libs/openengine/bullet/CMotionState.h b/libs/openengine/bullet/CMotionState.h new file mode 100644 index 000000000..3508ab4ef --- /dev/null +++ b/libs/openengine/bullet/CMotionState.h @@ -0,0 +1,52 @@ +#ifndef OENGINE_CMOTIONSTATE_H +#define OENGINE_CMOTIONSTATE_H + +#include +#include + +namespace OEngine { +namespace Physic +{ + class PhysicEngine; + + /** + * A CMotionState is associated with a single RigidBody. + * When the RigidBody is moved by bullet, bullet will call the function setWorldTransform. + * for more info, see the bullet Wiki at btMotionState. + */ + class CMotionState:public btMotionState + { + public: + + CMotionState(PhysicEngine* eng,std::string name); + + /** + * Return the position of the RigidBody. + */ + virtual void getWorldTransform(btTransform &worldTrans) const; + + /** + * Function called by bullet when the RigidBody is moved. + * It add an event to the EventList of the PhysicEngine class. + */ + virtual void setWorldTransform(const btTransform &worldTrans); + + protected: + PhysicEngine* pEng; + btTransform tr; + bool isNPC; + bool isPC; + + std::string pName; + }; + + struct PhysicEvent + { + bool isNPC; + bool isPC; + btTransform newTransform; + std::string RigidBodyName; + }; + +}} +#endif diff --git a/libs/openengine/bullet/btKinematicCharacterController.cpp b/libs/openengine/bullet/btKinematicCharacterController.cpp new file mode 100644 index 000000000..fc4f3278f --- /dev/null +++ b/libs/openengine/bullet/btKinematicCharacterController.cpp @@ -0,0 +1,643 @@ +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2008 Erwin Coumans http://bulletphysics.com + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +#include "LinearMath/btIDebugDraw.h" +#include "BulletCollision/CollisionDispatch/btGhostObject.h" +#include "BulletCollision/CollisionShapes/btMultiSphereShape.h" +#include "BulletCollision/BroadphaseCollision/btOverlappingPairCache.h" +#include "BulletCollision/BroadphaseCollision/btCollisionAlgorithm.h" +#include "BulletCollision/CollisionDispatch/btCollisionWorld.h" +#include "LinearMath/btDefaultMotionState.h" +#include "btKinematicCharacterController.h" + +///@todo Interact with dynamic objects, +///Ride kinematicly animated platforms properly +///Support ducking +class btKinematicClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback +{ +public: + btKinematicClosestNotMeRayResultCallback (btCollisionObject* me) : btCollisionWorld::ClosestRayResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)) + { + m_me[0] = me; + count = 1; + } + + btKinematicClosestNotMeRayResultCallback (btCollisionObject* me[], int count_) : btCollisionWorld::ClosestRayResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)) + { + count = count_; + + for(int i = 0; i < count; i++) + m_me[i] = me[i]; + } + + virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult,bool normalInWorldSpace) + { + for(int i = 0; i < count; i++) + if (rayResult.m_collisionObject == m_me[i]) + return 1.0; + + return ClosestRayResultCallback::addSingleResult (rayResult, normalInWorldSpace); + } +protected: + btCollisionObject* m_me[10]; + int count; +}; + +class btKinematicClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback +{ +public: + btKinematicClosestNotMeConvexResultCallback( btCollisionObject* me, const btVector3& up, btScalar minSlopeDot ) + : btCollisionWorld::ClosestConvexResultCallback( btVector3( 0.0, 0.0, 0.0 ), btVector3( 0.0, 0.0, 0.0 ) ), + m_me( me ), m_up( up ), m_minSlopeDot( minSlopeDot ) + { + } + + virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace) + { + if( convexResult.m_hitCollisionObject == m_me ) + return btScalar( 1 ); + + btVector3 hitNormalWorld; + if( normalInWorldSpace ) + { + hitNormalWorld = convexResult.m_hitNormalLocal; + } + else + { + ///need to transform normal into worldspace + hitNormalWorld = m_hitCollisionObject->getWorldTransform().getBasis()*convexResult.m_hitNormalLocal; + } + + // NOTE : m_hitNormalLocal is not always vertical on the ground with a capsule or a box... + + btScalar dotUp = m_up.dot(hitNormalWorld); + if( dotUp < m_minSlopeDot ) + return btScalar( 1 ); + + return ClosestConvexResultCallback::addSingleResult (convexResult, normalInWorldSpace); + } + +protected: + btCollisionObject* m_me; + const btVector3 m_up; + btScalar m_minSlopeDot; +}; + + + +btKinematicCharacterController::btKinematicCharacterController( btPairCachingGhostObject* externalGhostObject_, + btPairCachingGhostObject* internalGhostObject_, + btScalar stepHeight, + btScalar constantScale, + btScalar gravity, + btScalar fallVelocity, + btScalar jumpVelocity, + btScalar recoveringFactor ) +{ + m_upAxis = btKinematicCharacterController::Y_AXIS; + + m_walkDirection.setValue( btScalar( 0 ), btScalar( 0 ), btScalar( 0 ) ); + + m_useGhostObjectSweepTest = true; + + externalGhostObject = externalGhostObject_; + internalGhostObject = internalGhostObject_; + + m_recoveringFactor = recoveringFactor; + + m_stepHeight = stepHeight; + + m_useWalkDirection = true; // use walk direction by default, legacy behavior + m_velocityTimeInterval = btScalar( 0 ); + m_verticalVelocity = btScalar( 0 ); + m_verticalOffset = btScalar( 0 ); + + m_gravity = constantScale * gravity; + m_fallSpeed = constantScale * fallVelocity; // Terminal velocity of a sky diver in m/s. + + m_jumpSpeed = constantScale * jumpVelocity; // ? + m_wasJumping = false; + + setMaxSlope( btRadians( 45.0 ) ); + + mCollision = true; +} + + +btKinematicCharacterController::~btKinematicCharacterController () +{ +} + +void btKinematicCharacterController::setVerticalVelocity(float z) +{ + m_verticalVelocity = z; +} + +bool btKinematicCharacterController::recoverFromPenetration( btCollisionWorld* collisionWorld ) +{ + bool penetration = false; + + if(!mCollision) return penetration; + + collisionWorld->getDispatcher()->dispatchAllCollisionPairs( internalGhostObject->getOverlappingPairCache(), + collisionWorld->getDispatchInfo(), + collisionWorld->getDispatcher() ); + + btVector3 currentPosition = internalGhostObject->getWorldTransform().getOrigin(); + + btScalar maxPen = btScalar( 0 ); + + for( int i = 0; i < internalGhostObject->getOverlappingPairCache()->getNumOverlappingPairs(); i++ ) + { + m_manifoldArray.resize(0); + + btBroadphasePair* collisionPair = &internalGhostObject->getOverlappingPairCache()->getOverlappingPairArray()[i]; + + if( collisionPair->m_algorithm ) + collisionPair->m_algorithm->getAllContactManifolds( m_manifoldArray ); + + + for( int j = 0; j < m_manifoldArray.size(); j++ ) + { + btPersistentManifold* manifold = m_manifoldArray[j]; + + btScalar directionSign = manifold->getBody0() == internalGhostObject ? btScalar( -1.0 ) : btScalar( 1.0 ); + + for( int p = 0; p < manifold->getNumContacts(); p++ ) + { + const btManifoldPoint&pt = manifold->getContactPoint( p ); + if( (manifold->getBody1() == externalGhostObject && manifold->getBody0() == internalGhostObject) + ||(manifold->getBody0() == externalGhostObject && manifold->getBody1() == internalGhostObject) ) + { + } + else + { + btScalar dist = pt.getDistance(); + + if( dist < 0.0 ) + { + if( dist < maxPen ) + maxPen = dist; + + // NOTE : btScalar affects the stairs but the parkinson... + // 0.0 , the capsule can break the walls... + currentPosition += pt.m_normalWorldOnB * directionSign * dist * m_recoveringFactor; + + penetration = true; + } + } + } + + // ??? + //manifold->clearManifold(); + } + } + + btTransform transform = internalGhostObject->getWorldTransform(); + + transform.setOrigin( currentPosition ); + + internalGhostObject->setWorldTransform( transform ); + externalGhostObject->setWorldTransform( transform ); + + return penetration; +} + + +btVector3 btKinematicCharacterController::stepUp( btCollisionWorld* world, const btVector3& currentPosition, btScalar& currentStepOffset ) +{ + btVector3 targetPosition = currentPosition + getUpAxisDirections()[ m_upAxis ] * ( m_stepHeight + ( m_verticalOffset > btScalar( 0.0 ) ? m_verticalOffset : 0.0 ) ); + + //if the no collisions mode is on, no need to go any further + if(!mCollision) + { + currentStepOffset = m_stepHeight; + return targetPosition; + } + + // Retrieve the collision shape + // + btCollisionShape* collisionShape = externalGhostObject->getCollisionShape(); + btAssert( collisionShape->isConvex() ); + + btConvexShape* convexShape = ( btConvexShape* )collisionShape; + + // FIXME: Handle penetration properly + // + btTransform start; + start.setIdentity(); + start.setOrigin( currentPosition + getUpAxisDirections()[ m_upAxis ] * ( convexShape->getMargin() ) ); + + btTransform end; + end.setIdentity(); + end.setOrigin( targetPosition ); + + btKinematicClosestNotMeConvexResultCallback callback( externalGhostObject, -getUpAxisDirections()[ m_upAxis ], m_maxSlopeCosine ); + callback.m_collisionFilterGroup = externalGhostObject->getBroadphaseHandle()->m_collisionFilterGroup; + callback.m_collisionFilterMask = externalGhostObject->getBroadphaseHandle()->m_collisionFilterMask; + + // Sweep test + // + if( m_useGhostObjectSweepTest ) + externalGhostObject->convexSweepTest( convexShape, start, end, callback, world->getDispatchInfo().m_allowedCcdPenetration ); + + else + world->convexSweepTest( convexShape, start, end, callback ); + + if( callback.hasHit() ) + { + // Only modify the position if the hit was a slope and not a wall or ceiling. + // + if( callback.m_hitNormalWorld.dot(getUpAxisDirections()[m_upAxis]) > btScalar( 0.0 ) ) + { + // We moved up only a fraction of the step height + // + currentStepOffset = m_stepHeight * callback.m_closestHitFraction; + + return currentPosition.lerp( targetPosition, callback.m_closestHitFraction ); + } + + m_verticalVelocity = btScalar( 0.0 ); + m_verticalOffset = btScalar( 0.0 ); + + return currentPosition; + } + else + { + currentStepOffset = m_stepHeight; + return targetPosition; + } +} + + +///Reflect the vector d around the vector r +inline btVector3 reflect( const btVector3& d, const btVector3& r ) +{ + return d - ( btScalar( 2.0 ) * d.dot( r ) ) * r; +} + + +///Project a vector u on another vector v +inline btVector3 project( const btVector3& u, const btVector3& v ) +{ + return v * u.dot( v ); +} + + +///Helper for computing the character sliding +inline btVector3 slide( const btVector3& direction, const btVector3& planeNormal ) +{ + return direction - project( direction, planeNormal ); +} + + + +btVector3 slideOnCollision( const btVector3& fromPosition, const btVector3& toPosition, const btVector3& hitNormal ) +{ + btVector3 moveDirection = toPosition - fromPosition; + btScalar moveLength = moveDirection.length(); + + if( moveLength <= btScalar( SIMD_EPSILON ) ) + return toPosition; + + moveDirection.normalize(); + + btVector3 reflectDir = reflect( moveDirection, hitNormal ); + reflectDir.normalize(); + + return fromPosition + slide( reflectDir, hitNormal ) * moveLength; +} + + +btVector3 btKinematicCharacterController::stepForwardAndStrafe( btCollisionWorld* collisionWorld, const btVector3& currentPosition, const btVector3& walkMove ) +{ + // We go to ! + // + btVector3 targetPosition = currentPosition + walkMove; + + //if the no collisions mode is on, no need to go any further + if(!mCollision) return targetPosition; + + // Retrieve the collision shape + // + btCollisionShape* collisionShape = externalGhostObject->getCollisionShape(); + btAssert( collisionShape->isConvex() ); + + btConvexShape* convexShape = ( btConvexShape* )collisionShape; + + btTransform start; + start.setIdentity(); + + btTransform end; + end.setIdentity(); + + btScalar fraction = btScalar( 1.0 ); + + // This optimization scheme suffers in the corners. + // It basically jumps from a wall to another, then fails to find a new + // position (after 4 iterations here) and finally don't move at all. + // + // The stepping algorithm adds some problems with stairs. It seems + // the treads create some fake corner using capsules for collisions. + // + for( int i = 0; i < 4 && fraction > btScalar( 0.01 ); i++ ) + { + start.setOrigin( currentPosition ); + end.setOrigin( targetPosition ); + + btVector3 sweepDirNegative = currentPosition - targetPosition; + + btKinematicClosestNotMeConvexResultCallback callback( externalGhostObject, sweepDirNegative, btScalar( 0.0 ) ); + callback.m_collisionFilterGroup = externalGhostObject->getBroadphaseHandle()->m_collisionFilterGroup; + callback.m_collisionFilterMask = externalGhostObject->getBroadphaseHandle()->m_collisionFilterMask; + + if( m_useGhostObjectSweepTest ) + externalGhostObject->convexSweepTest( convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration ); + + else + collisionWorld->convexSweepTest( convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration ); + + if( callback.hasHit() ) + { + // Try another target position + // + targetPosition = slideOnCollision( currentPosition, targetPosition, callback.m_hitNormalWorld ); + fraction = callback.m_closestHitFraction; + } + else + + // Move to the valid target position + // + return targetPosition; + } + + // Don't move if you can't find a valid target position... + // It prevents some flickering. + // + return currentPosition; +} + + +///Handle the gravity +btScalar btKinematicCharacterController::addFallOffset( bool wasOnGround, btScalar currentStepOffset, btScalar dt ) +{ + btScalar downVelocity = ( m_verticalVelocity < 0.0 ? -m_verticalVelocity : btScalar( 0.0 ) ) * dt; + + if( downVelocity > btScalar( 0.0 ) && downVelocity < m_stepHeight && ( wasOnGround || !m_wasJumping ) ) + downVelocity = m_stepHeight; + + return currentStepOffset + downVelocity; +} + + +btVector3 btKinematicCharacterController::stepDown( btCollisionWorld* collisionWorld, const btVector3& currentPosition, btScalar currentStepOffset ) +{ + btVector3 stepDrop = getUpAxisDirections()[ m_upAxis ] * currentStepOffset; + + // Be sure we are falling from the last m_currentPosition + // It prevents some flickering + // + btVector3 targetPosition = currentPosition - stepDrop; + + //if the no collisions mode is on, no need to go any further + if(!mCollision) return targetPosition; + + btTransform start; + start.setIdentity(); + start.setOrigin( currentPosition ); + + btTransform end; + end.setIdentity(); + end.setOrigin( targetPosition ); + + btKinematicClosestNotMeConvexResultCallback callback( internalGhostObject, getUpAxisDirections()[ m_upAxis ], m_maxSlopeCosine ); + callback.m_collisionFilterGroup = internalGhostObject->getBroadphaseHandle()->m_collisionFilterGroup; + callback.m_collisionFilterMask = internalGhostObject->getBroadphaseHandle()->m_collisionFilterMask; + + // Retrieve the collision shape + // + btCollisionShape* collisionShape = internalGhostObject->getCollisionShape(); + btAssert( collisionShape->isConvex() ); + btConvexShape* convexShape = ( btConvexShape* )collisionShape; + + if( m_useGhostObjectSweepTest ) + externalGhostObject->convexSweepTest( convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration ); + + else + collisionWorld->convexSweepTest( convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration ); + + if( callback.hasHit() ) + { + m_verticalVelocity = btScalar( 0.0 ); + m_verticalOffset = btScalar( 0.0 ); + m_wasJumping = false; + + // We dropped a fraction of the height -> hit floor + // + return currentPosition.lerp( targetPosition, callback.m_closestHitFraction ); + } + else + + // We dropped the full height + // + return targetPosition; +} + + + +void btKinematicCharacterController::setWalkDirection( const btVector3& walkDirection ) +{ + m_useWalkDirection = true; + m_walkDirection = walkDirection; +} + + +void btKinematicCharacterController::setVelocityForTimeInterval( const btVector3& velocity, btScalar timeInterval ) +{ + m_useWalkDirection = false; + m_walkDirection = velocity; + m_velocityTimeInterval = timeInterval; +} + + +void btKinematicCharacterController::reset() +{ +} + + +void btKinematicCharacterController::warp( const btVector3& origin ) +{ + btTransform transform; + transform.setIdentity(); + transform.setOrigin( -origin ); + + externalGhostObject->setWorldTransform( transform ); + internalGhostObject->setWorldTransform( transform ); +} + + +void btKinematicCharacterController::preStep( btCollisionWorld* collisionWorld ) +{ + BT_PROFILE( "preStep" ); + + for( int i = 0; i < 4 && recoverFromPenetration ( collisionWorld ); i++ ); +} + + +void btKinematicCharacterController::playerStep( btCollisionWorld* collisionWorld, btScalar dt ) +{ + BT_PROFILE( "playerStep" ); + + if( !m_useWalkDirection && m_velocityTimeInterval <= btScalar( 0.0 ) ) + return; + + bool wasOnGround = onGround(); + + // Handle the gravity + // + m_verticalVelocity -= m_gravity * dt; + + if( m_verticalVelocity > 0.0 && m_verticalVelocity > m_jumpSpeed ) + m_verticalVelocity = m_jumpSpeed; + + if( m_verticalVelocity < 0.0 && btFabs( m_verticalVelocity ) > btFabs( m_fallSpeed ) ) + m_verticalVelocity = -btFabs( m_fallSpeed ); + + m_verticalOffset = m_verticalVelocity * dt; + + // This forced stepping up can cause problems when the character + // walks (jump in fact...) under too low ceilings. + // + btVector3 currentPosition = externalGhostObject->getWorldTransform().getOrigin(); + btScalar currentStepOffset; + + currentPosition = stepUp( collisionWorld, currentPosition, currentStepOffset ); + + // Move in the air and slide against the walls ignoring the stair steps. + // + if( m_useWalkDirection ) + currentPosition = stepForwardAndStrafe( collisionWorld, currentPosition, m_walkDirection ); + + else + { + btScalar dtMoving = ( dt < m_velocityTimeInterval ) ? dt : m_velocityTimeInterval; + m_velocityTimeInterval -= dt; + + // How far will we move while we are moving ? + // + btVector3 moveDirection = m_walkDirection * dtMoving; + + currentPosition = stepForwardAndStrafe( collisionWorld, currentPosition, moveDirection ); + } + + // Finally find the ground. + // + currentStepOffset = addFallOffset( wasOnGround, currentStepOffset, dt ); + + currentPosition = stepDown( collisionWorld, currentPosition, currentStepOffset ); + + // Apply the new position to the collision objects. + // + btTransform tranform; + tranform = externalGhostObject->getWorldTransform(); + tranform.setOrigin( currentPosition ); + + externalGhostObject->setWorldTransform( tranform ); + internalGhostObject->setWorldTransform( tranform ); +} + + +void btKinematicCharacterController::setFallSpeed( btScalar fallSpeed ) +{ + m_fallSpeed = fallSpeed; +} + + +void btKinematicCharacterController::setJumpSpeed( btScalar jumpSpeed ) +{ + m_jumpSpeed = jumpSpeed; +} + + +void btKinematicCharacterController::setMaxJumpHeight( btScalar maxJumpHeight ) +{ + m_maxJumpHeight = maxJumpHeight; +} + + +bool btKinematicCharacterController::canJump() const +{ + return onGround(); +} + + +void btKinematicCharacterController::jump() +{ + if( !canJump() ) + return; + + m_verticalVelocity = m_jumpSpeed; + m_wasJumping = true; +} + + +void btKinematicCharacterController::setGravity( btScalar gravity ) +{ + m_gravity = gravity; +} + + +btScalar btKinematicCharacterController::getGravity() const +{ + return m_gravity; +} + + +void btKinematicCharacterController::setMaxSlope( btScalar slopeRadians ) +{ + m_maxSlopeRadians = slopeRadians; + m_maxSlopeCosine = btCos( slopeRadians ); +} + + +btScalar btKinematicCharacterController::getMaxSlope() const +{ + return m_maxSlopeRadians; +} + + +bool btKinematicCharacterController::onGround() const +{ + return btFabs( m_verticalVelocity ) < btScalar( SIMD_EPSILON ) && + btFabs( m_verticalOffset ) < btScalar( SIMD_EPSILON ); +} + + +btVector3* btKinematicCharacterController::getUpAxisDirections() +{ + static btVector3 sUpAxisDirection[] = + { + btVector3( btScalar( 0.0 ), btScalar( 0.0 ), btScalar( 0.0 ) ), + btVector3( btScalar( 0.0 ), btScalar( 1.0 ), btScalar( 0.0 ) ), + btVector3( btScalar( 0.0 ), btScalar( 0.0 ), btScalar( 1.0 ) ) + }; + + return sUpAxisDirection; +} + + +void btKinematicCharacterController::debugDraw( btIDebugDraw* debugDrawer ) +{ +} diff --git a/libs/openengine/bullet/btKinematicCharacterController.h b/libs/openengine/bullet/btKinematicCharacterController.h new file mode 100644 index 000000000..d24cd9722 --- /dev/null +++ b/libs/openengine/bullet/btKinematicCharacterController.h @@ -0,0 +1,168 @@ +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2008 Erwin Coumans http://bulletphysics.com + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef KINEMATIC_CHARACTER_CONTROLLER_H +#define KINEMATIC_CHARACTER_CONTROLLER_H + +#include "LinearMath/btVector3.h" +#include "LinearMath/btQuickprof.h" + +#include "BulletDynamics/Character/btCharacterControllerInterface.h" + +#include "BulletCollision/BroadphaseCollision/btCollisionAlgorithm.h" + + +class btCollisionShape; +class btRigidBody; +class btCollisionWorld; +class btCollisionDispatcher; +class btPairCachingGhostObject; + +///btKinematicCharacterController is an object that supports a sliding motion in a world. +///It uses a ghost object and convex sweep test to test for upcoming collisions. This is combined with discrete collision detection to recover from penetrations. +///Interaction between btKinematicCharacterController and dynamic rigid bodies needs to be explicity implemented by the user. +class btKinematicCharacterController : public btCharacterControllerInterface +{ +public: + enum UpAxis + { + X_AXIS = 0, + Y_AXIS = 1, + Z_AXIS = 2 + }; + +private: + btPairCachingGhostObject* externalGhostObject; // use this for querying collisions for sliding and move + btPairCachingGhostObject* internalGhostObject; // and this for recoreving from penetrations + + btScalar m_verticalVelocity; + btScalar m_verticalOffset; + btScalar m_fallSpeed; + btScalar m_jumpSpeed; + btScalar m_maxJumpHeight; + btScalar m_maxSlopeRadians; // Slope angle that is set (used for returning the exact value) + btScalar m_maxSlopeCosine; // Cosine equivalent of m_maxSlopeRadians (calculated once when set, for optimization) + btScalar m_gravity; + btScalar m_recoveringFactor; + + btScalar m_stepHeight; + + ///this is the desired walk direction, set by the user + btVector3 m_walkDirection; + + ///keep track of the contact manifolds + btManifoldArray m_manifoldArray; + + ///Gravity attributes + bool m_wasJumping; + + bool m_useGhostObjectSweepTest; + bool m_useWalkDirection; + btScalar m_velocityTimeInterval; + + UpAxis m_upAxis; + + static btVector3* getUpAxisDirections(); + + bool recoverFromPenetration ( btCollisionWorld* collisionWorld ); + + btVector3 stepUp( btCollisionWorld* collisionWorld, const btVector3& currentPosition, btScalar& currentStepOffset ); + btVector3 stepForwardAndStrafe( btCollisionWorld* collisionWorld, const btVector3& currentPosition, const btVector3& walkMove ); + btScalar addFallOffset( bool wasJumping, btScalar currentStepOffset, btScalar dt ); + btVector3 stepDown( btCollisionWorld* collisionWorld, const btVector3& currentPosition, btScalar currentStepOffset ); + +public: + /// externalGhostObject is used for querying the collisions for sliding along the wall, + /// and internalGhostObject is used for querying the collisions for recovering from large penetrations. + /// These parameters can point on the same object. + /// Using a smaller internalGhostObject can help for removing some flickering but create some + /// stopping artefacts when sliding along stairs or small walls. + /// Don't forget to scale gravity and fallSpeed if you scale the world. + btKinematicCharacterController( btPairCachingGhostObject* externalGhostObject, + btPairCachingGhostObject* internalGhostObject, + btScalar stepHeight, + btScalar constantScale = btScalar( 1.0 ), + btScalar gravity = btScalar( 9.8 ), + btScalar fallVelocity = btScalar( 55.0 ), + btScalar jumpVelocity = btScalar( 9.8 ), + btScalar recoveringFactor = btScalar( 0.2 ) ); + + ~btKinematicCharacterController (); + + void setVerticalVelocity(float z); + + ///btActionInterface interface + virtual void updateAction( btCollisionWorld* collisionWorld, btScalar deltaTime ) + { + preStep( collisionWorld ); + playerStep( collisionWorld, deltaTime ); + } + + ///btActionInterface interface + void debugDraw( btIDebugDraw* debugDrawer ); + + void setUpAxis( UpAxis axis ) + { + m_upAxis = axis; + } + + /// This should probably be called setPositionIncrementPerSimulatorStep. + /// This is neither a direction nor a velocity, but the amount to + /// increment the position each simulation iteration, regardless + /// of dt. + /// This call will reset any velocity set by setVelocityForTimeInterval(). + virtual void setWalkDirection(const btVector3& walkDirection); + + /// Caller provides a velocity with which the character should move for + /// the given time period. After the time period, velocity is reset + /// to zero. + /// This call will reset any walk direction set by setWalkDirection(). + /// Negative time intervals will result in no motion. + virtual void setVelocityForTimeInterval(const btVector3& velocity, + btScalar timeInterval); + + void reset(); + void warp( const btVector3& origin ); + + void preStep( btCollisionWorld* collisionWorld ); + void playerStep( btCollisionWorld* collisionWorld, btScalar dt ); + + void setFallSpeed( btScalar fallSpeed ); + void setJumpSpeed( btScalar jumpSpeed ); + void setMaxJumpHeight( btScalar maxJumpHeight ); + bool canJump() const; + + void jump(); + + void setGravity( btScalar gravity ); + btScalar getGravity() const; + + /// The max slope determines the maximum angle that the controller can walk up. + /// The slope angle is measured in radians. + void setMaxSlope( btScalar slopeRadians ); + btScalar getMaxSlope() const; + + void setUseGhostSweepTest( bool useGhostObjectSweepTest ) + { + m_useGhostObjectSweepTest = useGhostObjectSweepTest; + } + + bool onGround() const; + + //if set to false, there will be no collision. + bool mCollision; +}; + +#endif // KINEMATIC_CHARACTER_CONTROLLER_H diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp new file mode 100644 index 000000000..07bad3053 --- /dev/null +++ b/libs/openengine/bullet/physic.cpp @@ -0,0 +1,375 @@ +#include "physic.hpp" +#include +#include +#include +//#include +#include "CMotionState.h" +#include "OgreRoot.h" +#include "btKinematicCharacterController.h" +#include "BtOgrePG.h" +#include "BtOgreGP.h" +#include "BtOgreExtras.h" + +#define BIT(x) (1<<(x)) + +namespace OEngine { +namespace Physic +{ + enum collisiontypes { + COL_NOTHING = 0, //setWorldTransform( transform ); + + btScalar externalCapsuleHeight = 120; + btScalar externalCapsuleWidth = 19; + + externalCollisionShape = new btCapsuleShapeZ( externalCapsuleWidth, externalCapsuleHeight ); + externalCollisionShape->setMargin( 0.1 ); + + externalGhostObject->setCollisionShape( externalCollisionShape ); + externalGhostObject->setCollisionFlags( btCollisionObject::CF_CHARACTER_OBJECT ); + + // Internal capsule + internalGhostObject = new PairCachingGhostObject(name); + internalGhostObject->setWorldTransform( transform ); + //internalGhostObject->getBroadphaseHandle()->s + btScalar internalCapsuleHeight = 110; + btScalar internalCapsuleWidth = 17; + + internalCollisionShape = new btCapsuleShapeZ( internalCapsuleWidth, internalCapsuleHeight ); + internalCollisionShape->setMargin( 0.1 ); + + internalGhostObject->setCollisionShape( internalCollisionShape ); + internalGhostObject->setCollisionFlags( btCollisionObject::CF_CHARACTER_OBJECT ); + + mCharacter = new btKinematicCharacterController( externalGhostObject,internalGhostObject,btScalar( 40 ),1,4,20,9.8,0.2 ); + mCharacter->setUpAxis(btKinematicCharacterController::Z_AXIS); + mCharacter->setUseGhostSweepTest(false); + + mCharacter->mCollision = false; + setGravity(0); + + mTranslation = btVector3(0,0,70); + } + + PhysicActor::~PhysicActor() + { + delete mCharacter; + delete internalGhostObject; + delete internalCollisionShape; + delete externalGhostObject; + delete externalCollisionShape; + } + + void PhysicActor::setGravity(float gravity) + { + mCharacter->setGravity(gravity); + //mCharacter-> + } + + void PhysicActor::enableCollisions(bool collision) + { + mCharacter->mCollision = collision; + } + + void PhysicActor::setVerticalVelocity(float z) + { + mCharacter->setVerticalVelocity(z); + } + + bool PhysicActor::getCollisionMode() + { + return mCharacter->mCollision; + } + + void PhysicActor::setWalkDirection(const btVector3& mvt) + { + mCharacter->setWalkDirection( mvt ); + } + + void PhysicActor::Rotate(const btQuaternion& quat) + { + externalGhostObject->getWorldTransform().setRotation( externalGhostObject->getWorldTransform().getRotation() * quat ); + internalGhostObject->getWorldTransform().setRotation( internalGhostObject->getWorldTransform().getRotation() * quat ); + } + + void PhysicActor::setRotation(const btQuaternion& quat) + { + externalGhostObject->getWorldTransform().setRotation( quat ); + internalGhostObject->getWorldTransform().setRotation( quat ); + } + + btVector3 PhysicActor::getPosition(void) + { + return internalGhostObject->getWorldTransform().getOrigin() -mTranslation; + } + + btQuaternion PhysicActor::getRotation(void) + { + return internalGhostObject->getWorldTransform().getRotation(); + } + + void PhysicActor::setPosition(const btVector3& pos) + { + internalGhostObject->getWorldTransform().setOrigin(pos+mTranslation); + externalGhostObject->getWorldTransform().setOrigin(pos+mTranslation); + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + + RigidBody::RigidBody(btRigidBody::btRigidBodyConstructionInfo& CI,std::string name) + :btRigidBody(CI),mName(name) + { + + }; + + + + /////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////// + + + + PhysicEngine::PhysicEngine(BulletShapeLoader* shapeLoader) + { + // Set up the collision configuration and dispatcher + collisionConfiguration = new btDefaultCollisionConfiguration(); + dispatcher = new btCollisionDispatcher(collisionConfiguration); + + // The actual physics solver + solver = new btSequentialImpulseConstraintSolver; + + //TODO: memory leak? + btOverlappingPairCache* pairCache = new btSortedOverlappingPairCache(); + //pairCache->setInternalGhostPairCallback( new btGhostPairCallback() ); + + broadphase = new btDbvtBroadphase(pairCache); + + // The world. + dynamicsWorld = new btDiscreteDynamicsWorld(dispatcher,broadphase,solver,collisionConfiguration); + dynamicsWorld->setGravity(btVector3(0,0,-10)); + + if(BulletShapeManager::getSingletonPtr() == NULL) + { + new BulletShapeManager(); + } + //TODO:singleton? + mShapeLoader = shapeLoader; + + isDebugCreated = false; + } + + void PhysicEngine::createDebugRendering() + { + if(!isDebugCreated) + { + Ogre::SceneManagerEnumerator::SceneManagerIterator iter = Ogre::Root::getSingleton().getSceneManagerIterator(); + iter.begin(); + Ogre::SceneManager* scn = iter.getNext(); + Ogre::SceneNode* node = scn->getRootSceneNode()->createChildSceneNode(); + node->pitch(Ogre::Degree(-90)); + mDebugDrawer = new BtOgre::DebugDrawer(node, dynamicsWorld); + dynamicsWorld->setDebugDrawer(mDebugDrawer); + isDebugCreated = true; + dynamicsWorld->debugDrawWorld(); + } + } + + void PhysicEngine::setDebugRenderingMode(int mode) + { + if(!isDebugCreated) + { + createDebugRendering(); + } + mDebugDrawer->setDebugMode(mode); + } + + PhysicEngine::~PhysicEngine() + { + delete dynamicsWorld; + delete solver; + delete collisionConfiguration; + delete dispatcher; + delete broadphase; + delete mShapeLoader; + } + + RigidBody* PhysicEngine::createRigidBody(std::string mesh,std::string name,float scale) + { + //get the shape from the .nif + mShapeLoader->load(mesh,"General"); + BulletShapeManager::getSingletonPtr()->load(mesh,"General"); + BulletShapePtr shape = BulletShapeManager::getSingleton().getByName(mesh,"General"); + shape->Shape->setLocalScaling(btVector3(scale,scale,scale)); + + //create the motionState + CMotionState* newMotionState = new CMotionState(this,name); + + //create the real body + btRigidBody::btRigidBodyConstructionInfo CI = btRigidBody::btRigidBodyConstructionInfo(0,newMotionState,shape->Shape); + RigidBody* body = new RigidBody(CI,name); + body->collide = shape->collide; + return body; + } + + void PhysicEngine::addRigidBody(RigidBody* body) + { + if(body->collide) + { + dynamicsWorld->addRigidBody(body,COL_WORLD,COL_WORLD|COL_ACTOR_INTERNAL|COL_ACTOR_EXTERNAL); + } + else + { + dynamicsWorld->addRigidBody(body,COL_WORLD,COL_NOTHING); + } + body->setActivationState(DISABLE_DEACTIVATION); + RigidBodyMap[body->mName] = body; + } + + void PhysicEngine::removeRigidBody(std::string name) + { + std::map::iterator it = RigidBodyMap.find(name); + if (it != RigidBodyMap.end() ) + { + RigidBody* body = it->second; + if(body != NULL) + { + // broadphase->getOverlappingPairCache()->removeOverlappingPairsContainingProxy(body->getBroadphaseProxy(),dispatcher); + /*std::map::iterator it2 = PhysicActorMap.begin(); + for(;it2!=PhysicActorMap.end();it++) + { + it2->second->internalGhostObject->getOverlappingPairCache()->removeOverlappingPairsContainingProxy(body->getBroadphaseProxy(),dispatcher); + it2->second->externalGhostObject->getOverlappingPairCache()->removeOverlappingPairsContainingProxy(body->getBroadphaseProxy(),dispatcher); + }*/ + dynamicsWorld->removeRigidBody(RigidBodyMap[name]); + } + } + } + + void PhysicEngine::deleteRigidBody(std::string name) + { + std::map::iterator it = RigidBodyMap.find(name); + if (it != RigidBodyMap.end() ) + { + RigidBody* body = it->second; + if(body != NULL) + { + delete body; + } + RigidBodyMap.erase(it); + } + } + + RigidBody* PhysicEngine::getRigidBody(std::string name) + { + RigidBody* body = RigidBodyMap[name]; + return body; + } + + void PhysicEngine::stepSimulation(double deltaT) + { + dynamicsWorld->stepSimulation(deltaT,1,1/50.); + if(isDebugCreated) + { + mDebugDrawer->step(); + } + } + + void PhysicEngine::addCharacter(std::string name) + { + PhysicActor* newActor = new PhysicActor(name); + dynamicsWorld->addCollisionObject( newActor->externalGhostObject, COL_ACTOR_EXTERNAL, COL_WORLD |COL_ACTOR_EXTERNAL ); + dynamicsWorld->addCollisionObject( newActor->internalGhostObject, COL_ACTOR_INTERNAL, COL_WORLD |COL_ACTOR_INTERNAL ); + dynamicsWorld->addAction( newActor->mCharacter ); + PhysicActorMap[name] = newActor; + } + + void PhysicEngine::removeCharacter(std::string name) + { + //std::cout << "remove"; + std::map::iterator it = PhysicActorMap.find(name); + if (it != PhysicActorMap.end() ) + { + PhysicActor* act = it->second; + if(act != NULL) + { + /*broadphase->getOverlappingPairCache()->removeOverlappingPairsContainingProxy(act->externalGhostObject->getBroadphaseHandle(),dispatcher); + broadphase->getOverlappingPairCache()->removeOverlappingPairsContainingProxy(act->internalGhostObject->getBroadphaseHandle(),dispatcher); + std::map::iterator it2 = PhysicActorMap.begin(); + for(;it2!=PhysicActorMap.end();it++) + { + it->second->internalGhostObject->getOverlappingPairCache()->removeOverlappingPairsContainingProxy(act->externalGhostObject->getBroadphaseHandle(),dispatcher); + it->second->externalGhostObject->getOverlappingPairCache()->removeOverlappingPairsContainingProxy(act->externalGhostObject->getBroadphaseHandle(),dispatcher); + it->second->internalGhostObject->getOverlappingPairCache()->removeOverlappingPairsContainingProxy(act->internalGhostObject->getBroadphaseHandle(),dispatcher); + it->second->externalGhostObject->getOverlappingPairCache()->removeOverlappingPairsContainingProxy(act->internalGhostObject->getBroadphaseHandle(),dispatcher); + }*/ + //act->externalGhostObject-> + dynamicsWorld->removeCollisionObject(act->externalGhostObject); + dynamicsWorld->removeCollisionObject(act->internalGhostObject); + dynamicsWorld->removeAction(act->mCharacter); + delete act; + } + PhysicActorMap.erase(it); + } + //std::cout << "ok"; + } + + PhysicActor* PhysicEngine::getCharacter(std::string name) + { + PhysicActor* act = PhysicActorMap[name]; + return act; + } + + void PhysicEngine::emptyEventLists(void) + { + } + + std::pair PhysicEngine::rayTest(btVector3& from,btVector3& to) + { + std::string name = ""; + float d = -1; + + float d1 = 10000.; + btCollisionWorld::ClosestRayResultCallback resultCallback1(from, to); + resultCallback1.m_collisionFilterMask = COL_WORLD; + dynamicsWorld->rayTest(from, to, resultCallback1); + if (resultCallback1.hasHit()) + { + name = static_cast(*resultCallback1.m_collisionObject).mName; + d1 = resultCallback1.m_closestHitFraction; + d = d1; + } + + btCollisionWorld::ClosestRayResultCallback resultCallback2(from, to); + resultCallback2.m_collisionFilterMask = COL_ACTOR_INTERNAL|COL_ACTOR_EXTERNAL; + dynamicsWorld->rayTest(from, to, resultCallback2); + float d2 = 10000.; + if (resultCallback2.hasHit()) + { + d2 = resultCallback1.m_closestHitFraction; + if(d2<=d1) + { + name = static_cast(*resultCallback2.m_collisionObject).mName; + d = d2; + } + } + + return std::pair(name,d); + } +}}; diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp new file mode 100644 index 000000000..88e3699ae --- /dev/null +++ b/libs/openengine/bullet/physic.hpp @@ -0,0 +1,230 @@ +#ifndef OENGINE_BULLET_PHYSIC_H +#define OENGINE_BULLET_PHYSIC_H + +#include +#include "BulletCollision/CollisionDispatch/btGhostObject.h" +#include +#include +#include +#include "BulletShapeLoader.h" + +class btRigidBody; +class btBroadphaseInterface; +class btDefaultCollisionConfiguration; +class btSequentialImpulseConstraintSolver; +class btCollisionDispatcher; +class btDiscreteDynamicsWorld; +class btKinematicCharacterController; + +namespace BtOgre +{ + class DebugDrawer; +} + +namespace MWWorld +{ + class World; +} + +namespace OEngine { +namespace Physic +{ + class CMotionState; + struct PhysicEvent; + + /** + *This is just used to be able to name objects. + */ + class PairCachingGhostObject : public btPairCachingGhostObject + { + public: + PairCachingGhostObject(std::string name) + :btPairCachingGhostObject(),mName(name) + { + } + std::string mName; + }; + + /** + * A physic Actor use a modifed KinematicCharacterController taken in the bullet forum. + */ + class PhysicActor + { + public: + PhysicActor(std::string name); + + ~PhysicActor(); + + /** + * This function set the walkDirection. This is not relative to the actor orientation. + * I think it's also needed to take time into account. A typical call should look like this: + * setWalkDirection( mvt * orientation * dt) + */ + void setWalkDirection(const btVector3& mvt); + + void Rotate(const btQuaternion& quat); + + void setRotation(const btQuaternion& quat); + + void setGravity(float gravity); + + void setVerticalVelocity(float z); + + void enableCollisions(bool collision); + + bool getCollisionMode(); + + btVector3 getPosition(void); + + btQuaternion getRotation(void); + + void setPosition(const btVector3& pos); + + btKinematicCharacterController* mCharacter; + + PairCachingGhostObject* internalGhostObject; + btCollisionShape* internalCollisionShape; + + PairCachingGhostObject* externalGhostObject; + btCollisionShape* externalCollisionShape; + + std::string mName; + + /** + *NPC scenenode is located on there feet, and you can't simply translate a btShape, so this vector is used + *each time get/setposition is called. + */ + btVector3 mTranslation; + }; + + /** + *This class is just an extension of normal btRigidBody in order to add extra info. + *When bullet give back a btRigidBody, you can just do a static_cast to RigidBody, + *so one never should use btRigidBody directly! + */ + class RigidBody: public btRigidBody + { + public: + RigidBody(btRigidBody::btRigidBodyConstructionInfo& CI,std::string name); + std::string mName; + + //is this body used for raycasting only? + bool collide; + }; + + /** + * The PhysicEngine class contain everything which is needed for Physic. + * It's needed that Ogre Resources are set up before the PhysicEngine is created. + * Note:deleting it WILL NOT delete the RigidBody! + * TODO:unload unused resources? + */ + class PhysicEngine + { + public: + /** + * Note that the shapeLoader IS destroyed by the phyic Engine!! + */ + PhysicEngine(BulletShapeLoader* shapeLoader); + + /** + * It DOES destroy the shape loader! + */ + ~PhysicEngine(); + + /** + * Create a RigidBody.It does not add it to the simulation, but it does add it to the rigidBody Map, + * so you can get it with the getRigidBody function. + */ + RigidBody* createRigidBody(std::string mesh,std::string name,float scale); + + /** + * Add a RigidBody to the simulation + */ + void addRigidBody(RigidBody* body); + + /** + * Remove a RigidBody from the simulation. It does not delete it, and does not remove it from the RigidBodyMap. + */ + void removeRigidBody(std::string name); + + /** + * Delete a RigidBody, and remove it from RigidBodyMap. + */ + void deleteRigidBody(std::string name); + + /** + * Return a pointer to a given rigid body. + * TODO:check if exist + */ + RigidBody* getRigidBody(std::string name); + + /** + * Create and add a character to the scene, and add it to the ActorMap. + */ + void addCharacter(std::string name); + + /** + * Remove a character from the scene. TODO:delete it! for now, a small memory leak^^ done? + */ + void removeCharacter(std::string name); + + /** + * Return a pointer to a character + * TODO:check if the actor exist... + */ + PhysicActor* getCharacter(std::string name); + + /** + * This step the simulation of a given time. + */ + void stepSimulation(double deltaT); + + /** + * Empty events lists + */ + void emptyEventLists(void); + + /** + * Create a debug rendering. It is called by setDebgRenderingMode if it's not created yet. + * Important Note: this will crash if the Render is not yet initialise! + */ + void createDebugRendering(); + + /** + * Set the debug rendering mode. 0 to turn it off. + * Important Note: this will crash if the Render is not yet initialise! + */ + void setDebugRenderingMode(int mode); + + /** + * Return the closest object hit by a ray. If there are no objects, it will return ("",-1). + */ + std::pair rayTest(btVector3& from,btVector3& to); + + //event list of non player object + std::list NPEventList; + + //event list affecting the player + std::list PEventList; + + //Bullet Stuff + btBroadphaseInterface* broadphase; + btDefaultCollisionConfiguration* collisionConfiguration; + btSequentialImpulseConstraintSolver* solver; + btCollisionDispatcher* dispatcher; + btDiscreteDynamicsWorld* dynamicsWorld; + + //the NIF file loader. + BulletShapeLoader* mShapeLoader; + + std::map RigidBodyMap; + std::map PhysicActorMap; + + //debug rendering + BtOgre::DebugDrawer* mDebugDrawer; + bool isDebugCreated; + }; + +}} + +#endif diff --git a/libs/openengine/gui/events.cpp b/libs/openengine/gui/events.cpp new file mode 100644 index 000000000..713db8585 --- /dev/null +++ b/libs/openengine/gui/events.cpp @@ -0,0 +1,80 @@ +#include +#include +#include + +#include "events.hpp" + +using namespace OIS; +using namespace OEngine::GUI; + +EventInjector::EventInjector(MyGUI::Gui *g) + : gui(g), mouseX(0), mouseY(0), enabled(true) +{ + assert(gui); + maxX = gui->getViewSize().width; + maxY = gui->getViewSize().height; +} + +template +void setRange(X &x, X min, X max) +{ + if(x < min) x = min; + else if(x > max) x = max; +} + +void EventInjector::event(Type type, int index, const void *p) +{ + if(!enabled) return; + + if(type & EV_Keyboard) + { + KeyEvent *key = (KeyEvent*)p; + MyGUI::KeyCode code = MyGUI::KeyCode::Enum(key->key); + if(type == EV_KeyDown) + { + /* + This is just a first approximation. Apparently, OIS is + unable to provide reliable unicode characters on all + platforms. At least that's what I surmise from the amount + of workaround that the MyGUI folks have put in place for + this. See Common/Input/OIS/InputManager.cpp in the MyGUI + sources for details. + + If the work they have done there is indeed necessary (I + haven't tested that it is, although I have had dubious + experinces with OIS events in the past), then we should + probably adapt all that code here. Or even better, + directly into the OIS input manager in Mangle. + + Note that all this only affects the 'text' field, and + should thus only affect typed text in input boxes (which + is still pretty significant.) + */ + MyGUI::Char text = (MyGUI::Char)key->text; + gui->injectKeyPress(code,text); + } + else + { + gui->injectKeyRelease(code); + } + } + else if(type & EV_Mouse) + { + MouseEvent *mouse = (MouseEvent*)p; + MyGUI::MouseButton id = MyGUI::MouseButton::Enum(index); + + // Update mouse position + mouseX += mouse->state.X.rel; + mouseY += mouse->state.Y.rel; + + setRange(mouseX,0,maxX); + setRange(mouseY,0,maxY); + + if(type == EV_MouseDown) + gui->injectMousePress(mouseX, mouseY, id); + else if(type == EV_MouseUp) + gui->injectMouseRelease(mouseX, mouseY, id); + else + gui->injectMouseMove(mouseX, mouseY, mouse->state.Z.abs); + } +} diff --git a/libs/openengine/gui/events.hpp b/libs/openengine/gui/events.hpp new file mode 100644 index 000000000..6ca83cf75 --- /dev/null +++ b/libs/openengine/gui/events.hpp @@ -0,0 +1,31 @@ +#ifndef OENGINE_MYGUI_EVENTS_H +#define OENGINE_MYGUI_EVENTS_H + +#include + +namespace MyGUI +{ + class Gui; +} + +namespace OEngine { +namespace GUI +{ + /** Event handler that injects OIS events into MyGUI + */ + class EventInjector : public Mangle::Input::Event + { + MyGUI::Gui *gui; + int mouseX, mouseY; + int maxX, maxY; + + public: + bool enabled; + + EventInjector(MyGUI::Gui *g); + void event(Type type, int index, const void *p); + }; + + typedef boost::shared_ptr EventInjectorPtr; +}} +#endif diff --git a/libs/openengine/gui/layout.hpp b/libs/openengine/gui/layout.hpp new file mode 100644 index 000000000..f02ddbdff --- /dev/null +++ b/libs/openengine/gui/layout.hpp @@ -0,0 +1,119 @@ +#ifndef OENGINE_MYGUI_LAYOUT_H +#define OENGINE_MYGUI_LAYOUT_H + +#include +#include + +namespace OEngine { +namespace GUI +{ + /** The Layout class is an utility class used to load MyGUI layouts + from xml files, and to manipulate member widgets. + */ + class Layout + { + public: + Layout(const std::string & _layout, MyGUI::WidgetPtr _parent = nullptr) + : mMainWidget(nullptr) + { initialise(_layout, _parent); } + virtual ~Layout() { shutdown(); } + + template + void getWidget(T * & _widget, const std::string & _name, bool _throw = true) + { + _widget = nullptr; + for (MyGUI::VectorWidgetPtr::iterator iter=mListWindowRoot.begin(); + iter!=mListWindowRoot.end(); ++iter) + { + MyGUI::WidgetPtr find = (*iter)->findWidget(mPrefix + _name); + if (nullptr != find) + { + T * cast = find->castType(false); + if (nullptr != cast) + _widget = cast; + else if (_throw) + { + MYGUI_EXCEPT("Error cast : dest type = '" << T::getClassTypeName() + << "' source name = '" << find->getName() + << "' source type = '" << find->getTypeName() << "' in layout '" << mLayoutName << "'"); + } + return; + } + } + MYGUI_ASSERT( ! _throw, "widget name '" << _name << "' in layout '" << mLayoutName << "' not found."); + } + + void initialise(const std::string & _layout, + MyGUI::WidgetPtr _parent = nullptr) + { + const std::string MAIN_WINDOW = "_Main"; + mLayoutName = _layout; + + if (mLayoutName.empty()) + mMainWidget = _parent; + else + { + mPrefix = MyGUI::utility::toString(this, "_"); + mListWindowRoot = MyGUI::LayoutManager::getInstance().loadLayout(mLayoutName, mPrefix, _parent); + + const std::string main_name = mPrefix + MAIN_WINDOW; + for (MyGUI::VectorWidgetPtr::iterator iter=mListWindowRoot.begin(); iter!=mListWindowRoot.end(); ++iter) + { + if ((*iter)->getName() == main_name) + { + mMainWidget = (*iter); + break; + } + } + MYGUI_ASSERT(mMainWidget, "root widget name '" << MAIN_WINDOW << "' in layout '" << mLayoutName << "' not found."); + } + } + + void shutdown() + { + MyGUI::LayoutManager::getInstance().unloadLayout(mListWindowRoot); + mListWindowRoot.clear(); + } + + void setCoord(int x, int y, int w, int h) + { + mMainWidget->setCoord(x,y,w,h); + } + + void setVisible(bool b) + { + mMainWidget->setVisible(b); + } + + void setText(const std::string& name, const std::string& caption) + { + MyGUI::WidgetPtr pt; + getWidget(pt, name); + pt->setCaption(caption); + } + + void setTextColor(const std::string& name, float r, float g, float b) + { + MyGUI::WidgetPtr pt; + getWidget(pt, name); + MyGUI::StaticText *st = dynamic_cast(pt); + if(st != NULL) + st->setTextColour(MyGUI::Colour(b,g,r)); + } + + void setImage(const std::string& name, const std::string& imgName) + { + MyGUI::StaticImagePtr pt; + getWidget(pt, name); + pt->setImageTexture(imgName); + } + + protected: + + MyGUI::WidgetPtr mMainWidget; + std::string mPrefix; + std::string mLayoutName; + MyGUI::VectorWidgetPtr mListWindowRoot; + }; +}} +#endif diff --git a/libs/openengine/gui/manager.cpp b/libs/openengine/gui/manager.cpp new file mode 100644 index 000000000..b606d7d16 --- /dev/null +++ b/libs/openengine/gui/manager.cpp @@ -0,0 +1,47 @@ +#include +#include +#include + +#include "manager.hpp" + +using namespace OEngine::GUI; + +void MyGUIManager::setup(Ogre::RenderWindow *wnd, Ogre::SceneManager *mgr, bool logging, const std::string& logDir) +{ + assert(wnd); + assert(mgr); + + using namespace MyGUI; + + // Enable/disable MyGUI logging to stdout. (Logging to MyGUI.log is + // still enabled.) In order to do this we have to initialize the log + // manager before the main gui system itself, otherwise the main + // object will get the chance to spit out a few messages before we + // can able to disable it. + LogManager::initialise(); + LogManager::setSTDOutputEnabled(logging); + + std::string theLogFile = std::string(MYGUI_PLATFORM_LOG_FILENAME); + if(!logDir.empty()) + theLogFile.insert(0, logDir); + + // Set up OGRE platform. We might make this more generic later. + mPlatform = new OgrePlatform(); + mPlatform->initialise(wnd, mgr, "General", theLogFile); + + // Create GUI + mGui = new Gui(); + mGui->initialise("core.xml", theLogFile); +} + +void MyGUIManager::shutdown() +{ + if(mGui) delete mGui; + if(mPlatform) + { + mPlatform->shutdown(); + delete mPlatform; + } + mGui = NULL; + mPlatform = NULL; +} diff --git a/libs/openengine/gui/manager.hpp b/libs/openengine/gui/manager.hpp new file mode 100644 index 000000000..781685acf --- /dev/null +++ b/libs/openengine/gui/manager.hpp @@ -0,0 +1,36 @@ +#ifndef OENGINE_MYGUI_MANAGER_H +#define OENGINE_MYGUI_MANAGER_H + +namespace MyGUI +{ + class OgrePlatform; + class Gui; +} + +namespace Ogre +{ + class RenderWindow; + class SceneManager; +} + +namespace OEngine { +namespace GUI +{ + class MyGUIManager + { + MyGUI::OgrePlatform *mPlatform; + MyGUI::Gui *mGui; + + public: + MyGUIManager() : mPlatform(NULL), mGui(NULL) {} + MyGUIManager(Ogre::RenderWindow *wnd, Ogre::SceneManager *mgr, bool logging=false, const std::string& logDir = std::string("")) + { setup(wnd,mgr,logging, logDir); } + ~MyGUIManager() { shutdown(); } + + void setup(Ogre::RenderWindow *wnd, Ogre::SceneManager *mgr, bool logging=false, const std::string& logDir = std::string("")); + void shutdown(); + + MyGUI::Gui *getGui() { return mGui; } + }; +}} +#endif diff --git a/libs/openengine/input/dispatch_map.hpp b/libs/openengine/input/dispatch_map.hpp new file mode 100644 index 000000000..f0d4cabe9 --- /dev/null +++ b/libs/openengine/input/dispatch_map.hpp @@ -0,0 +1,75 @@ +#ifndef OENGINE_INPUT_DISPATCHMAP_H +#define OENGINE_INPUT_DISPATCHMAP_H + +#include +#include +#include + +namespace OEngine { +namespace Input { + +/** + DispatchMap is a simple connection system that connects incomming + signals with outgoing signals. + + The signals can be connected one-to-one, many-to-one, one-to-many + or many-to-many. + + The dispatch map is completely system agnostic. It is a pure data + structure and all signals are just integer indices. It does not + delegate any actions, but used together with Dispatcher it can be + used to build an event system. + */ +struct DispatchMap +{ + typedef std::set OutList; + typedef std::map InMap; + + typedef OutList::iterator Oit; + typedef InMap::iterator Iit; + + InMap map; + + void bind(int in, int out) + { + map[in].insert(out); + } + + void unbind(int in, int out) + { + Iit it = map.find(in); + if(it != map.end()) + { + it->second.erase(out); + + // If there are no more elements, then remove the entire list + if(it->second.empty()) + map.erase(it); + } + } + + /// Check if a given input is bound to anything + bool isBound(int in) const + { + return map.find(in) != map.end(); + } + + /** + Get the list of outputs bound to the given input. Only call this + on inputs that you know are bound to something. + + The returned set is only intended for immediate iteration. Do not + store references to it. + */ + const OutList &getList(int in) const + { + assert(isBound(in)); + InMap::const_iterator it = map.find(in); + assert(it != map.end()); + const OutList &out = it->second; + assert(!out.empty()); + return out; + } +}; +}} +#endif diff --git a/libs/openengine/input/dispatcher.hpp b/libs/openengine/input/dispatcher.hpp new file mode 100644 index 000000000..a8d480d4b --- /dev/null +++ b/libs/openengine/input/dispatcher.hpp @@ -0,0 +1,78 @@ +#ifndef OENGINE_INPUT_DISPATCHER_H +#define OENGINE_INPUT_DISPATCHER_H + +#include +#include + +#include + +#include "dispatch_map.hpp" +#include "func_binder.hpp" + +namespace OEngine { +namespace Input { + +struct Dispatcher : Mangle::Input::Event +{ + DispatchMap map; + FuncBinder funcs; + + /** + Constructor. Takes the number of actions and passes it to + FuncBinder. + */ + Dispatcher(int actions) : funcs(actions) {} + + void bind(unsigned int action, int key) + { + assert(action < funcs.getSize()); + map.bind(key, action); + } + void unbind(unsigned int action, int key) + { + assert(action < funcs.getSize()); + map.unbind(key, action); + } + bool isBound(int key) const { return map.isBound(key); } + + /** + Instigate an event. It is translated through the dispatch map and + sent to the function bindings. + */ + typedef DispatchMap::OutList _O; + void event(Type type, int index, const void* p) + { + // No bindings, nothing happens + if(!isBound(index)) + return; + + // Get the mapped actions and execute them + const _O &list = map.getList(index); + _O::const_iterator it; + for(it = list.begin(); it != list.end(); it++) + { + //catch exceptions thrown in the input handlers so that pressing a key + //doesn't cause OpenMw to crash + try + { + funcs.call(*it, p); + } + catch(const std::exception& e) + { + std::cerr << "Exception in input handler: " << e.what() << std::endl; + } + catch(...) + { + std::cerr << "Unknown exception in input handler" << std::endl; + } + + } + } +}; + +// This helps us play nice with Mangle's EventPtr, but it should +// really be defined for all the classes in OEngine. + typedef boost::shared_ptr DispatcherPtr; + +}} +#endif diff --git a/libs/openengine/input/func_binder.hpp b/libs/openengine/input/func_binder.hpp new file mode 100644 index 000000000..7aa733edf --- /dev/null +++ b/libs/openengine/input/func_binder.hpp @@ -0,0 +1,106 @@ +#ifndef OENGINE_INPUT_FUNCBINDER_H +#define OENGINE_INPUT_FUNCBINDER_H + +#include +#include +#include +#include + +namespace OEngine { +namespace Input { + +/** + An Action defines the user defined action corresponding to a + binding. + + The first parameter is the action index that invoked this call. You + can assign the same function to multiple actions, and this can help + you keep track of which action was invoked. + + The second parameter is an optional user-defined parameter, + represented by a void pointer. In many cases it is practical to + point this to temporaries (stack values), so make sure not to store + permanent references to it unless you've planning for this on the + calling side as well. + */ +typedef boost::function Action; + +/** + The FuncBinder is a simple struct that binds user-defined indices + to functions. It is useful for binding eg. keyboard events to + specific actions in your program, but can potentially have many + other uses as well. + */ +class FuncBinder +{ + struct FuncBinding + { + std::string name; + Action action; + }; + + std::vector bindings; + +public: + /** + Constructor. Initialize the struct by telling it how many action + indices you intend to bind. + + The indices you use should be 0 <= i < number. + */ + FuncBinder(int number) : bindings(number) {} + + unsigned int getSize() { return bindings.size(); } + + /** + Bind an action to an index. + */ + void bind(int index, Action action, const std::string &name="") + { + assert(index >= 0 && index < (int)bindings.size()); + + FuncBinding &fb = bindings[index]; + fb.action = action; + fb.name = name; + } + + /** + Unbind an index, reverting a previous bind(). + */ + void unbind(int index) + { + assert(index >= 0 && index < (int)bindings.size()); + + bindings[index] = FuncBinding(); + } + + /** + Call a specific action. Takes an optional parameter that is + passed to the action. + */ + void call(int index, const void *p=NULL) const + { + assert(index >= 0 && index < (int)bindings.size()); + + const FuncBinding &fb = bindings[index]; + if(fb.action) fb.action(index, p); + } + + /// Check if a given index is bound to anything + bool isBound(int index) const + { + assert(index >= 0 && index < (int)bindings.size()); + + return !bindings[index].action.empty(); + } + + /// Return the name associated with an action (empty if not bound) + const std::string &getName(int index) const + { + assert(index >= 0 && index < (int)bindings.size()); + + return bindings[index].name; + } +}; +}} +#endif diff --git a/libs/openengine/input/poller.hpp b/libs/openengine/input/poller.hpp new file mode 100644 index 000000000..c544aed52 --- /dev/null +++ b/libs/openengine/input/poller.hpp @@ -0,0 +1,46 @@ +#ifndef OENGINE_INPUT_POLLER_H +#define OENGINE_INPUT_POLLER_H + +#include "dispatch_map.hpp" +#include + +namespace OEngine { +namespace Input { + +/** The poller is used to check (poll) for keys rather than waiting + for events. */ +struct Poller +{ + DispatchMap map; + Mangle::Input::Driver &input; + + Poller(Mangle::Input::Driver &drv) + : input(drv) {} + + /** Bind or unbind a given action with a key. The action is the first + parameter, the key is the second. + */ + void bind(int in, int out) { map.bind(in, out); } + void unbind(int in, int out) { map.unbind(in, out); } + bool isBound(int in) const { return map.isBound(in); } + + /// Check whether a given action button is currently pressed. + typedef DispatchMap::OutList _O; + bool isDown(int index) const + { + // No bindings, no action + if(!isBound(index)) + return false; + + // Get all the keys bound to this action, and check them. + const _O &list = map.getList(index); + _O::const_iterator it; + for(it = list.begin(); it != list.end(); it++) + // If there's any match, we're good to go. + if(input.isDown(*it)) return true; + + return false; + } +}; +}} +#endif diff --git a/libs/openengine/input/tests/Makefile b/libs/openengine/input/tests/Makefile new file mode 100644 index 000000000..91a0b2663 --- /dev/null +++ b/libs/openengine/input/tests/Makefile @@ -0,0 +1,18 @@ +GCC=g++ + +all: funcbind_test dispatch_map_test sdl_driver_test sdl_binder_test + +funcbind_test: funcbind_test.cpp ../func_binder.hpp + $(GCC) $< -o $@ + +dispatch_map_test: dispatch_map_test.cpp ../dispatch_map.hpp + $(GCC) $< -o $@ + +sdl_driver_test: sdl_driver_test.cpp + $(GCC) $< ../../mangle/input/servers/sdl_driver.cpp -o $@ -I/usr/include/SDL/ -lSDL -I../../ + +sdl_binder_test: sdl_binder_test.cpp + $(GCC) $< ../../mangle/input/servers/sdl_driver.cpp -o $@ -I/usr/include/SDL/ -lSDL -I../../ + +clean: + rm *_test diff --git a/libs/openengine/input/tests/dispatch_map_test.cpp b/libs/openengine/input/tests/dispatch_map_test.cpp new file mode 100644 index 000000000..5f262494e --- /dev/null +++ b/libs/openengine/input/tests/dispatch_map_test.cpp @@ -0,0 +1,54 @@ +#include +using namespace std; + +#include "../dispatch_map.hpp" + +using namespace OEngine::Input; + +typedef DispatchMap::OutList OutList; +typedef OutList::const_iterator Cit; + +void showList(const DispatchMap::OutList &out) +{ + for(Cit it = out.begin(); + it != out.end(); it++) + { + cout << " " << *it << endl; + } +} + +void showAll(DispatchMap &map) +{ + cout << "\nPrinting everything:\n"; + for(DispatchMap::Iit it = map.map.begin(); + it != map.map.end(); it++) + { + cout << it->first << ":\n"; + showList(map.getList(it->first)); + } +} + +int main() +{ + cout << "Testing the dispatch map\n"; + + DispatchMap dsp; + + dsp.bind(1,9); + dsp.bind(2,-5); + dsp.bind(2,9); + dsp.bind(3,10); + dsp.bind(3,12); + dsp.bind(3,10); + + showAll(dsp); + + dsp.unbind(1,9); + dsp.unbind(5,8); + dsp.unbind(3,11); + dsp.unbind(3,12); + dsp.unbind(3,12); + + showAll(dsp); + return 0; +} diff --git a/libs/openengine/input/tests/funcbind_test.cpp b/libs/openengine/input/tests/funcbind_test.cpp new file mode 100644 index 000000000..bb4ad34e1 --- /dev/null +++ b/libs/openengine/input/tests/funcbind_test.cpp @@ -0,0 +1,47 @@ +#include +using namespace std; + +#include "../func_binder.hpp" + +void f1(int i, const void *p) +{ + cout << " F1 i=" << i << endl; + + if(p) + cout << " Got a nice gift: " + << *((const float*)p) << endl; +} + +void f2(int i, const void *p) +{ + cout << " F2 i=" << i << endl; +} + +using namespace OEngine::Input; + +int main() +{ + cout << "This will test the function binding system\n"; + + FuncBinder bnd(5); + + bnd.bind(0, &f1, "This is action 1"); + bnd.bind(1, &f2); + bnd.bind(2, &f1, "This is action 3"); + bnd.bind(3, &f2, "This is action 4"); + + bnd.unbind(2); + + for(int i=0; i<5; i++) + { + cout << "Calling " << i << ": '" << bnd.getName(i) << "'\n"; + bnd.call(i); + if(!bnd.isBound(i)) cout << " (not bound)\n"; + } + + cout << "\nCalling with parameter:\n"; + float f = 3.1415; + bnd.call(0, &f); + + return 0; +} diff --git a/libs/openengine/input/tests/output/dispatch_map_test.out b/libs/openengine/input/tests/output/dispatch_map_test.out new file mode 100644 index 000000000..01aa9d9d9 --- /dev/null +++ b/libs/openengine/input/tests/output/dispatch_map_test.out @@ -0,0 +1,18 @@ +Testing the dispatch map + +Printing everything: +1: + 9 +2: + -5 + 9 +3: + 10 + 12 + +Printing everything: +2: + -5 + 9 +3: + 10 diff --git a/libs/openengine/input/tests/output/funcbind_test.out b/libs/openengine/input/tests/output/funcbind_test.out new file mode 100644 index 000000000..862c5c972 --- /dev/null +++ b/libs/openengine/input/tests/output/funcbind_test.out @@ -0,0 +1,15 @@ +This will test the function binding system +Calling 0: 'This is action 1' + F1 i=0 +Calling 1: '' + F2 i=1 +Calling 2: '' + (not bound) +Calling 3: 'This is action 4' + F2 i=3 +Calling 4: '' + (not bound) + +Calling with parameter: + F1 i=0 + Got a nice gift: 3.1415 diff --git a/libs/openengine/input/tests/output/sdl_binder_test.out b/libs/openengine/input/tests/output/sdl_binder_test.out new file mode 100644 index 000000000..fd4eb90e3 --- /dev/null +++ b/libs/openengine/input/tests/output/sdl_binder_test.out @@ -0,0 +1,4 @@ +Hold the Q key to quit: +You are running in script mode, aborting. Run this test with a parameter (any at all) to test the input loop properly + +Bye bye! diff --git a/libs/openengine/input/tests/output/sdl_driver_test.out b/libs/openengine/input/tests/output/sdl_driver_test.out new file mode 100644 index 000000000..fd4eb90e3 --- /dev/null +++ b/libs/openengine/input/tests/output/sdl_driver_test.out @@ -0,0 +1,4 @@ +Hold the Q key to quit: +You are running in script mode, aborting. Run this test with a parameter (any at all) to test the input loop properly + +Bye bye! diff --git a/libs/openengine/input/tests/sdl_binder_test.cpp b/libs/openengine/input/tests/sdl_binder_test.cpp new file mode 100644 index 000000000..7de5f5d4f --- /dev/null +++ b/libs/openengine/input/tests/sdl_binder_test.cpp @@ -0,0 +1,71 @@ +#include +#include +#include +#include "../dispatcher.hpp" +#include "../poller.hpp" + +using namespace std; +using namespace Mangle::Input; +using namespace OEngine::Input; + +enum Actions + { + A_Quit, + A_Left, + A_Right, + + A_LAST + }; + +bool quit=false; + +void doExit(int,const void*) +{ + quit = true; +} + +void goLeft(int,const void*) +{ + cout << "Going left\n"; +} + +int main(int argc, char** argv) +{ + SDL_Init(SDL_INIT_VIDEO); + SDL_SetVideoMode(640, 480, 0, SDL_SWSURFACE); + SDLDriver input; + Dispatcher *disp = new Dispatcher(A_LAST); + Poller poll(input); + + input.setEvent(EventPtr(disp)); + + disp->funcs.bind(A_Quit, &doExit); + disp->funcs.bind(A_Left, &goLeft); + + disp->bind(A_Quit, SDLK_q); + disp->bind(A_Left, SDLK_a); + disp->bind(A_Left, SDLK_LEFT); + + poll.bind(A_Right, SDLK_d); + poll.bind(A_Right, SDLK_RIGHT); + + cout << "Hold the Q key to quit:\n"; + //input->setEvent(&mycb); + while(!quit) + { + input.capture(); + if(poll.isDown(A_Right)) + cout << "We're going right!\n"; + SDL_Delay(20); + + if(argc == 1) + { + cout << "You are running in script mode, aborting. Run this test with a parameter (any at all) to test the input loop properly\n"; + break; + } + } + cout << "\nBye bye!\n"; + + SDL_Quit(); + return 0; +} diff --git a/libs/openengine/input/tests/sdl_driver_test.cpp b/libs/openengine/input/tests/sdl_driver_test.cpp new file mode 100644 index 000000000..1771bcfe4 --- /dev/null +++ b/libs/openengine/input/tests/sdl_driver_test.cpp @@ -0,0 +1,31 @@ +#include +#include +#include + +using namespace std; +using namespace Mangle::Input; + +int main(int argc, char** argv) +{ + SDL_Init(SDL_INIT_VIDEO); + SDL_SetVideoMode(640, 480, 0, SDL_SWSURFACE); + SDLDriver input; + + cout << "Hold the Q key to quit:\n"; + //input->setEvent(&mycb); + while(!input.isDown(SDLK_q)) + { + input.capture(); + SDL_Delay(20); + + if(argc == 1) + { + cout << "You are running in script mode, aborting. Run this test with a parameter (any at all) to test the input loop properly\n"; + break; + } + } + cout << "\nBye bye!\n"; + + SDL_Quit(); + return 0; +} diff --git a/libs/openengine/input/tests/test.sh b/libs/openengine/input/tests/test.sh new file mode 100755 index 000000000..2d07708ad --- /dev/null +++ b/libs/openengine/input/tests/test.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +make || exit + +mkdir -p output + +PROGS=*_test + +for a in $PROGS; do + if [ -f "output/$a.out" ]; then + echo "Running $a:" + ./$a | diff output/$a.out - + else + echo "Creating $a.out" + ./$a > "output/$a.out" + git add "output/$a.out" + fi +done diff --git a/libs/openengine/misc/list.hpp b/libs/openengine/misc/list.hpp new file mode 100644 index 000000000..b08b57494 --- /dev/null +++ b/libs/openengine/misc/list.hpp @@ -0,0 +1,178 @@ +#ifndef MISC_LIST_H +#define MISC_LIST_H + +#include + +namespace Misc{ + +/* + A simple and completely allocation-less doubly linked list. The + class only manages pointers to and between elements. It leaving all + memory management to the user. +*/ +template +struct List +{ + List() : head(0), tail(0), totalNum(0) {} + + // Empty the list. + void reset() + { + head = 0; + tail = 0; + totalNum = 0; + } + + // Insert an element at the end of the list. The element cannot be + // part of any other list when this is called. + void insert(Elem *p) + { + if(tail) + { + // There are existing elements. Insert the node at the end of + // the list. + assert(head && totalNum > 0); + tail->next = p; + } + else + { + // This is the first element + assert(head == 0 && totalNum == 0); + head = p; + } + + // These have to be done in either case + p->prev = tail; + p->next = 0; + tail = p; + + totalNum++; + } + + // Remove element from the list. The element MUST be part of the + // list when this is called. + void remove(Elem *p) + { + assert(totalNum > 0); + + if(p->next) + { + // There's an element following us. Set it up correctly. + p->next->prev = p->prev; + assert(tail && tail != p); + } + else + { + // We're the tail + assert(tail == p); + tail = p->prev; + } + + // Now do exactly the same for the previous element + if(p->prev) + { + p->prev->next = p->next; + assert(head && head != p); + } + else + { + assert(head == p); + head = p->next; + } + + totalNum--; + } + + // Pop the first element off the list + Elem *pop() + { + Elem *res = getHead(); + if(res) remove(res); + return res; + } + + // Swap the contents of this list with another of the same type + void swap(List &other) + { + Elem *tmp; + + tmp = head; + head = other.head; + other.head = tmp; + + tmp = tail; + tail = other.tail; + other.tail = tmp; + + unsigned int tmp2 = totalNum; + totalNum = other.totalNum; + other.totalNum = tmp2; + } + + /* Absorb the contents of another list. All the elements from the + list are moved to the end of this list, and the other list is + cleared. + */ + void absorb(List &other) + { + assert(&other != this); + if(other.totalNum) + { + absorb(other.head, other.tail, other.totalNum); + other.reset(); + } + assert(other.totalNum == 0); + } + + /* Absorb a range of elements, endpoints included. The elements are + assumed NOT to belong to any list, but they ARE assumed to be + connected with a chain between them. + + The connection MUST run all the way from 'first' to 'last' + through the ->next pointers, and vice versa through ->prev + pointers. + + The parameter 'num' must give the exact number of elements in the + chain. + + Passing first == last, num == 1 is allowed and is equivalent to + calling insert(). + */ + void absorb(Elem* first, Elem *last, int num) + { + assert(first && last && num>=1); + if(tail) + { + // There are existing elements. Insert the first node at the + // end of the list. + assert(head && totalNum > 0); + tail->next = first; + } + else + { + // This is the first element + assert(head == 0 && totalNum == 0); + head = first; + } + + // These have to be done in either case + first->prev = tail; + last->next = 0; + tail = last; + + totalNum += num; + } + + Elem* getHead() const { return head; } + Elem* getTail() const { return tail; } + unsigned int getNum() const { return totalNum; } + +private: + + Elem *head; + Elem *tail; + unsigned int totalNum; +}; + +} +#endif diff --git a/libs/openengine/ogre/.gitignore b/libs/openengine/ogre/.gitignore new file mode 100644 index 000000000..3367afdbb --- /dev/null +++ b/libs/openengine/ogre/.gitignore @@ -0,0 +1 @@ +old diff --git a/libs/openengine/ogre/exitlistener.hpp b/libs/openengine/ogre/exitlistener.hpp new file mode 100644 index 000000000..5a9d1ff68 --- /dev/null +++ b/libs/openengine/ogre/exitlistener.hpp @@ -0,0 +1,37 @@ +#ifndef OENGINE_OGRE_EXITLISTEN_H +#define OENGINE_OGRE_EXITLISTEN_H + +/* + This FrameListener simply exits the rendering loop when the window + is closed. You can also tell it to exit manually by setting the exit + member to true; + */ + +#include +#include + +namespace OEngine { +namespace Render +{ + struct ExitListener : Ogre::FrameListener + { + Ogre::RenderWindow *window; + bool exit; + + ExitListener(Ogre::RenderWindow *wnd) + : window(wnd), exit(false) {} + + bool frameStarted(const Ogre::FrameEvent &evt) + { + if(window->isClosed()) + exit = true; + + return !exit; + } + + // Function equivalent of setting exit=true. Handy when you need a + // delegate to bind to an event. + void exitNow() { exit = true; } + }; +}} +#endif diff --git a/libs/openengine/ogre/fader.cpp b/libs/openengine/ogre/fader.cpp new file mode 100644 index 000000000..062559e00 --- /dev/null +++ b/libs/openengine/ogre/fader.cpp @@ -0,0 +1,136 @@ +#include "fader.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#define FADE_OVERLAY_NAME "FadeInOutOverlay" +#define FADE_OVERLAY_PANEL_NAME "FadeInOutOverlayPanel" +#define FADE_MATERIAL_NAME "FadeInOutMaterial" + +using namespace Ogre; +using namespace OEngine::Render; + +Fader::Fader() : + mMode(FadingMode_In), + mRemainingTime(0.f), + mTargetTime(0.f), + mTargetAlpha(0.f), + mCurrentAlpha(0.f), + mStartAlpha(0.f) +{ + + // Create the fading material + MaterialPtr material = MaterialManager::getSingleton().create( FADE_MATERIAL_NAME, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME ); + Pass* pass = material->getTechnique(0)->getPass(0); + pass->setSceneBlending(SBT_TRANSPARENT_ALPHA); + mFadeTextureUnit = pass->createTextureUnitState(); + mFadeTextureUnit->setColourOperationEx(LBX_SOURCE1, LBS_MANUAL, LBS_CURRENT, ColourValue(0.f, 0.f, 0.f)); // always black colour + + // Create the overlay + OverlayManager& ovm = OverlayManager::getSingleton(); + + mOverlay = ovm.create( FADE_OVERLAY_NAME ); + + OverlayContainer* overlay_panel; + overlay_panel = (OverlayContainer*)ovm.createOverlayElement("Panel", FADE_OVERLAY_PANEL_NAME); + + // position it over the whole screen + overlay_panel->_setPosition(0, 0); + overlay_panel->_setDimensions(1, 1); + + overlay_panel->setMaterialName( FADE_MATERIAL_NAME ); + overlay_panel->show(); + mOverlay->add2D(overlay_panel); + mOverlay->hide(); +} + +void Fader::update(float dt) +{ + if (mRemainingTime > 0) + { + if (mMode == FadingMode_In) + { + mCurrentAlpha -= dt/mTargetTime * (mStartAlpha-mTargetAlpha); + if (mCurrentAlpha < mTargetAlpha) mCurrentAlpha = mTargetAlpha; + } + else if (mMode == FadingMode_Out) + { + mCurrentAlpha += dt/mTargetTime * (mTargetAlpha-mStartAlpha); + if (mCurrentAlpha > mTargetAlpha) mCurrentAlpha = mTargetAlpha; + } + + applyAlpha(); + + mRemainingTime -= dt; + } + + if (mCurrentAlpha == 0.f) mOverlay->hide(); +} + +void Fader::applyAlpha() +{ + mOverlay->show(); + mFadeTextureUnit->setAlphaOperation(LBX_SOURCE1, LBS_MANUAL, LBS_CURRENT, mCurrentAlpha); +} + +void Fader::fadeIn(float time) +{ + if (time<0.f) return; + if (time==0.f) + { + mCurrentAlpha = 0.f; + applyAlpha(); + return; + } + + mStartAlpha = mCurrentAlpha; + mTargetAlpha = 0.f; + mMode = FadingMode_In; + mTargetTime = time; + mRemainingTime = time; +} + +void Fader::fadeOut(const float time) +{ + if (time<0.f) return; + if (time==0.f) + { + mCurrentAlpha = 1.f; + applyAlpha(); + return; + } + + mStartAlpha = mCurrentAlpha; + mTargetAlpha = 1.f; + mMode = FadingMode_Out; + mTargetTime = time; + mRemainingTime = time; +} + +void Fader::fadeTo(const int percent, const float time) +{ + if (time<0.f) return; + if (time==0.f) + { + mCurrentAlpha = percent/100.f; + applyAlpha(); + return; + } + + mStartAlpha = mCurrentAlpha; + mTargetAlpha = percent/100.f; + + if (mTargetAlpha == mStartAlpha) return; + else if (mTargetAlpha > mStartAlpha) mMode = FadingMode_Out; + else mMode = FadingMode_In; + + mTargetTime = time; + mRemainingTime = time; +} diff --git a/libs/openengine/ogre/fader.hpp b/libs/openengine/ogre/fader.hpp new file mode 100644 index 000000000..f76ac51ef --- /dev/null +++ b/libs/openengine/ogre/fader.hpp @@ -0,0 +1,57 @@ +#ifndef OENGINE_OGRE_FADE_H +#define OENGINE_OGRE_FADE_H + +/* + A class that handles fading in the screen from black or fading it out to black. + + To achieve this, it uses a full-screen Ogre::Overlay + + inspired by http://www.ogre3d.org/tikiwiki/FadeEffectOverlay (heavily adjusted) + */ + +#include + +namespace Ogre +{ + class TextureUnitState; + class Overlay; +} + +namespace OEngine { +namespace Render +{ + class Fader + { + public: + Fader(); + + void update(float dt); + + void fadeIn(const float time); + void fadeOut(const float time); + void fadeTo(const int percent, const float time); + + private: + enum FadingMode + { + FadingMode_In, + FadingMode_Out + }; + + void applyAlpha(); + + Ogre::TextureUnitState* mFadeTextureUnit; + Ogre::Overlay* mOverlay; + + FadingMode mMode; + + float mRemainingTime; + float mTargetTime; + float mTargetAlpha; + float mCurrentAlpha; + float mStartAlpha; + + protected: + }; +}} +#endif diff --git a/libs/openengine/ogre/mouselook.cpp b/libs/openengine/ogre/mouselook.cpp new file mode 100644 index 000000000..841bab603 --- /dev/null +++ b/libs/openengine/ogre/mouselook.cpp @@ -0,0 +1,57 @@ +#include "mouselook.hpp" + +#include +#include +#include + +using namespace OIS; +using namespace Ogre; +using namespace OEngine::Render; + +void MouseLookEvent::event(Type type, int index, const void *p) +{ + if(type != EV_MouseMove || camera == NULL) return; + + MouseEvent *arg = (MouseEvent*)(p); + + float x = arg->state.X.rel * sensX; + float y = arg->state.Y.rel * sensY; + + camera->getParentSceneNode()->getParentSceneNode()->yaw(Degree(-x)); + camera->getParentSceneNode()->pitch(Degree(-y)); + if(flipProt) + { + // The camera before pitching + /*Quaternion nopitch = camera->getParentSceneNode()->getOrientation(); + + camera->getParentSceneNode()->pitch(Degree(-y)); + + // Apply some failsafe measures against the camera flipping + // upside down. Is the camera close to pointing straight up or + // down? + if(Ogre::Vector3(camera->getParentSceneNode()->getOrientation()*Ogre::Vector3::UNIT_Y)[1] <= 0.1) + // If so, undo the last pitch + camera->getParentSceneNode()->setOrientation(nopitch);*/ + //camera->getU + + // Angle of rotation around the X-axis. + float pitchAngle = (2 * Ogre::Degree(Ogre::Math::ACos(camera->getParentSceneNode()->getOrientation().w)).valueDegrees()); + + // Just to determine the sign of the angle we pick up above, the + // value itself does not interest us. + float pitchAngleSign = camera->getParentSceneNode()->getOrientation().x; + + // Limit the pitch between -90 degress and +90 degrees, Quake3-style. + if (pitchAngle > 90.0f) + { + if (pitchAngleSign > 0) + // Set orientation to 90 degrees on X-axis. + camera->getParentSceneNode()->setOrientation(Ogre::Quaternion(Ogre::Math::Sqrt(0.5f), + Ogre::Math::Sqrt(0.5f), 0, 0)); + else if (pitchAngleSign < 0) + // Sets orientation to -90 degrees on X-axis. + camera->getParentSceneNode()->setOrientation(Ogre::Quaternion(Ogre::Math::Sqrt(0.5f), + -Ogre::Math::Sqrt(0.5f), 0, 0)); + } + } +} diff --git a/libs/openengine/ogre/mouselook.hpp b/libs/openengine/ogre/mouselook.hpp new file mode 100644 index 000000000..6e09ff4a1 --- /dev/null +++ b/libs/openengine/ogre/mouselook.hpp @@ -0,0 +1,56 @@ +#ifndef OENGINE_OGRE_MOUSELOOK_H +#define OENGINE_OGRE_MOUSELOOK_H + +/* + A mouse-look class for Ogre. Accepts input events from Mangle::Input + and translates them. + + You can adjust the mouse sensibility and switch to a different + camera. The mouselook class also has an optional wrap protection + that keeps the camera from flipping upside down. + + You can disable the mouse looker at any time by calling + setCamera(NULL), and reenable it by setting the camera back. + + NOTE: The current implementation will ONLY work for native OIS + events. + */ + +#include + +namespace Ogre +{ + class Camera; +} + +namespace OEngine { +namespace Render +{ + class MouseLookEvent : public Mangle::Input::Event + { + Ogre::Camera* camera; + float sensX, sensY; // Mouse sensibility + bool flipProt; // Flip protection + + public: + MouseLookEvent(Ogre::Camera *cam=NULL, + float sX=0.2, float sY=0.2, + bool prot=true) + : camera(cam) + , sensX(sX) + , sensY(sY) + , flipProt(prot) + {} + + void setCamera(Ogre::Camera *cam) + { camera = cam; } + void setSens(float sX, float sY) + { sensX = sX; sensY = sY; } + void setProt(bool p) { flipProt = p; } + + void event(Type type, int index, const void *p); + }; + + typedef boost::shared_ptr MouseLookEventPtr; +}} +#endif diff --git a/libs/openengine/ogre/renderer.cpp b/libs/openengine/ogre/renderer.cpp new file mode 100644 index 000000000..ecded08d8 --- /dev/null +++ b/libs/openengine/ogre/renderer.cpp @@ -0,0 +1,118 @@ +#include "renderer.hpp" +#include "fader.hpp" + +#include "OgreRoot.h" +#include "OgreRenderWindow.h" +#include "OgreLogManager.h" +#include "OgreLog.h" + +#include + +using namespace Ogre; +using namespace OEngine::Render; + +void OgreRenderer::cleanup() +{ + if (mFader) + delete mFader; + + if(mRoot) + delete mRoot; + mRoot = NULL; +} + +void OgreRenderer::start() +{ + mRoot->startRendering(); +} + +void OgreRenderer::update(float dt) +{ + mFader->update(dt); +} + +void OgreRenderer::screenshot(const std::string &file) +{ + mWindow->writeContentsToFile(file); +} + +float OgreRenderer::getFPS() +{ + return mWindow->getLastFPS(); +} + +bool OgreRenderer::configure(bool showConfig, + const std::string &cfgPath, + const std::string &logPath, + const std::string &pluginCfg, + bool _logging) +{ + // Set up logging first + new LogManager; + Log *log = LogManager::getSingleton().createLog(logPath + std::string("Ogre.log")); + logging = _logging; + + if(logging) + // Full log detail + log->setLogDetail(LL_BOREME); + else + // Disable logging + log->setDebugOutputEnabled(false); + + mRoot = new Root(pluginCfg, cfgPath, ""); + + // Show the configuration dialog and initialise the system, if the + // showConfig parameter is specified. The settings are stored in + // ogre.cfg. If showConfig is false, the settings are assumed to + // already exist in ogre.cfg. + int result; + if(showConfig) + result = mRoot->showConfigDialog(); + else + result = mRoot->restoreConfig(); + + return !result; +} + +bool OgreRenderer::configure(bool showConfig, + const std::string &cfgPath, + const std::string &pluginCfg, + bool _logging) +{ + return configure(showConfig, cfgPath, cfgPath, pluginCfg, _logging); +} + +bool OgreRenderer::configure(bool showConfig, + const std::string &pluginCfg, + bool _logging) +{ + return configure(showConfig, "", pluginCfg, _logging); +} + +void OgreRenderer::createWindow(const std::string &title) +{ + assert(mRoot); + // Initialize OGRE window + mWindow = mRoot->initialise(true, title, ""); +} + +void OgreRenderer::createScene(const std::string camName, float fov, float nearClip) +{ + assert(mRoot); + assert(mWindow); + // Get the SceneManager, in this case a generic one + mScene = mRoot->createSceneManager(ST_GENERIC); + + // Create the camera + mCamera = mScene->createCamera(camName); + mCamera->setNearClipDistance(nearClip); + mCamera->setFOVy(Degree(fov)); + + // Create one viewport, entire window + mView = mWindow->addViewport(mCamera); + + // Alter the camera aspect ratio to match the viewport + mCamera->setAspectRatio(Real(mView->getActualWidth()) / Real(mView->getActualHeight())); + + mFader = new Fader(); +} diff --git a/libs/openengine/ogre/renderer.hpp b/libs/openengine/ogre/renderer.hpp new file mode 100644 index 000000000..f48383cbc --- /dev/null +++ b/libs/openengine/ogre/renderer.hpp @@ -0,0 +1,98 @@ +#ifndef OENGINE_OGRE_RENDERER_H +#define OENGINE_OGRE_RENDERER_H + +/* + Ogre renderer class + */ + +#include + +namespace Ogre +{ + class Root; + class RenderWindow; + class SceneManager; + class Camera; + class Viewport; +} + +namespace OEngine { +namespace Render +{ + class Fader; + class OgreRenderer + { + Ogre::Root *mRoot; + Ogre::RenderWindow *mWindow; + Ogre::SceneManager *mScene; + Ogre::Camera *mCamera; + Ogre::Viewport *mView; + Fader* mFader; + bool logging; + + public: + OgreRenderer() + : mRoot(NULL), mWindow(NULL), mScene(NULL), mFader(NULL) {} + ~OgreRenderer() { cleanup(); } + + /** Configure the renderer. This will load configuration files and + set up the Root and logging classes. */ + bool configure(bool showConfig, // Show config dialog box? + const std::string &cfgPath, // Path to directory where to store config files + const std::string &logPath, // Path to directory where to store log files + const std::string &pluginCfg, // plugin.cfg file + bool _logging); // Enable or disable logging + + bool configure(bool showConfig, // Show config dialog box? + const std::string &cfgPath, // Path to directory where to store config files + const std::string &pluginCfg, // plugin.cfg file + bool _logging); // Enable or disable logging + + /** Configure the renderer. This will load configuration files and + set up the Root and logging classes. */ + bool configure(bool showConfig, // Show config dialog box? + const std::string &pluginCfg, // plugin.cfg file + bool _logging); // Enable or disable logging + + /// Create a window with the given title + void createWindow(const std::string &title); + + /// Set up the scene manager, camera and viewport + void createScene(const std::string camName="Camera",// Camera name + float fov=55, // Field of view angle + float nearClip=5 // Near clip distance + ); + + /// Kill the renderer. + void cleanup(); + + /// Start the main rendering loop + void start(); + + void update(float dt); + + /// Write a screenshot to file + void screenshot(const std::string &file); + + float getFPS(); + + /// Get the Root + Ogre::Root *getRoot() { return mRoot; } + + /// Get the rendering window + Ogre::RenderWindow *getWindow() { return mWindow; } + + /// Get the scene manager + Ogre::SceneManager *getScene() { return mScene; } + + /// Get the screen colour fader + Fader *getFader() { return mFader; } + + /// Camera + Ogre::Camera *getCamera() { return mCamera; } + + /// Viewport + Ogre::Viewport *getViewport() { return mView; } + }; +}} +#endif diff --git a/libs/openengine/sound/sndmanager.cpp b/libs/openengine/sound/sndmanager.cpp new file mode 100644 index 000000000..02c6ba1e7 --- /dev/null +++ b/libs/openengine/sound/sndmanager.cpp @@ -0,0 +1,219 @@ +#include "sndmanager.hpp" + +#include "../misc/list.hpp" +#include + +using namespace OEngine::Sound; +using namespace Mangle::Sound; + +/** This is our own internal implementation of the + Mangle::Sound::Sound interface. This class links a SoundPtr to + itself and prevents itself from being deleted as long as the sound + is playing. + */ +struct OEngine::Sound::ManagedSound : SoundFilter +{ +private: + /** Who's your daddy? This is set if and only if we are listed + internally in the given SoundManager. + + It may be NULL if the manager has been deleted but the user + keeps their own SoundPtrs to the object. + */ + SoundManager *mgr; + + /** Keep a weak pointer to ourselves, which we convert into a + 'strong' pointer when we are playing. When 'self' is pointing to + ourselves, the object will never be deleted. + + This is used to make sure the sound is not deleted while + playing, unless it is explicitly ordered to do so by the + manager. + + TODO: This kind of construct is useful. If we need it elsewhere + later, template it. It would be generally useful in any system + where we poll to check if a resource is still needed, but where + manual references are allowed. + */ + WSoundPtr weak; + SoundPtr self; + + // Keep this object from being deleted + void lock() + { + self = SoundPtr(weak); + } + + // Release the lock. This may or may not delete the object. Never do + // anything after calling unlock()! + void unlock() + { + self.reset(); + } + +public: + // Used for putting ourselves in linked lists + ManagedSound *next, *prev; + + /** Detach this sound from its manager. This means that the manager + will no longer know we exist. Typically only called when either + the sound or the manager is about to get deleted. + + Since this means update() will no longer be called, we also have + to unlock the sound manually since it will no longer be able to + do that itself. This means that the sound may be deleted, even + if it is still playing, when the manager is deleted. + + However, you are still allowed to keep and manage your own + SoundPtr references, but the lock/unlock system is disabled + after the manager is gone. + */ + void detach() + { + if(mgr) + { + mgr->detach(this); + mgr = NULL; + } + + // Unlock must be last command. Object may get deleted at this + // point. + unlock(); + } + + ManagedSound(SoundPtr snd, SoundManager *mg) + : SoundFilter(snd), mgr(mg) + {} + ~ManagedSound() { detach(); } + + // Needed to set up the weak pointer + void setup(SoundPtr self) + { + weak = WSoundPtr(self); + } + + // Override play() to mark the object as locked + void play() + { + SoundFilter::play(); + + // Lock the object so that it is not deleted while playing. Only + // do this if we have a manager, otherwise the object will never + // get unlocked. + if(mgr) lock(); + } + + // Called regularly by the manager + void update() + { + // If we're no longer playing, don't force object retention. + if(!isPlaying()) + unlock(); + + // unlock() may delete the object, so don't do anything below this + // point. + } + + SoundPtr clone() + { + // Cloning only works when we have a manager. + assert(mgr); + return mgr->wrap(client->clone()); + } +}; + +struct SoundManager::SoundManagerList +{ +private: + // A linked list of ManagedSound objects. + typedef Misc::List SoundList; + SoundList list; + +public: + // Add a new sound to the list + void addNew(ManagedSound* snd) + { + list.insert(snd); + } + + // Remove a sound from the list + void remove(ManagedSound *snd) + { + list.remove(snd); + } + + // Number of sounds in the list + int numSounds() { return list.getNum(); } + + // Update all sounds + void updateAll() + { + ManagedSound *s = list.getHead(); + while(s) + { + ManagedSound *cur = s; + // Propagate first, since update() may delete object + s = s->next; + cur->update(); + } + } + + // Detach and unlock all sounds + void detachAll() + { + ManagedSound *s = list.getHead(); + while(s) + { + ManagedSound *cur = s; + s = s->next; + cur->detach(); + } + } +}; + +SoundManager::SoundManager(SoundFactoryPtr fact) + : FactoryFilter(fact) +{ + needsUpdate = true; + list = new SoundManagerList; +} + +SoundManager::~SoundManager() +{ + // Detach all sounds + list->detachAll(); +} + +SoundPtr SoundManager::wrap(SoundPtr client) +{ + // Create and set up the sound wrapper + ManagedSound *snd = new ManagedSound(client,this); + SoundPtr ptr(snd); + snd->setup(ptr); + + // Add ourselves to the list of all sounds + list->addNew(snd); + + return ptr; +} + +// Remove the sound from this manager. +void SoundManager::detach(ManagedSound *sound) +{ + list->remove(sound); +} + +int SoundManager::numSounds() +{ + return list->numSounds(); +} + +void SoundManager::update() +{ + // Update all the sounds we own + list->updateAll(); + + // Update the source if it needs it + if(client->needsUpdate) + client->update(); +} diff --git a/libs/openengine/sound/sndmanager.hpp b/libs/openengine/sound/sndmanager.hpp new file mode 100644 index 000000000..5ea0c4fc3 --- /dev/null +++ b/libs/openengine/sound/sndmanager.hpp @@ -0,0 +1,93 @@ +#ifndef OENGINE_SOUND_MANAGER_H +#define OENGINE_SOUND_MANAGER_H + +#include + +namespace OEngine +{ + namespace Sound + { + using namespace Mangle::Sound; + + class ManagedSound; + + /** A manager of Mangle::Sounds. + + The sound manager is a wrapper around the more low-level + SoundFactory - although it is also itself an implementation of + SoundFactory. It will: + - keep a list of all created sounds + - let you iterate the list + - keep references to playing sounds so you don't have to + - auto-release references to sounds that are finished playing + (ie. deleting them if you're not referencing them) + */ + class SoundManager : public FactoryFilter + { + // Shove the implementation details into the cpp file. + struct SoundManagerList; + SoundManagerList *list; + + // Create a new sound wrapper based on the given source sound. + SoundPtr wrap(SoundPtr snd); + + /** Internal function. Will completely disconnect the given + sound from this manager. Called from ManagedSound. + */ + friend class ManagedSound; + void detach(ManagedSound *sound); + public: + SoundManager(SoundFactoryPtr fact); + ~SoundManager(); + void update(); + + /// Get number of sounds currently managed by this manager. + int numSounds(); + + SoundPtr loadRaw(SampleSourcePtr input) + { return wrap(client->loadRaw(input)); } + + SoundPtr load(Mangle::Stream::StreamPtr input) + { return wrap(client->load(input)); } + + SoundPtr load(const std::string &file) + { return wrap(client->load(file)); } + + // Play a sound immediately, and release when done unless you + // keep the returned SoundPtr. + SoundPtr play(Mangle::Stream::StreamPtr sound) + { + SoundPtr snd = load(sound); + snd->play(); + return snd; + } + + SoundPtr play(const std::string &sound) + { + SoundPtr snd = load(sound); + snd->play(); + return snd; + } + + // Ditto for 3D sounds + SoundPtr play3D(Mangle::Stream::StreamPtr sound, float x, float y, float z) + { + SoundPtr snd = load(sound); + snd->setPos(x,y,z); + snd->play(); + return snd; + } + + SoundPtr play3D(const std::string &sound, float x, float y, float z) + { + SoundPtr snd = load(sound); + snd->setPos(x,y,z); + snd->play(); + return snd; + } + }; + + typedef boost::shared_ptr SoundManagerPtr; + } +} +#endif diff --git a/libs/openengine/sound/tests/Makefile b/libs/openengine/sound/tests/Makefile new file mode 100644 index 000000000..04952167f --- /dev/null +++ b/libs/openengine/sound/tests/Makefile @@ -0,0 +1,16 @@ +GCC=g++ -I../ + +all: sound_manager_test sound_3d_test + +L_FFMPEG=$(shell pkg-config --libs libavcodec libavformat) +L_OPENAL=$(shell pkg-config --libs openal) +L_AUDIERE=-laudiere + +sound_manager_test: sound_manager_test.cpp ../../mangle/sound/sources/audiere_source.cpp ../../mangle/sound/outputs/openal_out.cpp ../../mangle/stream/clients/audiere_file.cpp ../sndmanager.cpp + $(GCC) $^ -o $@ $(L_AUDIERE) $(L_OPENAL) -I../.. + +sound_3d_test: sound_3d_test.cpp ../../mangle/sound/sources/audiere_source.cpp ../../mangle/sound/outputs/openal_out.cpp ../../mangle/stream/clients/audiere_file.cpp ../sndmanager.cpp + $(GCC) $^ -o $@ $(L_AUDIERE) $(L_OPENAL) -I../.. + +clean: + rm *_test diff --git a/libs/openengine/sound/tests/output/sound_3d_test.out b/libs/openengine/sound/tests/output/sound_3d_test.out new file mode 100644 index 000000000..a443c84f0 --- /dev/null +++ b/libs/openengine/sound/tests/output/sound_3d_test.out @@ -0,0 +1,3 @@ +Playing at 0,0,0 +Playing at 1,1,0 +Playing at -1,0,0 diff --git a/libs/openengine/sound/tests/output/sound_manager_test.out b/libs/openengine/sound/tests/output/sound_manager_test.out new file mode 100644 index 000000000..2b458493d --- /dev/null +++ b/libs/openengine/sound/tests/output/sound_manager_test.out @@ -0,0 +1,5 @@ +Playing ../../mangle/sound/tests/cow.wav +Replaying +pause +restart +Done playing. diff --git a/libs/openengine/sound/tests/sound_3d_test.cpp b/libs/openengine/sound/tests/sound_3d_test.cpp new file mode 100644 index 000000000..f5b197fd0 --- /dev/null +++ b/libs/openengine/sound/tests/sound_3d_test.cpp @@ -0,0 +1,46 @@ +#include +#include +#include + +#include +#include + +#include + +using namespace std; +using namespace Mangle::Stream; +using namespace Mangle::Sound; +using namespace OEngine::Sound; + +const std::string sound = "../../mangle/sound/tests/cow.wav"; + +SoundManagerPtr m; + +// Play and wait for finish +void play(float x, float y, float z) +{ + cout << "Playing at " << x << "," << y << "," << z << endl; + + SoundPtr snd = m->play3D(sound,x,y,z); + + while(snd->isPlaying()) + { + usleep(10000); + m->update(); + } +} + +int main() +{ + SoundFactoryPtr oaf(new OpenAL_Audiere_Factory); + SoundManagerPtr mg(new SoundManager(oaf)); + m = mg; + + mg->setListenerPos(0,0,0,0,1,0,0,0,1); + + play(0,0,0); + play(1,1,0); + play(-1,0,0); + + return 0; +} diff --git a/libs/openengine/sound/tests/sound_manager_test.cpp b/libs/openengine/sound/tests/sound_manager_test.cpp new file mode 100644 index 000000000..3794c4a3c --- /dev/null +++ b/libs/openengine/sound/tests/sound_manager_test.cpp @@ -0,0 +1,73 @@ +#include +#include +#include + +#include +#include + +#include + +using namespace std; +using namespace Mangle::Stream; +using namespace Mangle::Sound; +using namespace OEngine::Sound; + +const std::string sound = "../../mangle/sound/tests/cow.wav"; + +int main() +{ + SoundFactoryPtr oaf(new OpenAL_Audiere_Factory); + SoundManagerPtr mg(new SoundManager(oaf)); + + cout << "Playing " << sound << "\n"; + + assert(mg->numSounds() == 0); + + /** Start the sound playing, and then let the pointer go out of + scope. Lower-level players (like 'oaf' above) will immediately + delete the sound. SoundManager OTOH will keep it until it's + finished. + */ + mg->play(sound); + + assert(mg->numSounds() == 1); + + // Loop while there are still sounds to manage + while(mg->numSounds() != 0) + { + assert(mg->numSounds() == 1); + usleep(10000); + if(mg->needsUpdate) + mg->update(); + } + + SoundPtr snd = mg->play(sound); + cout << "Replaying\n"; + int i = 0; + while(mg->numSounds() != 0) + { + assert(mg->numSounds() == 1); + usleep(10000); + if(mg->needsUpdate) + mg->update(); + + if(i++ == 70) + { + cout << "pause\n"; + snd->pause(); + } + if(i == 130) + { + cout << "restart\n"; + snd->play(); + // Let the sound go out of scope + snd.reset(); + } + } + + cout << "Done playing.\n"; + + assert(mg->numSounds() == 0); + + return 0; +} diff --git a/libs/openengine/sound/tests/test.sh b/libs/openengine/sound/tests/test.sh new file mode 100755 index 000000000..2d07708ad --- /dev/null +++ b/libs/openengine/sound/tests/test.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +make || exit + +mkdir -p output + +PROGS=*_test + +for a in $PROGS; do + if [ -f "output/$a.out" ]; then + echo "Running $a:" + ./$a | diff output/$a.out - + else + echo "Creating $a.out" + ./$a > "output/$a.out" + git add "output/$a.out" + fi +done diff --git a/libs/openengine/testall.sh b/libs/openengine/testall.sh new file mode 100755 index 000000000..097fdabd5 --- /dev/null +++ b/libs/openengine/testall.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +function run() +{ + echo "TESTING $1" + cd "$1/tests/" + ./test.sh + cd ../../ +} + +run input