diff --git a/Bitstream Vera License.txt b/Bitstream Vera License.txt
new file mode 100644
index 000000000..2b37cc1df
--- /dev/null
+++ b/Bitstream Vera License.txt
@@ -0,0 +1,123 @@
+Bitstream Vera Fonts Copyright
+
+The fonts have a generous copyright, allowing derivative works (as
+long as "Bitstream" or "Vera" are not in the names), and full
+redistribution (so long as they are not *sold* by themselves). They
+can be be bundled, redistributed and sold with any software.
+
+The fonts are distributed under the following copyright:
+
+Copyright
+=========
+
+Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream
+Vera is a trademark of Bitstream, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the fonts accompanying this license ("Fonts") and associated
+documentation files (the "Font Software"), to reproduce and distribute
+the Font Software, including without limitation the rights to use,
+copy, merge, publish, distribute, and/or sell copies of the Font
+Software, and to permit persons to whom the Font Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright and trademark notices and this permission notice
+shall be included in all copies of one or more of the Font Software
+typefaces.
+
+The Font Software may be modified, altered, or added to, and in
+particular the designs of glyphs or characters in the Fonts may be
+modified and additional glyphs or characters may be added to the
+Fonts, only if the fonts are renamed to names not containing either
+the words "Bitstream" or the word "Vera".
+
+This License becomes null and void to the extent applicable to Fonts
+or Font Software that has been modified and is distributed under the
+"Bitstream Vera" names.
+
+The Font Software may be sold as part of a larger software package but
+no copy of one or more of the Font Software typefaces may be sold by
+itself.
+
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL
+BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL,
+OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT
+SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
+
+Except as contained in this notice, the names of Gnome, the Gnome
+Foundation, and Bitstream Inc., shall not be used in advertising or
+otherwise to promote the sale, use or other dealings in this Font
+Software without prior written authorization from the Gnome Foundation
+or Bitstream Inc., respectively. For further information, contact:
+fonts at gnome dot org.
+
+Copyright FAQ
+=============
+
+ 1. I don't understand the resale restriction... What gives?
+
+ Bitstream is giving away these fonts, but wishes to ensure its
+ competitors can't just drop the fonts as is into a font sale system
+ and sell them as is. It seems fair that if Bitstream can't make money
+ from the Bitstream Vera fonts, their competitors should not be able to
+ do so either. You can sell the fonts as part of any software package,
+ however.
+
+ 2. I want to package these fonts separately for distribution and
+ sale as part of a larger software package or system. Can I do so?
+
+ Yes. A RPM or Debian package is a "larger software package" to begin
+ with, and you aren't selling them independently by themselves.
+ See 1. above.
+
+ 3. Are derivative works allowed?
+ Yes!
+
+ 4. Can I change or add to the font(s)?
+ Yes, but you must change the name(s) of the font(s).
+
+ 5. Under what terms are derivative works allowed?
+
+ You must change the name(s) of the fonts. This is to ensure the
+ quality of the fonts, both to protect Bitstream and Gnome. We want to
+ ensure that if an application has opened a font specifically of these
+ names, it gets what it expects (though of course, using fontconfig,
+ substitutions could still could have occurred during font
+ opening). You must include the Bitstream copyright. Additional
+ copyrights can be added, as per copyright law. Happy Font Hacking!
+
+ 6. If I have improvements for Bitstream Vera, is it possible they might get
+ adopted in future versions?
+
+ Yes. The contract between the Gnome Foundation and Bitstream has
+ provisions for working with Bitstream to ensure quality additions to
+ the Bitstream Vera font family. Please contact us if you have such
+ additions. Note, that in general, we will want such additions for the
+ entire family, not just a single font, and that you'll have to keep
+ both Gnome and Jim Lyles, Vera's designer, happy! To make sense to add
+ glyphs to the font, they must be stylistically in keeping with Vera's
+ design. Vera cannot become a "ransom note" font. Jim Lyles will be
+ providing a document describing the design elements used in Vera, as a
+ guide and aid for people interested in contributing to Vera.
+
+ 7. I want to sell a software package that uses these fonts: Can I do so?
+
+ Sure. Bundle the fonts with your software and sell your software
+ with the fonts. That is the intent of the copyright.
+
+ 8. If applications have built the names "Bitstream Vera" into them,
+ can I override this somehow to use fonts of my choosing?
+
+ This depends on exact details of the software. Most open source
+ systems and software (e.g., Gnome, KDE, etc.) are now converting to
+ use fontconfig (see www.fontconfig.org) to handle font configuration,
+ selection and substitution; it has provisions for overriding font
+ names and subsituting alternatives. An example is provided by the
+ supplied local.conf file, which chooses the family Bitstream Vera for
+ "sans", "serif" and "monospace". Other software (e.g., the XFree86
+ core server) has other mechanisms for font substitution.
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 74a43ede1..ba2799c83 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -4,9 +4,6 @@ if (APPLE)
set(APP_BUNDLE_NAME "${CMAKE_PROJECT_NAME}.app")
set(APP_BUNDLE_DIR "${OpenMW_BINARY_DIR}/${APP_BUNDLE_NAME}")
-
- # using 10.6 sdk
- set(CMAKE_OSX_SYSROOT "/Developer/SDKs/MacOSX10.6.sdk")
endif (APPLE)
# Macros
@@ -30,7 +27,6 @@ configure_file ("${OpenMW_SOURCE_DIR}/Docs/mainpage.hpp.cmake" "${OpenMW_SOURCE_
option(OGRE_STATIC "Link static build of Ogre and Ogre Plugins into the binaries" FALSE)
# Sound source selection
-option(USE_AUDIERE "use Audiere for sound" OFF)
option(USE_FFMPEG "use ffmpeg for sound" OFF)
option(USE_MPG123 "use mpg123 + libsndfile for sound" ON)
@@ -120,52 +116,31 @@ set(OENGINE_BULLET
${LIBDIR}/openengine/bullet/BulletShapeLoader.h
)
-# Sound setup
-if (USE_AUDIERE)
- set(MANGLE_SOUND_OUTPUT
- ${LIBDIR}/mangle/sound/sources/audiere_source.cpp
- ${LIBDIR}/mangle/sound/sources/sample_reader.cpp
- ${LIBDIR}/mangle/stream/clients/audiere_file.cpp)
- find_package(Audiere REQUIRED)
- set(SOUND_INPUT_INCLUDES ${AUDIERE_INCLUDE_DIR})
- set(SOUND_INPUT_LIBRARY ${AUDIERE_LIBRARY})
- set(SOUND_DEFINE -DOPENMW_USE_AUDIERE)
-endif (USE_AUDIERE)
-
-if (USE_FFMPEG)
- set(MANGLE_SOUND_OUTPUT
- ${LIBDIR}/mangle/sound/sources/ffmpeg_source.cpp)
- find_package(FFMPEG REQUIRED)
- set(SOUND_INPUT_INCLUDES ${FFMPEG_INCLUDE_DIR})
- set(SOUND_INPUT_LIBRARY ${FFMPEG_LIBRARIES})
- set(SOUND_DEFINE -DOPENMW_USE_FFMPEG)
-endif (USE_FFMPEG)
-
-if (USE_MPG123)
- set(MANGLE_SOUND_OUTPUT
- ${LIBDIR}/mangle/sound/sources/mpg123_source.cpp
- ${LIBDIR}/mangle/sound/sources/libsndfile.cpp
- ${LIBDIR}/mangle/sound/sources/sample_reader.cpp)
- find_package(MPG123 REQUIRED)
- find_package(SNDFILE REQUIRED)
- set(SOUND_INPUT_INCLUDES ${MPG123_INCLUDE_DIR} ${SNDFILE_INCLUDE_DIR})
- set(SOUND_INPUT_LIBRARY ${MPG123_LIBRARY} ${SNDFILE_LIBRARY})
- set(SOUND_DEFINE -DOPENMW_USE_MPG123)
-endif (USE_MPG123)
-
-set(OENGINE_SOUND
- # Mangle and OEngine sound files are sort of intertwined, so put
- # them together here
- ${LIBDIR}/openengine/sound/sndmanager.cpp
- ${LIBDIR}/mangle/sound/outputs/openal_out.cpp
- ${MANGLE_SOUND_OUTPUT}
-)
-set(OENGINE_ALL ${OENGINE_OGRE} ${OENGINE_GUI} ${OENGINE_SOUND} ${OENGINE_BULLET})
+set(OENGINE_ALL ${OENGINE_OGRE} ${OENGINE_GUI} ${OENGINE_BULLET})
source_group(libs\\openengine FILES ${OENGINE_ALL})
set(OPENMW_LIBS ${MANGLE_ALL} ${OENGINE_ALL})
set(OPENMW_LIBS_HEADER)
+# Sound setup
+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)
+endif (USE_FFMPEG)
+
+if (USE_MPG123)
+ 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)
+
# Platform specific
if (WIN32)
set(PLATFORM_INCLUDE_DIR "platform")
@@ -185,7 +160,7 @@ endif (APPLE)
# Dependencies
-# Fix for not visible pthreads functions for linker with glibc 2.15
+# Fix for not visible pthreads functions for linker with glibc 2.15
if (UNIX AND NOT APPLE)
find_package (Threads)
endif()
@@ -281,7 +256,16 @@ endif (APPLE)
# Compiler settings
if (CMAKE_COMPILER_IS_GNUCC)
- add_definitions (-Wall -Wextra -Wno-unused-parameter -Wno-unused-but-set-parameter -Wno-reorder)
+ add_definitions (-Wall -Wextra -Wno-unused-parameter -Wno-reorder)
+
+ # Silence warnings in OGRE headers. Remove once OGRE got fixed!
+ add_definitions (-Wno-ignored-qualifiers)
+
+ execute_process(COMMAND ${CMAKE_C_COMPILER} -dumpversion
+ OUTPUT_VARIABLE GCC_VERSION)
+ if ("${GCC_VERSION}" VERSION_GREATER 4.6 OR "${GCC_VERSION}" VERSION_EQUAL 4.6)
+ add_definitions (-Wno-unused-but-set-parameter)
+ endif("${GCC_VERSION}" VERSION_GREATER 4.6 OR "${GCC_VERSION}" VERSION_EQUAL 4.6)
endif (CMAKE_COMPILER_IS_GNUCC)
if(DPKG_PROGRAM)
@@ -341,6 +325,7 @@ if(WIN32)
FILE(GLOB files "${OpenMW_BINARY_DIR}/Release/*.*")
INSTALL(FILES ${files} DESTINATION ".")
INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" DESTINATION "." RENAME "openmw.cfg")
+ INSTALL(FILES "${OpenMW_SOURCE_DIR}/readme.txt" DESTINATION ".")
INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION ".")
SET(CPACK_GENERATOR "NSIS")
@@ -351,6 +336,7 @@ if(WIN32)
SET(CPACK_PACKAGE_VERSION_MINOR ${OPENMW_VERSION_MINO})
SET(CPACK_PACKAGE_VERSION_PATCH ${OPENMW_VERSION_RELEASE})
SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW;esmtool;Esmtool;omwlauncher;OpenMW Launcher")
+ set(CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '\$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Readme.lnk' '\$INSTDIR\\\\readme.txt'")
SET(CPACK_PACKAGE_DESCRIPTION_FILE "${OpenMW_SOURCE_DIR}/readme.txt")
SET(CPACK_RESOURCE_FILE_LICENSE "${OpenMW_SOURCE_DIR}/GPL3.txt")
SET(CPACK_NSIS_EXECUTABLES_DIRECTORY ".")
@@ -493,6 +479,7 @@ if (APPLE)
install(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" RENAME "openmw.cfg" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime)
install(FILES "${OpenMW_BINARY_DIR}/plugins.cfg" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime)
+ install(FILES "${OpenMW_BINARY_DIR}/launcher.qss" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime)
set(CPACK_GENERATOR "DragNDrop")
set(CPACK_PACKAGE_VERSION ${OPENMW_VERSION})
diff --git a/OFL.txt b/OFL.txt
new file mode 100644
index 000000000..043e85e83
--- /dev/null
+++ b/OFL.txt
@@ -0,0 +1,93 @@
+Copyright (c) 2010, 2011 Georg Duffner (http://www.georgduffner.at)
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp
index c96fc2c7b..c15274e74 100644
--- a/apps/launcher/datafilespage.cpp
+++ b/apps/launcher/datafilespage.cpp
@@ -225,7 +225,7 @@ void DataFilesPage::setupDataFiles()
msgBox.setIcon(QMessageBox::Warning);
msgBox.setStandardButtons(QMessageBox::Cancel);
msgBox.setText(tr("
Could not find the Data Files location
\
- The directory containing the Data Files was not found.
\
+ The directory containing the data files was not found.
\
Press \"Browse...\" to specify the location manually.
"));
QAbstractButton *dirSelectButton =
@@ -1057,16 +1057,8 @@ void DataFilesPage::writeConfig(QString profile)
return;
}
- // Prepare the OpenMW config
- QString config = QString::fromStdString((mCfgMgr.getLocalPath() / "openmw.cfg").string());
- QFile file(config);
-
- if (!file.exists()) {
- config = QString::fromStdString((mCfgMgr.getUserPath() / "openmw.cfg").string());
- }
-
- // Open the config as a QFile
- file.setFileName(config);
+ // Open the OpenMW config as a QFile
+ QFile file(QString::fromStdString((mCfgMgr.getUserPath() / "openmw.cfg").string()));
if (!file.open(QIODevice::ReadWrite | QIODevice::Text)) {
// File cannot be opened or created
diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp
index 49c0bd960..8bb618dd6 100644
--- a/apps/launcher/maindialog.cpp
+++ b/apps/launcher/maindialog.cpp
@@ -45,9 +45,28 @@ MainDialog::MainDialog()
setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint);
setMinimumSize(QSize(575, 575));
+ // Install the stylesheet font
+ QFile file;
+ QFontDatabase fontDatabase;
+
+ const QStringList fonts = fontDatabase.families();
+
+ // Check if the font is installed
+ if (!fonts.contains("EB Garamond")) {
+
+ QString font = QString::fromStdString((mCfgMgr.getGlobalDataPath() / "resources/mygui/EBGaramond-Regular.ttf").string());
+ file.setFileName(font);
+
+ if (!file.exists()) {
+ font = QString::fromStdString((mCfgMgr.getLocalPath() / "resources/mygui/EBGaramond-Regular.ttf").string());
+ }
+
+ fontDatabase.addApplicationFont(font);
+ }
+
// Load the stylesheet
QString config = QString::fromStdString((mCfgMgr.getGlobalDataPath() / "resources/launcher.qss").string());
- QFile file(config);
+ file.setFileName(config);
if (!file.exists()) {
file.setFileName(QString::fromStdString((mCfgMgr.getLocalPath() / "launcher.qss").string()));
@@ -175,6 +194,7 @@ void MainDialog::play()
QDir dir(QCoreApplication::applicationDirPath());
QString game = dir.absoluteFilePath("openmw");
QFile file(game);
+ game = "\"" + game + "\"";
#else
QString game = "./openmw";
QFile file(game);
diff --git a/apps/launcher/resources/images/openmw-header.png b/apps/launcher/resources/images/openmw-header.png
index a168d4d2a..a2ffab68b 100644
Binary files a/apps/launcher/resources/images/openmw-header.png and b/apps/launcher/resources/images/openmw-header.png differ
diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt
index 365600061..75bc57b0f 100644
--- a/apps/openmw/CMakeLists.txt
+++ b/apps/openmw/CMakeLists.txt
@@ -14,11 +14,8 @@ set(GAME_HEADER
source_group(game FILES ${GAME} ${GAME_HEADER})
add_openmw_dir (mwrender
-
- renderingmanager debugging sky player renderinginterface water
-
- animation npcanimation creatureanimation actors objects renderinginterface
-
+ renderingmanager debugging sky player animation npcanimation creatureanimation actors objects
+ renderinginterface localmap water
)
add_openmw_dir (mwinput
@@ -42,7 +39,7 @@ add_openmw_dir (mwscript
)
add_openmw_dir (mwsound
- soundmanager
+ soundmanager openal_output mpgsnd_decoder ffmpeg_decoder
)
add_openmw_dir (mwworld
@@ -92,7 +89,7 @@ target_link_libraries(openmw
${SOUND_INPUT_LIBRARY}
${BULLET_LIBRARIES}
${MYGUI_LIBRARIES}
- MyGUI.OgrePlatform #TODO MyGUI ogre platform is not added by the find script
+ ${MYGUI_PLATFORM_LIBRARIES}
components
)
diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp
index 433cc0c11..441c22769 100644
--- a/apps/openmw/engine.cpp
+++ b/apps/openmw/engine.cpp
@@ -117,11 +117,7 @@ bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt)
// sound
if (mUseSound)
- {
- mEnvironment.mSoundManager->playPlaylist();
-
mEnvironment.mSoundManager->update (evt.timeSinceLastFrame);
- }
// update GUI
Ogre::RenderWindow* window = mOgre->getWindow();
@@ -341,10 +337,7 @@ void OMW::Engine::go()
mExtensions, mFpsLevel, mNewGame, mOgre, mCfgMgr.getLogPath().string() + std::string("/"));
// Create sound system
- mEnvironment.mSoundManager = new MWSound::SoundManager(mOgre->getRoot(),
- mOgre->getCamera(),
- mDataDirs,
- mUseSound, mFSStrict, mEnvironment);
+ mEnvironment.mSoundManager = new MWSound::SoundManager(mUseSound, mEnvironment);
// Create script system
mScriptContext = new MWScript::CompilerContext (MWScript::CompilerContext::Type_Full,
diff --git a/apps/openmw/mwgui/layouts.cpp b/apps/openmw/mwgui/layouts.cpp
index ebabc6faf..dbd6154b7 100644
--- a/apps/openmw/mwgui/layouts.cpp
+++ b/apps/openmw/mwgui/layouts.cpp
@@ -15,6 +15,22 @@ using namespace MWGui;
HUD::HUD(int width, int height, int fpsLevel)
: Layout("openmw_hud_layout.xml")
+ , health(NULL)
+ , magicka(NULL)
+ , stamina(NULL)
+ , weapImage(NULL)
+ , spellImage(NULL)
+ , weapStatus(NULL)
+ , spellStatus(NULL)
+ , effectBox(NULL)
+ , effect1(NULL)
+ , minimap(NULL)
+ , compass(NULL)
+ , crosshair(NULL)
+ , fpsbox(NULL)
+ , fpscounter(NULL)
+ , trianglecounter(NULL)
+ , batchcounter(NULL)
{
setCoord(0,0, width, height);
@@ -61,6 +77,8 @@ HUD::HUD(int width, int height, int fpsLevel)
setSpellIcon("icons\\s\\b_tx_s_rstor_health.dds");
setSpellStatus(65, 100);
setEffect("icons\\s\\tx_s_chameleon.dds");
+
+ LocalMapBase::init(minimap, this);
}
void HUD::setFPS(float fps)
@@ -142,3 +160,162 @@ void HUD::setValue(const std::string& id, const MWMechanics::DynamicStat& v
}
}
}
+
+void HUD::setPlayerDir(const float x, const float y)
+{
+ MyGUI::ISubWidget* main = compass->getSubWidgetMain();
+ MyGUI::RotatingSkin* rotatingSubskin = main->castType();
+ rotatingSubskin->setCenter(MyGUI::IntPoint(16,16));
+ float angle = std::atan2(x,y);
+ rotatingSubskin->setAngle(angle);
+}
+
+void HUD::setPlayerPos(const float x, const float y)
+{
+ MyGUI::IntSize size = minimap->getCanvasSize();
+ MyGUI::IntPoint middle = MyGUI::IntPoint((1/3.f + x/3.f)*size.width,(1/3.f + y/3.f)*size.height);
+ MyGUI::IntCoord viewsize = minimap->getCoord();
+ MyGUI::IntPoint pos(0.5*viewsize.width - middle.left, 0.5*viewsize.height - middle.top);
+
+ minimap->setViewOffset(pos);
+ compass->setPosition(MyGUI::IntPoint(x*512-16, y*512-16));
+}
+
+MapWindow::MapWindow()
+ : Layout("openmw_map_window_layout.xml"), mGlobal(false)
+{
+ setCoord(500,0,320,300);
+ setText("WorldButton", "World");
+ setImage("Compass", "textures\\compass.dds");
+
+ // Obviously you should override this later on
+ setCellName("No Cell Loaded");
+
+ getWidget(mLocalMap, "LocalMap");
+ getWidget(mGlobalMap, "GlobalMap");
+ getWidget(mPlayerArrow, "Compass");
+
+ getWidget(mButton, "WorldButton");
+ mButton->eventMouseButtonClick += MyGUI::newDelegate(this, &MapWindow::onWorldButtonClicked);
+
+ MyGUI::Button* eventbox;
+ getWidget(eventbox, "EventBox");
+ eventbox->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag);
+ eventbox->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart);
+
+ LocalMapBase::init(mLocalMap, this);
+}
+
+void MapWindow::setVisible(bool b)
+{
+ mMainWidget->setVisible(b);
+ if (b)
+ mVisible = true;
+ else
+ mVisible = false;
+}
+
+void MapWindow::setCellName(const std::string& cellName)
+{
+ static_cast(mMainWidget)->setCaption(cellName);
+ adjustWindowCaption();
+}
+
+void MapWindow::setPlayerPos(const float x, const float y)
+{
+ if (mGlobal || mVisible) return;
+ MyGUI::IntSize size = mLocalMap->getCanvasSize();
+ MyGUI::IntPoint middle = MyGUI::IntPoint((1/3.f + x/3.f)*size.width,(1/3.f + y/3.f)*size.height);
+ MyGUI::IntCoord viewsize = mLocalMap->getCoord();
+ MyGUI::IntPoint pos(0.5*viewsize.width - middle.left, 0.5*viewsize.height - middle.top);
+ mLocalMap->setViewOffset(pos);
+
+ mPlayerArrow->setPosition(MyGUI::IntPoint(x*512-16, y*512-16));
+}
+
+void MapWindow::setPlayerDir(const float x, const float y)
+{
+ if (!mVisible) return;
+ MyGUI::ISubWidget* main = mPlayerArrow->getSubWidgetMain();
+ MyGUI::RotatingSkin* rotatingSubskin = main->castType();
+ rotatingSubskin->setCenter(MyGUI::IntPoint(16,16));
+ float angle = std::atan2(x,y);
+ rotatingSubskin->setAngle(angle);
+}
+
+void MapWindow::onDragStart(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id)
+{
+ if (_id!=MyGUI::MouseButton::Left) return;
+ if (!mGlobal)
+ mLastDragPos = MyGUI::IntPoint(_left, _top);
+}
+
+void MapWindow::onMouseDrag(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id)
+{
+ if (_id!=MyGUI::MouseButton::Left) return;
+
+ if (!mGlobal)
+ {
+ MyGUI::IntPoint diff = MyGUI::IntPoint(_left, _top) - mLastDragPos;
+ mLocalMap->setViewOffset( mLocalMap->getViewOffset() + diff );
+
+ mLastDragPos = MyGUI::IntPoint(_left, _top);
+ }
+}
+
+void MapWindow::onWorldButtonClicked(MyGUI::Widget* _sender)
+{
+ mGlobal = !mGlobal;
+ mGlobalMap->setVisible(mGlobal);
+ mLocalMap->setVisible(!mGlobal);
+
+ mButton->setCaption( mGlobal ? "Local" : "World" );
+}
+
+void LocalMapBase::init(MyGUI::ScrollView* widget, OEngine::GUI::Layout* layout)
+{
+ mLocalMap = widget;
+ mLayout = layout;
+}
+
+void LocalMapBase::setCellPrefix(const std::string& prefix)
+{
+ mPrefix = prefix;
+ mChanged = true;
+}
+
+void LocalMapBase::setActiveCell(const int x, const int y, bool interior)
+{
+ if (x==mCurX && y==mCurY && mInterior==interior && !mChanged) return; // don't do anything if we're still in the same cell
+ for (int mx=0; mx<3; ++mx)
+ {
+ for (int my=0; my<3; ++my)
+ {
+ std::string name = "Map_" + boost::lexical_cast(mx) + "_"
+ + boost::lexical_cast(my);
+
+ std::string image = mPrefix+"_"+ boost::lexical_cast(x + (mx-1)) + "_"
+ + boost::lexical_cast(y + (interior ? (my-1) : -1*(my-1)));
+
+ MyGUI::ImageBox* box;
+ mLayout->getWidget(box, name);
+ MyGUI::ImageBox* fog;
+ mLayout->getWidget(fog, name+"_fog");
+
+ if (MyGUI::RenderManager::getInstance().getTexture(image) != 0)
+ box->setImageTexture(image);
+ else
+ box->setImageTexture("black.png");
+
+ if (MyGUI::RenderManager::getInstance().getTexture(image+"_fog") != 0)
+ fog->setImageTexture(image+"_fog");
+ else
+ fog->setImageTexture("black.png");
+ }
+ }
+ mInterior = interior;
+ mCurX = x;
+ mCurY = y;
+ mChanged = false;
+}
+
diff --git a/apps/openmw/mwgui/layouts.hpp b/apps/openmw/mwgui/layouts.hpp
index 71181e48d..8d9a41a22 100644
--- a/apps/openmw/mwgui/layouts.hpp
+++ b/apps/openmw/mwgui/layouts.hpp
@@ -14,6 +14,8 @@
#include "../mwmechanics/stat.hpp"
#include "window_base.hpp"
+#include
+
/*
This file contains classes corresponding to window layouts
defined in resources/mygui/ *.xml.
@@ -29,7 +31,25 @@
namespace MWGui
{
- class HUD : public OEngine::GUI::Layout
+ class LocalMapBase
+ {
+ public:
+ void init(MyGUI::ScrollView* widget, OEngine::GUI::Layout* layout);
+
+ void setCellPrefix(const std::string& prefix);
+ void setActiveCell(const int x, const int y, bool interior=false);
+
+ protected:
+ int mCurX, mCurY;
+ bool mInterior;
+ MyGUI::ScrollView* mLocalMap;
+ std::string mPrefix;
+ bool mChanged;
+
+ OEngine::GUI::Layout* mLayout;
+ };
+
+ class HUD : public OEngine::GUI::Layout, public LocalMapBase
{
public:
HUD(int width, int height, int fpsLevel);
@@ -43,13 +63,15 @@ namespace MWGui
void setFPS(float fps);
void setTriangleCount(size_t count);
void setBatchCount(size_t count);
+ void setPlayerDir(const float x, const float y);
+ void setPlayerPos(const float x, const float y);
MyGUI::ProgressPtr health, magicka, stamina;
MyGUI::ImageBox *weapImage, *spellImage;
MyGUI::ProgressPtr weapStatus, spellStatus;
MyGUI::WidgetPtr effectBox;
MyGUI::ImageBox* effect1;
- MyGUI::ImageBox* minimap;
+ MyGUI::ScrollView* minimap;
MyGUI::ImageBox* compass;
MyGUI::ImageBox* crosshair;
@@ -59,24 +81,27 @@ namespace MWGui
MyGUI::TextBox* batchcounter;
};
- class MapWindow : public OEngine::GUI::Layout
+ class MapWindow : public OEngine::GUI::Layout, public LocalMapBase
{
public:
- MapWindow()
- : Layout("openmw_map_window_layout.xml")
- {
- setCoord(500,0,320,300);
- setText("WorldButton", "World");
- setImage("Compass", "compass.dds");
+ MapWindow();
- // Obviously you should override this later on
- setCellName("No Cell Loaded");
- }
+ void setVisible(bool b);
+ void setPlayerPos(const float x, const float y);
+ void setPlayerDir(const float x, const float y);
+ void setCellName(const std::string& cellName);
+
+ private:
+ void onDragStart(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id);
+ void onMouseDrag(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id);
+ void onWorldButtonClicked(MyGUI::Widget* _sender);
- void setCellName(const std::string& cellName)
- {
- static_cast(mMainWidget)->setCaption(cellName);
- }
+ MyGUI::ScrollView* mGlobalMap;
+ MyGUI::ImageBox* mPlayerArrow;
+ MyGUI::Button* mButton;
+ MyGUI::IntPoint mLastDragPos;
+ bool mVisible;
+ bool mGlobal;
};
class MainMenu : public OEngine::GUI::Layout
diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp
index bf3307acc..33155b2a0 100644
--- a/apps/openmw/mwgui/messagebox.hpp
+++ b/apps/openmw/mwgui/messagebox.hpp
@@ -7,6 +7,7 @@
#include "window_base.hpp"
#include "window_manager.hpp"
+#undef MessageBox
namespace MWGui
{
diff --git a/apps/openmw/mwgui/stats_window.cpp b/apps/openmw/mwgui/stats_window.cpp
index 243b6272a..12b0dcc79 100644
--- a/apps/openmw/mwgui/stats_window.cpp
+++ b/apps/openmw/mwgui/stats_window.cpp
@@ -13,9 +13,22 @@ const int StatsWindow::lineHeight = 18;
StatsWindow::StatsWindow (WindowManager& parWindowManager)
: WindowBase("openmw_stats_window_layout.xml", parWindowManager)
+ , skillAreaWidget(NULL)
+ , skillClientWidget(NULL)
+ , skillScrollerWidget(NULL)
, lastPos(0)
+ , clientHeight(0)
+ , majorSkills()
+ , minorSkills()
+ , miscSkills()
+ , skillValues()
+ , skillWidgetMap()
+ , factionWidgetMap()
+ , factions()
+ , birthSignId()
, reputation(0)
, bounty(0)
+ , skillWidgets()
{
setCoord(0,0,498, 342);
diff --git a/apps/openmw/mwgui/widgets.hpp b/apps/openmw/mwgui/widgets.hpp
index 4d31ad521..a7916285e 100644
--- a/apps/openmw/mwgui/widgets.hpp
+++ b/apps/openmw/mwgui/widgets.hpp
@@ -7,6 +7,9 @@
#include "../mwmechanics/stat.hpp"
+#undef MYGUI_EXPORT
+#define MYGUI_EXPORT
+
/*
This file contains various custom widgets used in OpenMW.
*/
diff --git a/apps/openmw/mwgui/window_manager.cpp b/apps/openmw/mwgui/window_manager.cpp
index 8ac4b11d7..a04e2dcb8 100644
--- a/apps/openmw/mwgui/window_manager.cpp
+++ b/apps/openmw/mwgui/window_manager.cpp
@@ -22,15 +22,40 @@ using namespace MWGui;
WindowManager::WindowManager(MWWorld::Environment& environment,
const Compiler::Extensions& extensions, int fpsLevel, bool newGame, OEngine::Render::OgreRenderer *mOgre, const std::string logpath)
- : environment(environment)
+ : mGuiManager(NULL)
+ , environment(environment)
+ , hud(NULL)
+ , map(NULL)
+ , menu(NULL)
+ , stats(NULL)
+ , mMessageBoxManager(NULL)
+ , console(NULL)
+ , mJournal(NULL)
, dialogueWindow(nullptr)
+ , mCharGen(NULL)
+ , playerClass()
+ , playerName()
+ , playerRaceId()
+ , playerBirthSignId()
+ , playerAttributes()
+ , playerMajorSkills()
+ , playerMinorSkills()
+ , playerSkillValues()
+ , playerHealth()
+ , playerMagicka()
+ , playerFatigue()
+ , gui(NULL)
, mode(GM_Game)
, nextMode(GM_Game)
, needModeChange(false)
+ , garbageDialogs()
, shown(GW_ALL)
, allowed(newGame ? GW_None : GW_ALL)
+ , showFPSLevel(fpsLevel)
+ , mFPS(0.0f)
+ , mTriangleCount(0)
+ , mBatchCount(0)
{
- showFPSLevel = fpsLevel;
// Set up the GUI system
mGuiManager = new OEngine::GUI::MyGUIManager(mOgre->getWindow(), mOgre->getScene(), false, logpath);
@@ -400,3 +425,47 @@ const ESMS::ESMStore& WindowManager::getStore() const
{
return environment.mWorld->getStore();
}
+
+void WindowManager::changeCell(MWWorld::Ptr::CellStore* cell)
+{
+ if (!(cell->cell->data.flags & ESM::Cell::Interior))
+ {
+ std::string name;
+ if (cell->cell->name != "")
+ name = cell->cell->name;
+ else
+ name = cell->cell->region;
+
+ map->setCellName( name );
+
+ map->setCellPrefix("Cell");
+ hud->setCellPrefix("Cell");
+ map->setActiveCell( cell->cell->data.gridX, cell->cell->data.gridY );
+ hud->setActiveCell( cell->cell->data.gridX, cell->cell->data.gridY );
+ }
+ else
+ {
+ map->setCellName( cell->cell->name );
+ map->setCellPrefix( cell->cell->name );
+ hud->setCellPrefix( cell->cell->name );
+ }
+
+}
+
+void WindowManager::setInteriorMapTexture(const int x, const int y)
+{
+ map->setActiveCell(x,y, true);
+ hud->setActiveCell(x,y, true);
+}
+
+void WindowManager::setPlayerPos(const float x, const float y)
+{
+ map->setPlayerPos(x,y);
+ hud->setPlayerPos(x,y);
+}
+
+void WindowManager::setPlayerDir(const float x, const float y)
+{
+ map->setPlayerDir(x,y);
+ hud->setPlayerDir(x,y);
+}
diff --git a/apps/openmw/mwgui/window_manager.hpp b/apps/openmw/mwgui/window_manager.hpp
index 0124c9239..582f438e8 100644
--- a/apps/openmw/mwgui/window_manager.hpp
+++ b/apps/openmw/mwgui/window_manager.hpp
@@ -18,6 +18,7 @@
#include
#include
#include "../mwmechanics/stat.hpp"
+#include "../mwworld/ptr.hpp"
#include "mode.hpp"
namespace MyGUI
@@ -152,6 +153,12 @@ namespace MWGui
void setBounty (int bounty); ///< set the current bounty value
void updateSkillArea(); ///< update display of skills, factions, birth sign, reputation and bounty
+ void changeCell(MWWorld::Ptr::CellStore* cell); ///< change the active cell
+ void setPlayerPos(const float x, const float y); ///< set player position in map space
+ void setPlayerDir(const float x, const float y); ///< set player view direction in map space
+
+ void setInteriorMapTexture(const int x, const int y);
+ ///< set the index of the map texture that should be used (for interiors)
template
void removeDialog(T*& dialog); ///< Casts to OEngine::GUI::Layout and calls removeDialog, then resets pointer to nullptr.
diff --git a/apps/openmw/mwrender/actors.cpp b/apps/openmw/mwrender/actors.cpp
index d8ca78e3a..6eb4a182b 100644
--- a/apps/openmw/mwrender/actors.cpp
+++ b/apps/openmw/mwrender/actors.cpp
@@ -8,6 +8,15 @@ using namespace Ogre;
using namespace MWRender;
using namespace NifOgre;
+Actors::~Actors(){
+
+ std::map::iterator it = mAllActors.begin();
+ for (; it != mAllActors.end(); ++it) {
+ delete it->second;
+ it->second = NULL;
+ }
+}
+
void Actors::setMwRoot(Ogre::SceneNode* root){
mMwRoot = root;
}
@@ -61,6 +70,7 @@ void Actors::insertCreature (const MWWorld::Ptr& ptr){
insertBegin(ptr, true, true);
CreatureAnimation* anim = new MWRender::CreatureAnimation(ptr, mEnvironment, mRend);
//mAllActors.insert(std::pair(ptr,anim));
+ delete mAllActors[ptr];
mAllActors[ptr] = anim;
//mAllActors.push_back(&anim);*/
}
diff --git a/apps/openmw/mwrender/actors.hpp b/apps/openmw/mwrender/actors.hpp
index 7179c08fb..d49c6e0f8 100644
--- a/apps/openmw/mwrender/actors.hpp
+++ b/apps/openmw/mwrender/actors.hpp
@@ -30,7 +30,7 @@ namespace MWRender{
public:
Actors(OEngine::Render::OgreRenderer& _rend, MWWorld::Environment& _env): mRend(_rend), mEnvironment(_env){}
- ~Actors(){}
+ ~Actors();
void setMwRoot(Ogre::SceneNode* root);
void insertBegin (const MWWorld::Ptr& ptr, bool enabled, bool static_);
void insertCreature (const MWWorld::Ptr& ptr);
diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp
index 7b0d7015c..f3a8f64d5 100644
--- a/apps/openmw/mwrender/animation.cpp
+++ b/apps/openmw/mwrender/animation.cpp
@@ -4,7 +4,30 @@
namespace MWRender{
std::map Animation::mUniqueIDs;
- Animation::~Animation(){
+ Animation::Animation(MWWorld::Environment& _env, OEngine::Render::OgreRenderer& _rend)
+ : insert(NULL)
+ , mRend(_rend)
+ , mEnvironment(_env)
+ , vecRotPos()
+ , shapeparts()
+ , time(0.0f)
+ , startTime(0.0f)
+ , stopTime(0.0f)
+ , animate(0)
+ , rindexI()
+ , tindexI()
+ , shapeNumber(0)
+ , shapeIndexI()
+ , shapes(NULL)
+ , entityparts()
+ , transformations(NULL)
+ , textmappings(NULL)
+ , base(NULL)
+ {
+ }
+
+ Animation::~Animation()
+ {
}
std::string Animation::getUniqueID(std::string mesh){
diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp
index d1e8071f0..7692c7128 100644
--- a/apps/openmw/mwrender/animation.hpp
+++ b/apps/openmw/mwrender/animation.hpp
@@ -60,14 +60,14 @@ class Animation{
std::string getUniqueID(std::string mesh);
public:
- Animation(MWWorld::Environment& _env, OEngine::Render::OgreRenderer& _rend): mRend(_rend), mEnvironment(_env), animate(0){};
- virtual void runAnimation(float timepassed) = 0;
- void startScript(std::string groupname, int mode, int loops);
- void stopScript();
-
-
- virtual ~Animation();
+ Animation(MWWorld::Environment& _env, OEngine::Render::OgreRenderer& _rend);
+ virtual void runAnimation(float timepassed) = 0;
+ void startScript(std::string groupname, int mode, int loops);
+ void stopScript();
+
+
+ virtual ~Animation();
};
}
-#endif
\ No newline at end of file
+#endif
diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp
new file mode 100644
index 000000000..ed218dc97
--- /dev/null
+++ b/apps/openmw/mwrender/localmap.cpp
@@ -0,0 +1,305 @@
+#include "localmap.hpp"
+#include "renderingmanager.hpp"
+
+#include "../mwworld/environment.hpp"
+#include "../mwgui/window_manager.hpp"
+
+#include
+#include
+
+using namespace MWRender;
+using namespace Ogre;
+
+LocalMap::LocalMap(OEngine::Render::OgreRenderer* rend, MWWorld::Environment* env)
+{
+ mRendering = rend;
+ mEnvironment = env;
+
+ mCellCamera = mRendering->getScene()->createCamera("CellCamera");
+ mCellCamera->setProjectionType(PT_ORTHOGRAPHIC);
+ // look down -y
+ const float sqrt0pt5 = 0.707106781;
+ mCellCamera->setOrientation(Quaternion(sqrt0pt5, -sqrt0pt5, 0, 0));
+}
+
+LocalMap::~LocalMap()
+{
+ deleteBuffers();
+}
+
+void LocalMap::deleteBuffers()
+{
+ mBuffers.clear();
+}
+
+void LocalMap::saveTexture(const std::string& texname, const std::string& filename)
+{
+ TexturePtr tex = TextureManager::getSingleton().getByName(texname);
+ if (tex.isNull()) return;
+ HardwarePixelBufferSharedPtr readbuffer = tex->getBuffer();
+ readbuffer->lock(HardwareBuffer::HBL_NORMAL );
+ const PixelBox &readrefpb = readbuffer->getCurrentLock();
+ uchar *readrefdata = static_cast(readrefpb.data);
+
+ Image img;
+ img = img.loadDynamicImage (readrefdata, tex->getWidth(),
+ tex->getHeight(), tex->getFormat());
+ img.save("./" + filename);
+
+ readbuffer->unlock();
+}
+
+std::string LocalMap::coordStr(const int x, const int y)
+{
+ return StringConverter::toString(x) + "_" + StringConverter::toString(y);
+}
+
+void LocalMap::saveFogOfWar(MWWorld::Ptr::CellStore* cell)
+{
+ if (!mInterior)
+ {
+ /*saveTexture("Cell_"+coordStr(mCellX, mCellY)+"_fog",
+ "Cell_"+coordStr(mCellX, mCellY)+"_fog.png");*/
+ }
+ else
+ {
+ Vector2 min(mBounds.getMinimum().x, mBounds.getMinimum().z);
+ Vector2 max(mBounds.getMaximum().x, mBounds.getMaximum().z);
+ /// \todo why is this workaround needed?
+ min *= 1.3;
+ max *= 1.3;
+ Vector2 length = max-min;
+
+ // divide into segments
+ const int segsX = std::ceil( length.x / sSize );
+ const int segsY = std::ceil( length.y / sSize );
+
+ for (int x=0; xcell->data.gridX, cell->cell->data.gridY);
+
+ int x = cell->cell->data.gridX;
+ int y = cell->cell->data.gridY;
+
+ render((x+0.5)*sSize, (-y-0.5)*sSize, -10000, 10000, sSize, sSize, name);
+}
+
+void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell,
+ AxisAlignedBox bounds)
+{
+ mInterior = true;
+ mBounds = bounds;
+
+ Vector2 z(bounds.getMaximum().y, bounds.getMinimum().y);
+ Vector2 min(bounds.getMinimum().x, bounds.getMinimum().z);
+ Vector2 max(bounds.getMaximum().x, bounds.getMaximum().z);
+
+ /// \todo why is this workaround needed?
+ min *= 1.3;
+ max *= 1.3;
+
+ Vector2 length = max-min;
+ Vector2 center(bounds.getCenter().x, bounds.getCenter().z);
+
+ // divide into segments
+ const int segsX = std::ceil( length.x / sSize );
+ const int segsY = std::ceil( length.y / sSize );
+
+ mInteriorName = cell->cell->name;
+
+ for (int x=0; xcell->name + "_" + coordStr(x,y));
+ }
+ }
+}
+
+void LocalMap::render(const float x, const float y,
+ const float zlow, const float zhigh,
+ const float xw, const float yw, const std::string& texture)
+{
+ // disable fog
+ // changing FOG_MODE is not a solution when using shaders, thus we have to push linear start/end
+ const float fStart = mRendering->getScene()->getFogStart();
+ const float fEnd = mRendering->getScene()->getFogEnd();
+ const ColourValue& clr = mRendering->getScene()->getFogColour();
+ mRendering->getScene()->setFog(FOG_LINEAR, clr, 0, 1000000, 10000000);
+
+ // make everything visible
+ mRendering->getScene()->setAmbientLight(ColourValue(1,1,1));
+
+ mCellCamera->setPosition(Vector3(x, zhigh+100000, y));
+ //mCellCamera->setFarClipDistance( (zhigh-zlow) * 1.1 );
+ mCellCamera->setFarClipDistance(0); // infinite
+
+ mCellCamera->setOrthoWindow(xw, yw);
+
+ TexturePtr tex;
+ // try loading from memory
+ tex = TextureManager::getSingleton().getByName(texture);
+ if (tex.isNull())
+ {
+ // try loading from disk
+ //if (boost::filesystem::exists(texture+".jpg"))
+ //{
+ /// \todo
+ //}
+ //else
+ {
+ // render
+ tex = TextureManager::getSingleton().createManual(
+ texture,
+ ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
+ TEX_TYPE_2D,
+ xw*sMapResolution/sSize, yw*sMapResolution/sSize,
+ 0,
+ PF_R8G8B8,
+ TU_RENDERTARGET);
+
+ RenderTarget* rtt = tex->getBuffer()->getRenderTarget();
+ rtt->setAutoUpdated(false);
+ Viewport* vp = rtt->addViewport(mCellCamera);
+ vp->setOverlaysEnabled(false);
+ vp->setShadowsEnabled(false);
+ vp->setBackgroundColour(ColourValue(0, 0, 0));
+ //vp->setVisibilityMask( ... );
+
+ rtt->update();
+
+ // create "fog of war" texture
+ TexturePtr tex2 = TextureManager::getSingleton().createManual(
+ texture + "_fog",
+ ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
+ TEX_TYPE_2D,
+ xw*sFogOfWarResolution/sSize, yw*sFogOfWarResolution/sSize,
+ 0,
+ PF_A8R8G8B8,
+ TU_DYNAMIC_WRITE_ONLY_DISCARDABLE);
+
+ // create a buffer to use for dynamic operations
+ std::vector buffer;
+ buffer.resize(sFogOfWarResolution*sFogOfWarResolution);
+
+ // initialize to (0, 0, 0, 1)
+ for (int p=0; pgetBuffer()->lock(HardwareBuffer::HBL_DISCARD), &buffer[0], sFogOfWarResolution*sFogOfWarResolution*4);
+ tex2->getBuffer()->unlock();
+
+ mBuffers[texture] = buffer;
+
+ // save to cache for next time
+ //rtt->writeContentsToFile("./" + texture + ".jpg");
+ }
+ }
+
+
+ // re-enable fog
+ mRendering->getScene()->setFog(FOG_LINEAR, clr, 0, fStart, fEnd);
+}
+
+void LocalMap::updatePlayer (const Ogre::Vector3& position, const Ogre::Vector3& direction)
+{
+ if (sFogOfWarSkip != 0)
+ {
+ static int count=0;
+ if (++count % sFogOfWarSkip != 0)
+ return;
+ }
+
+ // retrieve the x,y grid coordinates the player is in
+ int x,y;
+ Vector2 pos(position.x, position.z);
+ if (!mInterior)
+ {
+ x = std::ceil(pos.x / sSize)-1;
+ y = std::ceil(-pos.y / sSize)-1;
+ mCellX = x;
+ mCellY = y;
+ }
+ else
+ {
+ Vector2 min(mBounds.getMinimum().x, mBounds.getMinimum().z);
+ min *= 1.3;
+
+ x = std::ceil((pos.x - min.x)/sSize)-1;
+ y = std::ceil((pos.y - min.y)/sSize)-1;
+
+ mEnvironment->mWindowManager->setInteriorMapTexture(x,y);
+ }
+
+ // convert from world coordinates to texture UV coordinates
+ float u,v;
+ std::string texName;
+ if (!mInterior)
+ {
+ u = std::abs((pos.x - (sSize*x))/sSize);
+ v = 1-std::abs((pos.y + (sSize*y))/sSize);
+ texName = "Cell_"+coordStr(x,y);
+
+ }
+ else
+ {
+ Vector2 min(mBounds.getMinimum().x, mBounds.getMinimum().z);
+ min *= 1.3;
+
+ u = (pos.x - min.x - sSize*x)/sSize;
+ v = (pos.y - min.y - sSize*y)/sSize;
+
+ texName = mInteriorName + "_" + coordStr(x,y);
+ }
+ mEnvironment->mWindowManager->setPlayerPos(u, v);
+ mEnvironment->mWindowManager->setPlayerDir(direction.x, -direction.z);
+
+ // explore radius (squared)
+ const float sqrExploreRadius = 0.01 * sFogOfWarResolution*sFogOfWarResolution;
+
+ // get the appropriate fog of war texture
+ TexturePtr tex = TextureManager::getSingleton().getByName(texName+"_fog");
+ if (!tex.isNull())
+ {
+ // get its buffer
+ if (mBuffers.find(texName) == mBuffers.end()) return;
+ int i=0;
+ for (int texV = 0; texV> 24);
+ alpha = std::min( alpha, (uint8) (std::max(0.f, std::min(1.f, (sqrDist/sqrExploreRadius)))*255) );
+ mBuffers[texName][i] = (uint32) (alpha << 24);
+
+ ++i;
+ }
+ }
+
+ // copy to the texture
+ memcpy(tex->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), &mBuffers[texName][0], sFogOfWarResolution*sFogOfWarResolution*4);
+ tex->getBuffer()->unlock();
+ }
+}
diff --git a/apps/openmw/mwrender/localmap.hpp b/apps/openmw/mwrender/localmap.hpp
new file mode 100644
index 000000000..efbccf884
--- /dev/null
+++ b/apps/openmw/mwrender/localmap.hpp
@@ -0,0 +1,100 @@
+#ifndef _GAME_RENDER_LOCALMAP_H
+#define _GAME_RENDER_LOCALMAP_H
+
+#include "../mwworld/ptr.hpp"
+
+#include
+
+namespace MWWorld
+{
+ class Environment;
+}
+
+namespace MWRender
+{
+ ///
+ /// \brief Local map rendering
+ ///
+ class LocalMap
+ {
+ public:
+ LocalMap(OEngine::Render::OgreRenderer*, MWWorld::Environment* env);
+ ~LocalMap();
+
+ /**
+ * Request the local map for an exterior cell.
+ * @remarks It will either be loaded from a disk cache,
+ * or rendered if it is not already cached.
+ * @param exterior cell
+ */
+ void requestMap (MWWorld::Ptr::CellStore* cell);
+
+ /**
+ * Request the local map for an interior cell.
+ * @remarks It will either be loaded from a disk cache,
+ * or rendered if it is not already cached.
+ * @param interior cell
+ * @param bounding box of the cell
+ */
+ void requestMap (MWWorld::Ptr::CellStore* cell,
+ Ogre::AxisAlignedBox bounds);
+
+ /**
+ * Set the position & direction of the player.
+ * @remarks This is used to draw a "fog of war" effect
+ * to hide areas on the map the player has not discovered yet.
+ * @param position (OGRE coordinates)
+ * @param view direction (OGRE coordinates)
+ */
+ void updatePlayer (const Ogre::Vector3& position, const Ogre::Vector3& direction);
+
+ /**
+ * Save the fog of war for the current cell to disk.
+ * @remarks This should be called before loading a
+ * new cell, as well as when the game is quit.
+ * @param current cell
+ */
+ void saveFogOfWar(MWWorld::Ptr::CellStore* cell);
+
+ private:
+ OEngine::Render::OgreRenderer* mRendering;
+ MWWorld::Environment* mEnvironment;
+
+ // 1024*1024 pixels for a cell
+ static const int sMapResolution = 1024;
+
+ // the dynamic texture is a bottleneck, so don't set this too high
+ static const int sFogOfWarResolution = 32;
+
+ // frames to skip before rendering fog of war
+ static const int sFogOfWarSkip = 2;
+
+ // size of a map segment (for exteriors, 1 cell)
+ static const int sSize = 8192;
+
+ Ogre::Camera* mCellCamera;
+
+ void render(const float x, const float y,
+ const float zlow, const float zhigh,
+ const float xw, const float yw,
+ const std::string& texture);
+
+ void saveTexture(const std::string& texname, const std::string& filename);
+
+ std::string coordStr(const int x, const int y);
+
+ // a buffer for the "fog of war" texture of the current cell.
+ // interior cells could be divided into multiple textures,
+ // so we store in a map.
+ std::map > mBuffers;
+
+ void deleteBuffers();
+
+ bool mInterior;
+ int mCellX, mCellY;
+ Ogre::AxisAlignedBox mBounds;
+ std::string mInteriorName;
+ };
+
+}
+#endif
diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp
index 717064ada..e4e721227 100644
--- a/apps/openmw/mwrender/objects.cpp
+++ b/apps/openmw/mwrender/objects.cpp
@@ -109,6 +109,9 @@ void Objects::insertMesh (const MWWorld::Ptr& ptr, const std::string& mesh)
// If it is set too low:
// - there will be too many batches.
sg->setRegionDimensions(Ogre::Vector3(2500,2500,2500));
+
+ mBounds[ptr.getCell()] = Ogre::AxisAlignedBox::BOX_NULL;
+ mBounds[ptr.getCell()].merge(ent->getBoundingBox());
}
else
{
@@ -116,6 +119,7 @@ void Objects::insertMesh (const MWWorld::Ptr& ptr, const std::string& mesh)
}
sg->addEntity(ent,insert->_getDerivedPosition(),insert->_getDerivedOrientation(),insert->_getDerivedScale());
+ mBounds[ptr.getCell()].merge(insert->_getDerivedPosition());
mRenderer.getScene()->destroyEntity(ent);
}
@@ -202,6 +206,9 @@ void Objects::removeCell(MWWorld::Ptr::CellStore* store)
mRenderer.getScene()->destroyStaticGeometry (sg);
sg = 0;
}
+
+ if(mBounds.find(store) != mBounds.end())
+ mBounds.erase(store);
}
void Objects::buildStaticGeometry(ESMS::CellStore& cell)
@@ -212,3 +219,8 @@ void Objects::buildStaticGeometry(ESMS::CellStore& cell)
sg->build();
}
}
+
+Ogre::AxisAlignedBox Objects::getDimensions(MWWorld::Ptr::CellStore* cell)
+{
+ return mBounds[cell];
+}
diff --git a/apps/openmw/mwrender/objects.hpp b/apps/openmw/mwrender/objects.hpp
index d58455b9f..1ca81331d 100644
--- a/apps/openmw/mwrender/objects.hpp
+++ b/apps/openmw/mwrender/objects.hpp
@@ -14,6 +14,7 @@ class Objects{
OEngine::Render::OgreRenderer &mRenderer;
std::map mCellSceneNodes;
std::map mStaticGeometry;
+ std::map mBounds;
Ogre::SceneNode* mMwRoot;
bool mIsStatic;
static int uniqueID;
@@ -42,6 +43,9 @@ public:
void insertMesh (const MWWorld::Ptr& ptr, const std::string& mesh);
void insertLight (const MWWorld::Ptr& ptr, float r, float g, float b, float radius);
+ Ogre::AxisAlignedBox getDimensions(MWWorld::Ptr::CellStore*);
+ ///< get a bounding box that encloses all objects in the specified cell
+
bool deleteObject (const MWWorld::Ptr& ptr);
///< \return found?
diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp
index b4a6625dc..65665350f 100644
--- a/apps/openmw/mwrender/renderingmanager.cpp
+++ b/apps/openmw/mwrender/renderingmanager.cpp
@@ -59,6 +59,8 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const
mPlayer = new MWRender::Player (mRendering.getCamera(), playerNode);
mSun = 0;
+
+ mLocalMap = new MWRender::LocalMap(&mRendering, &environment);
}
RenderingManager::~RenderingManager ()
@@ -66,6 +68,7 @@ RenderingManager::~RenderingManager ()
//TODO: destroy mSun?
delete mPlayer;
delete mSkyManager;
+ delete mLocalMap;
}
MWRender::SkyManager* RenderingManager::getSkyManager()
@@ -149,6 +152,8 @@ void RenderingManager::update (float duration){
mSkyManager->update(duration);
mRendering.update(duration);
+
+ mLocalMap->updatePlayer( mRendering.getCamera()->getRealPosition(), mRendering.getCamera()->getRealDirection() );
}
void RenderingManager::waterAdded (MWWorld::Ptr::CellStore *store){
if(store->cell->data.flags & store->cell->HasWater){
@@ -357,4 +362,17 @@ void RenderingManager::setGlare(bool glare)
mSkyManager->setGlare(glare);
}
+void RenderingManager::requestMap(MWWorld::Ptr::CellStore* cell)
+{
+ if (!(cell->cell->data.flags & ESM::Cell::Interior))
+ mLocalMap->requestMap(cell);
+ else
+ mLocalMap->requestMap(cell, mObjects.getDimensions(cell));
+}
+
+void RenderingManager::preCellChange(MWWorld::Ptr::CellStore* cell)
+{
+ mLocalMap->saveFogOfWar(cell);
+}
+
} // namespace
diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp
index e06c64cfa..12d9cb3e9 100644
--- a/apps/openmw/mwrender/renderingmanager.hpp
+++ b/apps/openmw/mwrender/renderingmanager.hpp
@@ -25,6 +25,7 @@
#include "actors.hpp"
#include "player.hpp"
#include "water.hpp"
+#include "localmap.hpp"
namespace Ogre
{
@@ -79,9 +80,11 @@ class RenderingManager: private RenderingInterface {
void cellAdded (MWWorld::Ptr::CellStore *store);
void waterAdded(MWWorld::Ptr::CellStore *store);
-
void removeWater();
+ void preCellChange (MWWorld::Ptr::CellStore* store);
+ ///< this event is fired immediately before changing cell
+
void addObject (const MWWorld::Ptr& ptr);
void removeObject (const MWWorld::Ptr& ptr);
@@ -110,6 +113,9 @@ class RenderingManager: private RenderingInterface {
int skyGetSecundaPhase() const;
void skySetMoonColour (bool red);
void configureAmbient(ESMS::CellStore &mCell);
+
+ void requestMap (MWWorld::Ptr::CellStore* cell);
+ ///< request the local map for a cell
/// configure fog according to cell
void configureFog(ESMS::CellStore &mCell);
@@ -162,6 +168,8 @@ class RenderingManager: private RenderingInterface {
MWRender::Player *mPlayer;
MWRender::Debugging mDebugging;
+
+ MWRender::LocalMap* mLocalMap;
};
}
diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp
index a747b9be0..a41bc21e0 100644
--- a/apps/openmw/mwrender/sky.cpp
+++ b/apps/openmw/mwrender/sky.cpp
@@ -254,7 +254,7 @@ void SkyManager::ModVertexAlpha(Entity* ent, unsigned int meshType)
// Get a pointer to the vertex colour
ves_diffuse->baseVertexPointerToElement( pData, ¤tVertex );
- unsigned char alpha;
+ unsigned char alpha=0;
if (meshType == 0) alpha = i%2 ? 0 : 255; // this is a cylinder, so every second vertex belongs to the bottom-most row
else if (meshType == 1)
{
@@ -292,10 +292,40 @@ void SkyManager::ModVertexAlpha(Entity* ent, unsigned int meshType)
ent->getMesh()->getSubMesh(0)->vertexData->vertexBufferBinding->getBuffer(ves_diffuse->getSource())->unlock();
}
-SkyManager::SkyManager (SceneNode* pMwRoot, Camera* pCamera, MWWorld::Environment* env) :
- mGlareFade(0), mGlareEnabled(false)
+SkyManager::SkyManager (SceneNode* pMwRoot, Camera* pCamera, MWWorld::Environment* env)
+ : mEnvironment(env)
+ , mHour(0.0f)
+ , mDay(0)
+ , mMonth(0)
+ , mSun(NULL)
+ , mSunGlare(NULL)
+ , mMasser(NULL)
+ , mSecunda(NULL)
+ , mViewport(NULL)
+ , mRootNode(NULL)
+ , mSceneMgr(NULL)
+ , mAtmosphereDay(NULL)
+ , mAtmosphereNight(NULL)
+ , mCloudMaterial()
+ , mAtmosphereMaterial()
+ , mCloudFragmentShader()
+ , mClouds()
+ , mNextClouds()
+ , mCloudBlendFactor(0.0f)
+ , mCloudOpacity(0.0f)
+ , mCloudSpeed(0.0f)
+ , mStarsOpacity(0.0f)
+ , mThunderOverlay(NULL)
+ , mThunderTextureUnit(NULL)
+ , mRemainingTransitionTime(0.0f)
+ , mGlareFade(0.0f)
+ , mEnabled(true)
+ , mGlareEnabled(true)
+ , mSunEnabled(true)
+ , mMasserEnabled(true)
+ , mSecundaEnabled(true)
{
- mEnvironment = env;
+
mViewport = pCamera->getViewport();
mSceneMgr = pMwRoot->getCreator();
mRootNode = pCamera->getParentSceneNode()->createChildSceneNode();
@@ -446,6 +476,7 @@ SkyManager::SkyManager (SceneNode* pMwRoot, Camera* pCamera, MWWorld::Environmen
vshader->getDefaultParameters()->setNamedAutoConstant("worldViewProj", GpuProgramParameters::ACT_WORLDVIEWPROJ_MATRIX);
vshader->getDefaultParameters()->setNamedAutoConstant("emissive", GpuProgramParameters::ACT_SURFACE_EMISSIVE_COLOUR);
mAtmosphereMaterial->getTechnique(0)->getPass(0)->setVertexProgram(vshader->getName());
+ mAtmosphereMaterial->getTechnique(0)->getPass(0)->setFragmentProgram("");
// Clouds
NifOgre::NIFLoader::load("meshes\\sky_clouds_01.nif");
diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp
new file mode 100644
index 000000000..9298bf848
--- /dev/null
+++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp
@@ -0,0 +1,407 @@
+#ifdef OPENMW_USE_FFMPEG
+
+#include "ffmpeg_decoder.hpp"
+
+
+namespace MWSound
+{
+
+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;
+ return stream->read(buf, buf_size);
+}
+
+int FFmpeg_Decoder::writePacket(void *user_data, uint8_t *buf, int buf_size)
+{
+ Ogre::DataStreamPtr stream = static_cast(user_data)->mDataStream;
+ return stream->write(buf, buf_size);
+}
+
+int64_t FFmpeg_Decoder::seek(void *user_data, int64_t offset, int whence)
+{
+ Ogre::DataStreamPtr stream = static_cast(user_data)->mDataStream;
+
+ 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();
+}
+
+
+/* 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)
+{
+ PacketList *packet;
+
+ packet = (PacketList*)av_malloc(sizeof(*packet));
+ packet->next = NULL;
+
+next_packet:
+ while(av_read_frame(mFormatCtx, &packet->pkt) >= 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())
+ {
+ 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++;
+ }
+ /* Free the packet and look for another */
+ av_free_packet(&packet->pkt);
+ }
+ av_free(packet);
+
+ return false;
+}
+
+void FFmpeg_Decoder::MyStream::clearPackets()
+{
+ while(mPackets)
+ {
+ PacketList *self = mPackets;
+ mPackets = self->next;
+
+ av_free_packet(&self->pkt);
+ av_free(self);
+ }
+}
+
+void *FFmpeg_Decoder::MyStream::getAVAudioData(size_t *length)
+{
+ int size;
+ int len;
+
+ if(length) *length = 0;
+ if(mCodecCtx->codec_type != AVMEDIA_TYPE_AUDIO)
+ return NULL;
+
+ 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;
+
+ 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;
+}
+
+size_t FFmpeg_Decoder::MyStream::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(getAVAudioData(NULL) == NULL)
+ break;
+ }
+
+ 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;
+
+ /* 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;
+ }
+ }
+
+ /* Return the number of bytes we were able to get */
+ return dec;
+}
+
+
+
+void FFmpeg_Decoder::open(const std::string &fname)
+{
+ close();
+ mDataStream = mResourceMgr.openResource(fname);
+
+ if((mFormatCtx=avformat_alloc_context()) == NULL)
+ fail("Failed to allocate context");
+
+ mFormatCtx->pb = avio_alloc_context(NULL, 0, 0, this, readPacket, writePacket, seek);
+ if(!mFormatCtx->pb || avformat_open_input(&mFormatCtx, fname.c_str(), NULL, NULL) != 0)
+ {
+ avformat_free_context(mFormatCtx);
+ mFormatCtx = NULL;
+ fail("Failed to allocate input stream");
+ }
+
+ try
+ {
+ if(avformat_find_stream_info(mFormatCtx, NULL) < 0)
+ fail("Failed to find stream info in "+fname);
+
+ for(size_t j = 0;j < mFormatCtx->nb_streams;j++)
+ {
+ 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());
+ break;
+ }
+ }
+ if(mStreams.empty())
+ fail("No audio streams in "+fname);
+ }
+ catch(std::exception &e)
+ {
+ av_close_input_file(mFormatCtx);
+ mFormatCtx = NULL;
+ throw;
+ }
+}
+
+void FFmpeg_Decoder::close()
+{
+ while(!mStreams.empty())
+ {
+ MyStream *stream = mStreams.front();
+
+ stream->clearPackets();
+ avcodec_close(stream->mCodecCtx);
+ av_free(stream->mDecodedData);
+ delete stream;
+
+ mStreams.erase(mStreams.begin());
+ }
+ if(mFormatCtx)
+ av_close_input_file(mFormatCtx);
+ mFormatCtx = NULL;
+
+ mDataStream.setNull();
+}
+
+void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type)
+{
+ if(mStreams.empty())
+ fail("No audio stream info");
+
+ MyStream *stream = mStreams[0];
+ if(stream->mCodecCtx->sample_fmt == AV_SAMPLE_FMT_U8)
+ *type = SampleType_UInt8;
+ else if(stream->mCodecCtx->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));
+
+ if(stream->mCodecCtx->channel_layout == AV_CH_LAYOUT_MONO)
+ *chans = ChannelConfig_Mono;
+ else if(stream->mCodecCtx->channel_layout == AV_CH_LAYOUT_STEREO)
+ *chans = ChannelConfig_Stereo;
+ else if(stream->mCodecCtx->channel_layout == 0)
+ {
+ /* Unknown channel layout. Try to guess. */
+ if(stream->mCodecCtx->channels == 1)
+ *chans = ChannelConfig_Mono;
+ else if(stream->mCodecCtx->channels == 2)
+ *chans = ChannelConfig_Stereo;
+ else
+ {
+ std::stringstream sstr("Unsupported raw channel count: ");
+ sstr << stream->mCodecCtx->channels;
+ fail(sstr.str());
+ }
+ }
+ else
+ {
+ char str[1024];
+ av_get_channel_layout_string(str, sizeof(str), stream->mCodecCtx->channels,
+ stream->mCodecCtx->channel_layout);
+ fail(std::string("Unsupported channel layout: ")+str);
+ }
+
+ *samplerate = stream->mCodecCtx->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);
+}
+
+void FFmpeg_Decoder::readAll(std::vector &output)
+{
+ if(mStreams.empty())
+ fail("No audio streams");
+ MyStream *stream = mStreams.front();
+ char *inbuf;
+ size_t got;
+
+ while((inbuf=(char*)stream->getAVAudioData(&got)) != NULL && got > 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));
+}
+
+FFmpeg_Decoder::FFmpeg_Decoder() : mFormatCtx(NULL)
+{
+ static bool done_init = false;
+
+ /* We need to make sure ffmpeg is initialized. Optionally silence warning
+ * output from the lib */
+ if(!done_init)
+ {
+ av_register_all();
+ av_log_set_level(AV_LOG_ERROR);
+ done_init = true;
+ }
+}
+
+FFmpeg_Decoder::~FFmpeg_Decoder()
+{
+ close();
+}
+
+}
+
+#endif
diff --git a/apps/openmw/mwsound/ffmpeg_decoder.hpp b/apps/openmw/mwsound/ffmpeg_decoder.hpp
new file mode 100644
index 000000000..4344397c7
--- /dev/null
+++ b/apps/openmw/mwsound/ffmpeg_decoder.hpp
@@ -0,0 +1,59 @@
+#ifndef GAME_SOUND_FFMPEG_DECODER_H
+#define GAME_SOUND_FFMPEG_DECODER_H
+
+#include
+
+// FIXME: This can't be right? The headers refuse to build without UINT64_C,
+// which only gets defined in stdint.h in either C99 mode or with this macro
+// defined...
+#define __STDC_CONSTANT_MACROS
+#include
+extern "C"
+{
+#include
+#include
+}
+
+#include "sound_decoder.hpp"
+
+
+namespace MWSound
+{
+ class FFmpeg_Decoder : public Sound_Decoder
+ {
+ AVFormatContext *mFormatCtx;
+
+ struct MyStream;
+ std::vector mStreams;
+
+ bool getNextPacket(int streamidx);
+
+ 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);
+
+ virtual void open(const std::string &fname);
+ virtual void close();
+
+ 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();
+
+ FFmpeg_Decoder& operator=(const FFmpeg_Decoder &rhs);
+ FFmpeg_Decoder(const FFmpeg_Decoder &rhs);
+
+ FFmpeg_Decoder();
+ public:
+ virtual ~FFmpeg_Decoder();
+
+ friend class SoundManager;
+ };
+#ifndef DEFAULT_DECODER
+#define DEFAULT_DECODER (::MWSound::FFmpeg_Decoder)
+#endif
+};
+
+#endif
diff --git a/apps/openmw/mwsound/mpgsnd_decoder.cpp b/apps/openmw/mwsound/mpgsnd_decoder.cpp
new file mode 100644
index 000000000..9b91b4e74
--- /dev/null
+++ b/apps/openmw/mwsound/mpgsnd_decoder.cpp
@@ -0,0 +1,237 @@
+#ifdef OPENMW_USE_MPG123
+
+#include
+#include
+
+#include "mpgsnd_decoder.hpp"
+
+
+static void fail(const std::string &msg)
+{ throw std::runtime_error("MpgSnd exception: "+msg); }
+
+namespace MWSound
+{
+
+//
+// libSndFile io callbacks
+//
+sf_count_t MpgSnd_Decoder::ogresf_get_filelen(void *user_data)
+{
+ Ogre::DataStreamPtr stream = static_cast(user_data)->mDataStream;
+ return stream->size();
+}
+
+sf_count_t MpgSnd_Decoder::ogresf_seek(sf_count_t offset, int whence, void *user_data)
+{
+ Ogre::DataStreamPtr stream = static_cast(user_data)->mDataStream;
+
+ if(whence == SEEK_CUR)
+ stream->seek(stream->tell()+offset);
+ else if(whence == SEEK_SET)
+ stream->seek(offset);
+ else if(whence == SEEK_END)
+ stream->seek(stream->size()+offset);
+ else
+ return -1;
+
+ return stream->tell();
+}
+
+sf_count_t MpgSnd_Decoder::ogresf_read(void *ptr, sf_count_t count, void *user_data)
+{
+ Ogre::DataStreamPtr stream = static_cast(user_data)->mDataStream;
+ return stream->read(ptr, count);
+}
+
+sf_count_t MpgSnd_Decoder::ogresf_write(const void*, sf_count_t, void*)
+{ return -1; }
+
+sf_count_t MpgSnd_Decoder::ogresf_tell(void *user_data)
+{
+ Ogre::DataStreamPtr stream = static_cast(user_data)->mDataStream;
+ return stream->tell();
+}
+
+//
+// libmpg13 io callbacks
+//
+ssize_t MpgSnd_Decoder::ogrempg_read(void *user_data, void *ptr, size_t count)
+{
+ Ogre::DataStreamPtr stream = static_cast(user_data)->mDataStream;
+ return stream->read(ptr, count);
+}
+
+off_t MpgSnd_Decoder::ogrempg_lseek(void *user_data, off_t offset, int whence)
+{
+ Ogre::DataStreamPtr stream = static_cast(user_data)->mDataStream;
+
+ if(whence == SEEK_CUR)
+ stream->seek(stream->tell()+offset);
+ else if(whence == SEEK_SET)
+ stream->seek(offset);
+ else if(whence == SEEK_END)
+ stream->seek(stream->size()+offset);
+ else
+ return -1;
+
+ return stream->tell();
+}
+
+
+void MpgSnd_Decoder::open(const std::string &fname)
+{
+ close();
+ mDataStream = mResourceMgr.openResource(fname);
+
+ SF_VIRTUAL_IO streamIO = {
+ ogresf_get_filelen, ogresf_seek,
+ ogresf_read, ogresf_write, ogresf_tell
+ };
+ mSndFile = sf_open_virtual(&streamIO, SFM_READ, &mSndInfo, this);
+ if(mSndFile)
+ {
+ if(mSndInfo.channels == 1)
+ mChanConfig = ChannelConfig_Mono;
+ else if(mSndInfo.channels == 2)
+ mChanConfig = ChannelConfig_Stereo;
+ else
+ {
+ sf_close(mSndFile);
+ mSndFile = NULL;
+ fail("Unsupported channel count in "+fname);
+ }
+ mSampleRate = mSndInfo.samplerate;
+ return;
+ }
+ mDataStream->seek(0);
+
+ mMpgFile = mpg123_new(NULL, NULL);
+ if(mMpgFile && mpg123_replace_reader_handle(mMpgFile, ogrempg_read, ogrempg_lseek, NULL) == MPG123_OK &&
+ mpg123_open_handle(mMpgFile, this) == MPG123_OK)
+ {
+ try
+ {
+ int encoding, channels;
+ long rate;
+ if(mpg123_getformat(mMpgFile, &rate, &channels, &encoding) != MPG123_OK)
+ fail("Failed to get audio format");
+ if(encoding != MPG123_ENC_SIGNED_16)
+ fail("Unsupported encoding in "+fname);
+ if(channels != 1 && channels != 2)
+ fail("Unsupported channel count in "+fname);
+ mChanConfig = ((channels==2)?ChannelConfig_Stereo:ChannelConfig_Mono);
+ mSampleRate = rate;
+ return;
+ }
+ catch(std::exception &e)
+ {
+ mpg123_close(mMpgFile);
+ mpg123_delete(mMpgFile);
+ mMpgFile = NULL;
+ throw;
+ }
+ mpg123_close(mMpgFile);
+ }
+ if(mMpgFile)
+ mpg123_delete(mMpgFile);
+ mMpgFile = NULL;
+
+ fail("Unsupported file type: "+fname);
+}
+
+void MpgSnd_Decoder::close()
+{
+ if(mSndFile)
+ sf_close(mSndFile);
+ mSndFile = NULL;
+
+ if(mMpgFile)
+ {
+ mpg123_close(mMpgFile);
+ mpg123_delete(mMpgFile);
+ mMpgFile = NULL;
+ }
+
+ mDataStream.setNull();
+}
+
+void MpgSnd_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type)
+{
+ if(!mSndFile && !mMpgFile)
+ fail("No open file");
+
+ *samplerate = mSampleRate;
+ *chans = mChanConfig;
+ *type = SampleType_Int16;
+}
+
+size_t MpgSnd_Decoder::read(char *buffer, size_t bytes)
+{
+ size_t got = 0;
+
+ if(mSndFile)
+ {
+ got = sf_read_short(mSndFile, (short*)buffer, bytes/2)*2;
+ }
+ else if(mMpgFile)
+ {
+ int err;
+ err = mpg123_read(mMpgFile, (unsigned char*)buffer, bytes, &got);
+ if(err != MPG123_OK && err != MPG123_DONE)
+ fail("Failed to read from file");
+ }
+ return got;
+}
+
+void MpgSnd_Decoder::readAll(std::vector &output)
+{
+ if(mSndFile && mSndInfo.frames > 0)
+ {
+ size_t pos = output.size();
+ output.resize(pos + mSndInfo.frames*mSndInfo.channels*2);
+ sf_readf_short(mSndFile, (short*)(output.data()+pos), mSndInfo.frames);
+ return;
+ }
+ // Fallback in case we don't know the total already
+ Sound_Decoder::readAll(output);
+}
+
+void MpgSnd_Decoder::rewind()
+{
+ if(!mSndFile && !mMpgFile)
+ fail("No open file");
+
+ if(mSndFile)
+ {
+ if(sf_seek(mSndFile, 0, SEEK_SET) == -1)
+ fail("seek failed");
+ }
+ else if(mMpgFile)
+ {
+ if(mpg123_seek(mMpgFile, 0, SEEK_SET) < 0)
+ fail("seek failed");
+ }
+}
+
+MpgSnd_Decoder::MpgSnd_Decoder()
+ : mSndInfo()
+ , mSndFile(NULL)
+ , mMpgFile(NULL)
+ , mDataStream()
+ , mChanConfig(ChannelConfig_Stereo)
+ , mSampleRate(0)
+{
+ static bool initdone = false;
+ if(!initdone)
+ mpg123_init();
+ initdone = true;
+}
+
+MpgSnd_Decoder::~MpgSnd_Decoder()
+{
+ close();
+}
+
+}
+
+#endif
diff --git a/apps/openmw/mwsound/mpgsnd_decoder.hpp b/apps/openmw/mwsound/mpgsnd_decoder.hpp
new file mode 100644
index 000000000..870773edc
--- /dev/null
+++ b/apps/openmw/mwsound/mpgsnd_decoder.hpp
@@ -0,0 +1,57 @@
+#ifndef GAME_SOUND_MPGSND_DECODER_H
+#define GAME_SOUND_MPGSND_DECODER_H
+
+#include
+
+#include
+
+#include "mpg123.h"
+#include "sndfile.h"
+
+#include "sound_decoder.hpp"
+
+
+namespace MWSound
+{
+ class MpgSnd_Decoder : public Sound_Decoder
+ {
+ SF_INFO mSndInfo;
+ SNDFILE *mSndFile;
+ mpg123_handle *mMpgFile;
+
+ Ogre::DataStreamPtr mDataStream;
+ static sf_count_t ogresf_get_filelen(void *user_data);
+ static sf_count_t ogresf_seek(sf_count_t offset, int whence, void *user_data);
+ static sf_count_t ogresf_read(void *ptr, sf_count_t count, void *user_data);
+ static sf_count_t ogresf_write(const void*, sf_count_t, void*);
+ static sf_count_t ogresf_tell(void *user_data);
+ static ssize_t ogrempg_read(void*, void*, size_t);
+ static off_t ogrempg_lseek(void*, off_t, int);
+
+ ChannelConfig mChanConfig;
+ int mSampleRate;
+
+ virtual void open(const std::string &fname);
+ virtual void close();
+
+ 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();
+
+ MpgSnd_Decoder& operator=(const MpgSnd_Decoder &rhs);
+ MpgSnd_Decoder(const MpgSnd_Decoder &rhs);
+
+ MpgSnd_Decoder();
+ public:
+ virtual ~MpgSnd_Decoder();
+
+ friend class SoundManager;
+ };
+#ifndef DEFAULT_DECODER
+#define DEFAULT_DECODER (::MWSound::MpgSnd_Decoder)
+#endif
+};
+
+#endif
diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp
new file mode 100644
index 000000000..5122b3a5a
--- /dev/null
+++ b/apps/openmw/mwsound/openal_output.cpp
@@ -0,0 +1,793 @@
+#include
+#include
+#include
+#include
+
+#include
+
+#include "openal_output.hpp"
+#include "sound_decoder.hpp"
+#include "sound.hpp"
+#include "soundmanager.hpp"
+
+#ifndef ALC_ALL_DEVICES_SPECIFIER
+#define ALC_ALL_DEVICES_SPECIFIER 0x1013
+#endif
+
+
+namespace MWSound
+{
+
+static void fail(const std::string &msg)
+{ throw std::runtime_error("OpenAL exception: " + msg); }
+
+static void throwALCerror(ALCdevice *device)
+{
+ ALCenum err = alcGetError(device);
+ if(err != ALC_NO_ERROR)
+ fail(alcGetString(device, err));
+}
+
+static void throwALerror()
+{
+ ALenum err = alGetError();
+ if(err != AL_NO_ERROR)
+ fail(alGetString(err));
+}
+
+
+static ALenum getALFormat(ChannelConfig chans, SampleType type)
+{
+ static const struct {
+ ALenum format;
+ ChannelConfig chans;
+ SampleType type;
+ } fmtlist[] = {
+ { AL_FORMAT_MONO16, ChannelConfig_Mono, SampleType_Int16 },
+ { AL_FORMAT_MONO8, ChannelConfig_Mono, SampleType_UInt8 },
+ { AL_FORMAT_STEREO16, ChannelConfig_Stereo, SampleType_Int16 },
+ { AL_FORMAT_STEREO8, ChannelConfig_Stereo, SampleType_UInt8 },
+ };
+ static const size_t fmtlistsize = sizeof(fmtlist)/sizeof(fmtlist[0]);
+
+ for(size_t i = 0;i < fmtlistsize;i++)
+ {
+ if(fmtlist[i].chans == chans && fmtlist[i].type == type)
+ return fmtlist[i].format;
+ }
+ fail(std::string("Unsupported sound format (")+getChannelConfigName(chans)+", "+getSampleTypeName(type)+")");
+ return AL_NONE;
+}
+
+//
+// A streaming OpenAL sound.
+//
+class OpenAL_SoundStream : public Sound
+{
+ static const ALuint sNumBuffers = 6;
+ static const ALfloat sBufferLength;
+
+ OpenAL_Output &mOutput;
+
+ ALuint mSource;
+ ALuint mBuffers[sNumBuffers];
+
+ ALenum mFormat;
+ ALsizei mSampleRate;
+ ALuint mBufferSize;
+
+ DecoderPtr mDecoder;
+
+ volatile bool mIsFinished;
+
+ OpenAL_SoundStream(const OpenAL_SoundStream &rhs);
+ OpenAL_SoundStream& operator=(const OpenAL_SoundStream &rhs);
+
+public:
+ OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder);
+ virtual ~OpenAL_SoundStream();
+
+ virtual void stop();
+ virtual bool isPlaying();
+ virtual void setVolume(float volume);
+ virtual void update(const float *pos);
+
+ void play();
+ bool process();
+};
+
+const ALfloat OpenAL_SoundStream::sBufferLength = 0.125f;
+
+//
+// A background streaming thread (keeps active streams processed)
+//
+struct OpenAL_Output::StreamThread {
+ typedef std::vector StreamVec;
+ StreamVec mStreams;
+ boost::mutex mMutex;
+ boost::thread mThread;
+
+ StreamThread()
+ : mThread(boost::ref(*this))
+ {
+ }
+ ~StreamThread()
+ {
+ mThread.interrupt();
+ }
+
+ // boost::thread entry point
+ void operator()()
+ {
+ while(1)
+ {
+ mMutex.lock();
+ StreamVec::iterator iter = mStreams.begin();
+ while(iter != mStreams.end())
+ {
+ if((*iter)->process() == false)
+ iter = mStreams.erase(iter);
+ else
+ iter++;
+ }
+ mMutex.unlock();
+ boost::this_thread::sleep(boost::posix_time::milliseconds(50));
+ }
+ }
+
+ void add(OpenAL_SoundStream *stream)
+ {
+ mMutex.lock();
+ if(std::find(mStreams.begin(), mStreams.end(), stream) == mStreams.end())
+ mStreams.push_back(stream);
+ mMutex.unlock();
+ }
+
+ void remove(OpenAL_SoundStream *stream)
+ {
+ mMutex.lock();
+ StreamVec::iterator iter = std::find(mStreams.begin(), mStreams.end(), stream);
+ if(iter != mStreams.end())
+ mStreams.erase(iter);
+ mMutex.unlock();
+ }
+
+ void removeAll()
+ {
+ mMutex.lock();
+ mStreams.clear();
+ mMutex.unlock();
+ }
+
+private:
+ StreamThread(const StreamThread &rhs);
+ StreamThread& operator=(const StreamThread &rhs);
+};
+
+
+OpenAL_SoundStream::OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder)
+ : mOutput(output), mSource(src), mDecoder(decoder), mIsFinished(true)
+{
+ throwALerror();
+
+ alGenBuffers(sNumBuffers, mBuffers);
+ throwALerror();
+ try
+ {
+ int srate;
+ ChannelConfig chans;
+ SampleType type;
+
+ mDecoder->getInfo(&srate, &chans, &type);
+ mFormat = getALFormat(chans, type);
+ mSampleRate = srate;
+
+ mBufferSize = static_cast(sBufferLength*srate);
+ mBufferSize = framesToBytes(mBufferSize, chans, type);
+ }
+ catch(std::exception &e)
+ {
+ mOutput.mFreeSources.push_back(mSource);
+ alDeleteBuffers(sNumBuffers, mBuffers);
+ alGetError();
+ throw;
+ }
+}
+OpenAL_SoundStream::~OpenAL_SoundStream()
+{
+ mOutput.mStreamThread->remove(this);
+
+ alSourceStop(mSource);
+ alSourcei(mSource, AL_BUFFER, 0);
+
+ mOutput.mFreeSources.push_back(mSource);
+ alDeleteBuffers(sNumBuffers, mBuffers);
+ alGetError();
+
+ mDecoder->close();
+}
+
+void OpenAL_SoundStream::play()
+{
+ std::vector data(mBufferSize);
+
+ alSourceStop(mSource);
+ alSourcei(mSource, AL_BUFFER, 0);
+ throwALerror();
+
+ for(ALuint i = 0;i < sNumBuffers;i++)
+ {
+ size_t got;
+ got = mDecoder->read(data.data(), data.size());
+ alBufferData(mBuffers[i], mFormat, data.data(), got, mSampleRate);
+ }
+ throwALerror();
+
+ alSourceQueueBuffers(mSource, sNumBuffers, mBuffers);
+ alSourcePlay(mSource);
+ throwALerror();
+
+ mIsFinished = false;
+ mOutput.mStreamThread->add(this);
+}
+
+void OpenAL_SoundStream::stop()
+{
+ mOutput.mStreamThread->remove(this);
+ mIsFinished = true;
+
+ alSourceStop(mSource);
+ alSourcei(mSource, AL_BUFFER, 0);
+ throwALerror();
+
+ mDecoder->rewind();
+}
+
+bool OpenAL_SoundStream::isPlaying()
+{
+ ALint state;
+
+ alGetSourcei(mSource, AL_SOURCE_STATE, &state);
+ throwALerror();
+
+ if(state == AL_PLAYING)
+ return true;
+ return !mIsFinished;
+}
+
+void OpenAL_SoundStream::setVolume(float volume)
+{
+ alSourcef(mSource, AL_GAIN, volume*mBaseVolume);
+ throwALerror();
+ mVolume = volume;
+}
+
+void OpenAL_SoundStream::update(const float *pos)
+{
+ alSource3f(mSource, AL_POSITION, pos[0], pos[2], -pos[1]);
+ alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f);
+ alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f);
+ throwALerror();
+}
+
+bool OpenAL_SoundStream::process()
+{
+ 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.data(), data.size());
+ finished = (got < data.size());
+ if(got > 0)
+ {
+ alBufferData(bufid, mFormat, data.data(), got, mSampleRate);
+ alSourceQueueBuffers(mSource, 1, &bufid);
+ }
+ } while(processed > 0);
+ throwALerror();
+ }
+
+ if(state != AL_PLAYING && state != AL_PAUSED)
+ {
+ ALint queued;
+
+ alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued);
+ throwALerror();
+ if(queued > 0)
+ {
+ alSourcePlay(mSource);
+ throwALerror();
+ }
+ }
+
+ mIsFinished = finished;
+ return !finished;
+}
+
+//
+// A regular OpenAL sound
+//
+class OpenAL_Sound : public Sound
+{
+ OpenAL_Output &mOutput;
+
+ ALuint mSource;
+ ALuint mBuffer;
+
+ OpenAL_Sound(const OpenAL_Sound &rhs);
+ OpenAL_Sound& operator=(const OpenAL_Sound &rhs);
+
+public:
+ OpenAL_Sound(OpenAL_Output &output, ALuint src, ALuint buf);
+ virtual ~OpenAL_Sound();
+
+ virtual void stop();
+ virtual bool isPlaying();
+ virtual void setVolume(float volume);
+ virtual void update(const float *pos);
+};
+
+OpenAL_Sound::OpenAL_Sound(OpenAL_Output &output, ALuint src, ALuint buf)
+ : mOutput(output), mSource(src), mBuffer(buf)
+{
+}
+OpenAL_Sound::~OpenAL_Sound()
+{
+ alSourceStop(mSource);
+ alSourcei(mSource, AL_BUFFER, 0);
+
+ mOutput.mFreeSources.push_back(mSource);
+ mOutput.bufferFinished(mBuffer);
+}
+
+void OpenAL_Sound::stop()
+{
+ alSourceStop(mSource);
+ throwALerror();
+}
+
+bool OpenAL_Sound::isPlaying()
+{
+ ALint state;
+
+ alGetSourcei(mSource, AL_SOURCE_STATE, &state);
+ throwALerror();
+
+ return state==AL_PLAYING;
+}
+
+void OpenAL_Sound::setVolume(float volume)
+{
+ alSourcef(mSource, AL_GAIN, volume*mBaseVolume);
+ throwALerror();
+ mVolume = volume;
+}
+
+void OpenAL_Sound::update(const float *pos)
+{
+ alSource3f(mSource, AL_POSITION, pos[0], pos[2], -pos[1]);
+ alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f);
+ alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f);
+ throwALerror();
+}
+
+
+//
+// An OpenAL output device
+//
+std::vector OpenAL_Output::enumerate()
+{
+ std::vector devlist;
+ const ALCchar *devnames;
+
+ if(alcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT"))
+ devnames = alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER);
+ else
+ devnames = alcGetString(NULL, ALC_DEVICE_SPECIFIER);
+ while(devnames && *devnames)
+ {
+ devlist.push_back(devnames);
+ devnames += strlen(devnames)+1;
+ }
+ return devlist;
+}
+
+void OpenAL_Output::init(const std::string &devname)
+{
+ if(mDevice || mContext)
+ fail("Device already open");
+
+ mDevice = alcOpenDevice(devname.c_str());
+ if(!mDevice)
+ {
+ if(devname.empty())
+ fail("Failed to open default device");
+ else
+ fail("Failed to open \""+devname+"\"");
+ }
+ if(alcIsExtensionPresent(mDevice, "ALC_ENUMERATE_ALL_EXT"))
+ std::cout << "Opened \""<(maxmono+maxstereo, 256);
+ for(size_t i = 0;i < maxtotal;i++)
+ {
+ ALuint src = 0;
+ alGenSources(1, &src);
+ throwALerror();
+ mFreeSources.push_back(src);
+ }
+ }
+ catch(std::exception &e)
+ {
+ std::cout <<"Error: "<removeAll();
+
+ while(!mFreeSources.empty())
+ {
+ alDeleteSources(1, &mFreeSources.front());
+ mFreeSources.pop_front();
+ }
+
+ mBufferRefs.clear();
+ mUnusedBuffers.clear();
+ while(!mBufferCache.empty())
+ {
+ alDeleteBuffers(1, &mBufferCache.begin()->second);
+ mBufferCache.erase(mBufferCache.begin());
+ }
+
+ alcMakeContextCurrent(0);
+ if(mContext)
+ alcDestroyContext(mContext);
+ mContext = 0;
+ if(mDevice)
+ alcCloseDevice(mDevice);
+ mDevice = 0;
+}
+
+
+ALuint OpenAL_Output::getBuffer(const std::string &fname)
+{
+ ALuint buf = 0;
+
+ NameMap::iterator iditer = mBufferCache.find(fname);
+ if(iditer != mBufferCache.end())
+ {
+ buf = iditer->second;
+ if(mBufferRefs[buf]++ == 0)
+ {
+ IDDq::iterator iter = std::find(mUnusedBuffers.begin(),
+ mUnusedBuffers.end(), buf);
+ if(iter != mUnusedBuffers.end())
+ mUnusedBuffers.erase(iter);
+ }
+
+ return buf;
+ }
+ throwALerror();
+
+ std::vector data;
+ ChannelConfig chans;
+ SampleType type;
+ ALenum format;
+ int srate;
+
+ DecoderPtr decoder = mManager.getDecoder();
+ try
+ {
+ decoder->open(fname);
+ }
+ catch(Ogre::FileNotFoundException &e)
+ {
+ std::string::size_type pos = fname.rfind('.');
+ if(pos == std::string::npos)
+ throw;
+ decoder->open(fname.substr(0, pos)+".mp3");
+ }
+
+ decoder->getInfo(&srate, &chans, &type);
+ format = getALFormat(chans, type);
+
+ decoder->readAll(data);
+ decoder->close();
+
+ alGenBuffers(1, &buf);
+ throwALerror();
+
+ alBufferData(buf, format, data.data(), data.size(), srate);
+ mBufferCache[fname] = buf;
+ mBufferRefs[buf] = 1;
+
+ ALint bufsize = 0;
+ alGetBufferi(buf, AL_SIZE, &bufsize);
+ mBufferCacheMemSize += bufsize;
+
+ // NOTE: Max buffer cache: 15MB
+ while(mBufferCacheMemSize > 15*1024*1024)
+ {
+ if(mUnusedBuffers.empty())
+ {
+ std::cout <<"No more unused buffers to clear!"<< std::endl;
+ break;
+ }
+
+ ALuint oldbuf = mUnusedBuffers.front();
+ mUnusedBuffers.pop_front();
+
+ NameMap::iterator nameiter = mBufferCache.begin();
+ while(nameiter != mBufferCache.end())
+ {
+ if(nameiter->second == oldbuf)
+ mBufferCache.erase(nameiter++);
+ else
+ nameiter++;
+ }
+
+ bufsize = 0;
+ alGetBufferi(oldbuf, AL_SIZE, &bufsize);
+ alDeleteBuffers(1, &oldbuf);
+ mBufferCacheMemSize -= bufsize;
+ }
+ return buf;
+}
+
+void OpenAL_Output::bufferFinished(ALuint buf)
+{
+ if(mBufferRefs.at(buf)-- == 1)
+ {
+ mBufferRefs.erase(mBufferRefs.find(buf));
+ mUnusedBuffers.push_back(buf);
+ }
+}
+
+
+SoundPtr OpenAL_Output::playSound(const std::string &fname, float volume, float pitch, bool loop)
+{
+ throwALerror();
+
+ boost::shared_ptr sound;
+ ALuint src=0, buf=0;
+
+ if(mFreeSources.empty())
+ fail("No free sources");
+ src = mFreeSources.front();
+ mFreeSources.pop_front();
+
+ try
+ {
+ buf = getBuffer(fname);
+ sound.reset(new OpenAL_Sound(*this, src, buf));
+ }
+ catch(std::exception &e)
+ {
+ mFreeSources.push_back(src);
+ if(buf && alIsBuffer(buf))
+ bufferFinished(buf);
+ alGetError();
+ 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);
+
+ alSourcef(src, AL_GAIN, volume);
+ alSourcef(src, AL_PITCH, pitch);
+
+ alSourcei(src, AL_SOURCE_RELATIVE, AL_TRUE);
+ alSourcei(src, AL_LOOPING, (loop?AL_TRUE:AL_FALSE));
+ throwALerror();
+
+ alSourcei(src, AL_BUFFER, buf);
+ alSourcePlay(src);
+ throwALerror();
+
+ return sound;
+}
+
+SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const float *pos, float volume, float pitch,
+ float min, float max, bool loop)
+{
+ throwALerror();
+
+ boost::shared_ptr sound;
+ ALuint src=0, buf=0;
+
+ if(mFreeSources.empty())
+ fail("No free sources");
+ src = mFreeSources.front();
+ mFreeSources.pop_front();
+
+ try
+ {
+ buf = getBuffer(fname);
+ sound.reset(new OpenAL_Sound(*this, src, buf));
+ }
+ catch(std::exception &e)
+ {
+ mFreeSources.push_back(src);
+ if(buf && alIsBuffer(buf))
+ bufferFinished(buf);
+ alGetError();
+ throw;
+ }
+
+ alSource3f(src, AL_POSITION, pos[0], pos[2], -pos[1]);
+ alSource3f(src, AL_DIRECTION, 0.0f, 0.0f, 0.0f);
+ alSource3f(src, AL_VELOCITY, 0.0f, 0.0f, 0.0f);
+
+ alSourcef(src, AL_REFERENCE_DISTANCE, min);
+ alSourcef(src, AL_MAX_DISTANCE, max);
+ alSourcef(src, AL_ROLLOFF_FACTOR, 1.0f);
+
+ alSourcef(src, AL_GAIN, volume);
+ alSourcef(src, AL_PITCH, pitch);
+
+ alSourcei(src, AL_SOURCE_RELATIVE, AL_FALSE);
+ alSourcei(src, AL_LOOPING, (loop?AL_TRUE:AL_FALSE));
+ throwALerror();
+
+ alSourcei(src, AL_BUFFER, buf);
+ alSourcePlay(src);
+ throwALerror();
+
+ return sound;
+}
+
+
+SoundPtr OpenAL_Output::streamSound(const std::string &fname, float volume, float pitch)
+{
+ throwALerror();
+
+ boost::shared_ptr sound;
+ ALuint src;
+
+ if(mFreeSources.empty())
+ fail("No free sources");
+ src = mFreeSources.front();
+ mFreeSources.pop_front();
+
+ try
+ {
+ DecoderPtr decoder = mManager.getDecoder();
+ decoder->open(fname);
+ sound.reset(new OpenAL_SoundStream(*this, src, decoder));
+ }
+ catch(std::exception &e)
+ {
+ mFreeSources.push_back(src);
+ 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);
+
+ alSourcef(src, AL_GAIN, volume);
+ alSourcef(src, AL_PITCH, pitch);
+
+ alSourcei(src, AL_SOURCE_RELATIVE, AL_TRUE);
+ alSourcei(src, AL_LOOPING, AL_FALSE);
+ throwALerror();
+
+ sound->play();
+ return sound;
+}
+
+SoundPtr OpenAL_Output::streamSound3D(const std::string &fname, const float *pos, float volume, float pitch,
+ float min, float max)
+{
+ throwALerror();
+
+ boost::shared_ptr sound;
+ ALuint src;
+
+ if(mFreeSources.empty())
+ fail("No free sources");
+ src = mFreeSources.front();
+ mFreeSources.pop_front();
+
+ try
+ {
+ DecoderPtr decoder = mManager.getDecoder();
+ decoder->open(fname);
+ sound.reset(new OpenAL_SoundStream(*this, src, decoder));
+ }
+ catch(std::exception &e)
+ {
+ mFreeSources.push_back(src);
+ throw;
+ }
+
+ alSource3f(src, AL_POSITION, pos[0], pos[2], -pos[1]);
+ alSource3f(src, AL_DIRECTION, 0.0f, 0.0f, 0.0f);
+ alSource3f(src, AL_VELOCITY, 0.0f, 0.0f, 0.0f);
+
+ alSourcef(src, AL_REFERENCE_DISTANCE, min);
+ alSourcef(src, AL_MAX_DISTANCE, max);
+ alSourcef(src, AL_ROLLOFF_FACTOR, 1.0f);
+
+ alSourcef(src, AL_GAIN, volume);
+ alSourcef(src, AL_PITCH, pitch);
+
+ alSourcei(src, AL_SOURCE_RELATIVE, AL_FALSE);
+ alSourcei(src, AL_LOOPING, AL_FALSE);
+ throwALerror();
+
+ sound->play();
+ return sound;
+}
+
+
+void OpenAL_Output::updateListener(const float *pos, const float *atdir, const float *updir)
+{
+ float orient[6] = {
+ atdir[0], atdir[2], -atdir[1],
+ updir[0], updir[2], -updir[1]
+ };
+
+ alListener3f(AL_POSITION, pos[0], pos[2], -pos[1]);
+ alListenerfv(AL_ORIENTATION, orient);
+ throwALerror();
+}
+
+
+OpenAL_Output::OpenAL_Output(SoundManager &mgr)
+ : Sound_Output(mgr), mDevice(0), mContext(0), mBufferCacheMemSize(0),
+ mStreamThread(new StreamThread)
+{
+}
+
+OpenAL_Output::~OpenAL_Output()
+{
+ deinit();
+}
+
+}
diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp
new file mode 100644
index 000000000..d288a62f3
--- /dev/null
+++ b/apps/openmw/mwsound/openal_output.hpp
@@ -0,0 +1,71 @@
+#ifndef GAME_SOUND_OPENAL_OUTPUT_H
+#define GAME_SOUND_OPENAL_OUTPUT_H
+
+#include
+#include
+#include