diff --git a/CMakeLists.txt b/CMakeLists.txt index 36e45d78a2..208d348fbe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,8 +37,8 @@ option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF) option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest ang GMock frameworks" OFF) # Sound source selection -option(USE_FFMPEG "use ffmpeg for sound" OFF) -option(USE_AUDIERE "use audiere for sound" OFF) +option(USE_FFMPEG "use ffmpeg for sound" ON) +option(USE_AUDIERE "use audiere for sound" ON) option(USE_MPG123 "use mpg123 + libsndfile for sound" ON) # OS X deployment @@ -137,30 +137,54 @@ set(OPENMW_LIBS ${OENGINE_ALL}) set(OPENMW_LIBS_HEADER) # Sound setup +set(GOT_SOUND_INPUT 0) set(SOUND_INPUT_INCLUDES "") set(SOUND_INPUT_LIBRARY "") set(SOUND_DEFINE "") if (USE_FFMPEG) - find_package(FFMPEG REQUIRED) - set(SOUND_INPUT_INCLUDES ${SOUND_INPUT_INCLUDES} ${FFMPEG_INCLUDE_DIR}) - set(SOUND_INPUT_LIBRARY ${SOUND_INPUT_LIBRARY} ${FFMPEG_LIBRARIES}) - set(SOUND_DEFINE ${SOUND_DEFINE} -DOPENMW_USE_FFMPEG) + set(FFmpeg_FIND_COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE) + find_package(FFmpeg) + if (FFMPEG_FOUND) + set(SOUND_INPUT_INCLUDES ${SOUND_INPUT_INCLUDES} ${FFMPEG_INCLUDE_DIRS}) + set(SOUND_INPUT_LIBRARY ${SOUND_INPUT_LIBRARY} ${FFMPEG_LIBRARIES} ${SWSCALE_LIBRARIES}) + set(SOUND_DEFINE ${SOUND_DEFINE} -DOPENMW_USE_FFMPEG) + set(GOT_SOUND_INPUT 1) + endif (FFMPEG_FOUND) endif (USE_FFMPEG) -if (USE_AUDIERE) - find_package(Audiere REQUIRED) - set(SOUND_INPUT_INCLUDES ${SOUND_INPUT_INCLUDES} ${AUDIERE_INCLUDE_DIR}) - set(SOUND_INPUT_LIBRARY ${SOUND_INPUT_LIBRARY} ${AUDIERE_LIBRARY}) - set(SOUND_DEFINE ${SOUND_DEFINE} -DOPENMW_USE_AUDIERE) -endif (USE_AUDIERE) +if (USE_AUDIERE AND NOT GOT_SOUND_INPUT) + find_package(Audiere) + if (AUDIERE_FOUND) + set(SOUND_INPUT_INCLUDES ${SOUND_INPUT_INCLUDES} ${AUDIERE_INCLUDE_DIR}) + set(SOUND_INPUT_LIBRARY ${SOUND_INPUT_LIBRARY} ${AUDIERE_LIBRARY}) + set(SOUND_DEFINE ${SOUND_DEFINE} -DOPENMW_USE_AUDIERE) + set(GOT_SOUND_INPUT 1) + endif (AUDIERE_FOUND) +endif (USE_AUDIERE AND NOT GOT_SOUND_INPUT) -if (USE_MPG123) +if (USE_MPG123 AND NOT GOT_SOUND_INPUT) find_package(MPG123 REQUIRED) find_package(SNDFILE REQUIRED) - set(SOUND_INPUT_INCLUDES ${SOUND_INPUT_INCLUDES} ${MPG123_INCLUDE_DIR} ${SNDFILE_INCLUDE_DIR}) - set(SOUND_INPUT_LIBRARY ${SOUND_INPUT_LIBRARY} ${MPG123_LIBRARY} ${SNDFILE_LIBRARY}) - set(SOUND_DEFINE ${SOUND_DEFINE} -DOPENMW_USE_MPG123) -endif (USE_MPG123) + if (MPG123_FOUND AND SNDFILE_FOUND) + set(SOUND_INPUT_INCLUDES ${SOUND_INPUT_INCLUDES} ${MPG123_INCLUDE_DIR} ${SNDFILE_INCLUDE_DIR}) + set(SOUND_INPUT_LIBRARY ${SOUND_INPUT_LIBRARY} ${MPG123_LIBRARY} ${SNDFILE_LIBRARY}) + set(SOUND_DEFINE ${SOUND_DEFINE} -DOPENMW_USE_MPG123) + set(GOT_SOUND_INPUT 1) + endif (MPG123_FOUND AND SNDFILE_FOUND) +endif (USE_MPG123 AND NOT GOT_SOUND_INPUT) + +if (NOT GOT_SOUND_INPUT) + message(WARNING "--------------------") + message(WARNING "Failed to find any sound input packages") + message(WARNING "--------------------") +endif (NOT GOT_SOUND_INPUT) + +if (NOT FFMPEG_FOUND) + message(WARNING "--------------------") + message(WARNING "FFmpeg not found, video playback will be disabled") + message(WARNING "--------------------") +endif (NOT FFMPEG_FOUND) + # Platform specific if (WIN32) @@ -534,7 +558,9 @@ if (WIN32) set_target_properties(omwlauncher PROPERTIES COMPILE_FLAGS ${WARNINGS}) endif (BUILD_LAUNCHER) set_target_properties(openmw PROPERTIES COMPILE_FLAGS ${WARNINGS}) - set_target_properties(esmtool PROPERTIES COMPILE_FLAGS ${WARNINGS}) + if (BUILD_ESMTOOL) + set_target_properties(esmtool PROPERTIES COMPILE_FLAGS ${WARNINGS}) + endif (BUILD_ESMTOOL) endif(MSVC) # Same for MinGW diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index 0cd6e39053..fbfc884d70 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -165,23 +165,12 @@ bool parseOptions (int argc, char** argv, Arguments &info) // Font encoding settings info.encoding = variables["encoding"].as(); - if (info.encoding == "win1250") + if(info.encoding != "win1250" && info.encoding != "win1251" && info.encoding != "win1252") { - std::cout << "Using Central and Eastern European font encoding." << std::endl; - } - else if (info.encoding == "win1251") - { - std::cout << "Using Cyrillic font encoding." << std::endl; - } - else - { - if(info.encoding != "win1252") - { - std::cout << info.encoding << " is not a valid encoding option." << std::endl; - info.encoding = "win1252"; - } - std::cout << "Using default (English) font encoding." << std::endl; + std::cout << info.encoding << " is not a valid encoding option." << std::endl; + info.encoding = "win1252"; } + std::cout << ToUTF8::encodingUsingMessage(info.encoding) << std::endl; return true; } @@ -262,7 +251,8 @@ void printRaw(ESM::ESMReader &esm) int load(Arguments& info) { ESM::ESMReader& esm = info.reader; - esm.setEncoding(ToUTF8::calculateEncoding(info.encoding)); + ToUTF8::Utf8Encoder encoder (ToUTF8::calculateEncoding(info.encoding)); + esm.setEncoder(&encoder); std::string filename = info.filename; std::cout << "Loading file: " << filename << std::endl; @@ -432,7 +422,8 @@ int clone(Arguments& info) std::cout << std::endl << "Saving records to: " << info.outname << "..." << std::endl; ESM::ESMWriter& esm = info.writer; - esm.setEncoding(info.encoding); + ToUTF8::Utf8Encoder encoder (ToUTF8::calculateEncoding(info.encoding)); + esm.setEncoder(&encoder); esm.setAuthor(info.data.author); esm.setDescription(info.data.description); esm.setVersion(info.data.version); diff --git a/apps/launcher/model/datafilesmodel.cpp b/apps/launcher/model/datafilesmodel.cpp index 716c9e9026..e84dbe0acc 100644 --- a/apps/launcher/model/datafilesmodel.cpp +++ b/apps/launcher/model/datafilesmodel.cpp @@ -272,7 +272,8 @@ void DataFilesModel::addMasters(const QString &path) foreach (const QString &path, dir.entryList()) { try { ESM::ESMReader fileReader; - fileReader.setEncoding(ToUTF8::calculateEncoding(mEncoding.toStdString())); + ToUTF8::Utf8Encoder encoder (ToUTF8::calculateEncoding(mEncoding.toStdString())); + fileReader.setEncoder(&encoder); fileReader.open(dir.absoluteFilePath(path).toStdString()); ESM::ESMReader::MasterList mlist = fileReader.getMasters(); @@ -335,7 +336,8 @@ void DataFilesModel::addPlugins(const QString &path) try { ESM::ESMReader fileReader; - fileReader.setEncoding(ToUTF8::calculateEncoding(mEncoding.toStdString())); + ToUTF8::Utf8Encoder encoder (ToUTF8::calculateEncoding(mEncoding.toStdString())); + fileReader.setEncoder(&encoder); fileReader.open(dir.absoluteFilePath(path).toStdString()); ESM::ESMReader::MasterList mlist = fileReader.getMasters(); diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index def70615bc..6a7274e0a1 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -649,11 +649,12 @@ MwIniImporter::multistrmap MwIniImporter::loadIniFile(std::string filename) { std::string section(""); MwIniImporter::multistrmap map; boost::iostreams::streamfile(filename.c_str()); + ToUTF8::Utf8Encoder encoder(mEncoding); std::string line; while (std::getline(file, line)) { - line = toUTF8(line); + line = encoder.getUtf8(line); // unify Unix-style and Windows file ending if (!(line.empty()) && (line[line.length()-1]) == '\r') { @@ -829,14 +830,6 @@ void MwIniImporter::writeToFile(boost::iostreams::stream #include -#include "../../components/to_utf8/to_utf8.hpp" +#include class MwIniImporter { public: diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 56147b5008..482007090c 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -16,7 +16,7 @@ source_group(game FILES ${GAME} ${GAME_HEADER}) add_openmw_dir (mwrender renderingmanager debugging sky player animation npcanimation creatureanimation actors objects renderinginterface localmap occlusionquery terrain terrainmaterial water shadows - compositors characterpreview externalrendering globalmap + compositors characterpreview externalrendering globalmap videoplayer ) add_openmw_dir (mwinput diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index e2d28a808d..aadeb7f3a2 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -331,11 +331,15 @@ void OMW::Engine::go() // cursor replacer (converts the cursor from the bsa so they can be used by mygui) MWGui::CursorReplace replacer; + // Create encoder + ToUTF8::Utf8Encoder encoder (mEncoding); + // Create the world mEnvironment.setWorld (new MWWorld::World (*mOgre, mFileCollections, mMaster, - mResDir, mCfgMgr.getCachePath(), mNewGame, mEncoding, mFallbackMap)); + mResDir, mCfgMgr.getCachePath(), mNewGame, &encoder, mFallbackMap)); //Load translation data + mTranslationDataStorage.setEncoder(&encoder); mTranslationDataStorage.loadTranslationData(mFileCollections, mMaster); // Create window manager - this manages all the MW-specific GUI windows @@ -494,7 +498,6 @@ void OMW::Engine::showFPS(int level) void OMW::Engine::setEncoding(const ToUTF8::FromType& encoding) { mEncoding = encoding; - mTranslationDataStorage.setEncoding (encoding); } void OMW::Engine::setFallbackValues(std::map fallbackMap) diff --git a/apps/openmw/mwbase/environment.cpp b/apps/openmw/mwbase/environment.cpp index 9aaa5af85a..5a13a50ec9 100644 --- a/apps/openmw/mwbase/environment.cpp +++ b/apps/openmw/mwbase/environment.cpp @@ -128,12 +128,6 @@ float MWBase::Environment::getFrameDuration() const void MWBase::Environment::cleanup() { - delete mInputManager; - mInputManager = 0; - - delete mSoundManager; - mSoundManager = 0; - delete mMechanicsManager; mMechanicsManager = 0; @@ -146,11 +140,17 @@ void MWBase::Environment::cleanup() delete mScriptManager; mScriptManager = 0; + delete mWorld; + mWorld = 0; + + delete mSoundManager; + mSoundManager = 0; + delete mWindowManager; mWindowManager = 0; - delete mWorld; - mWorld = 0; + delete mInputManager; + mInputManager = 0; } const MWBase::Environment& MWBase::Environment::get() diff --git a/apps/openmw/mwbase/soundmanager.hpp b/apps/openmw/mwbase/soundmanager.hpp index 92c177ff30..0c706ab496 100644 --- a/apps/openmw/mwbase/soundmanager.hpp +++ b/apps/openmw/mwbase/soundmanager.hpp @@ -22,6 +22,8 @@ namespace MWWorld namespace MWSound { class Sound; + class Sound_Decoder; + typedef boost::shared_ptr DecoderPtr; } namespace MWBase @@ -32,7 +34,7 @@ namespace MWBase class SoundManager { public: - + /* These must all fit together */ enum PlayMode { Play_Normal = 0, /* tracked, non-looping, multi-instance, environment */ Play_Loop = 1<<0, /* Sound will continually loop until explicitly stopped */ @@ -41,6 +43,13 @@ namespace MWBase * but do not keep it updated (the sound will not move with * the object and will not stop when the object is deleted. */ }; + enum PlayType { + Play_TypeSfx = 1<<3, /* Normal SFX sound */ + Play_TypeVoice = 1<<4, /* Voice sound */ + Play_TypeMusic = 1<<5, /* Music track */ + Play_TypeMovie = 1<<6, /* Movie audio track */ + Play_TypeMask = Play_TypeSfx|Play_TypeVoice|Play_TypeMusic|Play_TypeMovie + }; private: @@ -89,12 +98,15 @@ namespace MWBase virtual void stopSay(MWWorld::Ptr reference=MWWorld::Ptr()) = 0; ///< Stop an actor speaking + virtual SoundPtr playTrack(const MWSound::DecoderPtr& decoder, PlayType type) = 0; + ///< Play a 2D audio track, using a custom decoder + virtual SoundPtr playSound(const std::string& soundId, float volume, float pitch, - int mode=Play_Normal) = 0; + PlayMode mode=Play_Normal) = 0; ///< Play a sound, independently of 3D-position virtual SoundPtr playSound3D(MWWorld::Ptr reference, const std::string& soundId, - float volume, float pitch, int mode=Play_Normal) = 0; + float volume, float pitch, PlayMode mode=Play_Normal) = 0; ///< Play a sound from an object virtual void stopSound3D(MWWorld::Ptr reference, const std::string& soundId) = 0; @@ -112,15 +124,16 @@ namespace MWBase virtual bool getSoundPlaying(MWWorld::Ptr reference, const std::string& soundId) const = 0; ///< Is the given sound currently playing on the given object? + virtual void pauseSounds(int types=Play_TypeMask) = 0; + ///< Pauses all currently playing sounds, including music. + + virtual void resumeSounds(int types=Play_TypeMask) = 0; + ///< Resumes all previously paused sounds. + virtual void update(float duration) = 0; virtual void setListenerPosDir(const Ogre::Vector3 &pos, const Ogre::Vector3 &dir, const Ogre::Vector3 &up) = 0; }; - - inline int operator|(SoundManager::PlayMode a, SoundManager::PlayMode b) - { return static_cast (a) | static_cast (b); } - inline int operator&(SoundManager::PlayMode a, SoundManager::PlayMode b) - { return static_cast (a) & static_cast (b); } } #endif diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index cc625b3067..25d999c93d 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -306,6 +306,11 @@ namespace MWBase /// 1 - only waiting \n /// 2 - player is underwater \n /// 3 - enemies are nearby (not implemented) + + + /// \todo this does not belong here + virtual void playVideo(const std::string& name, bool allowSkipping) = 0; + virtual void stopVideo() = 0; }; } diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index d721e209a6..a508bcd345 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -213,22 +213,26 @@ namespace MWGui void LoadingScreen::changeWallpaper () { - std::vector splash; - - Ogre::StringVectorPtr resources = Ogre::ResourceGroupManager::getSingleton ().listResourceNames ("General", false); - for (Ogre::StringVector::const_iterator it = resources->begin(); it != resources->end(); ++it) + if (mResources.isNull ()) { - if (it->size() < 6) - continue; - std::string start = it->substr(0, 6); - boost::to_lower(start); + mResources = Ogre::StringVectorPtr (new Ogre::StringVector); - if (start == "splash") - splash.push_back (*it); + Ogre::StringVectorPtr resources = Ogre::ResourceGroupManager::getSingleton ().listResourceNames ("General", false); + for (Ogre::StringVector::const_iterator it = resources->begin(); it != resources->end(); ++it) + { + if (it->size() < 6) + continue; + std::string start = it->substr(0, 6); + boost::to_lower(start); + + if (start == "splash") + mResources->push_back (*it); + } } - if (splash.size()) + + if (mResources->size()) { - std::string randomSplash = splash[rand() % splash.size()]; + std::string randomSplash = mResources->at (rand() % mResources->size()); Ogre::TexturePtr tex = Ogre::TextureManager::getSingleton ().load (randomSplash, "General"); mBackgroundImage->setImageTexture (randomSplash); diff --git a/apps/openmw/mwgui/loadingscreen.hpp b/apps/openmw/mwgui/loadingscreen.hpp index a012793caf..c14087a3b9 100644 --- a/apps/openmw/mwgui/loadingscreen.hpp +++ b/apps/openmw/mwgui/loadingscreen.hpp @@ -42,6 +42,7 @@ namespace MWGui Ogre::Rectangle2D* mRectangle; Ogre::MaterialPtr mBackgroundMaterial; + Ogre::StringVectorPtr mResources; bool mLoadingOn; diff --git a/apps/openmw/mwgui/mode.hpp b/apps/openmw/mwgui/mode.hpp index d791a1c99d..3195372978 100644 --- a/apps/openmw/mwgui/mode.hpp +++ b/apps/openmw/mwgui/mode.hpp @@ -45,7 +45,9 @@ namespace MWGui GM_Loading, GM_LoadingWallpaper, - GM_QuickKeysMenu + GM_QuickKeysMenu, + + GM_Video }; // Windows shown in inventory mode diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 099dafa6b9..8ec495550e 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -230,6 +230,8 @@ WindowManager::~WindowManager() delete mSpellCreationDialog; delete mEnchantingDialog; delete mTrainingWindow; + delete mCountDialog; + delete mQuickKeysMenu; cleanupGarbage(); @@ -407,6 +409,10 @@ void WindowManager::updateVisible() case GM_Loading: MyGUI::PointerManager::getInstance().setVisible(false); break; + case GM_Video: + MyGUI::PointerManager::getInstance().setVisible(false); + mHud->setVisible(false); + break; default: // Unsupported mode, switch back to game break; diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 995da8f34b..1f270df8be 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -515,6 +515,8 @@ namespace MWInput { if (mWindows.isGuiMode () && (mWindows.getMode () == MWGui::GM_MainMenu || mWindows.getMode () == MWGui::GM_Settings)) mWindows.popGuiMode(); + else if (mWindows.isGuiMode () && mWindows.getMode () == MWGui::GM_Video) + MWBase::Environment::get().getWorld ()->stopVideo (); else mWindows.pushGuiMode (MWGui::GM_MainMenu); } diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index 36f09e6d96..ee36126f8d 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -17,7 +17,7 @@ using namespace MWRender; -// These are the Morrowind.ini defaults +/// \todo Replace these, once fallback values from the ini file are available. float Objects::lightLinearValue = 3; float Objects::lightLinearRadiusMult = 1; @@ -31,7 +31,6 @@ int Objects::uniqueID = 0; void Objects::clearSceneNode (Ogre::SceneNode *node) { - /// \todo This should probably be moved into OpenEngine at some point. for (int i=node->numAttachedObjects()-1; i>=0; --i) { Ogre::MovableObject *object = node->getAttachedObject (i); @@ -235,8 +234,9 @@ void Objects::insertLight (const MWWorld::Ptr& ptr, float r, float g, float b, f else info.type = LT_Normal; - // random starting phase for the animation - info.time = Ogre::Math::RangeRandom(0, 2 * Ogre::Math::PI); + // randomize lights animations + info.time = Ogre::Math::RangeRandom(-500, +500); + info.phase = Ogre::Math::RangeRandom(-500, +500); // adjust the lights depending if we're in an interior or exterior cell // quadratic means the light intensity falls off quite fast, resulting in a @@ -373,6 +373,46 @@ void Objects::disableLights() } } +namespace MWRender +{ + namespace Pulse + { + static float amplitude (float phase) + { + return sin (phase); + } + } + + namespace Flicker + { + static const float fa = 0.785398f; + static const float fb = 1.17024f; + + static const float tdo = 0.94f; + static const float tdm = 2.48f; + + static const float f [3] = { 1.5708f, 4.18774f, 5.19934f }; + static const float o [3] = { 0.804248f, 2.11115f, 3.46832f }; + static const float m [3] = { 1.0f, 0.785f, 0.876f }; + static const float s = 0.394f; + + static const float phase_wavelength = 120.0f * 3.14159265359f / fa; + + static float frequency (float x) + { + return tdo + tdm * sin (fa * x); + } + + static float amplitude (float x) + { + float v = 0.0f; + for (int i = 0; i < 3; ++i) + v += sin (fb*x*f[i] + o[1])*m[i]; + return v * s; + } + } +} + void Objects::update(const float dt) { std::vector::iterator it = mLights.begin(); @@ -382,77 +422,63 @@ void Objects::update(const float dt) { Ogre::Light* light = mMwRoot->getCreator()->getLight(it->name); - // Light animation (pulse & flicker) - it->time += dt; - const float phase = std::fmod(static_cast (it->time), static_cast(32 * 2 * Ogre::Math::PI)) * 20; - float pulseConstant; + float brightness; + float cycle_time; + float time_distortion; + + if ((it->type == LT_Pulse) && (it->type == LT_PulseSlow)) + { + cycle_time = 2 * Ogre::Math::PI; + time_distortion = 20.0f; + } + else + { + cycle_time = 500.0f; + it->phase = fmod (it->phase + dt, Flicker::phase_wavelength); + time_distortion = Flicker::frequency (it->phase); + } + + it->time += it->dir*dt*time_distortion; + if (it->dir > 0 && it->time > +cycle_time) + { + it->dir = -1.0f; + it->time = +2*cycle_time - it->time; + } + if (it->dir < 0 && it->time < -cycle_time) + { + it->dir = +1.0f; + it->time = -2*cycle_time - it->time; + } + + static const float fast = 4.0f/1.0f; + static const float slow = 1.0f/1.0f; // These formulas are just guesswork, but they work pretty well if (it->type == LT_Normal) { // Less than 1/255 light modifier for a constant light: - pulseConstant = (const float)(1.0 + sin(phase) / 255.0 ); + brightness = (const float)(1.0 + Flicker::amplitude(it->time*slow) / 255.0 ); } else if (it->type == LT_Flicker) { - // Let's do a 50% -> 100% sine wave pulse over 1 second: - // This is 75% +/- 25% - pulseConstant = (const float)(0.75 + sin(phase) * 0.25); - - // Then add a 25% flicker variation: - it->resetTime -= dt; - if (it->resetTime < 0) - { - it->flickerVariation = (rand() % 1000) / 1000 * 0.25; - it->resetTime = 0.5; - } - if (it->resetTime > 0.25) - { - pulseConstant = (pulseConstant+it->flickerVariation) * (1-it->resetTime * 2.0f) + pulseConstant * it->resetTime * 2.0f; - } - else - { - pulseConstant = (pulseConstant+it->flickerVariation) * (it->resetTime * 2.0f) + pulseConstant * (1-it->resetTime * 2.0f); - } + brightness = (const float)(0.75 + Flicker::amplitude(it->time*fast) * 0.25); } else if (it->type == LT_FlickerSlow) { - // Let's do a 50% -> 100% sine wave pulse over 1 second: - // This is 75% +/- 25% - pulseConstant = (const float)(0.75 + sin(phase / 4.0) * 0.25); - - // Then add a 25% flicker variation: - it->resetTime -= dt; - if (it->resetTime < 0) - { - it->flickerVariation = (rand() % 1000) / 1000 * 0.25; - it->resetTime = 0.5; - } - if (it->resetTime > 0.5) - { - pulseConstant = (pulseConstant+it->flickerVariation) * (1-it->resetTime) + pulseConstant * it->resetTime; - } - else - { - pulseConstant = (pulseConstant+it->flickerVariation) * (it->resetTime) + pulseConstant * (1-it->resetTime); - } + brightness = (const float)(0.75 + Flicker::amplitude(it->time*slow) * 0.25); } else if (it->type == LT_Pulse) { - // Let's do a 75% -> 125% sine wave pulse over 1 second: - // This is 100% +/- 25% - pulseConstant = (const float)(1.0 + sin(phase) * 0.25); + brightness = (const float)(1.0 + Pulse::amplitude (it->time*fast) * 0.25); } else if (it->type == LT_PulseSlow) { - // Let's do a 75% -> 125% sine wave pulse over 1 second: - // This is 100% +/- 25% - pulseConstant = (const float)(1.0 + sin(phase / 4.0) * 0.25); + brightness = (const float)(1.0 + Pulse::amplitude (it->time*slow) * 0.25); } else assert(0 && "Invalid light type"); - light->setDiffuseColour( it->colour * pulseConstant ); + light->setDiffuseColour(it->colour * brightness); ++it; } @@ -476,8 +502,7 @@ void Objects::rebuildStaticGeometry() } } -void -Objects::updateObjectCell(const MWWorld::Ptr &ptr) +void Objects::updateObjectCell(const MWWorld::Ptr &ptr) { Ogre::SceneNode *node; MWWorld::CellStore *newCell = ptr.getCell(); diff --git a/apps/openmw/mwrender/objects.hpp b/apps/openmw/mwrender/objects.hpp index 8594e4fe40..48632dba83 100644 --- a/apps/openmw/mwrender/objects.hpp +++ b/apps/openmw/mwrender/objects.hpp @@ -34,16 +34,13 @@ struct LightInfo LightType type; // Runtime variables - float flickerVariation; // 25% flicker variation, reset once every 0.5 seconds - float flickerSlowVariation; // 25% flicker variation, reset once every 1.0 seconds - float resetTime; - long double time; - + float dir; // direction time is running... + float time; // current time + float phase; // current phase LightInfo() : - flickerVariation(0), resetTime(0.5), - flickerSlowVariation(0), time(0), interior(true), - type(LT_Normal), radius(1.0) + dir(1.0f), time(0.0f), phase (0.0f), + interior(true), type(LT_Normal), radius(1.0) { } }; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 8cb8e9fa8b..ae7d6612bd 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -37,6 +37,7 @@ #include "npcanimation.hpp" #include "externalrendering.hpp" #include "globalmap.hpp" +#include "videoplayer.hpp" using namespace MWRender; using namespace Ogre; @@ -160,6 +161,9 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const mOcclusionQuery = new OcclusionQuery(&mRendering, mSkyManager->getSunNode()); + mVideoPlayer = new VideoPlayer(mRendering.getScene ()); + mVideoPlayer->setResolution (Settings::Manager::getInt ("resolution x", "Video"), Settings::Manager::getInt ("resolution y", "Video")); + mSun = 0; mDebugging = new Debugging(mMwRoot, engine); @@ -181,7 +185,7 @@ RenderingManager::~RenderingManager () delete mOcclusionQuery; delete mCompositors; delete mWater; - + delete mVideoPlayer; delete mFactory; } @@ -332,6 +336,8 @@ void RenderingManager::update (float duration, bool paused) } mOcclusionQuery->update(duration); + mVideoPlayer->update (); + mRendering.update(duration); if(paused) @@ -838,6 +844,8 @@ void RenderingManager::windowResized(Ogre::RenderWindow* rw) mCompositors->recreate(); mWater->assignTextures(); + mVideoPlayer->setResolution (rw->getWidth(), rw->getHeight()); + const Settings::CategorySettingVector& changed = Settings::Manager::apply(); MWBase::Environment::get().getInputManager()->processChangedSettings(changed); //FIXME MWBase::Environment::get().getWindowManager()->processChangedSettings(changed); // FIXME @@ -921,4 +929,14 @@ void RenderingManager::setupExternalRendering (MWRender::ExternalRendering& rend rendering.setup (mRendering.getScene()); } +void RenderingManager::playVideo(const std::string& name, bool allowSkipping) +{ + mVideoPlayer->playVideo ("video/" + name, allowSkipping); +} + +void RenderingManager::stopVideo() +{ + mVideoPlayer->stopVideo (); +} + } // namespace diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 86346d1d6c..68f2d79c37 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -45,6 +45,7 @@ namespace MWRender class Compositors; class ExternalRendering; class GlobalMap; + class VideoPlayer; class RenderingManager: private RenderingInterface, public Ogre::WindowEventListener { @@ -195,6 +196,9 @@ class RenderingManager: private RenderingInterface, public Ogre::WindowEventList void setupExternalRendering (MWRender::ExternalRendering& rendering); + void playVideo(const std::string& name, bool allowSkipping); + void stopVideo(); + protected: virtual void windowResized(Ogre::RenderWindow* rw); virtual void windowClosed(Ogre::RenderWindow* rw); @@ -248,6 +252,8 @@ class RenderingManager: private RenderingInterface, public Ogre::WindowEventList MWRender::Shadows* mShadows; MWRender::Compositors* mCompositors; + + VideoPlayer* mVideoPlayer; }; } diff --git a/apps/openmw/mwrender/videoplayer.cpp b/apps/openmw/mwrender/videoplayer.cpp new file mode 100644 index 0000000000..baec2f7409 --- /dev/null +++ b/apps/openmw/mwrender/videoplayer.cpp @@ -0,0 +1,1151 @@ +#include "videoplayer.hpp" + +#define __STDC_CONSTANT_MACROS +#include + +#include +#include + +#include +#include + +#include + +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/soundmanager.hpp" +#include "../mwsound/sound_decoder.hpp" +#include "../mwsound/sound.hpp" + + +namespace MWRender +{ + +#ifdef OPENMW_USE_FFMPEG + +extern "C" +{ +#include +#include +#include +} + +#define MAX_AUDIOQ_SIZE (5 * 16 * 1024) +#define MAX_VIDEOQ_SIZE (5 * 256 * 1024) +#define AV_SYNC_THRESHOLD 0.01 +#define AUDIO_DIFF_AVG_NB 20 +#define VIDEO_PICTURE_QUEUE_SIZE 1 + +enum { + AV_SYNC_AUDIO_MASTER, + AV_SYNC_VIDEO_MASTER, + AV_SYNC_EXTERNAL_MASTER, + + AV_SYNC_DEFAULT = AV_SYNC_EXTERNAL_MASTER +}; + + +struct PacketQueue { + PacketQueue() + : first_pkt(NULL), last_pkt(NULL), flushing(false), nb_packets(0), size(0) + { } + ~PacketQueue() + { clear(); } + + AVPacketList *first_pkt, *last_pkt; + volatile bool flushing; + int nb_packets; + int size; + + boost::mutex mutex; + boost::condition_variable cond; + + void put(AVPacket *pkt); + int get(AVPacket *pkt, VideoState *is); + + void flush(); + void clear(); +}; + +struct VideoPicture { + VideoPicture() : pts(0.0) + { } + + std::vector data; + double pts; +}; + +struct VideoState { + VideoState() + : format_ctx(NULL), av_sync_type(AV_SYNC_DEFAULT) + , external_clock_base(0.0) + , audio_st(NULL) + , video_st(NULL), frame_last_pts(0.0), frame_last_delay(0.0), + video_clock(0.0), sws_context(NULL), rgbaFrame(NULL), pictq_size(0), + pictq_rindex(0), pictq_windex(0) + , refresh_rate_ms(10), refresh(false), quit(false), display_ready(false) + { + // Register all formats and codecs + av_register_all(); + } + + ~VideoState() + { deinit(); } + + void init(const std::string& resourceName); + void deinit(); + + int stream_open(int stream_index, AVFormatContext *pFormatCtx); + + bool update(Ogre::MaterialPtr &mat, Ogre::Rectangle2D *rect, int screen_width, int screen_height); + + static void video_thread_loop(VideoState *is); + static void decode_thread_loop(VideoState *is); + + void video_display(); + void video_refresh_timer(); + + int queue_picture(AVFrame *pFrame, double pts); + double synchronize_video(AVFrame *src_frame, double pts); + + static void video_refresh(VideoState *is); + + + double get_audio_clock() + { return this->AudioTrack->getTimeOffset(); } + + double get_video_clock() + { return this->frame_last_pts; } + + double get_external_clock() + { return ((uint64_t)av_gettime()-this->external_clock_base) / 1000000.0; } + + double get_master_clock() + { + if(this->av_sync_type == AV_SYNC_VIDEO_MASTER) + return this->get_video_clock(); + if(this->av_sync_type == AV_SYNC_AUDIO_MASTER) + return this->get_audio_clock(); + return this->get_external_clock(); + } + + + static int OgreResource_Read(void *user_data, uint8_t *buf, int buf_size); + static int OgreResource_Write(void *user_data, uint8_t *buf, int buf_size); + static int64_t OgreResource_Seek(void *user_data, int64_t offset, int whence); + + + Ogre::DataStreamPtr stream; + AVFormatContext* format_ctx; + + int av_sync_type; + uint64_t external_clock_base; + + AVStream** audio_st; + PacketQueue audioq; + MWBase::SoundPtr AudioTrack; + + AVStream** video_st; + double frame_last_pts; + double frame_last_delay; + double video_clock; ///pkt = *pkt; + pkt1->next = NULL; + + if(pkt1->pkt.destruct == NULL) + { + if(av_dup_packet(&pkt1->pkt) < 0) + { + av_free(pkt1); + throw std::runtime_error("Failed to duplicate packet"); + } + av_free_packet(pkt); + } + + this->mutex.lock (); + + if(!last_pkt) + this->first_pkt = pkt1; + else + this->last_pkt->next = pkt1; + this->last_pkt = pkt1; + this->nb_packets++; + this->size += pkt1->pkt.size; + this->cond.notify_one(); + + this->mutex.unlock(); +} + +int PacketQueue::get(AVPacket *pkt, VideoState *is) +{ + boost::unique_lock lock(this->mutex); + while(!is->quit) + { + AVPacketList *pkt1 = this->first_pkt; + if(pkt1) + { + this->first_pkt = pkt1->next; + if(!this->first_pkt) + this->last_pkt = NULL; + this->nb_packets--; + this->size -= pkt1->pkt.size; + + *pkt = pkt1->pkt; + av_free(pkt1); + + return 1; + } + + if(this->flushing) + break; + this->cond.wait(lock); + } + + return -1; +} + +void PacketQueue::flush() +{ + this->flushing = true; + this->cond.notify_one(); +} + +void PacketQueue::clear() +{ + AVPacketList *pkt, *pkt1; + + this->mutex.lock(); + for(pkt = this->first_pkt; pkt != NULL; pkt = pkt1) + { + pkt1 = pkt->next; + av_free_packet(&pkt->pkt); + av_freep(&pkt); + } + this->last_pkt = NULL; + this->first_pkt = NULL; + this->nb_packets = 0; + this->size = 0; + this->mutex.unlock (); +} + + +class MovieAudioDecoder : public MWSound::Sound_Decoder +{ + static void fail(const std::string &str) + { + throw std::runtime_error(str); + } + + struct AutoAVPacket : public AVPacket { + AutoAVPacket(int size=0) + { + if(av_new_packet(this, size) < 0) + throw std::bad_alloc(); + } + ~AutoAVPacket() + { av_free_packet(this); } + }; + + VideoState *mVideoState; + AVStream *mAVStream; + + AutoAVPacket mPacket; + AVFrame *mFrame; + ssize_t mFramePos; + ssize_t mFrameSize; + + double mAudioClock; + + /* averaging filter for audio sync */ + double mAudioDiffAccum; + const double mAudioDiffAvgCoef; + const double mAudioDiffThreshold; + int mAudioDiffAvgCount; + + /* Add or subtract samples to get a better sync, return number of bytes to + * skip (negative means to duplicate). */ + int synchronize_audio() + { + if(mVideoState->av_sync_type == AV_SYNC_AUDIO_MASTER) + return 0; + + int sample_skip = 0; + + // accumulate the clock difference + double diff = mVideoState->get_master_clock() - mVideoState->get_audio_clock(); + mAudioDiffAccum = diff + mAudioDiffAvgCoef * mAudioDiffAccum; + if(mAudioDiffAvgCount < AUDIO_DIFF_AVG_NB) + mAudioDiffAvgCount++; + else + { + double avg_diff = mAudioDiffAccum * (1.0 - mAudioDiffAvgCoef); + if(fabs(avg_diff) >= mAudioDiffThreshold) + { + int n = av_get_bytes_per_sample(mAVStream->codec->sample_fmt) * + mAVStream->codec->channels; + sample_skip = ((int)(diff * mAVStream->codec->sample_rate) * n); + } + } + + return sample_skip; + } + + int audio_decode_frame(AVFrame *frame) + { + AVPacket *pkt = &mPacket; + + for(;;) + { + while(pkt->size > 0) + { + int len1, got_frame; + + len1 = avcodec_decode_audio4(mAVStream->codec, frame, &got_frame, pkt); + if(len1 < 0) break; + + if(len1 <= pkt->size) + { + /* Move the unread data to the front and clear the end bits */ + int remaining = pkt->size - len1; + memmove(pkt->data, &pkt->data[len1], remaining); + av_shrink_packet(pkt, remaining); + } + + /* No data yet? Look for more frames */ + if(!got_frame || frame->nb_samples <= 0) + continue; + + mAudioClock += (double)frame->nb_samples / + (double)mAVStream->codec->sample_rate; + + /* We have data, return it and come back for more later */ + return frame->nb_samples * mAVStream->codec->channels * + av_get_bytes_per_sample(mAVStream->codec->sample_fmt); + } + av_free_packet(pkt); + + /* next packet */ + if(mVideoState->audioq.get(pkt, mVideoState) < 0) + return -1; + + /* if update, update the audio clock w/pts */ + if((uint64_t)pkt->pts != AV_NOPTS_VALUE) + mAudioClock = av_q2d(mAVStream->time_base)*pkt->pts; + } + } + + void open(const std::string&) + { fail(std::string("Invalid call to ")+__PRETTY_FUNCTION__); } + + void close() { } + + std::string getName() + { return mVideoState->stream->getName(); } + + void rewind() { } + +public: + MovieAudioDecoder(VideoState *is) + : mVideoState(is) + , mAVStream(*is->audio_st) + , mFrame(avcodec_alloc_frame()) + , mFramePos(0) + , mFrameSize(0) + , mAudioClock(0.0) + , mAudioDiffAccum(0.0) + , mAudioDiffAvgCoef(exp(log(0.01 / AUDIO_DIFF_AVG_NB))) + /* Correct audio only if larger error than this */ + , mAudioDiffThreshold(2.0 * 0.050/* 50 ms */) + , mAudioDiffAvgCount(0) + { } + virtual ~MovieAudioDecoder() + { + av_freep(&mFrame); + } + + void getInfo(int *samplerate, MWSound::ChannelConfig *chans, MWSound::SampleType * type) + { + if(mAVStream->codec->sample_fmt == AV_SAMPLE_FMT_U8) + *type = MWSound::SampleType_UInt8; + else if(mAVStream->codec->sample_fmt == AV_SAMPLE_FMT_S16) + *type = MWSound::SampleType_Int16; + else + fail(std::string("Unsupported sample format: ")+ + av_get_sample_fmt_name(mAVStream->codec->sample_fmt)); + + if(mAVStream->codec->channel_layout == AV_CH_LAYOUT_MONO) + *chans = MWSound::ChannelConfig_Mono; + else if(mAVStream->codec->channel_layout == AV_CH_LAYOUT_STEREO) + *chans = MWSound::ChannelConfig_Stereo; + else if(mAVStream->codec->channel_layout == AV_CH_LAYOUT_QUAD) + *chans = MWSound::ChannelConfig_Quad; + else if(mAVStream->codec->channel_layout == AV_CH_LAYOUT_5POINT1) + *chans = MWSound::ChannelConfig_5point1; + else if(mAVStream->codec->channel_layout == AV_CH_LAYOUT_7POINT1) + *chans = MWSound::ChannelConfig_7point1; + else if(mAVStream->codec->channel_layout == 0) + { + /* Unknown channel layout. Try to guess. */ + if(mAVStream->codec->channels == 1) + *chans = MWSound::ChannelConfig_Mono; + else if(mAVStream->codec->channels == 2) + *chans = MWSound::ChannelConfig_Stereo; + else + { + std::stringstream sstr("Unsupported raw channel count: "); + sstr << mAVStream->codec->channels; + fail(sstr.str()); + } + } + else + { + char str[1024]; + av_get_channel_layout_string(str, sizeof(str), mAVStream->codec->channels, + mAVStream->codec->channel_layout); + fail(std::string("Unsupported channel layout: ")+str); + } + + *samplerate = mAVStream->codec->sample_rate; + } + + size_t read(char *stream, size_t len) + { + int sample_skip = synchronize_audio(); + size_t total = 0; + + while(total < len) + { + if(mFramePos >= mFrameSize) + { + /* We have already sent all our data; get more */ + mFrameSize = audio_decode_frame(mFrame); + if(mFrameSize < 0) + { + /* If error, we're done */ + break; + } + + mFramePos = std::min(mFrameSize, sample_skip); + sample_skip -= mFramePos; + continue; + } + + size_t len1 = len - total; + if(mFramePos >= 0) + { + len1 = std::min(len1, mFrameSize-mFramePos); + memcpy(stream, mFrame->data[0]+mFramePos, len1); + } + else + { + len1 = std::min(len1, -mFramePos); + + int n = av_get_bytes_per_sample(mAVStream->codec->sample_fmt) * + mAVStream->codec->channels; + + /* add samples by copying the first sample*/ + if(n == 1) + memset(stream, *mFrame->data[0], len1); + else if(n == 2) + { + const int16_t val = *((int16_t*)mFrame->data[0]); + for(size_t nb = 0;nb < len1;nb += n) + *((int16_t*)(stream+nb)) = val; + } + else if(n == 4) + { + const int32_t val = *((int32_t*)mFrame->data[0]); + for(size_t nb = 0;nb < len1;nb += n) + *((int32_t*)(stream+nb)) = val; + } + else if(n == 8) + { + const int64_t val = *((int64_t*)mFrame->data[0]); + for(size_t nb = 0;nb < len1;nb += n) + *((int64_t*)(stream+nb)) = val; + } + else + { + for(size_t nb = 0;nb < len1;nb += n) + memcpy(stream+nb, mFrame->data[0], n); + } + } + + total += len1; + stream += len1; + mFramePos += len1; + } + + return total; + } + + size_t getSampleOffset() + { + ssize_t clock_delay = (mFrameSize-mFramePos) / mAVStream->codec->channels / + av_get_bytes_per_sample(mAVStream->codec->sample_fmt); + return (size_t)(mAudioClock*mAVStream->codec->sample_rate) - clock_delay; + } +}; + + +int VideoState::OgreResource_Read(void *user_data, uint8_t *buf, int buf_size) +{ + Ogre::DataStreamPtr stream = static_cast(user_data)->stream; + return stream->read(buf, buf_size); +} + +int VideoState::OgreResource_Write(void *user_data, uint8_t *buf, int buf_size) +{ + Ogre::DataStreamPtr stream = static_cast(user_data)->stream; + return stream->write(buf, buf_size); +} + +int64_t VideoState::OgreResource_Seek(void *user_data, int64_t offset, int whence) +{ + Ogre::DataStreamPtr stream = static_cast(user_data)->stream; + + whence &= ~AVSEEK_FORCE; + if(whence == AVSEEK_SIZE) + return stream->size(); + if(whence == SEEK_SET) + stream->seek(offset); + else if(whence == SEEK_CUR) + stream->seek(stream->tell()+offset); + else if(whence == SEEK_END) + stream->seek(stream->size()+offset); + else + return -1; + + return stream->tell(); +} + + +void VideoState::video_refresh(VideoState* is) +{ + boost::system_time t = boost::get_system_time(); + while(!is->quit) + { + t += boost::posix_time::milliseconds(is->refresh_rate_ms); + boost::this_thread::sleep(t); + is->refresh = true; + } +} + + +void VideoState::video_display() +{ + VideoPicture *vp = &this->pictq[this->pictq_rindex]; + + if((*this->video_st)->codec->width != 0 && (*this->video_st)->codec->height != 0) + { + Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton().getByName("VideoTexture"); + if(texture.isNull() || static_cast(texture->getWidth()) != (*this->video_st)->codec->width + || static_cast(texture->getHeight()) != (*this->video_st)->codec->height) + { + Ogre::TextureManager::getSingleton ().remove ("VideoTexture"); + texture = Ogre::TextureManager::getSingleton().createManual( + "VideoTexture", + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + Ogre::TEX_TYPE_2D, + (*this->video_st)->codec->width, (*this->video_st)->codec->height, + 0, + Ogre::PF_BYTE_RGBA, + Ogre::TU_DYNAMIC_WRITE_ONLY_DISCARDABLE); + } + Ogre::PixelBox pb((*this->video_st)->codec->width, (*this->video_st)->codec->height, 1, Ogre::PF_BYTE_RGBA, &vp->data[0]); + Ogre::HardwarePixelBufferSharedPtr buffer = texture->getBuffer(); + buffer->blitFromMemory(pb); + this->display_ready = true; + } +} + +void VideoState::video_refresh_timer() +{ + VideoPicture *vp; + double delay; + + if(this->pictq_size == 0) + return; + + vp = &this->pictq[this->pictq_rindex]; + + delay = vp->pts - this->frame_last_pts; /* the pts from last time */ + if(delay <= 0 || delay >= 1.0) { + /* if incorrect delay, use previous one */ + delay = this->frame_last_delay; + } + /* save for next time */ + this->frame_last_delay = delay; + this->frame_last_pts = vp->pts; + + /* FIXME: Syncing should be done in the decoding stage, where frames can be + * skipped or duplicated as needed. */ + /* update delay to sync to audio if not master source */ + if(this->av_sync_type != AV_SYNC_VIDEO_MASTER) + { + double diff = this->get_video_clock() - this->get_master_clock(); + + /* Skip or repeat the frame. Take delay into account + * FFPlay still doesn't "know if this is the best guess." */ + double sync_threshold = std::max(delay, AV_SYNC_THRESHOLD); + if(diff <= -sync_threshold) + delay = 0; + else if(diff >= sync_threshold) + delay = 2 * delay; + } + + this->refresh_rate_ms = std::max(1, (int)(delay*1000.0)); + /* show the picture! */ + this->video_display(); + + /* update queue for next picture! */ + this->pictq_rindex = (this->pictq_rindex+1) % VIDEO_PICTURE_QUEUE_SIZE; + this->pictq_mutex.lock(); + this->pictq_size--; + this->pictq_cond.notify_one(); + this->pictq_mutex.unlock(); +} + + +int VideoState::queue_picture(AVFrame *pFrame, double pts) +{ + VideoPicture *vp; + + /* wait until we have a new pic */ + { + boost::unique_lock lock(this->pictq_mutex); + while(this->pictq_size >= VIDEO_PICTURE_QUEUE_SIZE && !this->quit) + this->pictq_cond.timed_wait(lock, boost::posix_time::milliseconds(1)); + } + if(this->quit) + return -1; + + // windex is set to 0 initially + vp = &this->pictq[this->pictq_windex]; + + // Convert the image into RGBA format for Ogre + if(this->sws_context == NULL) + { + int w = (*this->video_st)->codec->width; + int h = (*this->video_st)->codec->height; + this->sws_context = sws_getContext(w, h, (*this->video_st)->codec->pix_fmt, + w, h, PIX_FMT_RGBA, SWS_BICUBIC, + NULL, NULL, NULL); + if(this->sws_context == NULL) + throw std::runtime_error("Cannot initialize the conversion context!\n"); + } + + vp->pts = pts; + vp->data.resize((*this->video_st)->codec->width * (*this->video_st)->codec->height * 4); + + uint8_t *dst = &vp->data[0]; + sws_scale(this->sws_context, pFrame->data, pFrame->linesize, + 0, (*this->video_st)->codec->height, &dst, this->rgbaFrame->linesize); + + // now we inform our display thread that we have a pic ready + this->pictq_windex = (this->pictq_windex+1) % VIDEO_PICTURE_QUEUE_SIZE; + this->pictq_mutex.lock(); + this->pictq_size++; + this->pictq_mutex.unlock(); + + return 0; +} + +double VideoState::synchronize_video(AVFrame *src_frame, double pts) +{ + double frame_delay; + + /* if we have pts, set video clock to it */ + if(pts != 0) + this->video_clock = pts; + else + pts = this->video_clock; + + /* update the video clock */ + frame_delay = av_q2d((*this->video_st)->codec->time_base); + + /* if we are repeating a frame, adjust clock accordingly */ + frame_delay += src_frame->repeat_pict * (frame_delay * 0.5); + this->video_clock += frame_delay; + + return pts; +} + + +/* These are called whenever we allocate a frame + * buffer. We use this to store the global_pts in + * a frame at the time it is allocated. + */ +static uint64_t global_video_pkt_pts = AV_NOPTS_VALUE; +static int our_get_buffer(struct AVCodecContext *c, AVFrame *pic) +{ + int ret = avcodec_default_get_buffer(c, pic); + uint64_t *pts = (uint64_t*)av_malloc(sizeof(uint64_t)); + *pts = global_video_pkt_pts; + pic->opaque = pts; + return ret; +} +static void our_release_buffer(struct AVCodecContext *c, AVFrame *pic) +{ + if(pic) av_freep(&pic->opaque); + avcodec_default_release_buffer(c, pic); +} + + +void VideoState::video_thread_loop(VideoState *self) +{ + AVPacket pkt1, *packet = &pkt1; + int frameFinished; + AVFrame *pFrame; + double pts; + + pFrame = avcodec_alloc_frame(); + + self->rgbaFrame = avcodec_alloc_frame(); + avpicture_alloc((AVPicture*)self->rgbaFrame, PIX_FMT_RGBA, (*self->video_st)->codec->width, (*self->video_st)->codec->height); + + while(self->videoq.get(packet, self) >= 0) + { + // Save global pts to be stored in pFrame + global_video_pkt_pts = packet->pts; + // Decode video frame + if(avcodec_decode_video2((*self->video_st)->codec, pFrame, &frameFinished, packet) < 0) + throw std::runtime_error("Error decoding video frame"); + + pts = 0; + if((uint64_t)packet->dts != AV_NOPTS_VALUE) + pts = packet->dts; + else if(pFrame->opaque && *(uint64_t*)pFrame->opaque != AV_NOPTS_VALUE) + pts = *(uint64_t*)pFrame->opaque; + pts *= av_q2d((*self->video_st)->time_base); + + av_free_packet(packet); + + // Did we get a video frame? + if(frameFinished) + { + pts = self->synchronize_video(pFrame, pts); + if(self->queue_picture(pFrame, pts) < 0) + break; + } + } + + av_free(pFrame); + + avpicture_free((AVPicture*)self->rgbaFrame); + av_free(self->rgbaFrame); +} + +void VideoState::decode_thread_loop(VideoState *self) +{ + AVFormatContext *pFormatCtx = self->format_ctx; + AVPacket pkt1, *packet = &pkt1; + + try + { + if(!self->video_st && !self->audio_st) + throw std::runtime_error("No streams to decode"); + + // main decode loop + while(!self->quit) + { + if((self->audio_st >= 0 && self->audioq.size > MAX_AUDIOQ_SIZE) || + (self->video_st >= 0 && self->videoq.size > MAX_VIDEOQ_SIZE)) + { + boost::this_thread::sleep(boost::posix_time::milliseconds(10)); + continue; + } + + if(av_read_frame(pFormatCtx, packet) < 0) + break; + + // Is this a packet from the video stream? + if(self->video_st && packet->stream_index == self->video_st-pFormatCtx->streams) + self->videoq.put(packet); + else if(self->audio_st && packet->stream_index == self->audio_st-pFormatCtx->streams) + self->audioq.put(packet); + else + av_free_packet(packet); + } + + /* all done - wait for it */ + self->videoq.flush(); + self->audioq.flush(); + while(!self->quit) + { + // EOF reached, all packets processed, we can exit now + if(self->audioq.nb_packets == 0 && self->videoq.nb_packets == 0) + break; + boost::this_thread::sleep(boost::posix_time::milliseconds(100)); + } + } + catch(std::runtime_error& e) { + std::cerr << "An error occured playing the video: " << e.what () << std::endl; + } + catch(Ogre::Exception& e) { + std::cerr << "An error occured playing the video: " << e.getFullDescription () << std::endl; + } + + self->quit = true; +} + + +bool VideoState::update(Ogre::MaterialPtr &mat, Ogre::Rectangle2D *rect, int screen_width, int screen_height) +{ + if(this->quit) + return false; + + if(this->refresh) + { + this->refresh = false; + this->video_refresh_timer(); + // Would be nice not to do this all the time... + if(this->display_ready) + mat->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName("VideoTexture"); + + // Correct aspect ratio by adding black bars + double videoaspect = av_q2d((*this->video_st)->codec->sample_aspect_ratio); + if(videoaspect == 0.0) + videoaspect = 1.0; + videoaspect *= static_cast((*this->video_st)->codec->width) / (*this->video_st)->codec->height; + + double screenaspect = static_cast(screen_width) / screen_height; + double aspect_correction = videoaspect / screenaspect; + + rect->setCorners(std::max(-1.0, -1.0 * aspect_correction), std::min( 1.0, 1.0 / aspect_correction), + std::min( 1.0, 1.0 * aspect_correction), std::max(-1.0, -1.0 / aspect_correction)); + } + return true; +} + + +int VideoState::stream_open(int stream_index, AVFormatContext *pFormatCtx) +{ + MWSound::DecoderPtr decoder; + AVCodecContext *codecCtx; + AVCodec *codec; + + if(stream_index < 0 || stream_index >= static_cast(pFormatCtx->nb_streams)) + return -1; + + // Get a pointer to the codec context for the video stream + codecCtx = pFormatCtx->streams[stream_index]->codec; + codec = avcodec_find_decoder(codecCtx->codec_id); + if(!codec || (avcodec_open2(codecCtx, codec, NULL) < 0)) + { + fprintf(stderr, "Unsupported codec!\n"); + return -1; + } + + switch(codecCtx->codec_type) + { + case AVMEDIA_TYPE_AUDIO: + this->audio_st = pFormatCtx->streams + stream_index; + + decoder.reset(new MovieAudioDecoder(this)); + this->AudioTrack = MWBase::Environment::get().getSoundManager()->playTrack(decoder, MWBase::SoundManager::Play_TypeMovie); + if(!this->AudioTrack) + { + avcodec_close((*this->audio_st)->codec); + this->audio_st = NULL; + return -1; + } + break; + + case AVMEDIA_TYPE_VIDEO: + this->video_st = pFormatCtx->streams + stream_index; + + this->frame_last_delay = 40e-3; + + codecCtx->get_buffer = our_get_buffer; + codecCtx->release_buffer = our_release_buffer; + this->video_thread = boost::thread(video_thread_loop, this); + this->refresh_thread = boost::thread(video_refresh, this); + break; + + default: + break; + } + + return 0; +} + +void VideoState::init(const std::string& resourceName) +{ + int video_index = -1; + int audio_index = -1; + unsigned int i; + + this->av_sync_type = AV_SYNC_DEFAULT; + this->refresh_rate_ms = 10; + this->refresh = false; + this->quit = false; + + this->stream = Ogre::ResourceGroupManager::getSingleton().openResource(resourceName); + if(this->stream.isNull()) + throw std::runtime_error("Failed to open video resource"); + + AVIOContext *ioCtx = avio_alloc_context(NULL, 0, 0, this, OgreResource_Read, OgreResource_Write, OgreResource_Seek); + if(!ioCtx) throw std::runtime_error("Failed to allocate AVIOContext"); + + this->format_ctx = avformat_alloc_context(); + if(this->format_ctx) + this->format_ctx->pb = ioCtx; + + // Open video file + /// \todo leak here, ffmpeg or valgrind bug ? + if(!this->format_ctx || avformat_open_input(&this->format_ctx, resourceName.c_str(), NULL, NULL)) + { + // "Note that a user-supplied AVFormatContext will be freed on failure." + this->format_ctx = NULL; + av_free(ioCtx); + throw std::runtime_error("Failed to open video input"); + } + + // Retrieve stream information + if(avformat_find_stream_info(this->format_ctx, NULL) < 0) + throw std::runtime_error("Failed to retrieve stream information"); + + // Dump information about file onto standard error + av_dump_format(this->format_ctx, 0, resourceName.c_str(), 0); + + for(i = 0;i < this->format_ctx->nb_streams;i++) + { + if(this->format_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO && video_index < 0) + video_index = i; + if(this->format_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO && audio_index < 0) + audio_index = i; + } + + this->external_clock_base = av_gettime(); + if(audio_index >= 0) + this->stream_open(audio_index, this->format_ctx); + if(video_index >= 0) + this->stream_open(video_index, this->format_ctx); + + this->parse_thread = boost::thread(decode_thread_loop, this); +} + +void VideoState::deinit() +{ + this->quit = true; + + this->audioq.cond.notify_one(); + this->videoq.cond.notify_one(); + + this->parse_thread.join(); + this->video_thread.join(); + this->refresh_thread.join(); + + if(this->audio_st) + avcodec_close((*this->audio_st)->codec); + this->audio_st = NULL; + if(this->video_st) + avcodec_close((*this->video_st)->codec); + this->video_st = NULL; + + if(this->sws_context) + sws_freeContext(this->sws_context); + this->sws_context = NULL; + + if(this->format_ctx) + { + AVIOContext *ioContext = this->format_ctx->pb; + avformat_close_input(&this->format_ctx); + av_free(ioContext); + } +} + +#else // defined OPENMW_USE_FFMPEG + +class VideoState +{ +public: + VideoState() { } + + void init(const std::string& resourceName) + { + throw std::runtime_error("FFmpeg not supported, cannot play \""+resourceName+"\""); + } + void deinit() { } + + void close() { } + + bool update(Ogre::MaterialPtr &mat, Ogre::Rectangle2D *rect, int screen_width, int screen_height) + { return false; } +}; + +#endif // defined OPENMW_USE_FFMPEG + + +VideoPlayer::VideoPlayer(Ogre::SceneManager* sceneMgr) + : mState(NULL) + , mSceneMgr(sceneMgr) + , mVideoMaterial(NULL) + , mRectangle(NULL) + , mNode(NULL) + , mAllowSkipping(false) +{ + mVideoMaterial = Ogre::MaterialManager::getSingleton().getByName("VideoMaterial", "General"); + if (mVideoMaterial.isNull ()) + { + mVideoMaterial = Ogre::MaterialManager::getSingleton().create("VideoMaterial", "General"); + mVideoMaterial->getTechnique(0)->getPass(0)->setDepthWriteEnabled(false); + mVideoMaterial->getTechnique(0)->getPass(0)->setDepthCheckEnabled(false); + mVideoMaterial->getTechnique(0)->getPass(0)->setLightingEnabled(false); + mVideoMaterial->getTechnique(0)->getPass(0)->createTextureUnitState(); + mVideoMaterial->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP); + } + mVideoMaterial->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName("black.png"); + + Ogre::MaterialPtr blackMaterial = Ogre::MaterialManager::getSingleton().getByName("BlackBarsMaterial", "General"); + if (blackMaterial.isNull ()) + { + blackMaterial = Ogre::MaterialManager::getSingleton().create("BlackBarsMaterial", "General"); + blackMaterial->getTechnique(0)->getPass(0)->setDepthWriteEnabled(false); + blackMaterial->getTechnique(0)->getPass(0)->setDepthCheckEnabled(false); + blackMaterial->getTechnique(0)->getPass(0)->setLightingEnabled(false); + blackMaterial->getTechnique(0)->getPass(0)->createTextureUnitState()->setTextureName("black.png"); + } + + mRectangle = new Ogre::Rectangle2D(true); + mRectangle->setCorners(-1.0, 1.0, 1.0, -1.0); + mRectangle->setMaterial("VideoMaterial"); + mRectangle->setRenderQueueGroup(Ogre::RENDER_QUEUE_OVERLAY+2); + mBackgroundRectangle = new Ogre::Rectangle2D(true); + mBackgroundRectangle->setCorners(-1.0, 1.0, 1.0, -1.0); + mBackgroundRectangle->setMaterial("BlackBarsMaterial"); + mBackgroundRectangle->setRenderQueueGroup(Ogre::RENDER_QUEUE_OVERLAY+1); + + // Use infinite AAB to always stay visible + Ogre::AxisAlignedBox aabInf; + aabInf.setInfinite(); + mRectangle->setBoundingBox(aabInf); + mBackgroundRectangle->setBoundingBox(aabInf); + + // Attach background to the scene + mNode = sceneMgr->getRootSceneNode()->createChildSceneNode(); + mNode->attachObject(mRectangle); + mBackgroundNode = sceneMgr->getRootSceneNode()->createChildSceneNode(); + mBackgroundNode->attachObject(mBackgroundRectangle); + + mRectangle->setVisible(false); + mRectangle->setVisibilityFlags(0x1); + mBackgroundRectangle->setVisible(false); + mBackgroundRectangle->setVisibilityFlags(0x1); +} + +VideoPlayer::~VideoPlayer() +{ + if(mState) + close(); + + mSceneMgr->destroySceneNode(mNode); + mSceneMgr->destroySceneNode(mBackgroundNode); + + delete mRectangle; + delete mBackgroundRectangle; +} + +void VideoPlayer::playVideo(const std::string &resourceName, bool allowSkipping) +{ + mAllowSkipping = allowSkipping; + + if(mState) + close(); + + mRectangle->setVisible(true); + mBackgroundRectangle->setVisible(true); + mVideoMaterial->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName("black.png"); + + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Video); + + // Turn off rendering except the GUI + mSceneMgr->clearSpecialCaseRenderQueues(); + // SCRQM_INCLUDE with RENDER_QUEUE_OVERLAY does not work. + for(int i = 0;i < Ogre::RENDER_QUEUE_MAX;++i) + { + if(i > 0 && i < 96) + mSceneMgr->addSpecialCaseRenderQueue(i); + } + mSceneMgr->setSpecialCaseRenderQueueMode(Ogre::SceneManager::SCRQM_EXCLUDE); + + MWBase::Environment::get().getSoundManager()->pauseSounds(); + + try { + mState = new VideoState; + mState->init(resourceName); + } + catch(std::exception& e) { + std::cerr<< "Failed to play video: "<update(mVideoMaterial, mRectangle, mWidth, mHeight)) + close(); + } +} + +void VideoPlayer::stopVideo () +{ + if (mAllowSkipping) + close(); +} + +void VideoPlayer::close() +{ + if(mState) + { + mState->deinit(); + + delete mState; + mState = NULL; + } + + MWBase::Environment::get().getSoundManager()->resumeSounds(); + + mRectangle->setVisible(false); + mBackgroundRectangle->setVisible(false); + MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Video); + + mSceneMgr->clearSpecialCaseRenderQueues(); + mSceneMgr->setSpecialCaseRenderQueueMode(Ogre::SceneManager::SCRQM_EXCLUDE); +} + +bool VideoPlayer::isPlaying () +{ + return mState != NULL; +} + +} diff --git a/apps/openmw/mwrender/videoplayer.hpp b/apps/openmw/mwrender/videoplayer.hpp new file mode 100644 index 0000000000..5e9d18ba41 --- /dev/null +++ b/apps/openmw/mwrender/videoplayer.hpp @@ -0,0 +1,52 @@ +#ifndef VIDEOPLAYER_H +#define VIDEOPLAYER_H + +#include + +namespace Ogre +{ + class SceneManager; + class SceneNode; + class Rectangle2D; +} + +namespace MWRender +{ + struct VideoState; + + class VideoPlayer + { + public: + VideoPlayer(Ogre::SceneManager* sceneMgr); + ~VideoPlayer(); + + void playVideo (const std::string& resourceName, bool allowSkipping); + + void update(); + + void close(); + void stopVideo(); + + bool isPlaying(); + + void setResolution (int w, int h) { mWidth = w; mHeight = h; } + + + private: + VideoState* mState; + + bool mAllowSkipping; + + Ogre::SceneManager* mSceneMgr; + Ogre::MaterialPtr mVideoMaterial; + Ogre::Rectangle2D* mRectangle; + Ogre::Rectangle2D* mBackgroundRectangle; + Ogre::SceneNode* mNode; + Ogre::SceneNode* mBackgroundNode; + + int mWidth; + int mHeight; + }; +} + +#endif diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index 89cacf65cb..7c009f0955 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -301,10 +301,14 @@ op 0x20001ea: LowerRank op 0x20001eb: LowerRank, explicit op 0x20001ec: GetPCCrimeLevel op 0x20001ed: SetPCCrimeLevel -op 0x20001ee: SetPCCrimeLevel +op 0x20001ee: ModPCCrimeLevel op 0x20001ef: GetCurrentAIPackage op 0x20001f0: GetCurrentAIPackage, explicit reference op 0x20001f1: GetDetected op 0x20001f2: GetDetected, explicit reference - -opcodes 0x20001f3-0x3ffffff unused +op 0x20001f3: AddSoulGem +op 0x20001f4: AddSoulGem, explicit reference +op 0x20001f5: RemoveSoulGem +op 0x20001f6: RemoveSoulGem, explicit reference +op 0x20001f7: PlayBink +opcodes 0x20001f8-0x3ffffff unused diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index b687471aa9..a348a0a49d 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -14,6 +14,8 @@ #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" +#include "../mwworld/manualref.hpp" +#include "../mwworld/containerstore.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/creaturestats.hpp" @@ -25,6 +27,22 @@ namespace MWScript { namespace Misc { + class OpPlayBink : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + std::string name = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + bool allowSkipping = runtime[0].mInteger; + runtime.pop(); + + MWBase::Environment::get().getWorld ()->playVideo (name, allowSkipping); + } + }; + class OpGetPcSleep : public Interpreter::Opcode0 { public: @@ -255,7 +273,7 @@ namespace MWScript static bool sActivate; public: - + virtual void execute(Interpreter::Runtime &runtime) { InterpreterContext& context = @@ -306,6 +324,65 @@ namespace MWScript } }; + template + class OpAddSoulGem : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + std::string creature = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + std::string gem = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + store.get().find(creature); // This line throws an exception if it can't find the creature + + MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), gem); + + ref.getPtr().getRefData().setCount (1); + + ref.getPtr().getCellRef().mSoul = creature; + + MWWorld::Class::get (ptr).getContainerStore (ptr).add (ref.getPtr()); + + } + }; + + template + class OpRemoveSoulGem : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + + MWWorld::Ptr ptr = R()(runtime); + + std::string soul = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + MWWorld::ContainerStore& store = MWWorld::Class::get (ptr).getContainerStore (ptr); + + + for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end(); ++iter) + { + if (::Misc::StringUtils::ciEqual(iter->getCellRef().mSoul, soul)) + { + if (iter->getRefData().getCount() <= 1) + iter->getRefData().setCount (0); + else + iter->getRefData().setCount (iter->getRefData().getCount() - 1); + break; + } + } + } + }; + template class OpGetAttacked : public Interpreter::Opcode0 { @@ -414,6 +491,10 @@ namespace MWScript const int opcodeGetLockedExplicit = 0x20001c8; const int opcodeGetEffect = 0x20001cf; const int opcodeGetEffectExplicit = 0x20001d0; + const int opcodeAddSoulGem = 0x20001f3; + const int opcodeAddSoulGemExplicit = 0x20001f4; + const int opcodeRemoveSoulGem = 0x20001f5; + const int opcodeRemoveSoulGemExplicit = 0x20001f6; const int opcodeGetAttacked = 0x20001d3; const int opcodeGetAttackedExplicit = 0x20001d4; const int opcodeGetWeaponDrawn = 0x20001d7; @@ -425,6 +506,8 @@ namespace MWScript const int opcodeSetDeleteExplicit = 0x20001e6; const int opcodeGetSquareRoot = 0x20001e7; + const int opcodePlayBink = 0x20001f7; + void registerExtensions (Compiler::Extensions& extensions) { extensions.registerFunction ("xbox", 'l', "", opcodeXBox); @@ -450,8 +533,11 @@ namespace MWScript extensions.registerInstruction ("tvm", "", opcodeToggleVanityMode); extensions.registerFunction ("getpcsleep", 'l', "", opcodeGetPcSleep); extensions.registerInstruction ("wakeuppc", "", opcodeWakeUpPc); + extensions.registerInstruction ("playbink", "Sl", opcodePlayBink); extensions.registerFunction ("getlocked", 'l', "", opcodeGetLocked, opcodeGetLockedExplicit); extensions.registerFunction ("geteffect", 'l', "l", opcodeGetEffect, opcodeGetEffectExplicit); + extensions.registerInstruction ("addsoulgem", "cc", opcodeAddSoulGem, opcodeAddSoulGemExplicit); + extensions.registerInstruction ("removesoulgem", "c", opcodeRemoveSoulGem, opcodeRemoveSoulGemExplicit); extensions.registerFunction ("getattacked", 'l', "", opcodeGetAttacked, opcodeGetAttackedExplicit); extensions.registerFunction ("getweapondrawn", 'l', "", opcodeGetWeaponDrawn, opcodeGetWeaponDrawnExplicit); extensions.registerFunction ("getspelleffects", 'l', "c", opcodeGetSpellEffects, opcodeGetSpellEffectsExplicit); @@ -481,10 +567,15 @@ namespace MWScript interpreter.installSegment5 (opcodeToggleVanityMode, new OpToggleVanityMode); interpreter.installSegment5 (opcodeGetPcSleep, new OpGetPcSleep); interpreter.installSegment5 (opcodeWakeUpPc, new OpWakeUpPc); + interpreter.installSegment5 (opcodePlayBink, new OpPlayBink); interpreter.installSegment5 (opcodeGetLocked, new OpGetLocked); interpreter.installSegment5 (opcodeGetLockedExplicit, new OpGetLocked); interpreter.installSegment5 (opcodeGetEffect, new OpGetEffect); interpreter.installSegment5 (opcodeGetEffectExplicit, new OpGetEffect); + interpreter.installSegment5 (opcodeAddSoulGem, new OpAddSoulGem); + interpreter.installSegment5 (opcodeAddSoulGemExplicit, new OpAddSoulGem); + interpreter.installSegment5 (opcodeRemoveSoulGem, new OpRemoveSoulGem); + interpreter.installSegment5 (opcodeRemoveSoulGemExplicit, new OpRemoveSoulGem); interpreter.installSegment5 (opcodeGetAttacked, new OpGetAttacked); interpreter.installSegment5 (opcodeGetAttackedExplicit, new OpGetAttacked); interpreter.installSegment5 (opcodeGetWeaponDrawn, new OpGetWeaponDrawn); diff --git a/apps/openmw/mwscript/soundextensions.cpp b/apps/openmw/mwscript/soundextensions.cpp index 35e66241eb..408a6f29d2 100644 --- a/apps/openmw/mwscript/soundextensions.cpp +++ b/apps/openmw/mwscript/soundextensions.cpp @@ -118,7 +118,8 @@ namespace MWScript std::string sound = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - MWBase::Environment::get().getSoundManager()->playSound3D (ptr, sound, 1.0, 1.0, mLoop ? MWBase::SoundManager::Play_Loop : 0); + MWBase::Environment::get().getSoundManager()->playSound3D (ptr, sound, 1.0, 1.0, mLoop ? MWBase::SoundManager::Play_Loop : + MWBase::SoundManager::Play_Normal); } }; @@ -144,7 +145,8 @@ namespace MWScript Interpreter::Type_Float pitch = runtime[0].mFloat; runtime.pop(); - MWBase::Environment::get().getSoundManager()->playSound3D (ptr, sound, volume, pitch, mLoop ? MWBase::SoundManager::Play_Loop : 0); + MWBase::Environment::get().getSoundManager()->playSound3D (ptr, sound, volume, pitch, mLoop ? MWBase::SoundManager::Play_Loop : + MWBase::SoundManager::Play_Normal); } }; diff --git a/apps/openmw/mwsound/audiere_decoder.cpp b/apps/openmw/mwsound/audiere_decoder.cpp index 4e73573a74..788d5ae40e 100644 --- a/apps/openmw/mwsound/audiere_decoder.cpp +++ b/apps/openmw/mwsound/audiere_decoder.cpp @@ -53,6 +53,9 @@ public: : mStream(stream), refs(1) { } virtual ~OgreFile() { } + + Ogre::String getName() + { return mStream->getName(); } }; @@ -60,7 +63,7 @@ void Audiere_Decoder::open(const std::string &fname) { close(); - audiere::FilePtr file(new OgreFile(mResourceMgr.openResource(fname))); + mSoundFile = audiere::FilePtr(new OgreFile(mResourceMgr.openResource(fname))); mSoundSource = audiere::OpenSampleSource(file); int channels, srate; @@ -86,9 +89,15 @@ void Audiere_Decoder::open(const std::string &fname) void Audiere_Decoder::close() { + mSoundFile = NULL; mSoundSource = NULL; } +std::string Audiere_Decoder::getName() +{ + return mSoundFile->getName(); +} + void Audiere_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) { *samplerate = mSampleRate; @@ -108,6 +117,11 @@ void Audiere_Decoder::rewind() mSoundSource->reset(); } +size_t Audiere_Decoder::getSampleOffset() +{ + return 0; +} + Audiere_Decoder::Audiere_Decoder() { } diff --git a/apps/openmw/mwsound/audiere_decoder.hpp b/apps/openmw/mwsound/audiere_decoder.hpp index 0ad026d515..91c07ccacb 100644 --- a/apps/openmw/mwsound/audiere_decoder.hpp +++ b/apps/openmw/mwsound/audiere_decoder.hpp @@ -12,6 +12,7 @@ namespace MWSound { class Audiere_Decoder : public Sound_Decoder { + audiere::FilePtr mSoundFile; audiere::SampleSourcePtr mSoundSource; int mSampleRate; SampleType mSampleType; @@ -20,10 +21,12 @@ namespace MWSound virtual void open(const std::string &fname); virtual void close(); + virtual std::string getName(); virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type); virtual size_t read(char *buffer, size_t bytes); virtual void rewind(); + virtual size_t getSampleOffset(); Audiere_Decoder& operator=(const Audiere_Decoder &rhs); Audiere_Decoder(const Audiere_Decoder &rhs); diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index 5f61ab8f08..261a86ca60 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -15,28 +15,6 @@ static void fail(const std::string &msg) { throw std::runtime_error("FFmpeg exception: "+msg); } -struct PacketList { - AVPacket pkt; - PacketList *next; -}; - -struct FFmpeg_Decoder::MyStream { - AVCodecContext *mCodecCtx; - int mStreamIdx; - - PacketList *mPackets; - - char *mDecodedData; - size_t mDecodedDataSize; - - FFmpeg_Decoder *mParent; - - void clearPackets(); - void *getAVAudioData(size_t *length); - size_t readAVAudioData(void *data, size_t length); -}; - - int FFmpeg_Decoder::readPacket(void *user_data, uint8_t *buf, int buf_size) { Ogre::DataStreamPtr stream = static_cast(user_data)->mDataStream; @@ -72,166 +50,84 @@ int64_t FFmpeg_Decoder::seek(void *user_data, int64_t offset, int whence) /* Used by getAV*Data to search for more compressed data, and buffer it in the * correct stream. It won't buffer data for streams that the app doesn't have a * handle for. */ -bool FFmpeg_Decoder::getNextPacket(int streamidx) +bool FFmpeg_Decoder::getNextPacket() { - PacketList *packet; + if(!mStream) + return false; - packet = (PacketList*)av_malloc(sizeof(*packet)); - packet->next = NULL; - -next_packet: - while(av_read_frame(mFormatCtx, &packet->pkt) >= 0) + int stream_idx = mStream - mFormatCtx->streams; + while(av_read_frame(mFormatCtx, &mPacket) >= 0) { - std::vector::iterator iter = mStreams.begin(); - - /* Check each stream the user has a handle for, looking for the one - * this packet belongs to */ - while(iter != mStreams.end()) + /* Check if the packet belongs to this stream */ + if(stream_idx == mPacket.stream_index) { - if((*iter)->mStreamIdx == packet->pkt.stream_index) - { - PacketList **last; - - last = &(*iter)->mPackets; - while(*last != NULL) - last = &(*last)->next; - - *last = packet; - if((*iter)->mStreamIdx == streamidx) - return true; - - packet = (PacketList*)av_malloc(sizeof(*packet)); - packet->next = NULL; - goto next_packet; - } - iter++; + if((uint64_t)mPacket.pts != AV_NOPTS_VALUE) + mNextPts = av_q2d((*mStream)->time_base)*mPacket.pts; + return true; } + /* Free the packet and look for another */ - av_free_packet(&packet->pkt); + av_free_packet(&mPacket); } - av_free(packet); return false; } -void FFmpeg_Decoder::MyStream::clearPackets() +bool FFmpeg_Decoder::getAVAudioData() { - while(mPackets) - { - PacketList *self = mPackets; - mPackets = self->next; + int got_frame, len; - av_free_packet(&self->pkt); - av_free(self); - } -} + if((*mStream)->codec->codec_type != AVMEDIA_TYPE_AUDIO) + return false; -void *FFmpeg_Decoder::MyStream::getAVAudioData(size_t *length) -{ - int size; - int len; + do { + if(mPacket.size == 0 && !getNextPacket()) + return false; - if(length) *length = 0; - if(mCodecCtx->codec_type != AVMEDIA_TYPE_AUDIO) - return NULL; + /* Decode some data, and check for errors */ + if((len=avcodec_decode_audio4((*mStream)->codec, mFrame, &got_frame, &mPacket)) < 0) + return false; - mDecodedDataSize = 0; - -next_packet: - if(!mPackets && !mParent->getNextPacket(mStreamIdx)) - return NULL; - - /* Decode some data, and check for errors */ - size = AVCODEC_MAX_AUDIO_FRAME_SIZE; - while((len=avcodec_decode_audio3(mCodecCtx, (int16_t*)mDecodedData, &size, - &mPackets->pkt)) == 0) - { - PacketList *self; - - if(size > 0) - break; - - /* Packet went unread and no data was given? Drop it and try the next, - * I guess... */ - self = mPackets; - mPackets = self->next; - - av_free_packet(&self->pkt); - av_free(self); - - if(!mPackets) - goto next_packet; - - size = AVCODEC_MAX_AUDIO_FRAME_SIZE; - } - - if(len < 0) - return NULL; - - if(len < mPackets->pkt.size) - { /* Move the unread data to the front and clear the end bits */ - int remaining = mPackets->pkt.size - len; - memmove(mPackets->pkt.data, &mPackets->pkt.data[len], remaining); - memset(&mPackets->pkt.data[remaining], 0, mPackets->pkt.size - remaining); - mPackets->pkt.size -= len; - } - else - { - PacketList *self; + int remaining = mPacket.size - len; + if(remaining <= 0) + av_free_packet(&mPacket); + else + { + memmove(mPacket.data, &mPacket.data[len], remaining); + av_shrink_packet(&mPacket, remaining); + } + } while(got_frame == 0 || mFrame->nb_samples == 0); + mNextPts += (double)mFrame->nb_samples / (double)(*mStream)->codec->sample_rate; - self = mPackets; - mPackets = self->next; - - av_free_packet(&self->pkt); - av_free(self); - } - - if(size == 0) - goto next_packet; - - /* Set the output buffer size */ - mDecodedDataSize = size; - if(length) *length = mDecodedDataSize; - - return mDecodedData; + return true; } -size_t FFmpeg_Decoder::MyStream::readAVAudioData(void *data, size_t length) +size_t FFmpeg_Decoder::readAVAudioData(void *data, size_t length) { size_t dec = 0; while(dec < length) { /* If there's no decoded data, find some */ - if(mDecodedDataSize == 0) + if(mFramePos >= mFrameSize) { - if(getAVAudioData(NULL) == NULL) + if(!getAVAudioData()) break; + mFramePos = 0; + mFrameSize = mFrame->nb_samples * (*mStream)->codec->channels * + av_get_bytes_per_sample((*mStream)->codec->sample_fmt); } - if(mDecodedDataSize > 0) - { - /* Get the amount of bytes remaining to be written, and clamp to - * the amount of decoded data we have */ - size_t rem = length-dec; - if(rem > mDecodedDataSize) - rem = mDecodedDataSize; + /* Get the amount of bytes remaining to be written, and clamp to + * the amount of decoded data we have */ + size_t rem = std::min(length-dec, mFrameSize-mFramePos); - /* Copy the data to the app's buffer and increment */ - if(data != NULL) - { - memcpy(data, mDecodedData, rem); - data = (char*)data + rem; - } - dec += rem; - - /* If there's any decoded data left, move it to the front of the - * buffer for next time */ - if(rem < mDecodedDataSize) - memmove(mDecodedData, &mDecodedData[rem], mDecodedDataSize - rem); - mDecodedDataSize -= rem; - } + /* Copy the data to the app's buffer and increment */ + memcpy(data, mFrame->data[0]+mFramePos, rem); + data = (char*)data + rem; + dec += rem; + mFramePos += rem; } /* Return the number of bytes we were able to get */ @@ -239,7 +135,6 @@ size_t FFmpeg_Decoder::MyStream::readAVAudioData(void *data, size_t length) } - void FFmpeg_Decoder::open(const std::string &fname) { close(); @@ -265,140 +160,155 @@ void FFmpeg_Decoder::open(const std::string &fname) { if(mFormatCtx->streams[j]->codec->codec_type == AVMEDIA_TYPE_AUDIO) { - std::auto_ptr stream(new MyStream); - stream->mCodecCtx = mFormatCtx->streams[j]->codec; - stream->mStreamIdx = j; - stream->mPackets = NULL; - - AVCodec *codec = avcodec_find_decoder(stream->mCodecCtx->codec_id); - if(!codec) - { - std::stringstream ss("No codec found for id "); - ss << stream->mCodecCtx->codec_id; - fail(ss.str()); - } - if(avcodec_open(stream->mCodecCtx, codec) < 0) - fail("Failed to open audio codec " + std::string(codec->long_name)); - - stream->mDecodedData = (char*)av_malloc(AVCODEC_MAX_AUDIO_FRAME_SIZE); - stream->mDecodedDataSize = 0; - - stream->mParent = this; - mStreams.push_back(stream.release()); + mStream = &mFormatCtx->streams[j]; break; } } - if(mStreams.empty()) + if(!mStream) fail("No audio streams in "+fname); + + AVCodec *codec = avcodec_find_decoder((*mStream)->codec->codec_id); + if(!codec) + { + std::stringstream ss("No codec found for id "); + ss << (*mStream)->codec->codec_id; + fail(ss.str()); + } + if(avcodec_open2((*mStream)->codec, codec, NULL) < 0) + fail("Failed to open audio codec " + std::string(codec->long_name)); + + mFrame = avcodec_alloc_frame(); } catch(std::exception &e) { - av_close_input_file(mFormatCtx); - mFormatCtx = NULL; + avformat_close_input(&mFormatCtx); throw; } } void FFmpeg_Decoder::close() { - while(!mStreams.empty()) - { - MyStream *stream = mStreams.front(); + if(mStream) + avcodec_close((*mStream)->codec); + mStream = NULL; - stream->clearPackets(); - avcodec_close(stream->mCodecCtx); - av_free(stream->mDecodedData); - delete stream; + av_free_packet(&mPacket); + av_freep(&mFrame); - mStreams.erase(mStreams.begin()); - } if(mFormatCtx) { AVIOContext* context = mFormatCtx->pb; + avformat_close_input(&mFormatCtx); av_free(context); - mFormatCtx->pb = NULL; - av_close_input_file(mFormatCtx); } - mFormatCtx = NULL; mDataStream.setNull(); } +std::string FFmpeg_Decoder::getName() +{ + return mFormatCtx->filename; +} + void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) { - if(mStreams.empty()) + if(!mStream) fail("No audio stream info"); - MyStream *stream = mStreams[0]; - if(stream->mCodecCtx->sample_fmt == AV_SAMPLE_FMT_U8) + if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_U8) *type = SampleType_UInt8; - else if(stream->mCodecCtx->sample_fmt == AV_SAMPLE_FMT_S16) + else if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_S16) *type = SampleType_Int16; else fail(std::string("Unsupported sample format: ")+ - av_get_sample_fmt_name(stream->mCodecCtx->sample_fmt)); + av_get_sample_fmt_name((*mStream)->codec->sample_fmt)); - if(stream->mCodecCtx->channel_layout == AV_CH_LAYOUT_MONO) + if((*mStream)->codec->channel_layout == AV_CH_LAYOUT_MONO) *chans = ChannelConfig_Mono; - else if(stream->mCodecCtx->channel_layout == AV_CH_LAYOUT_STEREO) + else if((*mStream)->codec->channel_layout == AV_CH_LAYOUT_STEREO) *chans = ChannelConfig_Stereo; - else if(stream->mCodecCtx->channel_layout == 0) + else if((*mStream)->codec->channel_layout == AV_CH_LAYOUT_QUAD) + *chans = ChannelConfig_Quad; + else if((*mStream)->codec->channel_layout == AV_CH_LAYOUT_5POINT1) + *chans = ChannelConfig_5point1; + else if((*mStream)->codec->channel_layout == AV_CH_LAYOUT_7POINT1) + *chans = ChannelConfig_7point1; + else if((*mStream)->codec->channel_layout == 0) { /* Unknown channel layout. Try to guess. */ - if(stream->mCodecCtx->channels == 1) + if((*mStream)->codec->channels == 1) *chans = ChannelConfig_Mono; - else if(stream->mCodecCtx->channels == 2) + else if((*mStream)->codec->channels == 2) *chans = ChannelConfig_Stereo; else { std::stringstream sstr("Unsupported raw channel count: "); - sstr << stream->mCodecCtx->channels; + sstr << (*mStream)->codec->channels; fail(sstr.str()); } } else { char str[1024]; - av_get_channel_layout_string(str, sizeof(str), stream->mCodecCtx->channels, - stream->mCodecCtx->channel_layout); + av_get_channel_layout_string(str, sizeof(str), (*mStream)->codec->channels, + (*mStream)->codec->channel_layout); fail(std::string("Unsupported channel layout: ")+str); } - *samplerate = stream->mCodecCtx->sample_rate; + *samplerate = (*mStream)->codec->sample_rate; } size_t FFmpeg_Decoder::read(char *buffer, size_t bytes) { - if(mStreams.empty()) - fail("No audio streams"); - - return mStreams.front()->readAVAudioData(buffer, bytes); + if(!mStream) + fail("No audio stream"); + return readAVAudioData(buffer, bytes); } void FFmpeg_Decoder::readAll(std::vector &output) { - if(mStreams.empty()) - fail("No audio streams"); - MyStream *stream = mStreams.front(); - char *inbuf; - size_t got; + if(!mStream) + fail("No audio stream"); - while((inbuf=(char*)stream->getAVAudioData(&got)) != NULL && got > 0) + while(getAVAudioData()) + { + size_t got = mFrame->nb_samples * (*mStream)->codec->channels * + av_get_bytes_per_sample((*mStream)->codec->sample_fmt); + const char *inbuf = reinterpret_cast(mFrame->data[0]); output.insert(output.end(), inbuf, inbuf+got); + } } void FFmpeg_Decoder::rewind() { - av_seek_frame(mFormatCtx, -1, 0, 0); - std::for_each(mStreams.begin(), mStreams.end(), std::mem_fun(&MyStream::clearPackets)); + int stream_idx = mStream - mFormatCtx->streams; + if(av_seek_frame(mFormatCtx, stream_idx, 0, 0) < 0) + fail("Failed to seek in audio stream"); + av_free_packet(&mPacket); + mFrameSize = mFramePos = 0; + mNextPts = 0.0; } -FFmpeg_Decoder::FFmpeg_Decoder() : mFormatCtx(NULL) +size_t FFmpeg_Decoder::getSampleOffset() { - static bool done_init = false; + int delay = (mFrameSize-mFramePos) / (*mStream)->codec->channels / + av_get_bytes_per_sample((*mStream)->codec->sample_fmt); + return (int)(mNextPts*(*mStream)->codec->sample_rate) - delay; +} + +FFmpeg_Decoder::FFmpeg_Decoder() + : mFormatCtx(NULL) + , mStream(NULL) + , mFrame(NULL) + , mFrameSize(0) + , mFramePos(0) + , mNextPts(0.0) +{ + memset(&mPacket, 0, sizeof(mPacket)); /* We need to make sure ffmpeg is initialized. Optionally silence warning * output from the lib */ + static bool done_init = false; if(!done_init) { av_register_all(); diff --git a/apps/openmw/mwsound/ffmpeg_decoder.hpp b/apps/openmw/mwsound/ffmpeg_decoder.hpp index 7b028e1d0b..32b2797ed7 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.hpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.hpp @@ -10,8 +10,8 @@ #include extern "C" { -#include -#include +#include +#include } #include "sound_decoder.hpp" @@ -22,25 +22,36 @@ namespace MWSound class FFmpeg_Decoder : public Sound_Decoder { AVFormatContext *mFormatCtx; + AVStream **mStream; - struct MyStream; - std::vector mStreams; + AVPacket mPacket; + AVFrame *mFrame; - bool getNextPacket(int streamidx); + int mFrameSize; + int mFramePos; + + double mNextPts; + + bool getNextPacket(); Ogre::DataStreamPtr mDataStream; static int readPacket(void *user_data, uint8_t *buf, int buf_size); static int writePacket(void *user_data, uint8_t *buf, int buf_size); static int64_t seek(void *user_data, int64_t offset, int whence); + bool getAVAudioData(); + size_t readAVAudioData(void *data, size_t length); + virtual void open(const std::string &fname); virtual void close(); + virtual std::string getName(); virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type); virtual size_t read(char *buffer, size_t bytes); virtual void readAll(std::vector &output); virtual void rewind(); + virtual size_t getSampleOffset(); FFmpeg_Decoder& operator=(const FFmpeg_Decoder &rhs); FFmpeg_Decoder(const FFmpeg_Decoder &rhs); diff --git a/apps/openmw/mwsound/mpgsnd_decoder.cpp b/apps/openmw/mwsound/mpgsnd_decoder.cpp index 7f7a84889a..fb187f8442 100644 --- a/apps/openmw/mwsound/mpgsnd_decoder.cpp +++ b/apps/openmw/mwsound/mpgsnd_decoder.cpp @@ -155,6 +155,11 @@ void MpgSnd_Decoder::close() mDataStream.setNull(); } +std::string MpgSnd_Decoder::getName() +{ + return mDataStream->getName(); +} + void MpgSnd_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) { if(!mSndFile && !mMpgFile) @@ -213,6 +218,11 @@ void MpgSnd_Decoder::rewind() } } +size_t MpgSnd_Decoder::getSampleOffset() +{ + return 0; +} + MpgSnd_Decoder::MpgSnd_Decoder() : mSndInfo() , mSndFile(NULL) diff --git a/apps/openmw/mwsound/mpgsnd_decoder.hpp b/apps/openmw/mwsound/mpgsnd_decoder.hpp index 09082c2f47..be52f6f491 100644 --- a/apps/openmw/mwsound/mpgsnd_decoder.hpp +++ b/apps/openmw/mwsound/mpgsnd_decoder.hpp @@ -34,11 +34,13 @@ namespace MWSound virtual void open(const std::string &fname); virtual void close(); + virtual std::string getName(); virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type); virtual size_t read(char *buffer, size_t bytes); virtual void readAll(std::vector &output); virtual void rewind(); + virtual size_t getSampleOffset(); MpgSnd_Decoder& operator=(const MpgSnd_Decoder &rhs); MpgSnd_Decoder(const MpgSnd_Decoder &rhs); diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index e5169d878b..67008e2bcd 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -61,10 +61,50 @@ static ALenum getALFormat(ChannelConfig chans, SampleType type) if(fmtlist[i].chans == chans && fmtlist[i].type == type) return fmtlist[i].format; } + + if(alIsExtensionPresent("AL_EXT_MCFORMATS")) + { + static const struct { + char name[32]; + ChannelConfig chans; + SampleType type; + } mcfmtlist[] = { + { "AL_FORMAT_QUAD16", ChannelConfig_Quad, SampleType_Int16 }, + { "AL_FORMAT_QUAD8", ChannelConfig_Quad, SampleType_UInt8 }, + { "AL_FORMAT_51CHN16", ChannelConfig_5point1, SampleType_Int16 }, + { "AL_FORMAT_51CHN8", ChannelConfig_5point1, SampleType_UInt8 }, + { "AL_FORMAT_71CHN16", ChannelConfig_7point1, SampleType_Int16 }, + { "AL_FORMAT_71CHN8", ChannelConfig_7point1, SampleType_UInt8 }, + }; + static const size_t mcfmtlistsize = sizeof(mcfmtlist)/sizeof(mcfmtlist[0]); + + for(size_t i = 0;i < mcfmtlistsize;i++) + { + if(mcfmtlist[i].chans == chans && mcfmtlist[i].type == type) + { + ALenum format = alGetEnumValue(mcfmtlist[i].name); + if(format != 0 && format != -1) + return format; + } + } + } + fail(std::string("Unsupported sound format (")+getChannelConfigName(chans)+", "+getSampleTypeName(type)+")"); return AL_NONE; } +static ALint getBufferSampleCount(ALuint buf) +{ + ALint size, bits, channels; + + alGetBufferi(buf, AL_SIZE, &size); + alGetBufferi(buf, AL_BITS, &bits); + alGetBufferi(buf, AL_CHANNELS, &channels); + throwALerror(); + + return size / channels * 8 / bits; +} + // // A streaming OpenAL sound. // @@ -82,19 +122,26 @@ class OpenAL_SoundStream : public Sound ALsizei mSampleRate; ALuint mBufferSize; + ALuint mSamplesQueued; + DecoderPtr mDecoder; volatile bool mIsFinished; + void updateAll(bool local); + OpenAL_SoundStream(const OpenAL_SoundStream &rhs); OpenAL_SoundStream& operator=(const OpenAL_SoundStream &rhs); + friend class OpenAL_Output; + public: - OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder); + OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder, float basevol, float pitch, int flags); virtual ~OpenAL_SoundStream(); virtual void stop(); virtual bool isPlaying(); + virtual double getTimeOffset(); virtual void update(); void play(); @@ -109,7 +156,7 @@ const ALfloat OpenAL_SoundStream::sBufferLength = 0.125f; struct OpenAL_Output::StreamThread { typedef std::vector StreamVec; StreamVec mStreams; - boost::mutex mMutex; + boost::recursive_mutex mMutex; boost::thread mThread; StreamThread() @@ -170,8 +217,9 @@ private: }; -OpenAL_SoundStream::OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder) - : mOutput(output), mSource(src), mDecoder(decoder), mIsFinished(true) +OpenAL_SoundStream::OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder, float basevol, float pitch, int flags) + : Sound(Ogre::Vector3(0.0f), 1.0f, basevol, pitch, 1.0f, 1000.0f, flags) + , mOutput(output), mSource(src), mSamplesQueued(0), mDecoder(decoder), mIsFinished(true) { throwALerror(); @@ -189,6 +237,8 @@ OpenAL_SoundStream::OpenAL_SoundStream(OpenAL_Output &output, ALuint src, Decode mBufferSize = static_cast(sBufferLength*srate); mBufferSize = framesToBytes(mBufferSize, chans, type); + + mOutput.mActiveSounds.push_back(this); } catch(std::exception &e) { @@ -209,22 +259,20 @@ OpenAL_SoundStream::~OpenAL_SoundStream() alGetError(); mDecoder->close(); + + mOutput.mActiveSounds.erase(std::find(mOutput.mActiveSounds.begin(), + mOutput.mActiveSounds.end(), this)); } void OpenAL_SoundStream::play() { - std::vector data(mBufferSize); - alSourceStop(mSource); alSourcei(mSource, AL_BUFFER, 0); throwALerror(); + mSamplesQueued = 0; for(ALuint i = 0;i < sNumBuffers;i++) - { - size_t got; - got = mDecoder->read(&data[0], data.size()); - alBufferData(mBuffers[i], mFormat, &data[0], got, mSampleRate); - } + alBufferData(mBuffers[i], mFormat, this, 0, mSampleRate); throwALerror(); alSourceQueueBuffers(mSource, sNumBuffers, mBuffers); @@ -243,6 +291,7 @@ void OpenAL_SoundStream::stop() alSourceStop(mSource); alSourcei(mSource, AL_BUFFER, 0); throwALerror(); + mSamplesQueued = 0; mDecoder->rewind(); } @@ -254,11 +303,49 @@ bool OpenAL_SoundStream::isPlaying() alGetSourcei(mSource, AL_SOURCE_STATE, &state); throwALerror(); - if(state == AL_PLAYING) + if(state == AL_PLAYING || state == AL_PAUSED) return true; return !mIsFinished; } +double OpenAL_SoundStream::getTimeOffset() +{ + ALint state = AL_STOPPED; + ALfloat offset = 0.0f; + double t; + + mOutput.mStreamThread->mMutex.lock(); + alGetSourcef(mSource, AL_SEC_OFFSET, &offset); + alGetSourcei(mSource, AL_SOURCE_STATE, &state); + if(state == AL_PLAYING || state == AL_PAUSED) + t = (double)(mDecoder->getSampleOffset() - mSamplesQueued)/(double)mSampleRate + offset; + else + t = (double)mDecoder->getSampleOffset() / (double)mSampleRate; + mOutput.mStreamThread->mMutex.unlock(); + + throwALerror(); + return t; +} + +void OpenAL_SoundStream::updateAll(bool local) +{ + alSourcef(mSource, AL_REFERENCE_DISTANCE, mMinDistance); + alSourcef(mSource, AL_MAX_DISTANCE, mMaxDistance); + if(local) + { + alSourcef(mSource, AL_ROLLOFF_FACTOR, 0.0f); + alSourcei(mSource, AL_SOURCE_RELATIVE, AL_TRUE); + } + else + { + alSourcef(mSource, AL_ROLLOFF_FACTOR, 1.0f); + alSourcei(mSource, AL_SOURCE_RELATIVE, AL_FALSE); + } + alSourcei(mSource, AL_LOOPING, AL_FALSE); + + update(); +} + void OpenAL_SoundStream::update() { ALfloat gain = mVolume*mBaseVolume; @@ -279,52 +366,58 @@ void OpenAL_SoundStream::update() bool OpenAL_SoundStream::process() { - bool finished = mIsFinished; - ALint processed, state; + try { + bool finished = mIsFinished; + ALint processed, state; - alGetSourcei(mSource, AL_SOURCE_STATE, &state); - alGetSourcei(mSource, AL_BUFFERS_PROCESSED, &processed); - throwALerror(); - - if(processed > 0) - { - std::vector data(mBufferSize); - do { - ALuint bufid; - size_t got; - - alSourceUnqueueBuffers(mSource, 1, &bufid); - processed--; - - if(finished) - continue; - - got = mDecoder->read(&data[0], data.size()); - finished = (got < data.size()); - if(got > 0) - { - alBufferData(bufid, mFormat, &data[0], got, mSampleRate); - alSourceQueueBuffers(mSource, 1, &bufid); - } - } while(processed > 0); + alGetSourcei(mSource, AL_SOURCE_STATE, &state); + alGetSourcei(mSource, AL_BUFFERS_PROCESSED, &processed); throwALerror(); - } - if(state != AL_PLAYING && state != AL_PAUSED) - { - ALint queued; - - alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued); - throwALerror(); - if(queued > 0) + if(processed > 0) { - alSourcePlay(mSource); + std::vector data(mBufferSize); + do { + ALuint bufid = 0; + size_t got; + + alSourceUnqueueBuffers(mSource, 1, &bufid); + mSamplesQueued -= getBufferSampleCount(bufid); + processed--; + + if(finished) + continue; + + got = mDecoder->read(&data[0], data.size()); + finished = (got < data.size()); + if(got > 0) + { + alBufferData(bufid, mFormat, &data[0], got, mSampleRate); + alSourceQueueBuffers(mSource, 1, &bufid); + mSamplesQueued += getBufferSampleCount(bufid); + } + } while(processed > 0); throwALerror(); } - } - mIsFinished = finished; - return !finished; + if(state != AL_PLAYING && state != AL_PAUSED) + { + ALint queued = 0; + + alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued); + if(queued > 0) + alSourcePlay(mSource); + throwALerror(); + } + + mIsFinished = finished; + } + catch(std::exception &e) { + std::cout<< "Error updating stream \""<getName()<<"\"" <removeAll(); - while(!mFreeSources.empty()) - { - alDeleteSources(1, &mFreeSources.front()); - mFreeSources.pop_front(); - } + for(size_t i = 0;i < mFreeSources.size();i++) + alDeleteSources(1, &mFreeSources[i]); + mFreeSources.clear(); mBufferRefs.clear(); mUnusedBuffers.clear(); @@ -642,7 +773,7 @@ void OpenAL_Output::bufferFinished(ALuint buf) } -MWBase::SoundPtr OpenAL_Output::playSound(const std::string &fname, float volume, float pitch, int flags) +MWBase::SoundPtr OpenAL_Output::playSound(const std::string &fname, float vol, float basevol, float pitch, int flags) { boost::shared_ptr sound; ALuint src=0, buf=0; @@ -655,7 +786,7 @@ MWBase::SoundPtr OpenAL_Output::playSound(const std::string &fname, float volume try { buf = getBuffer(fname); - sound.reset(new OpenAL_Sound(*this, src, buf)); + sound.reset(new OpenAL_Sound(*this, src, buf, Ogre::Vector3(0.0f), vol, basevol, pitch, 1.0f, 1000.0f, flags)); } catch(std::exception &e) { @@ -666,25 +797,7 @@ MWBase::SoundPtr OpenAL_Output::playSound(const std::string &fname, float volume throw; } - alSource3f(src, AL_POSITION, 0.0f, 0.0f, 0.0f); - alSource3f(src, AL_DIRECTION, 0.0f, 0.0f, 0.0f); - alSource3f(src, AL_VELOCITY, 0.0f, 0.0f, 0.0f); - - alSourcef(src, AL_REFERENCE_DISTANCE, 1.0f); - alSourcef(src, AL_MAX_DISTANCE, 1000.0f); - alSourcef(src, AL_ROLLOFF_FACTOR, 0.0f); - - if(!(flags&MWBase::SoundManager::Play_NoEnv) && mLastEnvironment == Env_Underwater) - { - volume *= 0.9f; - pitch *= 0.7f; - } - alSourcef(src, AL_GAIN, volume); - alSourcef(src, AL_PITCH, pitch); - - alSourcei(src, AL_SOURCE_RELATIVE, AL_TRUE); - alSourcei(src, AL_LOOPING, (flags&MWBase::SoundManager::Play_Loop) ? AL_TRUE : AL_FALSE); - throwALerror(); + sound->updateAll(true); alSourcei(src, AL_BUFFER, buf); alSourcePlay(src); @@ -693,8 +806,8 @@ MWBase::SoundPtr OpenAL_Output::playSound(const std::string &fname, float volume return sound; } -MWBase::SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre::Vector3 &pos, float volume, float pitch, - float min, float max, int flags) +MWBase::SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre::Vector3 &pos, float vol, float basevol, float pitch, + float min, float max, int flags) { boost::shared_ptr sound; ALuint src=0, buf=0; @@ -707,7 +820,7 @@ MWBase::SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre try { buf = getBuffer(fname); - sound.reset(new OpenAL_Sound3D(*this, src, buf)); + sound.reset(new OpenAL_Sound3D(*this, src, buf, pos, vol, basevol, pitch, min, max, flags)); } catch(std::exception &e) { @@ -718,26 +831,7 @@ MWBase::SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre throw; } - alSource3f(src, AL_POSITION, pos.x, pos.z, -pos.y); - alSource3f(src, AL_DIRECTION, 0.0f, 0.0f, 0.0f); - alSource3f(src, AL_VELOCITY, 0.0f, 0.0f, 0.0f); - - alSourcef(src, AL_REFERENCE_DISTANCE, min); - alSourcef(src, AL_MAX_DISTANCE, max); - alSourcef(src, AL_ROLLOFF_FACTOR, 1.0f); - - if(!(flags&MWBase::SoundManager::Play_NoEnv) && mLastEnvironment == Env_Underwater) - { - volume *= 0.9f; - pitch *= 0.7f; - } - alSourcef(src, AL_GAIN, (pos.squaredDistance(mPos) > max*max) ? - 0.0f : volume); - alSourcef(src, AL_PITCH, pitch); - - alSourcei(src, AL_SOURCE_RELATIVE, AL_FALSE); - alSourcei(src, AL_LOOPING, (flags&MWBase::SoundManager::Play_Loop) ? AL_TRUE : AL_FALSE); - throwALerror(); + sound->updateAll(false); alSourcei(src, AL_BUFFER, buf); alSourcePlay(src); @@ -747,7 +841,7 @@ MWBase::SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre } -MWBase::SoundPtr OpenAL_Output::streamSound(const std::string &fname, float volume, float pitch, int flags) +MWBase::SoundPtr OpenAL_Output::streamSound(DecoderPtr decoder, float volume, float pitch, int flags) { boost::shared_ptr sound; ALuint src; @@ -757,13 +851,11 @@ MWBase::SoundPtr OpenAL_Output::streamSound(const std::string &fname, float volu src = mFreeSources.front(); mFreeSources.pop_front(); + if((flags&MWBase::SoundManager::Play_Loop)) + std::cout <<"Warning: cannot loop stream \""<getName()<<"\""<< std::endl; try { - if((flags&MWBase::SoundManager::Play_Loop)) - std::cout <<"Warning: cannot loop stream "<open(fname); - sound.reset(new OpenAL_SoundStream(*this, src, decoder)); + sound.reset(new OpenAL_SoundStream(*this, src, decoder, volume, pitch, flags)); } catch(std::exception &e) { @@ -771,25 +863,7 @@ MWBase::SoundPtr OpenAL_Output::streamSound(const std::string &fname, float volu throw; } - alSource3f(src, AL_POSITION, 0.0f, 0.0f, 0.0f); - alSource3f(src, AL_DIRECTION, 0.0f, 0.0f, 0.0f); - alSource3f(src, AL_VELOCITY, 0.0f, 0.0f, 0.0f); - - alSourcef(src, AL_REFERENCE_DISTANCE, 1.0f); - alSourcef(src, AL_MAX_DISTANCE, 1000.0f); - alSourcef(src, AL_ROLLOFF_FACTOR, 0.0f); - - if(!(flags&MWBase::SoundManager::Play_NoEnv) && mLastEnvironment == Env_Underwater) - { - volume *= 0.9f; - pitch *= 0.7f; - } - alSourcef(src, AL_GAIN, volume); - alSourcef(src, AL_PITCH, pitch); - - alSourcei(src, AL_SOURCE_RELATIVE, AL_TRUE); - alSourcei(src, AL_LOOPING, AL_FALSE); - throwALerror(); + sound->updateAll(true); sound->play(); return sound; @@ -814,6 +888,61 @@ void OpenAL_Output::updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 } +void OpenAL_Output::pauseSounds(int types) +{ + std::vector sources; + SoundVec::const_iterator iter = mActiveSounds.begin(); + while(iter != mActiveSounds.end()) + { + const OpenAL_SoundStream *stream = dynamic_cast(*iter); + if(stream) + { + if(stream->mSource && (stream->getPlayType()&types)) + sources.push_back(stream->mSource); + } + else + { + const OpenAL_Sound *sound = dynamic_cast(*iter); + if(sound && sound->mSource && (sound->getPlayType()&types)) + sources.push_back(sound->mSource); + } + iter++; + } + if(sources.size() > 0) + { + alSourcePausev(sources.size(), &sources[0]); + throwALerror(); + } +} + +void OpenAL_Output::resumeSounds(int types) +{ + std::vector sources; + SoundVec::const_iterator iter = mActiveSounds.begin(); + while(iter != mActiveSounds.end()) + { + const OpenAL_SoundStream *stream = dynamic_cast(*iter); + if(stream) + { + if(stream->mSource && (stream->getPlayType()&types)) + sources.push_back(stream->mSource); + } + else + { + const OpenAL_Sound *sound = dynamic_cast(*iter); + if(sound && sound->mSource && (sound->getPlayType()&types)) + sources.push_back(sound->mSource); + } + iter++; + } + if(sources.size() > 0) + { + alSourcePlayv(sources.size(), &sources[0]); + throwALerror(); + } +} + + OpenAL_Output::OpenAL_Output(SoundManager &mgr) : Sound_Output(mgr), mDevice(0), mContext(0), mBufferCacheMemSize(0), mLastEnvironment(Env_Normal), mStreamThread(new StreamThread) diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index fecffa5752..02706b50ca 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -33,6 +33,9 @@ namespace MWSound uint64_t mBufferCacheMemSize; + typedef std::vector SoundVec; + SoundVec mActiveSounds; + ALuint getBuffer(const std::string &fname); void bufferFinished(ALuint buffer); @@ -42,13 +45,16 @@ namespace MWSound virtual void init(const std::string &devname=""); virtual void deinit(); - virtual MWBase::SoundPtr playSound(const std::string &fname, float volume, float pitch, int flags); + virtual MWBase::SoundPtr playSound(const std::string &fname, float vol, float basevol, float pitch, int flags); virtual MWBase::SoundPtr playSound3D(const std::string &fname, const Ogre::Vector3 &pos, - float volume, float pitch, float min, float max, int flags); - virtual MWBase::SoundPtr streamSound(const std::string &fname, float volume, float pitch, int flags); + float vol, float basevol, float pitch, float min, float max, int flags); + virtual MWBase::SoundPtr streamSound(DecoderPtr decoder, float volume, float pitch, int flags); virtual void updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir, Environment env); + virtual void pauseSounds(int types); + virtual void resumeSounds(int types); + OpenAL_Output& operator=(const OpenAL_Output &rhs); OpenAL_Output(const OpenAL_Output &rhs); @@ -64,7 +70,7 @@ namespace MWSound friend class SoundManager; }; #ifndef DEFAULT_OUTPUT -#define DEFAULT_OUTPUT (::MWSound::OpenAL_Output) +#define DEFAULT_OUTPUT(x) ::MWSound::OpenAL_Output((x)) #endif } diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp index 729147f75a..8deaf26a64 100644 --- a/apps/openmw/mwsound/sound.hpp +++ b/apps/openmw/mwsound/sound.hpp @@ -26,16 +26,22 @@ namespace MWSound public: virtual void stop() = 0; virtual bool isPlaying() = 0; + virtual double getTimeOffset() = 0; void setPosition(const Ogre::Vector3 &pos) { mPos = pos; } void setVolume(float volume) { mVolume = volume; } - Sound() : mPos(0.0f, 0.0f, 0.0f) - , mVolume(1.0f) - , mBaseVolume(1.0f) - , mPitch(1.0f) - , mMinDistance(20.0f) /* 1 * min_range_scale */ - , mMaxDistance(12750.0f) /* 255 * max_range_scale */ - , mFlags(MWBase::SoundManager::Play_Normal) + MWBase::SoundManager::PlayType getPlayType() const + { return (MWBase::SoundManager::PlayType)(mFlags&MWBase::SoundManager::Play_TypeMask); } + + + Sound(const Ogre::Vector3& pos, float vol, float basevol, float pitch, float mindist, float maxdist, int flags) + : mPos(pos) + , mVolume(vol) + , mBaseVolume(basevol) + , mPitch(pitch) + , mMinDistance(mindist) + , mMaxDistance(maxdist) + , mFlags(flags) { } virtual ~Sound() { } diff --git a/apps/openmw/mwsound/sound_decoder.hpp b/apps/openmw/mwsound/sound_decoder.hpp index 9c28d5ff55..29d99e8fa4 100644 --- a/apps/openmw/mwsound/sound_decoder.hpp +++ b/apps/openmw/mwsound/sound_decoder.hpp @@ -15,7 +15,10 @@ namespace MWSound enum ChannelConfig { ChannelConfig_Mono, - ChannelConfig_Stereo + ChannelConfig_Stereo, + ChannelConfig_Quad, + ChannelConfig_5point1, + ChannelConfig_7point1 }; const char *getChannelConfigName(ChannelConfig config); @@ -29,11 +32,13 @@ namespace MWSound virtual void open(const std::string &fname) = 0; virtual void close() = 0; + virtual std::string getName() = 0; virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) = 0; virtual size_t read(char *buffer, size_t bytes) = 0; virtual void readAll(std::vector &output); virtual void rewind() = 0; + virtual size_t getSampleOffset() = 0; Sound_Decoder() : mResourceMgr(Ogre::ResourceGroupManager::getSingleton()) { } diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index 2680ec1dbe..b5ccc946af 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -24,13 +24,16 @@ namespace MWSound virtual void init(const std::string &devname="") = 0; virtual void deinit() = 0; - virtual MWBase::SoundPtr playSound(const std::string &fname, float volume, float pitch, int flags) = 0; + virtual MWBase::SoundPtr playSound(const std::string &fname, float vol, float basevol, float pitch, int flags) = 0; virtual MWBase::SoundPtr playSound3D(const std::string &fname, const Ogre::Vector3 &pos, - float volume, float pitch, float min, float max, int flags) = 0; - virtual MWBase::SoundPtr streamSound(const std::string &fname, float volume, float pitch, int flags) = 0; + float vol, float basevol, float pitch, float min, float max, int flags) = 0; + virtual MWBase::SoundPtr streamSound(DecoderPtr decoder, float volume, float pitch, int flags) = 0; virtual void updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir, Environment env) = 0; + virtual void pauseSounds(int types) = 0; + virtual void resumeSounds(int types) = 0; + Sound_Output& operator=(const Sound_Output &rhs); Sound_Output(const Sound_Output &rhs); diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 8c4798c9d7..8a69fc96bc 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -52,6 +52,7 @@ namespace MWSound , mMusicVolume(1.0f) , mFootstepsVolume(1.0f) , mVoiceVolume(1.0f) + , mPausedSoundTypes(0) { if(!useSound) return; @@ -86,7 +87,7 @@ namespace MWSound { if(devname.empty()) throw; - std::cout <<"Failed to open device \""<init(); Settings::Manager::setString("device", "Sound", ""); } @@ -137,6 +138,27 @@ namespace MWSound return "Sound/"+snd->mSound; } + // Gets the combined volume settings for the given sound type + float SoundManager::volumeFromType(PlayType type) const + { + float volume = mMasterVolume; + switch(type) + { + case Play_TypeSfx: + volume *= mSFXVolume; + break; + case Play_TypeVoice: + volume *= mVoiceVolume; + break; + case Play_TypeMusic: + case Play_TypeMovie: + volume *= mMusicVolume; + break; + case Play_TypeMask: + break; + } + return volume; + } bool SoundManager::isPlaying(MWWorld::Ptr ptr, const std::string &id) const { @@ -165,11 +187,13 @@ namespace MWSound std::cout <<"Playing "<streamSound(filename, basevol, 1.0f, Play_NoEnv); - mMusic->mBaseVolume = basevol; - mMusic->mFlags = Play_NoEnv; + + DecoderPtr decoder = getDecoder(); + decoder->open(filename); + + mMusic = mOutput->streamSound(decoder, volumeFromType(Play_TypeMusic), + 1.0f, Play_NoEnv|Play_TypeMusic); } catch(std::exception &e) { @@ -212,16 +236,13 @@ namespace MWSound try { // The range values are not tested - float basevol = mMasterVolume * mVoiceVolume; + float basevol = volumeFromType(Play_TypeVoice); std::string filePath = "Sound/"+filename; const ESM::Position &pos = ptr.getRefData().getPosition(); const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]); - MWBase::SoundPtr sound = mOutput->playSound3D(filePath, objpos, basevol, 1.0f, - 20.0f, 12750.0f, Play_Normal); - sound->mPos = objpos; - sound->mBaseVolume = basevol; - + MWBase::SoundPtr sound = mOutput->playSound3D(filePath, objpos, 1.0f, basevol, 1.0f, + 20.0f, 12750.0f, Play_Normal|Play_TypeVoice); mActiveSounds[sound] = std::make_pair(ptr, std::string("_say_sound")); } catch(std::exception &e) @@ -236,12 +257,10 @@ namespace MWSound return; try { - float basevol = mMasterVolume * mVoiceVolume; + float basevol = volumeFromType(Play_TypeVoice); std::string filePath = "Sound/"+filename; - MWBase::SoundPtr sound = mOutput->playSound(filePath, basevol, 1.0f, Play_Normal); - sound->mBaseVolume = basevol; - + MWBase::SoundPtr sound = mOutput->playSound(filePath, 1.0f, basevol, 1.0f, Play_Normal|Play_TypeVoice); mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), std::string("_say_sound")); } catch(std::exception &e) @@ -271,26 +290,35 @@ namespace MWSound } + MWBase::SoundPtr SoundManager::playTrack(const DecoderPtr& decoder, PlayType type) + { + MWBase::SoundPtr track; + if(!mOutput->isInitialized()) + return track; + try + { + track = mOutput->streamSound(decoder, volumeFromType(type), 1.0f, Play_NoEnv|type); + } + catch(std::exception &e) + { + std::cout <<"Sound Error: "<isInitialized()) return sound; try { - float basevol = mMasterVolume * mSFXVolume; + float basevol = volumeFromType(Play_TypeSfx); float min, max; - std::string file = lookup(soundId, basevol, min, max); - - sound = mOutput->playSound(file, volume*basevol, pitch, mode); - sound->mVolume = volume; - sound->mBaseVolume = basevol; - sound->mPitch = pitch; - sound->mMinDistance = min; - sound->mMaxDistance = max; - sound->mFlags = mode; + std::string file = lookup(soundId, volume, min, max); + sound = mOutput->playSound(file, volume, basevol, pitch, mode|Play_TypeSfx); mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), soundId); } catch(std::exception &e) @@ -301,7 +329,7 @@ namespace MWSound } MWBase::SoundPtr SoundManager::playSound3D(MWWorld::Ptr ptr, const std::string& soundId, - float volume, float pitch, int mode) + float volume, float pitch, PlayMode mode) { MWBase::SoundPtr sound; if(!mOutput->isInitialized()) @@ -309,21 +337,13 @@ namespace MWSound try { // Look up the sound in the ESM data - float basevol = mMasterVolume * mSFXVolume; + float basevol = volumeFromType(Play_TypeSfx); float min, max; - std::string file = lookup(soundId, basevol, min, max); + std::string file = lookup(soundId, volume, min, max); const ESM::Position &pos = ptr.getRefData().getPosition();; const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]); - sound = mOutput->playSound3D(file, objpos, volume*basevol, pitch, min, max, mode); - sound->mPos = objpos; - sound->mVolume = volume; - sound->mBaseVolume = basevol; - sound->mPitch = pitch; - sound->mMinDistance = min; - sound->mMaxDistance = max; - sound->mFlags = mode; - + sound = mOutput->playSound3D(file, objpos, volume, basevol, pitch, min, max, mode|Play_TypeSfx); if((mode&Play_NoTrack)) mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), soundId); else @@ -404,6 +424,27 @@ namespace MWSound } + void SoundManager::pauseSounds(int types) + { + if(mOutput->isInitialized()) + { + types &= Play_TypeMask; + mOutput->pauseSounds(types); + mPausedSoundTypes |= types; + } + } + + void SoundManager::resumeSounds(int types) + { + if(mOutput->isInitialized()) + { + types &= types&Play_TypeMask&mPausedSoundTypes; + mOutput->resumeSounds(types); + mPausedSoundTypes &= ~types; + } + } + + void SoundManager::updateRegionSound(float duration) { MWWorld::Ptr::CellStore *current = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getCell(); @@ -525,24 +566,13 @@ namespace MWSound SoundMap::iterator snditer = mActiveSounds.begin(); while(snditer != mActiveSounds.end()) { - if(snditer->second.second != "_say_sound") - { - float basevol = mMasterVolume * mSFXVolume; - float min, max; - lookup(snditer->second.second, basevol, min, max); - snditer->first->mBaseVolume = basevol; - } - else - { - float basevol = mMasterVolume * mVoiceVolume; - snditer->first->mBaseVolume = basevol; - } + snditer->first->mBaseVolume = volumeFromType(snditer->first->getPlayType()); snditer->first->update(); snditer++; } if(mMusic) { - mMusic->mBaseVolume = mMasterVolume * mMusicVolume; + mMusic->mBaseVolume = volumeFromType(mMusic->getPlayType()); mMusic->update(); } } @@ -585,8 +615,11 @@ namespace MWSound { switch(config) { - case ChannelConfig_Mono: return "Mono"; - case ChannelConfig_Stereo: return "Stereo"; + case ChannelConfig_Mono: return "Mono"; + case ChannelConfig_Stereo: return "Stereo"; + case ChannelConfig_Quad: return "Quad"; + case ChannelConfig_5point1: return "5.1 Surround"; + case ChannelConfig_7point1: return "7.1 Surround"; } return "(unknown channel config)"; } @@ -595,8 +628,11 @@ namespace MWSound { switch(config) { - case ChannelConfig_Mono: frames *= 1; break; - case ChannelConfig_Stereo: frames *= 2; break; + case ChannelConfig_Mono: frames *= 1; break; + case ChannelConfig_Stereo: frames *= 2; break; + case ChannelConfig_Quad: frames *= 4; break; + case ChannelConfig_5point1: frames *= 6; break; + case ChannelConfig_7point1: frames *= 8; break; } switch(type) { diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index a84aa3b9a7..b475449d90 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -22,8 +22,6 @@ namespace MWSound struct Sound_Decoder; class Sound; - typedef boost::shared_ptr DecoderPtr; - enum Environment { Env_Normal, Env_Underwater @@ -54,6 +52,8 @@ namespace MWSound Ogre::Vector3 mListenerDir; Ogre::Vector3 mListenerUp; + int mPausedSoundTypes; + std::string lookup(const std::string &soundId, float &volume, float &min, float &max); void streamMusicFull(const std::string& filename); @@ -61,6 +61,8 @@ namespace MWSound void updateSounds(float duration); void updateRegionSound(float duration); + float volumeFromType(PlayType type) const; + SoundManager(const SoundManager &rhs); SoundManager& operator=(const SoundManager &rhs); @@ -105,11 +107,14 @@ namespace MWSound virtual void stopSay(MWWorld::Ptr reference=MWWorld::Ptr()); ///< Stop an actor speaking - virtual MWBase::SoundPtr playSound(const std::string& soundId, float volume, float pitch, int mode=Play_Normal); + virtual MWBase::SoundPtr playTrack(const DecoderPtr& decoder, PlayType type); + ///< Play a 2D audio track, using a custom decoder + + virtual MWBase::SoundPtr playSound(const std::string& soundId, float volume, float pitch, PlayMode mode=Play_Normal); ///< Play a sound, independently of 3D-position virtual MWBase::SoundPtr playSound3D(MWWorld::Ptr reference, const std::string& soundId, - float volume, float pitch, int mode=Play_Normal); + float volume, float pitch, PlayMode mode=Play_Normal); ///< Play a sound from an object virtual void stopSound3D(MWWorld::Ptr reference, const std::string& soundId); @@ -127,6 +132,12 @@ namespace MWSound virtual bool getSoundPlaying(MWWorld::Ptr reference, const std::string& soundId) const; ///< Is the given sound currently playing on the given object? + virtual void pauseSounds(int types=Play_TypeMask); + ///< Pauses all currently playing sounds, including music. + + virtual void resumeSounds(int types=Play_TypeMask); + ///< Resumes all previously paused sounds. + virtual void update(float duration); virtual void setListenerPosDir(const Ogre::Vector3 &pos, const Ogre::Vector3 &dir, const Ogre::Vector3 &up); diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 009b325c06..19bc881f7f 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -738,7 +738,7 @@ void WeatherManager::update(float duration) if (std::find(mSoundsPlaying.begin(), mSoundsPlaying.end(), ambientSnd) == mSoundsPlaying.end()) { mSoundsPlaying.push_back(ambientSnd); - MWBase::Environment::get().getSoundManager()->playSound(ambientSnd, 1.0, 1.0, true); + MWBase::Environment::get().getSoundManager()->playSound(ambientSnd, 1.0, 1.0, MWBase::SoundManager::Play_Loop); } } @@ -749,7 +749,7 @@ void WeatherManager::update(float duration) if (std::find(mSoundsPlaying.begin(), mSoundsPlaying.end(), rainSnd) == mSoundsPlaying.end()) { mSoundsPlaying.push_back(rainSnd); - MWBase::Environment::get().getSoundManager()->playSound(rainSnd, 1.0, 1.0, true); + MWBase::Environment::get().getSoundManager()->playSound(rainSnd, 1.0, 1.0, MWBase::SoundManager::Play_Loop); } } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 3995123b0e..d59623ae58 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -170,7 +170,7 @@ namespace MWWorld World::World (OEngine::Render::OgreRenderer& renderer, const Files::Collections& fileCollections, const std::string& master, const boost::filesystem::path& resDir, const boost::filesystem::path& cacheDir, bool newGame, - const ToUTF8::FromType& encoding, std::map fallbackMap) + ToUTF8::Utf8Encoder* encoder, std::map fallbackMap) : mPlayer (0), mLocalScripts (mStore), mGlobalVariables (0), mSky (true), mCells (mStore, mEsm), mNumFacing(0) @@ -187,7 +187,7 @@ namespace MWWorld std::cout << "Loading ESM " << masterPath.string() << "\n"; // This parses the ESM file and loads a sample cell - mEsm.setEncoding(encoding); + mEsm.setEncoder(encoder); mEsm.open (masterPath.string()); mStore.load (mEsm); @@ -1300,4 +1300,14 @@ namespace MWWorld return 0; } + + void World::playVideo (const std::string &name, bool allowSkipping) + { + mRendering->playVideo(name, allowSkipping); + } + + void World::stopVideo () + { + mRendering->stopVideo(); + } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 71ae9597f8..35652f01df 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -95,7 +95,7 @@ namespace MWWorld World (OEngine::Render::OgreRenderer& renderer, const Files::Collections& fileCollections, const std::string& master, const boost::filesystem::path& resDir, const boost::filesystem::path& cacheDir, bool newGame, - const ToUTF8::FromType& encoding, std::map fallbackMap); + ToUTF8::Utf8Encoder* encoder, std::map fallbackMap); virtual ~World(); @@ -336,6 +336,10 @@ namespace MWWorld /// 1 - only waiting \n /// 2 - player is underwater \n /// 3 - enemies are nearby (not implemented) + + /// \todo this does not belong here + virtual void playVideo(const std::string& name, bool allowSkipping); + virtual void stopVideo(); }; } diff --git a/cmake/FindFFMPEG.cmake b/cmake/FindFFMPEG.cmake deleted file mode 100644 index 2e755d047d..0000000000 --- a/cmake/FindFFMPEG.cmake +++ /dev/null @@ -1,105 +0,0 @@ -# Find the FFmpeg library -# -# Sets -# FFMPEG_FOUND. If false, don't try to use ffmpeg -# FFMPEG_INCLUDE_DIR -# FFMPEG_LIBRARIES -# -# Modified by Nicolay Korslund for OpenMW - -SET( FFMPEG_FOUND "NO" ) - -FIND_PATH( FFMPEG_general_INCLUDE_DIR libavcodec/avcodec.h libavformat/avformat.h - HINTS - PATHS - /usr/include - /usr/local/include - /usr/include/ffmpeg - /usr/local/include/ffmpeg - /usr/include/ffmpeg/libavcodec - /usr/local/include/ffmpeg/libavcodec - /usr/include/libavcodec - /usr/local/include/libavcodec - ) - -FIND_PATH( FFMPEG_avcodec_INCLUDE_DIR avcodec.h - HINTS - PATHS - ${FFMPEG_general_INCLUDE_DIR}/libavcodec - /usr/include - /usr/local/include - /usr/include/ffmpeg - /usr/local/include/ffmpeg - /usr/include/ffmpeg/libavcodec - /usr/local/include/ffmpeg/libavcodec - /usr/include/libavcodec - /usr/local/include/libavcodec -) - -FIND_PATH( FFMPEG_avformat_INCLUDE_DIR avformat.h - HINTS - PATHS - ${FFMPEG_general_INCLUDE_DIR}/libavformat - /usr/include - /usr/local/include - /usr/include/ffmpeg - /usr/local/include/ffmpeg - /usr/include/ffmpeg/libavformat - /usr/local/include/ffmpeg/libavformat - /usr/include/libavformat - /usr/local/include/libavformat -) - -set(FFMPEG_INCLUDE_DIR ${FFMPEG_general_INCLUDE_DIR} ${FFMPEG_avcodec_INCLUDE_DIR} ${FFMPEG_avformat_INCLUDE_DIR}) - -IF( FFMPEG_INCLUDE_DIR ) - -FIND_PROGRAM( FFMPEG_CONFIG ffmpeg-config - /usr/bin - /usr/local/bin - ${HOME}/bin -) - -IF( FFMPEG_CONFIG ) - EXEC_PROGRAM( ${FFMPEG_CONFIG} ARGS "--libs avformat" OUTPUT_VARIABLE FFMPEG_LIBS ) - SET( FFMPEG_FOUND "YES" ) - SET( FFMPEG_LIBRARIES "${FFMPEG_LIBS}" ) - -ELSE( FFMPEG_CONFIG ) - - FIND_LIBRARY( FFMPEG_avcodec_LIBRARY avcodec - /usr/lib - /usr/local/lib - /usr/lib64 - /usr/local/lib64 - ) - - FIND_LIBRARY( FFMPEG_avformat_LIBRARY avformat - /usr/lib - /usr/local/lib - /usr/lib64 - /usr/local/lib64 - ) - - FIND_LIBRARY( FFMPEG_avutil_LIBRARY avutil - /usr/lib - /usr/local/lib - /usr/lib64 - /usr/local/lib64 - ) - - IF( FFMPEG_avcodec_LIBRARY ) - IF( FFMPEG_avformat_LIBRARY ) - - SET( FFMPEG_FOUND "YES" ) - SET( FFMPEG_LIBRARIES ${FFMPEG_avformat_LIBRARY} ${FFMPEG_avcodec_LIBRARY} ) - IF( FFMPEG_avutil_LIBRARY ) - SET( FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${FFMPEG_avutil_LIBRARY} ) - ENDIF( FFMPEG_avutil_LIBRARY ) - - ENDIF( FFMPEG_avformat_LIBRARY ) - ENDIF( FFMPEG_avcodec_LIBRARY ) - -ENDIF( FFMPEG_CONFIG ) - -ENDIF( FFMPEG_INCLUDE_DIR ) diff --git a/cmake/FindFFmpeg.cmake b/cmake/FindFFmpeg.cmake new file mode 100644 index 0000000000..c80203a25b --- /dev/null +++ b/cmake/FindFFmpeg.cmake @@ -0,0 +1,148 @@ +# vim: ts=2 sw=2 +# - Try to find the required ffmpeg components(default: AVFORMAT, AVUTIL, AVCODEC) +# +# Once done this will define +# FFMPEG_FOUND - System has the all required components. +# FFMPEG_INCLUDE_DIRS - Include directory necessary for using the required components headers. +# FFMPEG_LIBRARIES - Link these to use the required ffmpeg components. +# FFMPEG_DEFINITIONS - Compiler switches required for using the required ffmpeg components. +# +# For each of the components it will additionaly set. +# - AVCODEC +# - AVDEVICE +# - AVFORMAT +# - AVUTIL +# - POSTPROCESS +# - SWSCALE +# the following variables will be defined +# _FOUND - System has +# _INCLUDE_DIRS - Include directory necessary for using the headers +# _LIBRARIES - Link these to use +# _DEFINITIONS - Compiler switches required for using +# _VERSION - The components version +# +# Copyright (c) 2006, Matthias Kretz, +# Copyright (c) 2008, Alexander Neundorf, +# Copyright (c) 2011, Michael Jansen, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +include(FindPackageHandleStandardArgs) + +# The default components were taken from a survey over other FindFFMPEG.cmake files +if (NOT FFmpeg_FIND_COMPONENTS) + set(FFmpeg_FIND_COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE) +endif () + +# +### Macro: set_component_found +# +# Marks the given component as found if both *_LIBRARIES AND *_INCLUDE_DIRS is present. +# +macro(set_component_found _component ) + if (${_component}_LIBRARIES AND ${_component}_INCLUDE_DIRS) + # message(STATUS " - ${_component} found.") + set(${_component}_FOUND TRUE) + else () + # message(STATUS " - ${_component} not found.") + endif () +endmacro() + +# +### Macro: find_component +# +# Checks for the given component by invoking pkgconfig and then looking up the libraries and +# include directories. +# +macro(find_component _component _pkgconfig _library _header) + + if (NOT WIN32) + # use pkg-config to get the directories and then use these values + # in the FIND_PATH() and FIND_LIBRARY() calls + find_package(PkgConfig) + if (PKG_CONFIG_FOUND) + pkg_check_modules(PC_${_component} ${_pkgconfig}) + endif () + endif (NOT WIN32) + + find_path(${_component}_INCLUDE_DIRS ${_header} + HINTS + ${PC_LIB${_component}_INCLUDEDIR} + ${PC_LIB${_component}_INCLUDE_DIRS} + PATH_SUFFIXES + ffmpeg + ) + + find_library(${_component}_LIBRARIES NAMES ${_library} + HINTS + ${PC_LIB${_component}_LIBDIR} + ${PC_LIB${_component}_LIBRARY_DIRS} + ) + + set(${_component}_DEFINITIONS ${PC_${_component}_CFLAGS_OTHER} CACHE STRING "The ${_component} CFLAGS.") + set(${_component}_VERSION ${PC_${_component}_VERSION} CACHE STRING "The ${_component} version number.") + + set_component_found(${_component}) + + mark_as_advanced( + ${_component}_INCLUDE_DIRS + ${_component}_LIBRARIES + ${_component}_DEFINITIONS + ${_component}_VERSION) + +endmacro() + + +# Check for cached results. If there are skip the costly part. +if (NOT FFMPEG_LIBRARIES) + + # Check for all possible component. + find_component(AVCODEC libavcodec avcodec libavcodec/avcodec.h) + find_component(AVFORMAT libavformat avformat libavformat/avformat.h) + find_component(AVDEVICE libavdevice avdevice libavdevice/avdevice.h) + find_component(AVUTIL libavutil avutil libavutil/avutil.h) + find_component(SWSCALE libswscale swscale libswscale/swscale.h) + find_component(POSTPROC libpostproc postproc libpostproc/postprocess.h) + + # Check if the required components were found and add their stuff to the FFMPEG_* vars. + foreach (_component ${FFmpeg_FIND_COMPONENTS}) + if (${_component}_FOUND) + # message(STATUS "Required component ${_component} present.") + set(FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${${_component}_LIBRARIES}) + set(FFMPEG_DEFINITIONS ${FFMPEG_DEFINITIONS} ${${_component}_DEFINITIONS}) + list(APPEND FFMPEG_INCLUDE_DIRS ${${_component}_INCLUDE_DIRS}) + else () + # message(STATUS "Required component ${_component} missing.") + endif () + endforeach () + + # Build the include path with duplicates removed. + if (FFMPEG_INCLUDE_DIRS) + list(REMOVE_DUPLICATES FFMPEG_INCLUDE_DIRS) + endif () + + # cache the vars. + set(FFMPEG_INCLUDE_DIRS ${FFMPEG_INCLUDE_DIRS} CACHE STRING "The FFmpeg include directories." FORCE) + set(FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} CACHE STRING "The FFmpeg libraries." FORCE) + set(FFMPEG_DEFINITIONS ${FFMPEG_DEFINITIONS} CACHE STRING "The FFmpeg cflags." FORCE) + + mark_as_advanced(FFMPEG_INCLUDE_DIRS + FFMPEG_LIBRARIES + FFMPEG_DEFINITIONS) + +endif () + +# Now set the noncached _FOUND vars for the components. +foreach (_component AVCODEC AVDEVICE AVFORMAT AVUTIL POSTPROCESS SWSCALE) + set_component_found(${_component}) +endforeach () + +# Compile the list of required vars +set(_FFmpeg_REQUIRED_VARS FFMPEG_LIBRARIES FFMPEG_INCLUDE_DIRS) +foreach (_component ${FFmpeg_FIND_COMPONENTS}) + list(APPEND _FFmpeg_REQUIRED_VARS ${_component}_LIBRARIES ${_component}_INCLUDE_DIRS) +endforeach () + +# Give a nice error message if some of the required vars are missing. +find_package_handle_standard_args(FFmpeg DEFAULT_MSG ${_FFmpeg_REQUIRED_VARS}) diff --git a/cmake/FindSDL.cmake b/cmake/FindSDL.cmake new file mode 100644 index 0000000000..0dc02f5b60 --- /dev/null +++ b/cmake/FindSDL.cmake @@ -0,0 +1,177 @@ +# Locate SDL library +# This module defines +# SDL_LIBRARY, the name of the library to link against +# SDL_FOUND, if false, do not try to link to SDL +# SDL_INCLUDE_DIR, where to find SDL.h +# +# This module responds to the the flag: +# SDL_BUILDING_LIBRARY +# If this is defined, then no SDL_main will be linked in because +# only applications need main(). +# Otherwise, it is assumed you are building an application and this +# module will attempt to locate and set the the proper link flags +# as part of the returned SDL_LIBRARY variable. +# +# Don't forget to include SDLmain.h and SDLmain.m your project for the +# OS X framework based version. (Other versions link to -lSDLmain which +# this module will try to find on your behalf.) Also for OS X, this +# module will automatically add the -framework Cocoa on your behalf. +# +# +# Additional Note: If you see an empty SDL_LIBRARY_TEMP in your configuration +# and no SDL_LIBRARY, it means CMake did not find your SDL library +# (SDL.dll, libsdl.so, SDL.framework, etc). +# Set SDL_LIBRARY_TEMP to point to your SDL library, and configure again. +# Similarly, if you see an empty SDLMAIN_LIBRARY, you should set this value +# as appropriate. These values are used to generate the final SDL_LIBRARY +# variable, but when these values are unset, SDL_LIBRARY does not get created. +# +# +# $SDLDIR is an environment variable that would +# correspond to the ./configure --prefix=$SDLDIR +# used in building SDL. +# l.e.galup 9-20-02 +# +# Modified by Eric Wing. +# Added code to assist with automated building by using environmental variables +# and providing a more controlled/consistent search behavior. +# Added new modifications to recognize OS X frameworks and +# additional Unix paths (FreeBSD, etc). +# Also corrected the header search path to follow "proper" SDL guidelines. +# Added a search for SDLmain which is needed by some platforms. +# Added a search for threads which is needed by some platforms. +# Added needed compile switches for MinGW. +# +# On OSX, this will prefer the Framework version (if found) over others. +# People will have to manually change the cache values of +# SDL_LIBRARY to override this selection or set the CMake environment +# CMAKE_INCLUDE_PATH to modify the search paths. +# +# Note that the header path has changed from SDL/SDL.h to just SDL.h +# This needed to change because "proper" SDL convention +# is #include "SDL.h", not . This is done for portability +# reasons because not all systems place things in SDL/ (see FreeBSD). + +#============================================================================= +# Copyright 2003-2009 Kitware, Inc. +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# (To distribute this file outside of CMake, substitute the full +# License text for the above reference.) + +FIND_PATH(SDL_INCLUDE_DIR SDL.h + HINTS + $ENV{SDLDIR} + PATH_SUFFIXES include/SDL include + PATHS + ~/Library/Frameworks + /Library/Frameworks + /usr/local/include/SDL12 + /usr/local/include/SDL11 # FreeBSD ports + /usr/include/SDL12 + /usr/include/SDL11 + /sw # Fink + /opt/local # DarwinPorts + /opt/csw # Blastwave + /opt +) +#MESSAGE("SDL_INCLUDE_DIR is ${SDL_INCLUDE_DIR}") + +# SDL-1.1 is the name used by FreeBSD ports... +# don't confuse it for the version number. +FIND_LIBRARY(SDL_LIBRARY_TEMP + NAMES SDL SDL-1.1 + HINTS + $ENV{SDLDIR} + PATH_SUFFIXES lib64 lib + PATHS + /sw + /opt/local + /opt/csw + /opt +) + +#MESSAGE("SDL_LIBRARY_TEMP is ${SDL_LIBRARY_TEMP}") + +IF(NOT SDL_BUILDING_LIBRARY) + IF(NOT ${SDL_INCLUDE_DIR} MATCHES ".framework") + # Non-OS X framework versions expect you to also dynamically link to + # SDLmain. This is mainly for Windows and OS X. Other (Unix) platforms + # seem to provide SDLmain for compatibility even though they don't + # necessarily need it. + FIND_LIBRARY(SDLMAIN_LIBRARY + NAMES SDLmain SDLmain-1.1 + HINTS + $ENV{SDLDIR} + PATH_SUFFIXES lib64 lib + PATHS + /sw + /opt/local + /opt/csw + /opt + ) + ENDIF(NOT ${SDL_INCLUDE_DIR} MATCHES ".framework") +ENDIF(NOT SDL_BUILDING_LIBRARY) + +# SDL may require threads on your system. +# The Apple build may not need an explicit flag because one of the +# frameworks may already provide it. +# But for non-OSX systems, I will use the CMake Threads package. +IF(NOT APPLE) + FIND_PACKAGE(Threads) +ENDIF(NOT APPLE) + +# MinGW needs an additional library, mwindows +# It's total link flags should look like -lmingw32 -lSDLmain -lSDL -lmwindows +# (Actually on second look, I think it only needs one of the m* libraries.) +IF(MINGW) + SET(MINGW32_LIBRARY mingw32 CACHE STRING "mwindows for MinGW") +ENDIF(MINGW) + +SET(SDL_FOUND "NO") +IF(SDL_LIBRARY_TEMP) + # For SDLmain + IF(NOT SDL_BUILDING_LIBRARY) + IF(SDLMAIN_LIBRARY) + SET(SDL_LIBRARY_TEMP ${SDLMAIN_LIBRARY} ${SDL_LIBRARY_TEMP}) + ENDIF(SDLMAIN_LIBRARY) + ENDIF(NOT SDL_BUILDING_LIBRARY) + + # For OS X, SDL uses Cocoa as a backend so it must link to Cocoa. + # CMake doesn't display the -framework Cocoa string in the UI even + # though it actually is there if I modify a pre-used variable. + # I think it has something to do with the CACHE STRING. + # So I use a temporary variable until the end so I can set the + # "real" variable in one-shot. + IF(APPLE) + SET(SDL_LIBRARY_TEMP ${SDL_LIBRARY_TEMP} "-framework Cocoa") + ENDIF(APPLE) + + # For threads, as mentioned Apple doesn't need this. + # In fact, there seems to be a problem if I used the Threads package + # and try using this line, so I'm just skipping it entirely for OS X. + IF(NOT APPLE) + SET(SDL_LIBRARY_TEMP ${SDL_LIBRARY_TEMP} ${CMAKE_THREAD_LIBS_INIT}) + ENDIF(NOT APPLE) + + # For MinGW library + IF(MINGW) + SET(SDL_LIBRARY_TEMP ${MINGW32_LIBRARY} ${SDL_LIBRARY_TEMP}) + ENDIF(MINGW) + + # Set the final string here so the GUI reflects the final state. + SET(SDL_LIBRARY ${SDL_LIBRARY_TEMP} CACHE STRING "Where the SDL Library can be found") + # Set the temp variable to INTERNAL so it is not seen in the CMake GUI + SET(SDL_LIBRARY_TEMP "${SDL_LIBRARY_TEMP}" CACHE INTERNAL "") + + SET(SDL_FOUND "YES") +ENDIF(SDL_LIBRARY_TEMP) + +#MESSAGE("SDL_LIBRARY is ${SDL_LIBRARY}") + diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index f3d5f4aee9..3da09ecb8f 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -48,7 +48,7 @@ add_component_dir (misc add_component_dir (files linuxpath windowspath macospath fixedpath multidircollection collections fileops configurationmanager - filelibrary ogreplugin + filelibrary ogreplugin constrainedfiledatastream lowlevelfile ) add_component_dir (compiler diff --git a/components/bsa/bsa_file.cpp b/components/bsa/bsa_file.cpp index 1700e1aabb..5e529e18e8 100644 --- a/components/bsa/bsa_file.cpp +++ b/components/bsa/bsa_file.cpp @@ -23,94 +23,15 @@ #include "bsa_file.hpp" -#include -#include -#include +//#include +//#include +//#include -#include +#include "../files/constrainedfiledatastream.hpp" using namespace std; using namespace Bsa; -class ConstrainedDataStream : public Ogre::DataStream { - std::ifstream mStream; - const size_t mStart; - size_t mPos; - bool mIsEOF; - -public: - ConstrainedDataStream(const Ogre::String &fname, size_t start, size_t length) - : mStream(fname.c_str(), std::ios_base::binary), mStart(start), mPos(0), mIsEOF(false) - { - mSize = length; - if(!mStream.seekg(mStart, std::ios_base::beg)) - throw std::runtime_error("Error seeking to start of BSA entry"); - } - - ConstrainedDataStream(const Ogre::String &name, const Ogre::String &fname, - size_t start, size_t length) - : Ogre::DataStream(name), mStream(fname.c_str(), std::ios_base::binary), - mStart(start), mPos(0), mIsEOF(false) - { - mSize = length; - if(!mStream.seekg(mStart, std::ios_base::beg)) - throw std::runtime_error("Error seeking to start of BSA entry"); - } - - - virtual size_t read(void *buf, size_t count) - { - mStream.clear(); - - if(count > mSize-mPos) - { - count = mSize-mPos; - mIsEOF = true; - } - mStream.read(reinterpret_cast(buf), count); - - count = mStream.gcount(); - mPos += count; - return count; - } - - virtual void skip(long count) - { - if((count >= 0 && (size_t)count <= mSize-mPos) || - (count < 0 && (size_t)-count <= mPos)) - { - mStream.clear(); - if(mStream.seekg(count, std::ios_base::cur)) - { - mPos += count; - mIsEOF = false; - } - } - } - - virtual void seek(size_t pos) - { - if(pos < mSize) - { - mStream.clear(); - if(mStream.seekg(pos+mStart, std::ios_base::beg)) - { - mPos = pos; - mIsEOF = false; - } - } - } - - virtual size_t tell() const - { return mPos; } - - virtual bool eof() const - { return mIsEOF; } - - virtual void close() - { mStream.close(); } -}; - /// Error handling void BSAFile::fail(const string &msg) @@ -253,5 +174,5 @@ Ogre::DataStreamPtr BSAFile::getFile(const char *file) fail("File not found: " + string(file)); const FileStruct &fs = files[i]; - return Ogre::DataStreamPtr(new ConstrainedDataStream(filename, fs.offset, fs.fileSize)); + return openConstrainedFileDataStream (filename.c_str (), fs.offset, fs.fileSize); } diff --git a/components/esm/esmreader.cpp b/components/esm/esmreader.cpp index 580e006dfe..eca0d78548 100644 --- a/components/esm/esmreader.cpp +++ b/components/esm/esmreader.cpp @@ -1,6 +1,8 @@ #include "esmreader.hpp" #include +#include "../files/constrainedfiledatastream.hpp" + namespace ESM { @@ -13,6 +15,11 @@ ESM_Context ESMReader::getContext() return mCtx; } +ESMReader::ESMReader(void): + mBuffer(50*1024) +{ +} + void ESMReader::restoreContext(const ESM_Context &rc) { // Reopen the file if necessary @@ -108,16 +115,12 @@ void ESMReader::open(Ogre::DataStreamPtr _esm, const std::string &name) void ESMReader::open(const std::string &file) { - std::ifstream *stream = OGRE_NEW_T(std::ifstream, Ogre::MEMCATEGORY_GENERAL)(file.c_str(), std::ios_base::binary); - // Ogre will delete the stream for us - open(Ogre::DataStreamPtr(new Ogre::FileStreamDataStream(stream)), file); + open (openConstrainedFileDataStream (file.c_str ()), file); } void ESMReader::openRaw(const std::string &file) { - std::ifstream *stream = OGRE_NEW_T(std::ifstream, Ogre::MEMCATEGORY_GENERAL)(file.c_str(), std::ios_base::binary); - // Ogre will delete the stream for us - openRaw(Ogre::DataStreamPtr(new Ogre::FileStreamDataStream(stream)), file); + openRaw (openConstrainedFileDataStream (file.c_str ()), file); } int64_t ESMReader::getHNLong(const char *name) @@ -325,11 +328,21 @@ void ESMReader::getExact(void*x, int size) std::string ESMReader::getString(int size) { - char *ptr = ToUTF8::getBuffer(size); - mEsm->read(ptr, size); + size_t s = size; + if (mBuffer.size() <= s) + // Add some extra padding to reduce the chance of having to resize + // again later. + mBuffer.resize(3*s); + + // And make sure the string is zero terminated + mBuffer[s] = 0; + + // read ESM data + char *ptr = &mBuffer[0]; + getExact(ptr, size); // Convert to UTF8 and return - return ToUTF8::getUtf8(mEncoding); + return mEncoder->getUtf8(ptr, size); } void ESMReader::fail(const std::string &msg) @@ -347,9 +360,9 @@ void ESMReader::fail(const std::string &msg) throw std::runtime_error(ss.str()); } -void ESMReader::setEncoding(const ToUTF8::FromType& encoding) +void ESMReader::setEncoder(ToUTF8::Utf8Encoder* encoder) { - mEncoding = encoding; + mEncoder = encoder; } } diff --git a/components/esm/esmreader.hpp b/components/esm/esmreader.hpp index 1d0f6f5806..d52be25aa2 100644 --- a/components/esm/esmreader.hpp +++ b/components/esm/esmreader.hpp @@ -20,6 +20,8 @@ class ESMReader { public: + ESMReader(void); + /************************************************************************* * * Public type definitions @@ -233,8 +235,8 @@ public: /// Used for error handling void fail(const std::string &msg); - /// Sets font encoding for ESM strings - void setEncoding(const ToUTF8::FromType& encoding); + /// Sets font encoder for ESM strings + void setEncoder(ToUTF8::Utf8Encoder* encoder); private: Ogre::DataStreamPtr mEsm; @@ -244,9 +246,12 @@ private: // Special file signifier (see SpecialFile enum above) int mSpf; + // Buffer for ESM strings + std::vector mBuffer; + SaveData mSaveData; MasterList mMasters; - ToUTF8::FromType mEncoding; + ToUTF8::Utf8Encoder* mEncoder; }; } #endif diff --git a/components/esm/esmwriter.cpp b/components/esm/esmwriter.cpp index c1ae064903..e2f878a257 100644 --- a/components/esm/esmwriter.cpp +++ b/components/esm/esmwriter.cpp @@ -157,12 +157,8 @@ void ESMWriter::writeHString(const std::string& data) write("\0", 1); else { - char *ptr = ToUTF8::getBuffer(data.size()+1); - strncpy(ptr, &data[0], data.size()); - ptr[data.size()] = '\0'; - // Convert to UTF8 and return - std::string ascii = ToUTF8::getLegacyEnc(m_encoding); + std::string ascii = m_encoder->getLegacyEnc(data); write(ascii.c_str(), ascii.size()); } @@ -192,21 +188,9 @@ void ESMWriter::write(const char* data, int size) m_stream->write(data, size); } -void ESMWriter::setEncoding(const std::string& encoding) +void ESMWriter::setEncoder(ToUTF8::Utf8Encoder* encoder) { - if (encoding == "win1250") - { - m_encoding = ToUTF8::WINDOWS_1250; - } - else if (encoding == "win1251") - { - m_encoding = ToUTF8::WINDOWS_1251; - } - else - { - // Default Latin encoding - m_encoding = ToUTF8::WINDOWS_1252; - } + m_encoder = encoder; } } diff --git a/components/esm/esmwriter.hpp b/components/esm/esmwriter.hpp index d3777be813..b557a29ad8 100644 --- a/components/esm/esmwriter.hpp +++ b/components/esm/esmwriter.hpp @@ -6,7 +6,7 @@ #include #include "esmcommon.hpp" -#include "../to_utf8/to_utf8.hpp" +#include namespace ESM { @@ -24,7 +24,7 @@ public: void setVersion(int ver); int getType(); void setType(int type); - void setEncoding(const std::string& encoding); // Write strings as UTF-8? + void setEncoder(ToUTF8::Utf8Encoder *encoding); // Write strings as UTF-8? void setAuthor(const std::string& author); void setDescription(const std::string& desc); @@ -94,11 +94,10 @@ private: std::list m_records; std::ostream* m_stream; std::streampos m_headerPos; - ToUTF8::FromType m_encoding; + ToUTF8::Utf8Encoder* m_encoder; int m_recordCount; HEDRstruct m_header; - SaveData m_saveData; }; } diff --git a/components/files/constrainedfiledatastream.cpp b/components/files/constrainedfiledatastream.cpp new file mode 100644 index 0000000000..dd2985e561 --- /dev/null +++ b/components/files/constrainedfiledatastream.cpp @@ -0,0 +1,175 @@ +#include "constrainedfiledatastream.hpp" +#include "lowlevelfile.hpp" + +#include +#include +#ifndef __clang__ +#include +#else +#include +#endif + +namespace { + +class ConstrainedDataStream : public Ogre::DataStream { +public: + + static const size_t sBufferSize = 4096; // somewhat arbitrary though 64KB buffers didn't seem to improve performance any + static const size_t sBufferThreshold = 1024; // reads larger than this bypass buffering as cost of memcpy outweighs cost of system call + + ConstrainedDataStream(const Ogre::String &fname, size_t start, size_t length) + { + mFile.open (fname.c_str ()); + mSize = length != 0xFFFFFFFF ? length : mFile.size () - start; + + mPos = 0; + mOrigin = start; + mExtent = start + mSize; + + mBufferOrigin = 0; + mBufferExtent = 0; + } + + + size_t read(void* buf, size_t count) + { + assert (mPos <= mSize); + + uint8_t * out = reinterpret_cast (buf); + + size_t posBeg = mOrigin + mPos; + size_t posEnd = posBeg + count; + + if (posEnd > mExtent) + posEnd = mExtent; + + size_t posCur = posBeg; + + while (posCur != posEnd) + { + size_t readLeft = posEnd - posCur; + + if (posCur < mBufferOrigin || posCur >= mBufferExtent) + { + if (readLeft >= sBufferThreshold || (posCur == mOrigin && posEnd == mExtent)) + { + assert (mFile.tell () == mBufferExtent); + + if (posCur != mBufferExtent) + mFile.seek (posCur); + + posCur += mFile.read (out, readLeft); + + mBufferOrigin = mBufferExtent = posCur; + + mPos = posCur - mOrigin; + + return posCur - posBeg; + } + else + { + size_t newBufferOrigin; + + if ((posCur < mBufferOrigin) && (mBufferOrigin - posCur < sBufferSize)) + newBufferOrigin = std::max (mOrigin, mBufferOrigin > sBufferSize ? mBufferOrigin - sBufferSize : 0); + else + newBufferOrigin = posCur; + + fill (newBufferOrigin); + } + } + + size_t xfer = std::min (readLeft, mBufferExtent - posCur); + + memcpy (out, mBuffer + (posCur - mBufferOrigin), xfer); + + posCur += xfer; + out += xfer; + } + + count = posEnd - posBeg; + mPos += count; + return count; + } + + void skip(long count) + { + assert (mPos <= mSize); + + if((count >= 0 && (size_t)count <= mSize-mPos) || + (count < 0 && (size_t)-count <= mPos)) + mPos += count; + } + + void seek(size_t pos) + { + assert (mPos <= mSize); + + if (pos < mSize) + mPos = pos; + } + + virtual size_t tell() const + { + assert (mPos <= mSize); + + return mPos; + } + + virtual bool eof() const + { + assert (mPos <= mSize); + + return mPos == mSize; + } + + virtual void close() + { + mFile.close(); + } + +private: + + void fill (size_t newOrigin) + { + assert (mFile.tell () == mBufferExtent); + + size_t newExtent = newOrigin + sBufferSize; + + if (newExtent > mExtent) + newExtent = mExtent; + + size_t oldExtent = mBufferExtent; + + if (newOrigin != oldExtent) + mFile.seek (newOrigin); + + mBufferOrigin = mBufferExtent = newOrigin; + + size_t amountRequested = newExtent - newOrigin; + + size_t amountRead = mFile.read (mBuffer, amountRequested); + + if (amountRead != amountRequested) + throw std::runtime_error ("An unexpected condition occurred while reading from a file."); + + mBufferExtent = newExtent; + } + + LowLevelFile mFile; + + size_t mOrigin; + size_t mExtent; + size_t mPos; + + uint8_t mBuffer [sBufferSize]; + size_t mBufferOrigin; + size_t mBufferExtent; +}; + +} // end of unnamed namespace + +Ogre::DataStreamPtr openConstrainedFileDataStream (char const * filename, size_t offset, size_t length) +{ + return Ogre::DataStreamPtr(new ConstrainedDataStream(filename, offset, length)); +} diff --git a/components/files/constrainedfiledatastream.hpp b/components/files/constrainedfiledatastream.hpp new file mode 100644 index 0000000000..367defcbcb --- /dev/null +++ b/components/files/constrainedfiledatastream.hpp @@ -0,0 +1,8 @@ +#ifndef COMPONENTS_FILES_CONSTRAINEDFILEDATASTREAM_HPP +#define COMPONENTS_FILES_CONSTRAINEDFILEDATASTREAM_HPP + +#include + +Ogre::DataStreamPtr openConstrainedFileDataStream (char const * filename, size_t offset = 0, size_t length = 0xFFFFFFFF); + +#endif // COMPONENTS_FILES_CONSTRAINEDFILEDATASTREAM_HPP diff --git a/components/files/lowlevelfile.cpp b/components/files/lowlevelfile.cpp new file mode 100644 index 0000000000..71fd1b523c --- /dev/null +++ b/components/files/lowlevelfile.cpp @@ -0,0 +1,299 @@ +#include "lowlevelfile.hpp" + +#include +#include +#include + +#if FILE_API == FILE_API_POSIX +#include +#include +#include +#endif + +#if FILE_API == FILE_API_STDIO +/* + * + * Implementation of LowLevelFile methods using c stdio + * + */ + +LowLevelFile::LowLevelFile () +{ + mHandle = NULL; +} + +LowLevelFile::~LowLevelFile () +{ + if (mHandle != NULL) + fclose (mHandle); +} + +void LowLevelFile::open (char const * filename) +{ + assert (mHandle == NULL); + + mHandle = fopen (filename, "rb"); + + if (mHandle == NULL) + { + std::ostringstream os; + os << "Failed to open '" << filename << "' for reading."; + throw std::runtime_error (os.str ()); + } +} + +void LowLevelFile::close () +{ + assert (mHandle != NULL); + + fclose (mHandle); + + mHandle = NULL; +} + +size_t LowLevelFile::size () +{ + assert (mHandle != NULL); + + long oldPosition = ftell (mHandle); + + if (oldPosition == -1) + throw std::runtime_error ("A query operation on a file failed."); + + if (fseek (mHandle, 0, SEEK_END) != 0) + throw std::runtime_error ("A query operation on a file failed."); + + long Size = ftell (mHandle); + + if (Size == -1) + throw std::runtime_error ("A query operation on a file failed."); + + if (fseek (mHandle, oldPosition, SEEK_SET) != 0) + throw std::runtime_error ("A query operation on a file failed."); + + return size_t (Size); +} + +void LowLevelFile::seek (size_t Position) +{ + assert (mHandle != NULL); + + if (fseek (mHandle, Position, SEEK_SET) != 0) + throw std::runtime_error ("A seek operation on a file failed."); +} + +size_t LowLevelFile::tell () +{ + assert (mHandle != NULL); + + long Position = ftell (mHandle); + + if (Position == -1) + throw std::runtime_error ("A query operation on a file failed."); + + return size_t (Position); +} + +size_t LowLevelFile::read (void * data, size_t size) +{ + assert (mHandle != NULL); + + int amount = fread (data, 1, size, mHandle); + + if (amount == 0 && ferror (mHandle)) + throw std::runtime_error ("A read operation on a file failed."); + + return amount; +} + +#elif FILE_API == FILE_API_POSIX +/* + * + * Implementation of LowLevelFile methods using posix IO calls + * + */ + +LowLevelFile::LowLevelFile () +{ + mHandle = -1; +} + +LowLevelFile::~LowLevelFile () +{ + if (mHandle != -1) + ::close (mHandle); +} + +void LowLevelFile::open (char const * filename) +{ + assert (mHandle == -1); + +#ifdef O_BINARY + static const int openFlags = O_RDONLY | O_BINARY; +#else + static const int openFlags = O_RDONLY; +#endif + + mHandle = ::open (filename, openFlags, 0); + + if (mHandle == -1) + { + std::ostringstream os; + os << "Failed to open '" << filename << "' for reading."; + throw std::runtime_error (os.str ()); + } +} + +void LowLevelFile::close () +{ + assert (mHandle != -1); + + ::close (mHandle); + + mHandle = -1; +} + +size_t LowLevelFile::size () +{ + assert (mHandle != -1); + + size_t oldPosition = ::lseek (mHandle, 0, SEEK_CUR); + + if (oldPosition == size_t (-1)) + throw std::runtime_error ("A query operation on a file failed."); + + size_t Size = ::lseek (mHandle, 0, SEEK_END); + + if (Size == size_t (-1)) + throw std::runtime_error ("A query operation on a file failed."); + + if (lseek (mHandle, oldPosition, SEEK_SET) == -1) + throw std::runtime_error ("A query operation on a file failed."); + + return Size; +} + +void LowLevelFile::seek (size_t Position) +{ + assert (mHandle != -1); + + if (::lseek (mHandle, Position, SEEK_SET) == -1) + throw std::runtime_error ("A seek operation on a file failed."); +} + +size_t LowLevelFile::tell () +{ + assert (mHandle != -1); + + size_t Position = ::lseek (mHandle, 0, SEEK_CUR); + + if (Position == size_t (-1)) + throw std::runtime_error ("A query operation on a file failed."); + + return Position; +} + +size_t LowLevelFile::read (void * data, size_t size) +{ + assert (mHandle != -1); + + int amount = ::read (mHandle, data, size); + + if (amount == -1) + throw std::runtime_error ("A read operation on a file failed."); + + return amount; +} + +#elif FILE_API == FILE_API_WIN32 +/* + * + * Implementation of LowLevelFile methods using Win32 API calls + * + */ + +LowLevelFile::LowLevelFile () +{ + mHandle = INVALID_HANDLE_VALUE; +} + +LowLevelFile::~LowLevelFile () +{ + if (mHandle == INVALID_HANDLE_VALUE) + CloseHandle (mHandle); +} + +void LowLevelFile::open (char const * filename) +{ + assert (mHandle == INVALID_HANDLE_VALUE); + + HANDLE handle = CreateFileA (filename, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0); + + if (handle == NULL) + { + std::ostringstream os; + os << "Failed to open '" << filename << "' for reading."; + throw std::runtime_error (os.str ()); + } + + mHandle = handle; +} + +void LowLevelFile::close () +{ + assert (mHandle != INVALID_HANDLE_VALUE); + + CloseHandle (mHandle); + + mHandle = INVALID_HANDLE_VALUE; +} + +size_t LowLevelFile::size () +{ + assert (mHandle != INVALID_HANDLE_VALUE); + + BY_HANDLE_FILE_INFORMATION info; + + if (!GetFileInformationByHandle (mHandle, &info)) + throw std::runtime_error ("A query operation on a file failed."); + + if (info.nFileSizeHigh != 0) + throw std::runtime_error ("Files greater that 4GB are not supported."); + + return info.nFileSizeLow; +} + +void LowLevelFile::seek (size_t Position) +{ + assert (mHandle != INVALID_HANDLE_VALUE); + + if (SetFilePointer (mHandle, Position, NULL, SEEK_SET) == INVALID_SET_FILE_POINTER) + if (GetLastError () != NO_ERROR) + throw std::runtime_error ("A seek operation on a file failed."); +} + +size_t LowLevelFile::tell () +{ + assert (mHandle != INVALID_HANDLE_VALUE); + + DWORD value = SetFilePointer (mHandle, 0, NULL, SEEK_CUR); + + if (value == INVALID_SET_FILE_POINTER && GetLastError () != NO_ERROR) + throw std::runtime_error ("A query operation on a file failed."); + + return value; +} + +size_t LowLevelFile::read (void * data, size_t size) +{ + assert (mHandle != INVALID_HANDLE_VALUE); + + DWORD read; + + if (!ReadFile (mHandle, data, size, &read, NULL)) + throw std::runtime_error ("A read operation on a file failed."); + + return read; +} + +#endif diff --git a/components/files/lowlevelfile.hpp b/components/files/lowlevelfile.hpp new file mode 100644 index 0000000000..f49c466a51 --- /dev/null +++ b/components/files/lowlevelfile.hpp @@ -0,0 +1,56 @@ +#ifndef COMPONENTS_FILES_LOWLEVELFILE_HPP +#define COMPONENTS_FILES_LOWLEVELFILE_HPP + +#include + +#include + +#define FILE_API_STDIO 0 +#define FILE_API_POSIX 1 +#define FILE_API_WIN32 2 + +#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX +#define FILE_API FILE_API_POSIX +#elif OGRE_PLATFORM == OGRE_PLATFORM_WIN32 +#define FILE_API FILE_API_WIN32 +#else +#define FILE_API FILE_API_STDIO +#endif + +#if FILE_API == FILE_API_STDIO +#include +#elif FILE_API == FILE_API_POSIX +#elif FILE_API == FILE_API_WIN32 +#include +#else +#error Unsupported File API +#endif + +class LowLevelFile +{ +public: + + LowLevelFile (); + ~LowLevelFile (); + + void open (char const * filename); + void close (); + + size_t size (); + + void seek (size_t Position); + size_t tell (); + + size_t read (void * data, size_t size); + +private: +#if FILE_API == FILE_API_STDIO + FILE* mHandle; +#elif FILE_API == FILE_API_POSIX + int mHandle; +#elif FILE_API == FILE_API_WIN32 + HANDLE mHandle; +#endif +}; + +#endif diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index ad51d50b95..31d873489c 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -43,16 +43,26 @@ #include +#include #include #include typedef unsigned char ubyte; -using namespace std; -using namespace Nif; -using namespace NifOgre; +namespace std +{ +// These operators allow extra data types to be stored in an Ogre::Any +// object, which can then be stored in user object bindings on the nodes +// TODO: Do something useful +ostream& operator<<(ostream &o, const NifOgre::TextKeyMap&) +{ return o; } + +} + +namespace NifOgre +{ // Helper class that computes the bounding box and of a mesh class BoundsFinder { @@ -62,7 +72,7 @@ class BoundsFinder MaxMinFinder() { - min = numeric_limits::infinity(); + min = std::numeric_limits::infinity(); max = -min; } @@ -152,8 +162,9 @@ static void fail(const std::string &msg) } -static void insertTextKeys(const Nif::NiTextKeyExtraData *tk, TextKeyMap *textkeys) +static TextKeyMap extractTextKeys(const Nif::NiTextKeyExtraData *tk) { + TextKeyMap textkeys; for(size_t i = 0;i < tk->list.size();i++) { const std::string &str = tk->list[i].text; @@ -166,11 +177,12 @@ static void insertTextKeys(const Nif::NiTextKeyExtraData *tk, TextKeyMap *textke break; std::string::size_type nextpos = std::min(str.find('\r', pos), str.find('\n', pos)); - textkeys->insert(std::make_pair(tk->list[i].time, str.substr(pos, nextpos-pos))); + textkeys.insert(std::make_pair(tk->list[i].time, str.substr(pos, nextpos-pos))); pos = nextpos; } } + return textkeys; } @@ -197,6 +209,17 @@ void buildBones(Ogre::Skeleton *skel, const Nif::Node *node, std::vectornext; } + Nif::ExtraPtr e = node->extra; + while(!e.empty()) + { + if(e->recType == Nif::RC_NiTextKeyExtraData) + { + const Nif::NiTextKeyExtraData *tk = static_cast(e.getPtr()); + bone->getUserObjectBindings().setUserAny("TextKeyExtraData", Ogre::Any(extractTextKeys(tk))); + } + e = e->extra; + } + const Nif::NiNode *ninode = dynamic_cast(node); if(ninode) { @@ -219,7 +242,7 @@ struct KeyTimeSort }; -typedef std::map LoaderMap; +typedef std::map LoaderMap; static LoaderMap sLoaders; public: @@ -269,16 +292,16 @@ void loadResource(Ogre::Resource *resource) Nif::NiKeyframeData *kf = kfc->data.getPtr(); /* Get the keyframes and make sure they're sorted first to last */ - QuaternionKeyList quatkeys = kf->mRotations; - Vector3KeyList trankeys = kf->mTranslations; - FloatKeyList scalekeys = kf->mScales; + Nif::QuaternionKeyList quatkeys = kf->mRotations; + Nif::Vector3KeyList trankeys = kf->mTranslations; + Nif::FloatKeyList scalekeys = kf->mScales; std::sort(quatkeys.mKeys.begin(), quatkeys.mKeys.end(), KeyTimeSort()); std::sort(trankeys.mKeys.begin(), trankeys.mKeys.end(), KeyTimeSort()); std::sort(scalekeys.mKeys.begin(), scalekeys.mKeys.end(), KeyTimeSort()); - QuaternionKeyList::VecType::const_iterator quatiter = quatkeys.mKeys.begin(); - Vector3KeyList::VecType::const_iterator traniter = trankeys.mKeys.begin(); - FloatKeyList::VecType::const_iterator scaleiter = scalekeys.mKeys.begin(); + Nif::QuaternionKeyList::VecType::const_iterator quatiter = quatkeys.mKeys.begin(); + Nif::Vector3KeyList::VecType::const_iterator traniter = trankeys.mKeys.begin(); + Nif::FloatKeyList::VecType::const_iterator scaleiter = scalekeys.mKeys.begin(); Ogre::Bone *bone = skel->getBone(targets[i]); const Ogre::Quaternion startquat = bone->getInitialOrientation(); @@ -346,7 +369,7 @@ void loadResource(Ogre::Resource *resource) kframe->setRotation(curquat); else { - QuaternionKeyList::VecType::const_iterator last = quatiter-1; + Nif::QuaternionKeyList::VecType::const_iterator last = quatiter-1; float diff = (curtime-last->mTime) / (quatiter->mTime-last->mTime); kframe->setRotation(Ogre::Quaternion::nlerp(diff, lastquat, curquat)); } @@ -354,7 +377,7 @@ void loadResource(Ogre::Resource *resource) kframe->setTranslate(curtrans); else { - Vector3KeyList::VecType::const_iterator last = traniter-1; + Nif::Vector3KeyList::VecType::const_iterator last = traniter-1; float diff = (curtime-last->mTime) / (traniter->mTime-last->mTime); kframe->setTranslate(lasttrans + ((curtrans-lasttrans)*diff)); } @@ -362,7 +385,7 @@ void loadResource(Ogre::Resource *resource) kframe->setScale(curscale); else { - FloatKeyList::VecType::const_iterator last = scaleiter-1; + Nif::FloatKeyList::VecType::const_iterator last = scaleiter-1; float diff = (curtime-last->mTime) / (scaleiter->mTime-last->mTime); kframe->setScale(lastscale + ((curscale-lastscale)*diff)); } @@ -371,35 +394,18 @@ void loadResource(Ogre::Resource *resource) anim->optimise(); } -bool createSkeleton(const std::string &name, const std::string &group, TextKeyMap *textkeys, const Nif::Node *node) +bool createSkeleton(const std::string &name, const std::string &group, const Nif::Node *node) { - if(textkeys) - { - Nif::ExtraPtr e = node->extra; - while(!e.empty()) - { - if(e->recType == Nif::RC_NiTextKeyExtraData) - { - const Nif::NiTextKeyExtraData *tk = static_cast(e.getPtr()); - insertTextKeys(tk, textkeys); - } - e = e->extra; - } - } - if(node->boneTrafo != NULL) { Ogre::SkeletonManager &skelMgr = Ogre::SkeletonManager::getSingleton(); - Ogre::SkeletonPtr skel = skelMgr.getByName(name); if(skel.isNull()) { NIFSkeletonLoader *loader = &sLoaders[name]; skel = skelMgr.create(name, group, true, loader); } - - if(!textkeys || textkeys->size() > 0) - return true; + return true; } const Nif::NiNode *ninode = dynamic_cast(node); @@ -410,7 +416,7 @@ bool createSkeleton(const std::string &name, const std::string &group, TextKeyMa { if(!children[i].empty()) { - if(createSkeleton(name, group, textkeys, children[i].getPtr())) + if(createSkeleton(name, group, children[i].getPtr())) return true; } } @@ -484,7 +490,7 @@ static void fail(const std::string &msg) public: -static Ogre::String getMaterial(const NiTriShape *shape, const Ogre::String &name, const Ogre::String &group) +static Ogre::String getMaterial(const Nif::NiTriShape *shape, const Ogre::String &name, const Ogre::String &group) { Ogre::MaterialManager &matMgr = Ogre::MaterialManager::getSingleton(); Ogre::MaterialPtr material = matMgr.getByName(name); @@ -504,24 +510,24 @@ static Ogre::String getMaterial(const NiTriShape *shape, const Ogre::String &nam bool vertexColour = (shape->data->colors.size() != 0); // These are set below if present - const NiTexturingProperty *t = NULL; - const NiMaterialProperty *m = NULL; - const NiAlphaProperty *a = NULL; + const Nif::NiTexturingProperty *t = NULL; + const Nif::NiMaterialProperty *m = NULL; + const Nif::NiAlphaProperty *a = NULL; // Scan the property list for material information - const PropertyList &list = shape->props; + const Nif::PropertyList &list = shape->props; for (size_t i = 0;i < list.length();i++) { // Entries may be empty if (list[i].empty()) continue; - const Property *pr = list[i].getPtr(); - if (pr->recType == RC_NiTexturingProperty) - t = static_cast(pr); - else if (pr->recType == RC_NiMaterialProperty) - m = static_cast(pr); - else if (pr->recType == RC_NiAlphaProperty) - a = static_cast(pr); + const Nif::Property *pr = list[i].getPtr(); + if (pr->recType == Nif::RC_NiTexturingProperty) + t = static_cast(pr); + else if (pr->recType == Nif::RC_NiMaterialProperty) + m = static_cast(pr); + else if (pr->recType == Nif::RC_NiAlphaProperty) + a = static_cast(pr); else warn("Skipped property type: "+pr->recName); } @@ -529,18 +535,27 @@ static Ogre::String getMaterial(const NiTriShape *shape, const Ogre::String &nam // Texture if (t && t->textures[0].inUse) { - NiSourceTexture *st = t->textures[0].texture.getPtr(); + Nif::NiSourceTexture *st = t->textures[0].texture.getPtr(); if (st->external) { /* Bethesda at some at some point converted all their BSA * textures from tga to dds for increased load speed, but all * texture file name references were kept as .tga. */ - texName = "textures\\" + st->filename; - if(!Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(texName)) + static const char path[] = "textures\\"; + + texName = path + st->filename; + Ogre::String::size_type pos = texName.rfind('.'); + if(pos != Ogre::String::npos && texName.compare(pos, texName.length() - pos, ".dds") != 0) { - Ogre::String::size_type pos = texName.rfind('.'); + // since we know all (GOTY edition or less) textures end + // in .dds, we change the extension texName.replace(pos, texName.length(), ".dds"); + + // if it turns out that the above wasn't true in all cases (not for vanilla, but maybe mods) + // verify, and revert if false (this call succeeds quickly, but fails slowly) + if(!Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(texName)) + texName = path + st->filename; } } else warn("Found internal texture, ignoring."); @@ -918,7 +933,7 @@ class NIFMeshLoader : Ogre::ManualResourceLoader } - typedef std::map LoaderMap; + typedef std::map LoaderMap; static LoaderMap sLoaders; public: @@ -981,7 +996,7 @@ public: if(node->recType == Nif::RC_NiTriShape) { - const NiTriShape *shape = dynamic_cast(node); + const Nif::NiTriShape *shape = dynamic_cast(node); Ogre::MeshManager &meshMgr = Ogre::MeshManager::getSingleton(); std::string fullname = mName+"@shape="+shape->name; @@ -1004,7 +1019,7 @@ public: mesh->setAutoBuildEdgeLists(false); } - meshes.push_back(std::make_pair(mesh, shape->name)); + meshes.push_back(std::make_pair(mesh->getName(), shape->name)); } else if(node->recType != Nif::RC_NiNode && node->recType != Nif::RC_RootCollisionNode && node->recType != Nif::RC_NiRotatingParticles) @@ -1025,13 +1040,19 @@ public: NIFMeshLoader::LoaderMap NIFMeshLoader::sLoaders; -MeshPairList NIFLoader::load(std::string name, std::string skelName, TextKeyMap *textkeys, const std::string &group) -{ - MeshPairList meshes; +typedef std::map MeshPairMap; +static MeshPairMap sMeshPairMap; +MeshPairList NIFLoader::load(std::string name, std::string skelName, const std::string &group) +{ std::transform(name.begin(), name.end(), name.begin(), ::tolower); std::transform(skelName.begin(), skelName.end(), skelName.begin(), ::tolower); + MeshPairMap::const_iterator meshiter = sMeshPairMap.find(name+"@skel="+skelName); + if(meshiter != sMeshPairMap.end()) + return meshiter->second; + + MeshPairList &meshes = sMeshPairMap[name+"@skel="+skelName]; Nif::NIFFile nif(name); if (nif.numRecords() < 1) { @@ -1052,7 +1073,7 @@ MeshPairList NIFLoader::load(std::string name, std::string skelName, TextKeyMap } NIFSkeletonLoader skelldr; - bool hasSkel = skelldr.createSkeleton(skelName, group, textkeys, node); + bool hasSkel = skelldr.createSkeleton(name, group, node); NIFMeshLoader meshldr(name, group, (hasSkel ? skelName : std::string())); meshldr.createMeshes(node, meshes); @@ -1064,19 +1085,37 @@ EntityList NIFLoader::createEntities(Ogre::SceneNode *parent, TextKeyMap *textke { EntityList entitylist; - MeshPairList meshes = load(name, name, textkeys, group); + MeshPairList meshes = load(name, name, group); if(meshes.size() == 0) return entitylist; Ogre::SceneManager *sceneMgr = parent->getCreator(); for(size_t i = 0;i < meshes.size();i++) { - entitylist.mEntities.push_back(sceneMgr->createEntity(meshes[i].first->getName())); + entitylist.mEntities.push_back(sceneMgr->createEntity(meshes[i].first)); Ogre::Entity *entity = entitylist.mEntities.back(); if(!entitylist.mSkelBase && entity->hasSkeleton()) entitylist.mSkelBase = entity; } + if(entitylist.mSkelBase && textkeys) + { + // Would be nice if Ogre::SkeletonInstance allowed access to the 'master' Ogre::SkeletonPtr. + Ogre::SkeletonManager &skelMgr = Ogre::SkeletonManager::getSingleton(); + Ogre::SkeletonPtr skel = skelMgr.getByName(entitylist.mSkelBase->getSkeleton()->getName()); + Ogre::Skeleton::BoneIterator iter = skel->getBoneIterator(); + while(iter.hasMoreElements()) + { + Ogre::Bone *bone = iter.getNext(); + const Ogre::Any &data = bone->getUserObjectBindings().getUserAny("TextKeyExtraData"); + if(!data.isEmpty()) + { + *textkeys = Ogre::any_cast(data); + break; + } + } + } + if(entitylist.mSkelBase) { parent->attachObject(entitylist.mSkelBase); @@ -1101,13 +1140,6 @@ EntityList NIFLoader::createEntities(Ogre::SceneNode *parent, TextKeyMap *textke return entitylist; } -struct checklow { - bool operator()(const char &a, const char &b) const - { - return ::tolower(a) == ::tolower(b); - } -}; - EntityList NIFLoader::createEntities(Ogre::Entity *parent, const std::string &bonename, Ogre::SceneNode *parentNode, const std::string &name, @@ -1115,23 +1147,24 @@ EntityList NIFLoader::createEntities(Ogre::Entity *parent, const std::string &bo { EntityList entitylist; - MeshPairList meshes = load(name, parent->getMesh()->getSkeletonName(), NULL, group); + MeshPairList meshes = load(name, parent->getMesh()->getSkeletonName(), group); if(meshes.size() == 0) return entitylist; Ogre::SceneManager *sceneMgr = parentNode->getCreator(); - std::string filter = "Tri "+bonename; + std::string filter = "tri "+bonename; + std::transform(filter.begin()+4, filter.end(), filter.begin()+4, ::tolower); for(size_t i = 0;i < meshes.size();i++) { - Ogre::Entity *ent = sceneMgr->createEntity(meshes[i].first->getName()); + Ogre::Entity *ent = sceneMgr->createEntity(meshes[i].first); if(ent->hasSkeleton()) { + std::transform(meshes[i].second.begin(), meshes[i].second.end(), meshes[i].second.begin(), ::tolower); + if(meshes[i].second.length() < filter.length() || - std::mismatch(filter.begin(), filter.end(), meshes[i].second.begin(), checklow()).first != filter.end()) + meshes[i].second.compare(0, filter.length(), filter) != 0) { sceneMgr->destroyEntity(ent); - meshes.erase(meshes.begin()+i); - i--; continue; } if(!entitylist.mSkelBase) @@ -1216,3 +1249,5 @@ extern "C" void ogre_insertTexture(char* name, uint32_t width, uint32_t height, */ + +} // nsmaepace NifOgre diff --git a/components/nifogre/ogre_nif_loader.hpp b/components/nifogre/ogre_nif_loader.hpp index a203112b50..3e05c58734 100644 --- a/components/nifogre/ogre_nif_loader.hpp +++ b/components/nifogre/ogre_nif_loader.hpp @@ -30,23 +30,6 @@ #include #include -#include -#include - -#include "../nif/node.hpp" - -#include - -class BoundsFinder; - -struct ciLessBoost : std::binary_function -{ - bool operator() (const std::string & s1, const std::string & s2) const - { - //case insensitive version of is_less - return boost::algorithm::lexicographical_compare(s1, s2, boost::algorithm::is_iless()); - } -}; namespace Nif { @@ -69,9 +52,8 @@ struct EntityList { }; -/** This holds a list of meshes along with the names of their parent nodes - */ -typedef std::vector< std::pair > MeshPairList; +/** This holds a list of mesh names along with the names of their parent nodes */ +typedef std::vector< std::pair > MeshPairList; /** Manual resource loader for NIF meshes. This is the main class responsible for translating the internal NIF mesh structure into @@ -87,7 +69,7 @@ typedef std::vector< std::pair > MeshPairList; */ class NIFLoader { - static MeshPairList load(std::string name, std::string skelName, TextKeyMap *textkeys, const std::string &group); + static MeshPairList load(std::string name, std::string skelName, const std::string &group); public: static EntityList createEntities(Ogre::Entity *parent, const std::string &bonename, diff --git a/components/to_utf8/tests/.gitignore b/components/to_utf8/tests/.gitignore new file mode 100644 index 0000000000..8144904045 --- /dev/null +++ b/components/to_utf8/tests/.gitignore @@ -0,0 +1 @@ +*_test diff --git a/components/to_utf8/tests/output/to_utf8_test.out b/components/to_utf8/tests/output/to_utf8_test.out new file mode 100644 index 0000000000..dcb32359ab --- /dev/null +++ b/components/to_utf8/tests/output/to_utf8_test.out @@ -0,0 +1,4 @@ +original: Без вопросов отдаете ему рулет, зная, что позже вы сможете привести с собой своих друзей и тогда он получит по заслугам? +converted: Без вопросов отдаете ему рулет, зная, что позже вы сможете привести с собой своих друзей и тогда он получит по заслугам? +original: Vous lui donnez le gâteau sans protester avant d’aller chercher tous vos amis et de revenir vous venger. +converted: Vous lui donnez le gâteau sans protester avant d’aller chercher tous vos amis et de revenir vous venger. diff --git a/components/to_utf8/tests/test.sh b/components/to_utf8/tests/test.sh new file mode 100755 index 0000000000..2d07708adc --- /dev/null +++ b/components/to_utf8/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/components/to_utf8/tests/test_data/french-utf8.txt b/components/to_utf8/tests/test_data/french-utf8.txt new file mode 100644 index 0000000000..aaaccac737 --- /dev/null +++ b/components/to_utf8/tests/test_data/french-utf8.txt @@ -0,0 +1 @@ +Vous lui donnez le gâteau sans protester avant d’aller chercher tous vos amis et de revenir vous venger. \ No newline at end of file diff --git a/components/to_utf8/tests/test_data/french-win1252.txt b/components/to_utf8/tests/test_data/french-win1252.txt new file mode 100644 index 0000000000..1de4593e94 --- /dev/null +++ b/components/to_utf8/tests/test_data/french-win1252.txt @@ -0,0 +1 @@ +Vous lui donnez le gteau sans protester avant daller chercher tous vos amis et de revenir vous venger. \ No newline at end of file diff --git a/components/to_utf8/tests/test_data/russian-utf8.txt b/components/to_utf8/tests/test_data/russian-utf8.txt new file mode 100644 index 0000000000..eb20b32dd6 --- /dev/null +++ b/components/to_utf8/tests/test_data/russian-utf8.txt @@ -0,0 +1 @@ +Без вопросов отдаете ему рулет, зная, что позже вы сможете привести с собой своих друзей и тогда он получит по заслугам? \ No newline at end of file diff --git a/components/to_utf8/tests/test_data/russian-win1251.txt b/components/to_utf8/tests/test_data/russian-win1251.txt new file mode 100644 index 0000000000..086e57edd6 --- /dev/null +++ b/components/to_utf8/tests/test_data/russian-win1251.txt @@ -0,0 +1 @@ + , , ? \ No newline at end of file diff --git a/components/to_utf8/tests/to_utf8_test.cpp b/components/to_utf8/tests/to_utf8_test.cpp new file mode 100644 index 0000000000..3fcddd1581 --- /dev/null +++ b/components/to_utf8/tests/to_utf8_test.cpp @@ -0,0 +1,59 @@ +#include +#include +#include +#include + +#include "../to_utf8.hpp" + +std::string getFirstLine(const std::string &filename); +void testEncoder(ToUTF8::FromType encoding, const std::string &legacyEncFile, + const std::string &utf8File); + +/// Test character encoding conversion to and from UTF-8 +void testEncoder(ToUTF8::FromType encoding, const std::string &legacyEncFile, + const std::string &utf8File) +{ + // get some test data + std::string legacyEncLine = getFirstLine(legacyEncFile); + std::string utf8Line = getFirstLine(utf8File); + + // create an encoder for specified character encoding + ToUTF8::Utf8Encoder encoder (encoding); + + // convert text to UTF-8 + std::string convertedUtf8Line = encoder.getUtf8(legacyEncLine); + + std::cout << "original: " << utf8Line << std::endl; + std::cout << "converted: " << convertedUtf8Line << std::endl; + + // check correctness + assert(convertedUtf8Line == utf8Line); + + // convert UTF-8 text to legacy encoding + std::string convertedLegacyEncLine = encoder.getLegacyEnc(utf8Line); + // check correctness + assert(convertedLegacyEncLine == legacyEncLine); +} + +std::string getFirstLine(const std::string &filename) +{ + std::string line; + std::ifstream text (filename.c_str()); + + if (!text.is_open()) + { + throw std::runtime_error("Unable to open file " + filename); + } + + std::getline(text, line); + text.close(); + + return line; +} + +int main() +{ + testEncoder(ToUTF8::WINDOWS_1251, "test_data/russian-win1251.txt", "test_data/russian-utf8.txt"); + testEncoder(ToUTF8::WINDOWS_1252, "test_data/french-win1252.txt", "test_data/french-utf8.txt"); + return 0; +} diff --git a/components/to_utf8/to_utf8.cpp b/components/to_utf8/to_utf8.cpp index 7db6112475..275f5483f3 100644 --- a/components/to_utf8/to_utf8.cpp +++ b/components/to_utf8/to_utf8.cpp @@ -2,6 +2,8 @@ #include #include +#include +#include /* This file contains the code to translate from WINDOWS-1252 (native charset used in English version of Morrowind) to UTF-8. The library @@ -39,341 +41,298 @@ // Generated tables #include "tables_gen.hpp" -// Shared global buffers, we love you. These initial sizes are large -// enough to hold the largest books in Morrowind.esm, but we will -// resize automaticall if necessary. -static std::vector buf (50*1024); -static std::vector output (50*1024); -static int size; +using namespace ToUTF8; -// Make sure the given vector is large enough for 'size' bytes, -// including a terminating zero after it. -static void resize(std::vector &buf, size_t size) +Utf8Encoder::Utf8Encoder(const FromType sourceEncoding): + mOutput(50*1024) { - if(buf.size() <= size) - // Add some extra padding to reduce the chance of having to resize - // again later. - buf.resize(3*size); - - // And make sure the string is zero terminated - buf[size] = 0; + switch (sourceEncoding) + { + case ToUTF8::WINDOWS_1252: + { + translationArray = ToUTF8::windows_1252; + break; + } + case ToUTF8::WINDOWS_1250: + { + translationArray = ToUTF8::windows_1250; + break; + } + case ToUTF8::WINDOWS_1251: + { + translationArray = ToUTF8::windows_1251; + break; + } + default: + { + assert(0); + } + } } -// This is just used to spew out a reusable input buffer for the -// conversion process. -char *ToUTF8::getBuffer(int s) +std::string Utf8Encoder::getUtf8(const char* input, int size) { - // Remember the requested size - size = s; - resize(buf, size); - return &buf[0]; + // Double check that the input string stops at some point (it might + // contain zero terminators before this, inside its own data, which + // is also ok.) + assert(input[size] == 0); + + // TODO: The rest of this function is designed for single-character + // input encodings only. It also assumes that the input the input + // encoding shares its first 128 values (0-127) with ASCII. These + // conditions must be checked again if you add more input encodings + // later. + + // Compute output length, and check for pure ascii input at the same + // time. + bool ascii; + size_t outlen = getLength(input, ascii); + + // If we're pure ascii, then don't bother converting anything. + if(ascii) + return std::string(input, outlen); + + // Make sure the output is large enough + resize(outlen); + char *out = &mOutput[0]; + + // Translate + while (*input) + copyFromArray(*(input++), out); + + // Make sure that we wrote the correct number of bytes + assert((out-&mOutput[0]) == (int)outlen); + + // And make extra sure the output is null terminated + assert(mOutput.size() > outlen); + assert(mOutput[outlen] == 0); + + // Return a string + return std::string(&mOutput[0], outlen); +} + +std::string Utf8Encoder::getLegacyEnc(const char *input, int size) +{ + // Double check that the input string stops at some point (it might + // contain zero terminators before this, inside its own data, which + // is also ok.) + assert(input[size] == 0); + + // TODO: The rest of this function is designed for single-character + // input encodings only. It also assumes that the input the input + // encoding shares its first 128 values (0-127) with ASCII. These + // conditions must be checked again if you add more input encodings + // later. + + // Compute output length, and check for pure ascii input at the same + // time. + bool ascii; + size_t outlen = getLength2(input, ascii); + + // If we're pure ascii, then don't bother converting anything. + if(ascii) + return std::string(input, outlen); + + // Make sure the output is large enough + resize(outlen); + char *out = &mOutput[0]; + + // Translate + while(*input) + copyFromArray2(input, out); + + // Make sure that we wrote the correct number of bytes + assert((out-&mOutput[0]) == (int)outlen); + + // And make extra sure the output is null terminated + assert(mOutput.size() > outlen); + assert(mOutput[outlen] == 0); + + // Return a string + return std::string(&mOutput[0], outlen); +} + +// Make sure the output vector is large enough for 'size' bytes, +// including a terminating zero after it. +void Utf8Encoder::resize(size_t size) +{ + if (mOutput.size() <= size) + // Add some extra padding to reduce the chance of having to resize + // again later. + mOutput.resize(3*size); + + // And make sure the string is zero terminated + mOutput[size] = 0; } /** Get the total length length needed to decode the given string with - the given translation array. The arrays are encoded with 6 bytes - per character, with the first giving the length and the next 5 the - actual data. + the given translation array. The arrays are encoded with 6 bytes + per character, with the first giving the length and the next 5 the + actual data. - The function serves a dual purpose for optimization reasons: it - checks if the input is pure ascii (all values are <= 127). If this - is the case, then the ascii parameter is set to true, and the - caller can optimize for this case. + The function serves a dual purpose for optimization reasons: it + checks if the input is pure ascii (all values are <= 127). If this + is the case, then the ascii parameter is set to true, and the + caller can optimize for this case. */ -static size_t getLength(const char *arr, const char* input, bool &ascii) +size_t Utf8Encoder::getLength(const char* input, bool &ascii) { - ascii = true; - size_t len = 0; - const char* ptr = input; - unsigned char inp = *ptr; + ascii = true; + size_t len = 0; + const char* ptr = input; + unsigned char inp = *ptr; - // Do away with the ascii part of the string first (this is almost - // always the entire string.) - while(inp && inp < 128) - inp = *(++ptr); - len += (ptr-input); + // Do away with the ascii part of the string first (this is almost + // always the entire string.) + while (inp && inp < 128) + inp = *(++ptr); + len += (ptr-input); - // If we're not at the null terminator at this point, then there - // were some non-ascii characters to deal with. Go to slow-mode for - // the rest of the string. - if(inp) + // If we're not at the null terminator at this point, then there + // were some non-ascii characters to deal with. Go to slow-mode for + // the rest of the string. + if (inp) { - ascii = false; - while(inp) + ascii = false; + while (inp) { - // Find the translated length of this character in the - // lookup table. - len += arr[inp*6]; - inp = *(++ptr); + // Find the translated length of this character in the + // lookup table. + len += translationArray[inp*6]; + inp = *(++ptr); } } - return len; + return len; } // Translate one character 'ch' using the translation array 'arr', and // advance the output pointer accordingly. -static void copyFromArray(const char *arr, unsigned char ch, char* &out) +void Utf8Encoder::copyFromArray(unsigned char ch, char* &out) { - // Optimize for ASCII values - if(ch < 128) + // Optimize for ASCII values + if (ch < 128) { - *(out++) = ch; - return; + *(out++) = ch; + return; } - const char *in = arr + ch*6; - int len = *(in++); - for(int i=0; i outlen); - assert(output[outlen] == 0); - - // Return a string - return std::string(&output[0], outlen); -} - -static size_t getLength2(const char *arr, const char* input, bool &ascii) -{ - ascii = true; - size_t len = 0; - const char* ptr = input; - unsigned char inp = *ptr; - - // Do away with the ascii part of the string first (this is almost - // always the entire string.) - while(inp && inp < 128) - inp = *(++ptr); - len += (ptr-input); - - // If we're not at the null terminator at this point, then there - // were some non-ascii characters to deal with. Go to slow-mode for - // the rest of the string. - if(inp) - { - ascii = false; - while(inp) + ascii = false; + while(inp) { len += 1; - // Find the translated length of this character in the - // lookup table. + // Find the translated length of this character in the + // lookup table. switch(inp) { - case 0xe2: len -= 2; break; - case 0xc2: - case 0xcb: - case 0xc4: - case 0xc6: - case 0xc3: - case 0xd0: - case 0xd1: - case 0xd2: - case 0xc5: len -= 1; break; + case 0xe2: len -= 2; break; + case 0xc2: + case 0xcb: + case 0xc4: + case 0xc6: + case 0xc3: + case 0xd0: + case 0xd1: + case 0xd2: + case 0xc5: len -= 1; break; } - inp = *(++ptr); + inp = *(++ptr); } } - return len; + return len; } -#include -#include - -static void copyFromArray2(const char *arr, char*& chp, char* &out) +void Utf8Encoder::copyFromArray2(const char*& chp, char* &out) { unsigned char ch = *(chp++); - // Optimize for ASCII values - if(ch < 128) + // Optimize for ASCII values + if (ch < 128) { - *(out++) = ch; - return; + *(out++) = ch; + return; } - int len = 1; - switch (ch) - { - case 0xe2: len = 3; break; - case 0xc2: - case 0xcb: - case 0xc4: - case 0xc6: - case 0xc3: - case 0xd0: - case 0xd1: - case 0xd2: - case 0xc5: len = 2; break; - } - - if (len == 1) // There is no 1 length utf-8 glyph that is not 0x20 (empty space) - { - *(out++) = ch; - return; - } - - unsigned char ch2 = *(chp++); - unsigned char ch3 = '\0'; - if (len == 3) - ch3 = *(chp++); - - for (int i = 128; i < 256; i++) - { - unsigned char b1 = arr[i*6 + 1], b2 = arr[i*6 + 2], b3 = arr[i*6 + 3]; - if (b1 == ch && b2 == ch2 && (len != 3 || b3 == ch3)) - { - *(out++) = (char)i; - return; - } - } - - std::cout << "Could not find glyph " << std::hex << (int)ch << " " << (int)ch2 << " " << (int)ch3 << std::endl; - - *(out++) = ch; // Could not find glyph, just put whatever -} - -std::string ToUTF8::getLegacyEnc(ToUTF8::FromType to) -{ - // Pick translation array - const char *arr; - switch (to) - { - case ToUTF8::WINDOWS_1252: + int len = 1; + switch (ch) { - arr = ToUTF8::windows_1252; - break; + case 0xe2: len = 3; break; + case 0xc2: + case 0xcb: + case 0xc4: + case 0xc6: + case 0xc3: + case 0xd0: + case 0xd1: + case 0xd2: + case 0xc5: len = 2; break; } - case ToUTF8::WINDOWS_1250: + + if (len == 1) // There is no 1 length utf-8 glyph that is not 0x20 (empty space) { - arr = ToUTF8::windows_1250; - break; + *(out++) = ch; + return; } - case ToUTF8::WINDOWS_1251: + + unsigned char ch2 = *(chp++); + unsigned char ch3 = '\0'; + if (len == 3) + ch3 = *(chp++); + + for (int i = 128; i < 256; i++) { - arr = ToUTF8::windows_1251; - break; + unsigned char b1 = translationArray[i*6 + 1], b2 = translationArray[i*6 + 2], b3 = translationArray[i*6 + 3]; + if (b1 == ch && b2 == ch2 && (len != 3 || b3 == ch3)) + { + *(out++) = (char)i; + return; + } } - default: - { - assert(0); - } - } - // Double check that the input string stops at some point (it might - // contain zero terminators before this, inside its own data, which - // is also ok.) - char* input = &buf[0]; - assert(input[size] == 0); + std::cout << "Could not find glyph " << std::hex << (int)ch << " " << (int)ch2 << " " << (int)ch3 << std::endl; - // TODO: The rest of this function is designed for single-character - // input encodings only. It also assumes that the input the input - // encoding shares its first 128 values (0-127) with ASCII. These - // conditions must be checked again if you add more input encodings - // later. - - // Compute output length, and check for pure ascii input at the same - // time. - bool ascii; - size_t outlen = getLength2(arr, input, ascii); - - // If we're pure ascii, then don't bother converting anything. - if(ascii) - return std::string(input, outlen); - - // Make sure the output is large enough - resize(output, outlen); - char *out = &output[0]; - - // Translate - while(*input) - copyFromArray2(arr, input, out); - - // Make sure that we wrote the correct number of bytes - assert((out-&output[0]) == (int)outlen); - - // And make extra sure the output is null terminated - assert(output.size() > outlen); - assert(output[outlen] == 0); - - // Return a string - return std::string(&output[0], outlen); + *(out++) = ch; // Could not find glyph, just put whatever } ToUTF8::FromType ToUTF8::calculateEncoding(const std::string& encodingName) { - if (encodingName == "win1250") - return ToUTF8::WINDOWS_1250; - else if (encodingName == "win1251") - return ToUTF8::WINDOWS_1251; - else - return ToUTF8::WINDOWS_1252; + if (encodingName == "win1250") + return ToUTF8::WINDOWS_1250; + else if (encodingName == "win1251") + return ToUTF8::WINDOWS_1251; + else + return ToUTF8::WINDOWS_1252; } std::string ToUTF8::encodingUsingMessage(const std::string& encodingName) { - if (encodingName == "win1250") - return "Using Central and Eastern European font encoding."; - else if (encodingName == "win1251") - return "Using Cyrillic font encoding."; - else - return "Using default (English) font encoding."; + if (encodingName == "win1250") + return "Using Central and Eastern European font encoding."; + else if (encodingName == "win1251") + return "Using Cyrillic font encoding."; + else + return "Using default (English) font encoding."; } diff --git a/components/to_utf8/to_utf8.hpp b/components/to_utf8/to_utf8.hpp index f52ae73bd8..e150cf17ba 100644 --- a/components/to_utf8/to_utf8.hpp +++ b/components/to_utf8/to_utf8.hpp @@ -2,29 +2,53 @@ #define COMPONENTS_TOUTF8_H #include +#include +#include namespace ToUTF8 { - // These are all the currently supported code pages - enum FromType + // These are all the currently supported code pages + enum FromType { - WINDOWS_1250, // Central ane Eastern European languages - WINDOWS_1251, // Cyrillic languages - WINDOWS_1252 // Used by English version of Morrowind (and - // probably others) + WINDOWS_1250, // Central ane Eastern European languages + WINDOWS_1251, // Cyrillic languages + WINDOWS_1252 // Used by English version of Morrowind (and + // probably others) }; - // Return a writable buffer of at least 'size' bytes. The buffer - // does not have to be freed. - char* getBuffer(int size); + FromType calculateEncoding(const std::string& encodingName); + std::string encodingUsingMessage(const std::string& encodingName); - // Convert the previously written buffer to UTF8 from the given code - // page. - std::string getUtf8(FromType from); - std::string getLegacyEnc(FromType to); + // class - FromType calculateEncoding(const std::string& encodingName); - std::string encodingUsingMessage(const std::string& encodingName); + class Utf8Encoder + { + public: + Utf8Encoder(FromType sourceEncoding); + + // Convert to UTF8 from the previously given code page. + std::string getUtf8(const char *input, int size); + inline std::string getUtf8(const std::string &str) + { + return getUtf8(str.c_str(), str.size()); + } + + std::string getLegacyEnc(const char *input, int size); + inline std::string getLegacyEnc(const std::string &str) + { + return getLegacyEnc(str.c_str(), str.size()); + } + + private: + void resize(size_t size); + size_t getLength(const char* input, bool &ascii); + void copyFromArray(unsigned char chp, char* &out); + size_t getLength2(const char* input, bool &ascii); + void copyFromArray2(const char*& chp, char* &out); + + std::vector mOutput; + char* translationArray; + }; } #endif diff --git a/components/translation/translation.cpp b/components/translation/translation.cpp index fb5b038612..d0ea4b7fb6 100644 --- a/components/translation/translation.cpp +++ b/components/translation/translation.cpp @@ -30,7 +30,7 @@ namespace Translation { std::string path = dataFileCollections.getCollection (extension).getPath (fileName).string(); - std::ifstream stream (path); + std::ifstream stream (path.c_str()); if (!stream.is_open()) throw std::runtime_error ("failed to open translation file: " + fileName); @@ -50,10 +50,7 @@ namespace Translation if (!line.empty()) { - char* buffer = ToUTF8::getBuffer(line.size() + 1); - //buffer has at least line.size() + 1 bytes, so it must be safe - strcpy(buffer, line.c_str()); - line = ToUTF8::getUtf8(mEncoding); + line = mEncoder->getUtf8(line); size_t tab_pos = line.find('\t'); if (tab_pos != std::string::npos && tab_pos > 0 && tab_pos < line.size() - 1) @@ -104,9 +101,9 @@ namespace Translation return phrase; } - void Storage::setEncoding (const ToUTF8::FromType& encoding) + void Storage::setEncoder(ToUTF8::Utf8Encoder* encoder) { - mEncoding = encoding; + mEncoder = encoder; } bool Storage::hasTranslation() const diff --git a/components/translation/translation.hpp b/components/translation/translation.hpp index 80d44d871d..bca9ea255c 100644 --- a/components/translation/translation.hpp +++ b/components/translation/translation.hpp @@ -19,7 +19,7 @@ namespace Translation // Standard form usually means nominative case std::string topicStandardForm(const std::string& phrase) const; - void setEncoding (const ToUTF8::FromType& encoding); + void setEncoder(ToUTF8::Utf8Encoder* encoder); bool hasTranslation() const; @@ -34,7 +34,7 @@ namespace Translation void loadDataFromStream(ContainerType& container, std::istream& stream); - ToUTF8::FromType mEncoding; + ToUTF8::Utf8Encoder* mEncoder; ContainerType mCellNamesTranslations, mTopicIDs, mPhraseForms; }; } diff --git a/files/gbuffer/gbuffer.compositor b/files/gbuffer/gbuffer.compositor index 04600ce9b7..0a0675fa00 100644 --- a/files/gbuffer/gbuffer.compositor +++ b/files/gbuffer/gbuffer.compositor @@ -72,7 +72,7 @@ compositor gbufferFinalizer pass render_scene { first_render_queue 51 - last_render_queue 100 + last_render_queue 105 } } target_output diff --git a/libs/openengine/gui/manager.cpp b/libs/openengine/gui/manager.cpp index acb4ed9df7..925891e1b3 100644 --- a/libs/openengine/gui/manager.cpp +++ b/libs/openengine/gui/manager.cpp @@ -6,6 +6,19 @@ using namespace OEngine::GUI; +/* + * As of MyGUI 3.2.0, MyGUI::OgreDataManager::isDataExist is unnecessarily complex + * this override fixes the resulting performance issue. + */ +class FixedOgreDataManager : public MyGUI::OgreDataManager +{ +public: + bool isDataExist(const std::string& _name) + { + return Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup (_name); + } +}; + void MyGUIManager::setup(Ogre::RenderWindow *wnd, Ogre::SceneManager *mgr, bool logging, const std::string& logDir) { assert(wnd); @@ -25,11 +38,18 @@ void MyGUIManager::setup(Ogre::RenderWindow *wnd, Ogre::SceneManager *mgr, bool if(!logDir.empty()) theLogFile.insert(0, logDir); - // Set up OGRE platform. We might make this more generic later. - mPlatform = new OgrePlatform(); - LogManager::getInstance().setSTDOutputEnabled(logging); - mPlatform->initialise(wnd, mgr, "General", theLogFile); + // Set up OGRE platform (bypassing OgrePlatform). We might make this more generic later. + mLogManager = new LogManager(); + mRenderManager = new OgreRenderManager(); + mDataManager = new FixedOgreDataManager(); + LogManager::getInstance().setSTDOutputEnabled(logging); + + if (!theLogFile.empty()) + LogManager::getInstance().createDefaultSource(theLogFile); + + mRenderManager->initialise(wnd, mgr); + mDataManager->initialise("General"); // Create GUI mGui = new Gui(); @@ -40,11 +60,22 @@ void MyGUIManager::shutdown() { mGui->shutdown (); delete mGui; - if(mPlatform) + if(mRenderManager) { - mPlatform->shutdown(); - delete mPlatform; + mRenderManager->shutdown(); + delete mRenderManager; + mRenderManager = NULL; + } + if(mDataManager) + { + mDataManager->shutdown(); + delete mDataManager; + mDataManager = NULL; + } + if (mLogManager) + { + delete mLogManager; + mLogManager = NULL; } mGui = NULL; - mPlatform = NULL; } diff --git a/libs/openengine/gui/manager.hpp b/libs/openengine/gui/manager.hpp index 1ec2e2fcf3..c0f98da88b 100644 --- a/libs/openengine/gui/manager.hpp +++ b/libs/openengine/gui/manager.hpp @@ -3,8 +3,10 @@ namespace MyGUI { - class OgrePlatform; class Gui; + class LogManager; + class OgreDataManager; + class OgreRenderManager; } namespace Ogre @@ -18,12 +20,15 @@ namespace GUI { class MyGUIManager { - MyGUI::OgrePlatform *mPlatform; MyGUI::Gui *mGui; + MyGUI::LogManager* mLogManager; + MyGUI::OgreDataManager* mDataManager; + MyGUI::OgreRenderManager* mRenderManager; Ogre::SceneManager* mSceneMgr; + public: - MyGUIManager() : mPlatform(NULL), mGui(NULL) {} + MyGUIManager() : mLogManager(NULL), mDataManager(NULL), mRenderManager(NULL), mGui(NULL) {} MyGUIManager(Ogre::RenderWindow *wnd, Ogre::SceneManager *mgr, bool logging=false, const std::string& logDir = std::string("")) { setup(wnd,mgr,logging, logDir); diff --git a/libs/openengine/ogre/imagerotate.cpp b/libs/openengine/ogre/imagerotate.cpp index 11fd5eea6a..3dd5840785 100644 --- a/libs/openengine/ogre/imagerotate.cpp +++ b/libs/openengine/ogre/imagerotate.cpp @@ -16,6 +16,8 @@ void ImageRotate::rotate(const std::string& sourceImage, const std::string& dest { Root* root = Ogre::Root::getSingletonPtr(); + std::string destImageRot = std::string(destImage) + std::string("_rot"); + SceneManager* sceneMgr = root->createSceneManager(ST_GENERIC); Camera* camera = sceneMgr->createCamera("ImageRotateCamera"); @@ -48,8 +50,8 @@ void ImageRotate::rotate(const std::string& sourceImage, const std::string& dest unsigned int width = sourceTexture->getWidth(); unsigned int height = sourceTexture->getHeight(); - TexturePtr destTexture = TextureManager::getSingleton().createManual( - destImage, + TexturePtr destTextureRot = TextureManager::getSingleton().createManual( + destImageRot, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, TEX_TYPE_2D, width, height, @@ -57,7 +59,7 @@ void ImageRotate::rotate(const std::string& sourceImage, const std::string& dest PF_FLOAT16_RGBA, TU_RENDERTARGET); - RenderTarget* rtt = destTexture->getBuffer()->getRenderTarget(); + RenderTarget* rtt = destTextureRot->getBuffer()->getRenderTarget(); rtt->setAutoUpdated(false); Viewport* vp = rtt->addViewport(camera); vp->setOverlaysEnabled(false); @@ -66,7 +68,20 @@ void ImageRotate::rotate(const std::string& sourceImage, const std::string& dest rtt->update(); + //copy the rotated image to a static texture + TexturePtr destTexture = TextureManager::getSingleton().createManual( + destImage, + ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + TEX_TYPE_2D, + width, height, + 0, + PF_FLOAT16_RGBA, + Ogre::TU_STATIC); + + destTexture->getBuffer()->blit(destTextureRot->getBuffer()); + // remove all the junk we've created + TextureManager::getSingleton().remove(destImageRot); MaterialManager::getSingleton().remove("ImageRotateMaterial"); root->destroySceneManager(sceneMgr); delete rect; diff --git a/libs/openengine/ogre/renderer.cpp b/libs/openengine/ogre/renderer.cpp index 87ebe11397..3cdb005186 100644 --- a/libs/openengine/ogre/renderer.cpp +++ b/libs/openengine/ogre/renderer.cpp @@ -226,7 +226,7 @@ void OgreRenderer::createWindow(const std::string &title, const WindowSettings& 1, 1, 0, Ogre::PF_A8R8G8B8, - Ogre::TU_DYNAMIC_WRITE_ONLY); + Ogre::TU_WRITE_ONLY); } void OgreRenderer::createScene(const std::string& camName, float fov, float nearClip)