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 3e97b5858..442710a02 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 @@ -27,11 +24,17 @@ set (OPENMW_VERSION "${OPENMW_VERSION_MAJOR}.${OPENMW_VERSION_MINOR}.${OPENMW_VE configure_file ("${OpenMW_SOURCE_DIR}/Docs/mainpage.hpp.cmake" "${OpenMW_SOURCE_DIR}/Docs/mainpage.hpp") +option(MYGUI_STATIC "Link static build of Mygui into the binaries" FALSE) option(OGRE_STATIC "Link static build of Ogre and Ogre Plugins into the binaries" FALSE) +# Apps and tools +option(BUILD_ESMTOOL "build ESM inspector" ON) +option(BUILD_LAUNCHER "build Launcher" ON) +option(BUILD_MWINIIMPORTER "build MWiniImporter" ON) + # Sound source selection -option(USE_AUDIERE "use Audiere for sound" OFF) option(USE_FFMPEG "use ffmpeg for sound" OFF) +option(USE_AUDIERE "use audiere for sound" OFF) option(USE_MPG123 "use mpg123 + libsndfile for sound" ON) find_program(DPKG_PROGRAM dpkg DOC "dpkg program of Debian-based systems") @@ -99,6 +102,7 @@ set(OENGINE_OGRE ${LIBDIR}/openengine/ogre/renderer.cpp ${LIBDIR}/openengine/ogre/mouselook.cpp ${LIBDIR}/openengine/ogre/fader.cpp + ${LIBDIR}/openengine/ogre/imagerotate.cpp ) set(OENGINE_GUI ${LIBDIR}/openengine/gui/events.cpp @@ -120,54 +124,41 @@ 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) +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) - 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) + 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_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_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) + 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) -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}) -source_group(libs\\openengine FILES ${OENGINE_ALL}) - -set(OPENMW_LIBS ${MANGLE_ALL} ${OENGINE_ALL}) -set(OPENMW_LIBS_HEADER) - # Platform specific if (WIN32) + set(Boost_USE_STATIC_LIBS ON) set(PLATFORM_INCLUDE_DIR "platform") add_definitions(-DBOOST_ALL_NO_LIB) else (WIN32) @@ -185,7 +176,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() @@ -206,6 +197,7 @@ ENDIF(WIN32) ENDIF(OGRE_STATIC) include_directories("." ${OGRE_INCLUDE_DIR} ${OGRE_INCLUDE_DIR}/Ogre ${OGRE_INCLUDE_DIR}/OGRE ${OGRE_PLUGIN_INCLUDE_DIRS} + ${OGRE_Terrain_INCLUDE_DIR} ${OIS_INCLUDE_DIRS} ${Boost_INCLUDE_DIR} ${PLATFORM_INCLUDE_DIR} ${MYGUI_INCLUDE_DIRS} @@ -225,6 +217,7 @@ if(APPLE) "Plugin_ParticleFX") endif(APPLE) +add_subdirectory( files/) add_subdirectory( files/mygui ) # Specify build paths @@ -237,6 +230,12 @@ endif (APPLE) # Other files +configure_file(${OpenMW_SOURCE_DIR}/files/settings-default.cfg + "${OpenMW_BINARY_DIR}/settings-default.cfg") + +configure_file(${OpenMW_SOURCE_DIR}/files/transparency-overrides.cfg + "${OpenMW_BINARY_DIR}/transparency-overrides.cfg") + configure_file(${OpenMW_SOURCE_DIR}/files/openmw.cfg.local "${OpenMW_BINARY_DIR}/openmw.cfg") configure_file(${OpenMW_SOURCE_DIR}/files/openmw.cfg @@ -250,6 +249,9 @@ endif (WIN32) if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") configure_file(${OpenMW_SOURCE_DIR}/files/plugins.cfg.linux "${OpenMW_BINARY_DIR}/plugins.cfg") + + configure_file(${OpenMW_SOURCE_DIR}/files/openmw.desktop + "${OpenMW_BINARY_DIR}/openmw.desktop") endif() if (APPLE) @@ -263,8 +265,14 @@ if (APPLE) "${APP_BUNDLE_DIR}/Contents/Resources/OpenMW.icns" COPYONLY) # prepare plugins - if (${CMAKE_BUILD_TYPE} MATCHES "Release" OR - ${CMAKE_BUILD_TYPE} MATCHES "RelWithDebugInfo") + if (${CMAKE_BUILD_TYPE} MATCHES "Release") + set(OPENMW_RELEASE_BUILD 1) + endif() + if (${CMAKE_BUILD_TYPE} MATCHES "RelWithDebugInfo") + set(OPENMW_RELEASE_BUILD 1) + endif() + + if (${OPENMW_RELEASE_BUILD}) set(OGRE_PLUGIN_DIR ${OGRE_PLUGIN_DIR_REL}) else() set(OGRE_PLUGIN_DIR ${OGRE_PLUGIN_DIR_DBG}) @@ -280,7 +288,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) @@ -298,10 +315,12 @@ if(DPKG_PROGRAM) endif() #Install icon and desktop file - INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/openmw.desktop" DESTINATION "share/applications/" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "openmw") + INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.desktop" DESTINATION "share/applications/" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "openmw") INSTALL(FILES "${OpenMW_SOURCE_DIR}/apps/launcher/resources/images/openmw.png" DESTINATION "share/pixmaps/" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "openmw") #Install global configuration files + INSTALL(FILES "${OpenMW_BINARY_DIR}/settings-default.cfg" DESTINATION "../etc/openmw/" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "openmw") + INSTALL(FILES "${OpenMW_BINARY_DIR}/transparency-overrides.cfg" DESTINATION "../etc/openmw/" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "openmw") INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" DESTINATION "../etc/openmw/" RENAME "openmw.cfg" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "openmw") INSTALL(FILES "${OpenMW_BINARY_DIR}/plugins.cfg" DESTINATION "../etc/openmw/" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "openmw") @@ -319,7 +338,7 @@ if(DPKG_PROGRAM) Data files from the original game is required to run it.") SET(CPACK_DEBIAN_PACKAGE_NAME "openmw") SET(CPACK_DEBIAN_PACKAGE_VERSION "${VERSION_STRING}") - SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW esmtool;Esmtool omwlauncher;OMWLauncher") + SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW esmtool;Esmtool omwlauncher;OMWLauncher mwiniimporter;MWiniImporter") SET(CPACK_DEBIAN_PACKAGE_DEPENDS "nvidia-cg-toolkit (>= 2.1), libboost-filesystem1.46.1 (>= 1.46.1), libboost-program-options1.46.1 (>= 1.46.1), libboost-system1.46.1 (>= 1.46.1), libboost-thread1.46.1 (>= 1.46.1), libc6 (>= 2.11.2), libfreetype6 (>= 2.2.1), libgcc1 (>= 1:4.1.1), libmpg123-0 (>= 1.12.1), libois-1.3.0 (>= 1.3.0), libopenal1 (>= 1:1.12.854), libsndfile1 (>= 1.0.23), libstdc++6 (>= 4.4.5), libuuid1 (>= 2.17.2), libqtgui4 (>= 4.7.0)") SET(CPACK_DEBIAN_PACKAGE_SECTION "Games") @@ -340,6 +359,10 @@ 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" + "${OpenMW_BINARY_DIR}/settings-default.cfg" + DESTINATION ".") INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION ".") SET(CPACK_GENERATOR "NSIS") @@ -349,7 +372,12 @@ if(WIN32) SET(CPACK_PACKAGE_VERSION_MAJOR ${OPENMW_VERSION_MAJOR}) 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_PACKAGE_EXECUTABLES "openmw;OpenMW;omwlauncher;OpenMW Launcher") + SET(CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '\$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Readme.lnk' '\$INSTDIR\\\\readme.txt'") + SET(CPACK_NSIS_DELETE_ICONS_EXTRA " + !insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP + Delete \\\"$SMPROGRAMS\\\\$MUI_TEMP\\\\Readme.lnk\\\" + ") 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 ".") @@ -393,16 +421,18 @@ add_subdirectory (components) # Apps and tools add_subdirectory( apps/openmw ) -option(BUILD_ESMTOOL "build ESM inspector" ON) if (BUILD_ESMTOOL) add_subdirectory( apps/esmtool ) endif() -option(BUILD_LAUNCHER "build Launcher inspector" ON) if (BUILD_LAUNCHER) add_subdirectory( apps/launcher ) endif() +if (BUILD_MWINIIMPORTER) + add_subdirectory( apps/mwiniimporter ) +endif() + if (WIN32) if (MSVC) if (USE_DEBUG_CONSOLE) @@ -492,6 +522,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/CMakeLists.txt b/apps/launcher/CMakeLists.txt index fd736e011..ccefee1ee 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -95,5 +95,5 @@ else() "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/launcher.qss") configure_file(${CMAKE_SOURCE_DIR}/files/launcher.cfg - "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}launcher.cfg") + "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/launcher.cfg") endif() diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index c96fc2c7b..c15274e74 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -225,7 +225,7 @@ void DataFilesPage::setupDataFiles() msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::Cancel); msgBox.setText(tr("
Could not find the Data Files location

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

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

\ Press \"Browse...\" to specify the location manually.
")); QAbstractButton *dirSelectButton = @@ -1057,16 +1057,8 @@ void DataFilesPage::writeConfig(QString profile) return; } - // Prepare the OpenMW config - QString config = QString::fromStdString((mCfgMgr.getLocalPath() / "openmw.cfg").string()); - QFile file(config); - - if (!file.exists()) { - config = QString::fromStdString((mCfgMgr.getUserPath() / "openmw.cfg").string()); - } - - // Open the config as a QFile - file.setFileName(config); + // Open the OpenMW config as a QFile + QFile file(QString::fromStdString((mCfgMgr.getUserPath() / "openmw.cfg").string())); if (!file.open(QIODevice::ReadWrite | QIODevice::Text)) { // File cannot be opened or created diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 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/mwiniimporter/CMakeLists.txt b/apps/mwiniimporter/CMakeLists.txt new file mode 100644 index 000000000..2a8c0f5fe --- /dev/null +++ b/apps/mwiniimporter/CMakeLists.txt @@ -0,0 +1,20 @@ +set(MWINIIMPORT + main.cpp + importer.cpp +) + +set(MWINIIMPORT_HEADER + importer.hpp +) + +source_group(launcher FILES ${MWINIIMPORT} ${MWINIIMPORT_HEADER}) + +add_executable(mwiniimport + ${MWINIIMPORT} +) + +target_link_libraries(mwiniimport + ${Boost_LIBRARIES} + components +) + diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp new file mode 100644 index 000000000..5503a7c1a --- /dev/null +++ b/apps/mwiniimporter/importer.cpp @@ -0,0 +1,216 @@ +#include "importer.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +MwIniImporter::MwIniImporter() { + const char *map[][2] = + { + { "fps", "General:Show FPS" }, + { "nosound", "General:Disable Audio" }, + { 0, 0 } + }; + const char *fallback[] = { + "Weather:Sunrise Time", + "Weather:Sunset Time", + 0 + }; + + for(int i=0; map[i][0]; i++) { + mMergeMap.insert(std::make_pair(map[i][0], map[i][1])); + } + + for(int i=0; fallback[i]; i++) { + mMergeFallback.push_back(fallback[i]); + } +} + +void MwIniImporter::setVerbose(bool verbose) { + mVerbose = verbose; +} + +std::string MwIniImporter::numberToString(int n) { + std::stringstream str; + str << n; + return str.str(); +} + +MwIniImporter::multistrmap MwIniImporter::loadIniFile(std::string filename) { + std::cout << "load ini file: " << filename << std::endl; + + std::string section(""); + MwIniImporter::multistrmap map; + boost::iostreams::streamfile(filename.c_str()); + + std::string line; + while (std::getline(file, line)) { + + if(line[0] == '[') { + if(line.length() > 2) { + section = line.substr(1, line.length()-3); + } + continue; + } + + int comment_pos = line.find(";"); + if(comment_pos > 0) { + line = line.substr(0,comment_pos); + } + + if(line.empty()) { + continue; + } + + int pos = line.find("="); + if(pos < 1) { + continue; + } + + std::string key(section + ":" + line.substr(0,pos)); + std::string value(line.substr(pos+1)); + + multistrmap::iterator it; + if((it = map.find(key)) == map.end()) { + map.insert( std::make_pair > (key, std::vector() ) ); + } + map[key].push_back(value); + } + + return map; +} + +MwIniImporter::multistrmap MwIniImporter::loadCfgFile(std::string filename) { + std::cout << "load cfg file: " << filename << std::endl; + + MwIniImporter::multistrmap map; + boost::iostreams::streamfile(filename.c_str()); + + std::string line; + while (std::getline(file, line)) { + + // we cant say comment by only looking at first char anymore + int comment_pos = line.find("#"); + if(comment_pos > 0) { + line = line.substr(0,comment_pos); + } + + if(line.empty()) { + continue; + } + + int pos = line.find("="); + if(pos < 1) { + continue; + } + + std::string key(line.substr(0,pos)); + std::string value(line.substr(pos+1)); + + multistrmap::iterator it; + if((it = map.find(key)) == map.end()) { + map.insert( std::make_pair > (key, std::vector() ) ); + } + map[key].push_back(value); + } + + return map; +} + +void MwIniImporter::merge(multistrmap &cfg, multistrmap &ini) { + multistrmap::iterator cfgIt; + multistrmap::iterator iniIt; + for(strmap::iterator it=mMergeMap.begin(); it!=mMergeMap.end(); it++) { + if((iniIt = ini.find(it->second)) != ini.end()) { + for(std::vector::iterator vc = iniIt->second.begin(); vc != iniIt->second.end(); vc++) { + cfg.erase(it->first); + insertMultistrmap(cfg, it->first, *vc); + } + } + } +} + +void MwIniImporter::mergeFallback(multistrmap &cfg, multistrmap &ini) { + cfg.erase("fallback"); + + multistrmap::iterator cfgIt; + multistrmap::iterator iniIt; + for(std::vector::iterator it=mMergeFallback.begin(); it!=mMergeFallback.end(); it++) { + if((iniIt = ini.find(*it)) != ini.end()) { + for(std::vector::iterator vc = iniIt->second.begin(); vc != iniIt->second.end(); vc++) { + std::string value(*it); + std::replace( value.begin(), value.end(), ' ', '_' ); + std::replace( value.begin(), value.end(), ':', '_' ); + value.append(",").append(vc->substr(0,vc->length()-1)); + insertMultistrmap(cfg, "fallback", value); + } + } + } +}; + +void MwIniImporter::insertMultistrmap(multistrmap &cfg, std::string key, std::string value) { + multistrmap::iterator it = cfg.find(key); + if(it == cfg.end()) { + cfg.insert(std::make_pair >(key, std::vector() )); + } + cfg[key].push_back(value); +} + +void MwIniImporter::importGameFiles(multistrmap &cfg, multistrmap &ini) { + std::vector esmFiles; + std::vector espFiles; + std::string baseGameFile("Game Files:GameFile"); + std::string gameFile(""); + + multistrmap::iterator it = ini.begin(); + for(int i=0; it != ini.end(); i++) { + gameFile = baseGameFile; + gameFile.append(this->numberToString(i)); + + it = ini.find(gameFile); + if(it == ini.end()) { + break; + } + + for(std::vector::iterator entry = it->second.begin(); entry!=it->second.end(); entry++) { + std::string filetype(entry->substr(entry->length()-4, 3)); + std::transform(filetype.begin(), filetype.end(), filetype.begin(), ::tolower); + + if(filetype.compare("esm") == 0) { + esmFiles.push_back(*entry); + } + else if(filetype.compare("esp") == 0) { + espFiles.push_back(*entry); + } + } + + gameFile = ""; + } + + cfg.erase("master"); + cfg.insert( std::make_pair > ("master", std::vector() ) ); + + for(std::vector::iterator it=esmFiles.begin(); it!=esmFiles.end(); it++) { + cfg["master"].push_back(*it); + } + + cfg.erase("plugin"); + cfg.insert( std::make_pair > ("plugin", std::vector() ) ); + + for(std::vector::iterator it=espFiles.begin(); it!=espFiles.end(); it++) { + cfg["plugin"].push_back(*it); + } +} + +void MwIniImporter::writeToFile(boost::iostreams::stream &out, multistrmap &cfg) { + + for(multistrmap::iterator it=cfg.begin(); it != cfg.end(); it++) { + for(std::vector::iterator entry=it->second.begin(); entry != it->second.end(); entry++) { + out << (it->first) << "=" << (*entry) << std::endl; + } + } +} diff --git a/apps/mwiniimporter/importer.hpp b/apps/mwiniimporter/importer.hpp new file mode 100644 index 000000000..ced332a72 --- /dev/null +++ b/apps/mwiniimporter/importer.hpp @@ -0,0 +1,34 @@ +#ifndef MWINIIMPORTER_IMPORTER +#define MWINIIMPORTER_IMPORTER 1 + +#include +#include +#include +#include +#include +#include + +class MwIniImporter { + public: + typedef std::map strmap; + typedef std::map > multistrmap; + + MwIniImporter(); + void setVerbose(bool verbose); + multistrmap loadIniFile(std::string filename); + multistrmap loadCfgFile(std::string filename); + void merge(multistrmap &cfg, multistrmap &ini); + void mergeFallback(multistrmap &cfg, multistrmap &ini); + void importGameFiles(multistrmap &cfg, multistrmap &ini); + void writeToFile(boost::iostreams::stream &out, multistrmap &cfg); + + private: + void insertMultistrmap(multistrmap &cfg, std::string key, std::string value); + std::string numberToString(int n); + bool mVerbose; + strmap mMergeMap; + std::vector mMergeFallback; +}; + + +#endif diff --git a/apps/mwiniimporter/main.cpp b/apps/mwiniimporter/main.cpp new file mode 100644 index 000000000..234d7d57d --- /dev/null +++ b/apps/mwiniimporter/main.cpp @@ -0,0 +1,75 @@ +#include "importer.hpp" + +#include +#include +#include +#include + +namespace bpo = boost::program_options; + +int main(int argc, char *argv[]) { + + bpo::options_description desc("Syntax: mwiniimporter inifile configfile\nAllowed options"); + bpo::positional_options_description p_desc; + desc.add_options() + ("help,h", "produce help message") + ("verbose,v", "verbose output") + ("ini,i", bpo::value(), "morrowind.ini file") + ("cfg,c", bpo::value(), "openmw.cfg file") + ("output,o", bpo::value()->default_value(""), "openmw.cfg file") + ("game-files,g", "import esm and esp files") + ; + p_desc.add("ini", 1).add("cfg", 1); + + bpo::variables_map vm; + bpo::parsed_options parsed = bpo::command_line_parser(argc, argv) + .options(desc) + .positional(p_desc) + .run(); + + bpo::store(parsed, vm); + + if(vm.count("help") || !vm.count("ini") || !vm.count("cfg")) { + std::cout << desc; + return 0; + } + + bpo::notify(vm); + + std::string iniFile = vm["ini"].as(); + std::string cfgFile = vm["cfg"].as(); + + // if no output is given, write back to cfg file + std::string outputFile(vm["output"].as()); + if(vm["output"].defaulted()) { + outputFile = vm["cfg"].as(); + } + + if(!boost::filesystem::exists(iniFile)) { + std::cerr << "ini file does not exist" << std::endl; + return -3; + } + if(!boost::filesystem::exists(cfgFile)) { + std::cerr << "cfg file does not exist" << std::endl; + return -4; + } + + MwIniImporter importer; + importer.setVerbose(vm.count("verbose")); + + MwIniImporter::multistrmap ini = importer.loadIniFile(iniFile); + MwIniImporter::multistrmap cfg = importer.loadCfgFile(cfgFile); + + importer.merge(cfg, ini); + importer.mergeFallback(cfg, ini); + + if(vm.count("game-files")) { + importer.importGameFiles(cfg, ini); + } + + std::cout << "write to: " << outputFile << std::endl; + boost::iostreams::stream file(outputFile); + importer.writeToFile(file, cfg); + + return 0; +} diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 6fb8a24c3..769bcc6c3 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -15,7 +15,7 @@ source_group(game FILES ${GAME} ${GAME_HEADER}) add_openmw_dir (mwrender renderingmanager debugging sky player animation npcanimation creatureanimation actors objects - renderinginterface localmap + renderinginterface localmap occlusionquery terrain terrainmaterial water shadows shaderhelper ) add_openmw_dir (mwinput @@ -25,6 +25,7 @@ add_openmw_dir (mwinput add_openmw_dir (mwgui layouts text_input widgets race class birth review window_manager console dialogue dialogue_history window_base stats_window messagebox journalwindow charactercreation container + map_window window_pinnable_base cursorreplace ) add_openmw_dir (mwdialogue @@ -39,7 +40,7 @@ add_openmw_dir (mwscript ) add_openmw_dir (mwsound - soundmanager + soundmanager openal_output audiere_decoder mpgsnd_decoder ffmpeg_decoder ) add_openmw_dir (mwworld @@ -54,7 +55,7 @@ add_openmw_dir (mwclass ) add_openmw_dir (mwmechanics - mechanicsmanager stat creaturestats magiceffects movement + mechanicsmanager stat creaturestats magiceffects movement actors drawstate spells ) # Main executable @@ -82,6 +83,7 @@ add_definitions(${SOUND_DEFINE}) target_link_libraries(openmw ${OGRE_LIBRARIES} + ${OGRE_Terrain_LIBRARY} ${OGRE_STATIC_PLUGINS} ${OIS_LIBRARIES} ${Boost_LIBRARIES} @@ -89,7 +91,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 89068ce53..2d3c872dd 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -20,6 +20,8 @@ #include #include #include +#include +#include #include #include @@ -27,6 +29,7 @@ #include "mwinput/inputmanager.hpp" #include "mwgui/window_manager.hpp" +#include "mwgui/cursorreplace.hpp" #include "mwscript/scriptmanager.hpp" #include "mwscript/compilercontext.hpp" @@ -82,12 +85,20 @@ void OMW::Engine::updateFocusReport (float duration) if (!handle.empty()) { - MWWorld::Ptr ptr = mEnvironment.mWorld->getPtrViaHandle (handle); + // the faced handle is not updated immediately, so on a cell change it might + // point to an object that doesn't exist anymore + // therefore, we are catching the "Unknown Ogre handle" exception that occurs in this case + try + { + MWWorld::Ptr ptr = mEnvironment.mWorld->getPtrViaHandle (handle); - if (!ptr.isEmpty()){ - name = MWWorld::Class::get (ptr).getName (ptr); + if (!ptr.isEmpty()){ + name = MWWorld::Class::get (ptr).getName (ptr); + } } + catch (std::runtime_error& e) + {} } if (name!=mFocusName) @@ -115,13 +126,12 @@ bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt) { mEnvironment.mFrameDuration = evt.timeSinceLastFrame; + // update input + mEnvironment.mInputManager->update(); + // sound if (mUseSound) - { - mEnvironment.mSoundManager->playPlaylist(); - mEnvironment.mSoundManager->update (evt.timeSinceLastFrame); - } // update GUI Ogre::RenderWindow* window = mOgre->getWindow(); @@ -152,7 +162,8 @@ bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt) // update actors std::vector > movement; - mEnvironment.mMechanicsManager->update (movement); + mEnvironment.mMechanicsManager->update (movement, mEnvironment.mFrameDuration, + mEnvironment.mWindowManager->getMode()!=MWGui::GM_Game); if (mEnvironment.mWindowManager->getMode()==MWGui::GM_Game) mEnvironment.mWorld->doPhysics (movement, mEnvironment.mFrameDuration); @@ -208,13 +219,18 @@ OMW::Engine::~Engine() void OMW::Engine::loadBSA() { const Files::MultiDirCollection& bsa = mFileCollections.getCollection (".bsa"); - std::string dataDirectory; + for (Files::MultiDirCollection::TIter iter(bsa.begin()); iter!=bsa.end(); ++iter) { std::cout << "Adding " << iter->second.string() << std::endl; Bsa::addBSA(iter->second.string()); + } - dataDirectory = iter->second.parent_path().string(); + const Files::PathContainer& dataDirs = mFileCollections.getPaths(); + std::string dataDirectory; + for (Files::PathContainer::const_iterator iter = dataDirs.begin(); iter != dataDirs.end(); ++iter) + { + dataDirectory = iter->string(); std::cout << "Data dir " << dataDirectory << std::endl; Bsa::addDir(dataDirectory, mFSStrict); } @@ -312,6 +328,36 @@ void OMW::Engine::go() { boost::filesystem::create_directories(configPath); } + + // Create the settings manager and load default settings file + Settings::Manager settings; + const std::string localdefault = mCfgMgr.getLocalPath().string() + "/settings-default.cfg"; + const std::string globaldefault = mCfgMgr.getGlobalPath().string() + "/settings-default.cfg"; + + // prefer local + if (boost::filesystem::exists(localdefault)) + settings.loadDefault(localdefault); + else if (boost::filesystem::exists(globaldefault)) + settings.loadDefault(globaldefault); + + // load user settings if they exist, otherwise just load the default settings as user settings + const std::string settingspath = mCfgMgr.getUserPath().string() + "/settings.cfg"; + if (boost::filesystem::exists(settingspath)) + settings.loadUser(settingspath); + else if (boost::filesystem::exists(localdefault)) + settings.loadUser(localdefault); + else if (boost::filesystem::exists(globaldefault)) + settings.loadUser(globaldefault); + + mFpsLevel = settings.getInt("fps", "HUD"); + + // load nif overrides + NifOverrides::Overrides nifOverrides; + if (boost::filesystem::exists(mCfgMgr.getLocalPath().string() + "/transparency-overrides.cfg")) + nifOverrides.loadTransparencyOverrides(mCfgMgr.getLocalPath().string() + "/transparency-overrides.cfg"); + else if (boost::filesystem::exists(mCfgMgr.getGlobalPath().string() + "/transparency-overrides.cfg")) + nifOverrides.loadTransparencyOverrides(mCfgMgr.getGlobalPath().string() + "/transparency-overrides.cfg"); + mOgre->configure(!boost::filesystem::is_regular_file(mCfgMgr.getOgreConfigPath()), mCfgMgr.getOgreConfigPath().string(), mCfgMgr.getLogPath().string(), @@ -319,16 +365,25 @@ void OMW::Engine::go() // This has to be added BEFORE MyGUI is initialized, as it needs // to find core.xml here. + + //addResourcesDirectory(mResDir); + addResourcesDirectory(mResDir / "mygui"); + addResourcesDirectory(mResDir / "water"); + addResourcesDirectory(mResDir / "gbuffer"); + addResourcesDirectory(mResDir / "shadows"); // Create the window mOgre->createWindow("OpenMW"); loadBSA(); + // cursor replacer (converts the cursor from the bsa so they can be used by mygui) + MWGui::CursorReplace replacer; + // Create the world mEnvironment.mWorld = new MWWorld::World (*mOgre, mFileCollections, mMaster, - mResDir, mNewGame, mEnvironment, mEncoding); + mResDir, mNewGame, mEnvironment, mEncoding, mFallbackMap); // Create window manager - this manages all the MW-specific GUI windows MWScript::registerExtensions (mExtensions); @@ -337,10 +392,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, @@ -405,6 +457,9 @@ void OMW::Engine::go() // Start the main rendering loop mOgre->start(); + // Save user settings + settings.saveUser(settingspath); + std::cout << "Quitting peacefully.\n"; } @@ -418,10 +473,21 @@ void OMW::Engine::activate() if (handle.empty()) return; - MWWorld::Ptr ptr = mEnvironment.mWorld->getPtrViaHandle (handle); + // the faced handle is not updated immediately, so on a cell change it might + // point to an object that doesn't exist anymore + // therefore, we are catching the "Unknown Ogre handle" exception that occurs in this case + MWWorld::Ptr ptr; + try + { + ptr = mEnvironment.mWorld->getPtrViaHandle (handle); - if (ptr.isEmpty()) + if (ptr.isEmpty()) + return; + } + catch (std::runtime_error&) + { return; + } MWScript::InterpreterContext interpreterContext (mEnvironment, &ptr.getRefData().getLocals(), ptr); @@ -487,3 +553,8 @@ void OMW::Engine::setEncoding(const std::string& encoding) { mEncoding = encoding; } + +void OMW::Engine::setFallbackValues(std::map fallbackMap) +{ + mFallbackMap = fallbackMap; +} diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 690430784..6eae20cc0 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -76,6 +76,7 @@ namespace OMW bool mReportFocus; float mFocusTDiff; std::string mFocusName; + std::map mFallbackMap; MWWorld::Environment mEnvironment; Compiler::Extensions mExtensions; @@ -163,6 +164,8 @@ namespace OMW void setAnimationVerbose(bool animverbose); + void setFallbackValues(std::map map); + private: Files::ConfigurationManager& mCfgMgr; }; diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index cd1e0e26e..df52faab1 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -54,6 +54,41 @@ inline boost::filesystem::path lexical_cast mMap; +}; + +void validate(boost::any &v, std::vector const &tokens, FallbackMap*, int) +{ + if(v.empty()) + { + v = boost::any(FallbackMap()); + } + + FallbackMap *map = boost::any_cast(&v); + + std::map::iterator mapIt; + for(std::vector::const_iterator it=tokens.begin(); it != tokens.end(); it++) + { + int sep = it->find(","); + if(sep < 1 || sep == (int)it->length()-1) +#if (BOOST_VERSION < 104200) + throw boost::program_options::validation_error("invalid value"); +#else + throw boost::program_options::validation_error(boost::program_options::validation_error::invalid_option_value); +#endif + + std::string key(it->substr(0,sep)); + std::string value(it->substr(sep+1)); + + if((mapIt = map->mMap.find(key)) == map->mMap.end()) + { + map->mMap.insert(std::make_pair(key,value)); + } + } +} + + /** * \brief Parses application command line and calls \ref Cfg::ConfigurationManager * to parse configuration files. @@ -92,39 +127,40 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat ("plugin", bpo::value()->default_value(StringsVector(), "") ->multitoken(), "plugin file(s)") - ("fps", boost::program_options::value()->implicit_value(1) - ->default_value(0), "fps counter detail (0 = off, 1 = fps counter, 2 = full detail)") - - ("anim-verbose", boost::program_options::value()->implicit_value(true) + ("anim-verbose", bpo::value()->implicit_value(true) ->default_value(false), "output animation indices files") - ("debug", boost::program_options::value()->implicit_value(true) + ("debug", bpo::value()->implicit_value(true) ->default_value(false), "debug mode") - ("nosound", boost::program_options::value()->implicit_value(true) + ("nosound", bpo::value()->implicit_value(true) ->default_value(false), "disable all sounds") - ("script-verbose", boost::program_options::value()->implicit_value(true) + ("script-verbose", bpo::value()->implicit_value(true) ->default_value(false), "verbose script output") - ("new-game", boost::program_options::value()->implicit_value(true) + ("new-game", bpo::value()->implicit_value(true) ->default_value(false), "activate char gen/new game mechanics") - ("script-all", boost::program_options::value()->implicit_value(true) + ("script-all", bpo::value()->implicit_value(true) ->default_value(false), "compile all scripts (excluding dialogue scripts) at startup") - ("fs-strict", boost::program_options::value()->implicit_value(true) + ("fs-strict", bpo::value()->implicit_value(true) ->default_value(false), "strict file system handling (no case folding)") - ( "encoding", boost::program_options::value()-> + ( "encoding", bpo::value()-> default_value("win1252"), "Character encoding used in OpenMW game messages:\n" "\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages\n" "\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n" "\n\twin1252 - Western European (Latin) alphabet, used by default") - ("report-focus", boost::program_options::value()->implicit_value(true) + ("report-focus", bpo::value()->implicit_value(true) ->default_value(false), "write name of focussed object to cout") + + ("fallback", bpo::value()->default_value(FallbackMap(), "") + ->multitoken()->composing(), "fallback values") + ; bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv) @@ -225,13 +261,13 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat engine.setNewGame(variables["new-game"].as()); // other settings - engine.showFPS(variables["fps"].as()); engine.setDebugMode(variables["debug"].as()); engine.setSoundUsage(!variables["nosound"].as()); engine.setScriptsVerbosity(variables["script-verbose"].as()); engine.setCompileAll(variables["script-all"].as()); engine.setReportFocus(variables["report-focus"].as()); engine.setAnimationVerbose(variables["anim-verbose"].as()); + engine.setFallbackValues(variables["fallback"].as().mMap); return true; } diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index 8ac589db6..4bd23d8d8 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -56,7 +56,7 @@ namespace MWClass boost::shared_ptr Apparatus::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const { - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); @@ -70,6 +70,14 @@ namespace MWClass return ref->base->script; } + int Apparatus::getValue (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return ref->base->data.value; + } + void Apparatus::registerSelf() { boost::shared_ptr instance (new Apparatus); diff --git a/apps/openmw/mwclass/apparatus.hpp b/apps/openmw/mwclass/apparatus.hpp index 2d1175951..cf8930780 100644 --- a/apps/openmw/mwclass/apparatus.hpp +++ b/apps/openmw/mwclass/apparatus.hpp @@ -25,6 +25,9 @@ namespace MWClass virtual std::string getScript (const MWWorld::Ptr& ptr) const; ///< Return name of the script attached to ptr + virtual int getValue (const MWWorld::Ptr& ptr) const; + ///< Return trade value of the object. Throws an exception, if the object can't be traded. + static void registerSelf(); virtual std::string getUpSoundId (const MWWorld::Ptr& ptr) const; diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index 597094a45..e8613c4f5 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -60,7 +60,7 @@ namespace MWClass boost::shared_ptr Armor::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const { - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); @@ -160,6 +160,14 @@ namespace MWClass return ESM::Skill::HeavyArmor; } + int Armor::getValue (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return ref->base->data.value; + } + void Armor::registerSelf() { boost::shared_ptr instance (new Armor); diff --git a/apps/openmw/mwclass/armor.hpp b/apps/openmw/mwclass/armor.hpp index aada97eec..7a8ec185a 100644 --- a/apps/openmw/mwclass/armor.hpp +++ b/apps/openmw/mwclass/armor.hpp @@ -40,6 +40,9 @@ namespace MWClass /// Return the index of the skill this item corresponds to when equiopped or -1, if there is /// no such skill. + virtual int getValue (const MWWorld::Ptr& ptr) const; + ///< Return trade value of the object. Throws an exception, if the object can't be traded. + static void registerSelf(); virtual std::string getUpSoundId (const MWWorld::Ptr& ptr, const MWWorld::Environment& environment) const; diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index f22191f26..643f41887 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -58,7 +58,7 @@ namespace MWClass { // TODO implement reading - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); @@ -72,6 +72,14 @@ namespace MWClass return ref->base->script; } + int Book::getValue (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return ref->base->data.value; + } + void Book::registerSelf() { boost::shared_ptr instance (new Book); diff --git a/apps/openmw/mwclass/book.hpp b/apps/openmw/mwclass/book.hpp index 2d37bffea..9496d5c0c 100644 --- a/apps/openmw/mwclass/book.hpp +++ b/apps/openmw/mwclass/book.hpp @@ -25,6 +25,9 @@ namespace MWClass virtual std::string getScript (const MWWorld::Ptr& ptr) const; ///< Return name of the script attached to ptr + virtual int getValue (const MWWorld::Ptr& ptr) const; + ///< Return trade value of the object. Throws an exception, if the object can't be traded. + static void registerSelf(); virtual std::string getUpSoundId (const MWWorld::Ptr& ptr, const MWWorld::Environment& environment) const; diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 176ab26dc..dddebd208 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -57,7 +57,7 @@ namespace MWClass boost::shared_ptr Clothing::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const { - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); @@ -123,6 +123,14 @@ namespace MWClass return -1; } + int Clothing::getValue (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return ref->base->data.value; + } + void Clothing::registerSelf() { boost::shared_ptr instance (new Clothing); diff --git a/apps/openmw/mwclass/clothing.hpp b/apps/openmw/mwclass/clothing.hpp index b7084a4d5..b27767c13 100644 --- a/apps/openmw/mwclass/clothing.hpp +++ b/apps/openmw/mwclass/clothing.hpp @@ -34,6 +34,9 @@ namespace MWClass /// Return the index of the skill this item corresponds to when equiopped or -1, if there is /// no such skill. + virtual int getValue (const MWWorld::Ptr& ptr) const; + ///< Return trade value of the object. Throws an exception, if the object can't be traded. + static void registerSelf(); virtual std::string getUpSoundId (const MWWorld::Ptr& ptr, const MWWorld::Environment& environment) const; diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index dd5f3add3..f8fda0d5e 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -87,7 +87,7 @@ namespace MWClass { // TODO check for key std::cout << "Locked container" << std::endl; - environment.mSoundManager->playSound3D (ptr, lockedSound, 1.0, 1.0, false); + environment.mSoundManager->playSound3D (ptr, lockedSound, 1.0, 1.0); return boost::shared_ptr (new MWWorld::NullAction); } else @@ -103,7 +103,7 @@ namespace MWClass { // Trap activation goes here std::cout << "Activated trap: " << ptr.getCellRef().trap << std::endl; - environment.mSoundManager->playSound3D (ptr, trapActivationSound, 1.0, 1.0, false); + environment.mSoundManager->playSound3D (ptr, trapActivationSound, 1.0, 1.0); ptr.getCellRef().trap = ""; return boost::shared_ptr (new MWWorld::NullAction); } diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 5654dff69..9d6c6a78d 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -73,7 +73,7 @@ namespace MWClass // TODO check for key // TODO report failure to player (message, sound?). Look up behaviour of original MW. std::cout << "Locked!" << std::endl; - environment.mSoundManager->playSound3D (ptr, lockedSound, 1.0, 1.0, false); + environment.mSoundManager->playSound3D (ptr, lockedSound, 1.0, 1.0); return boost::shared_ptr (new MWWorld::NullAction); } @@ -81,7 +81,7 @@ namespace MWClass { // Trap activation std::cout << "Activated trap: " << ptr.getCellRef().trap << std::endl; - environment.mSoundManager->playSound3D(ptr, trapActivationSound, 1.0, 1.0, false); + environment.mSoundManager->playSound3D(ptr, trapActivationSound, 1.0, 1.0); ptr.getCellRef().trap = ""; return boost::shared_ptr (new MWWorld::NullAction); } @@ -110,7 +110,7 @@ namespace MWClass // TODO return action for rotating the door // This is a little pointless, but helps with testing - environment.mSoundManager->playSound3D (ptr, openSound, 1.0, 1.0, false); + environment.mSoundManager->playSound3D (ptr, openSound, 1.0, 1.0); return boost::shared_ptr (new MWWorld::NullAction); } } diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index 7b96817e9..25b6eda3b 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -54,7 +54,7 @@ namespace MWClass boost::shared_ptr Ingredient::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const { - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); @@ -68,6 +68,14 @@ namespace MWClass return ref->base->script; } + int Ingredient::getValue (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return ref->base->data.value; + } + void Ingredient::registerSelf() { boost::shared_ptr instance (new Ingredient); diff --git a/apps/openmw/mwclass/ingredient.hpp b/apps/openmw/mwclass/ingredient.hpp index f79534868..198f56b1b 100644 --- a/apps/openmw/mwclass/ingredient.hpp +++ b/apps/openmw/mwclass/ingredient.hpp @@ -25,6 +25,9 @@ namespace MWClass virtual std::string getScript (const MWWorld::Ptr& ptr) const; ///< Return name of the script attached to ptr + virtual int getValue (const MWWorld::Ptr& ptr) const; + ///< Return trade value of the object. Throws an exception, if the object can't be traded. + static void registerSelf(); virtual std::string getUpSoundId (const MWWorld::Ptr& ptr, const MWWorld::Environment& environment) const; diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index 50fc39023..d1c915210 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -59,7 +59,7 @@ namespace MWClass if (!ref->base->sound.empty()) { - environment.mSoundManager->playSound3D (ptr, ref->base->sound, 1.0, 1.0, true); + environment.mSoundManager->playSound3D (ptr, ref->base->sound, 1.0, 1.0, MWSound::Play_Loop); } } @@ -83,7 +83,7 @@ namespace MWClass if (!(ref->base->data.flags & ESM::Light::Carry)) return boost::shared_ptr (new MWWorld::NullAction); - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); @@ -110,6 +110,14 @@ namespace MWClass return std::make_pair (slots, false); } + int Light::getValue (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return ref->base->data.value; + } + void Light::registerSelf() { boost::shared_ptr instance (new Light); diff --git a/apps/openmw/mwclass/light.hpp b/apps/openmw/mwclass/light.hpp index bd04401ce..6dbb42267 100644 --- a/apps/openmw/mwclass/light.hpp +++ b/apps/openmw/mwclass/light.hpp @@ -34,6 +34,9 @@ namespace MWClass ///< \return first: Return IDs of the slot this object can be equipped in; second: can object /// stay stacked when equipped? + virtual int getValue (const MWWorld::Ptr& ptr) const; + ///< Return trade value of the object. Throws an exception, if the object can't be traded. + static void registerSelf(); virtual std::string getUpSoundId (const MWWorld::Ptr& ptr, const MWWorld::Environment& environment) const; diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 49300eb09..20e370ff6 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -58,7 +58,7 @@ namespace MWClass boost::shared_ptr Lockpick::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const { - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); @@ -81,6 +81,14 @@ namespace MWClass return std::make_pair (slots, false); } + int Lockpick::getValue (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return ref->base->data.value; + } + void Lockpick::registerSelf() { boost::shared_ptr instance (new Lockpick); diff --git a/apps/openmw/mwclass/lockpick.hpp b/apps/openmw/mwclass/lockpick.hpp index 4d5938a69..b9e8dc393 100644 --- a/apps/openmw/mwclass/lockpick.hpp +++ b/apps/openmw/mwclass/lockpick.hpp @@ -29,6 +29,9 @@ namespace MWClass ///< \return first: Return IDs of the slot this object can be equipped in; second: can object /// stay stacked when equipped? + virtual int getValue (const MWWorld::Ptr& ptr) const; + ///< Return trade value of the object. Throws an exception, if the object can't be traded. + static void registerSelf(); virtual std::string getUpSoundId (const MWWorld::Ptr& ptr, const MWWorld::Environment& environment) const; diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index cd06d1073..573899543 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -56,7 +56,7 @@ namespace MWClass boost::shared_ptr Miscellaneous::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const { - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); @@ -70,6 +70,14 @@ namespace MWClass return ref->base->script; } + int Miscellaneous::getValue (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return ref->base->data.value; + } + void Miscellaneous::registerSelf() { boost::shared_ptr instance (new Miscellaneous); diff --git a/apps/openmw/mwclass/misc.hpp b/apps/openmw/mwclass/misc.hpp index 39d771dc9..4eb12c6ea 100644 --- a/apps/openmw/mwclass/misc.hpp +++ b/apps/openmw/mwclass/misc.hpp @@ -25,6 +25,9 @@ namespace MWClass virtual std::string getScript (const MWWorld::Ptr& ptr) const; ///< Return name of the script attached to ptr + virtual int getValue (const MWWorld::Ptr& ptr) const; + ///< Return trade value of the object. Throws an exception, if the object can't be traded. + static void registerSelf(); virtual std::string getUpSoundId (const MWWorld::Ptr& ptr, const MWWorld::Environment& environment) const; diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 83a94d27d..43e04ab52 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -53,28 +53,40 @@ namespace MWClass // NPC stats if (!ref->base->faction.empty()) { - // TODO research how initial rank is stored. The information in loadnpc.hpp are at - // best very unclear. - data->mNpcStats.mFactionRank[ref->base->faction] = 0; + if(ref->base->npdt52.gold != -10) + { + data->mNpcStats.mFactionRank[ref->base->faction] = (int)ref->base->npdt52.rank; + } + else + { + data->mNpcStats.mFactionRank[ref->base->faction] = (int)ref->base->npdt12.rank; + } } - for (int i=0; i<27; ++i) - data->mNpcStats.mSkill[i].setBase (ref->base->npdt52.skills[i]); - - // creature stats - data->mCreatureStats.mAttributes[0].set (ref->base->npdt52.strength); - data->mCreatureStats.mAttributes[1].set (ref->base->npdt52.intelligence); - data->mCreatureStats.mAttributes[2].set (ref->base->npdt52.willpower); - data->mCreatureStats.mAttributes[3].set (ref->base->npdt52.agility); - data->mCreatureStats.mAttributes[4].set (ref->base->npdt52.speed); - data->mCreatureStats.mAttributes[5].set (ref->base->npdt52.endurance); - data->mCreatureStats.mAttributes[6].set (ref->base->npdt52.personality); - data->mCreatureStats.mAttributes[7].set (ref->base->npdt52.luck); - data->mCreatureStats.mDynamic[0].set (ref->base->npdt52.health); - data->mCreatureStats.mDynamic[1].set (ref->base->npdt52.mana); - data->mCreatureStats.mDynamic[2].set (ref->base->npdt52.fatigue); - - data->mCreatureStats.mLevel = ref->base->npdt52.level; + if(ref->base->npdt52.gold != -10) + { + for (int i=0; i<27; ++i) + data->mNpcStats.mSkill[i].setBase (ref->base->npdt52.skills[i]); + + // creature stats + data->mCreatureStats.mAttributes[0].set (ref->base->npdt52.strength); + data->mCreatureStats.mAttributes[1].set (ref->base->npdt52.intelligence); + data->mCreatureStats.mAttributes[2].set (ref->base->npdt52.willpower); + data->mCreatureStats.mAttributes[3].set (ref->base->npdt52.agility); + data->mCreatureStats.mAttributes[4].set (ref->base->npdt52.speed); + data->mCreatureStats.mAttributes[5].set (ref->base->npdt52.endurance); + data->mCreatureStats.mAttributes[6].set (ref->base->npdt52.personality); + data->mCreatureStats.mAttributes[7].set (ref->base->npdt52.luck); + data->mCreatureStats.mDynamic[0].set (ref->base->npdt52.health); + data->mCreatureStats.mDynamic[1].set (ref->base->npdt52.mana); + data->mCreatureStats.mDynamic[2].set (ref->base->npdt52.fatigue); + + data->mCreatureStats.mLevel = ref->base->npdt52.level; + } + else + { + //TODO: do something with npdt12 maybe:p + } // \todo add initial container content @@ -281,7 +293,7 @@ namespace MWClass void Npc::registerSelf() { boost::shared_ptr instance (new Npc); - + std::cout << "class npc:" << typeid (ESM::NPC).name(); registerClass (typeid (ESM::NPC).name(), instance); } } diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index a50d19736..6c91a46cd 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -56,7 +56,7 @@ namespace MWClass boost::shared_ptr Potion::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const { - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); @@ -70,6 +70,14 @@ namespace MWClass return ref->base->script; } + int Potion::getValue (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return ref->base->data.value; + } + void Potion::registerSelf() { boost::shared_ptr instance (new Potion); diff --git a/apps/openmw/mwclass/potion.hpp b/apps/openmw/mwclass/potion.hpp index 5eb9a3e46..64dfc4dc7 100644 --- a/apps/openmw/mwclass/potion.hpp +++ b/apps/openmw/mwclass/potion.hpp @@ -25,6 +25,9 @@ namespace MWClass virtual std::string getScript (const MWWorld::Ptr& ptr) const; ///< Return name of the script attached to ptr + virtual int getValue (const MWWorld::Ptr& ptr) const; + ///< Return trade value of the object. Throws an exception, if the object can't be traded. + static void registerSelf(); virtual std::string getUpSoundId (const MWWorld::Ptr& ptr, const MWWorld::Environment& environment) const; diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index 3f8dc2a44..4bc8002df 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -57,7 +57,7 @@ namespace MWClass boost::shared_ptr Probe::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const { - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); @@ -80,6 +80,14 @@ namespace MWClass return std::make_pair (slots, false); } + int Probe::getValue (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return ref->base->data.value; + } + void Probe::registerSelf() { boost::shared_ptr instance (new Probe); diff --git a/apps/openmw/mwclass/probe.hpp b/apps/openmw/mwclass/probe.hpp index 1d9ce7d86..bb46cbdd5 100644 --- a/apps/openmw/mwclass/probe.hpp +++ b/apps/openmw/mwclass/probe.hpp @@ -29,6 +29,9 @@ namespace MWClass ///< \return first: Return IDs of the slot this object can be equipped in; second: can object /// stay stacked when equipped? + virtual int getValue (const MWWorld::Ptr& ptr) const; + ///< Return trade value of the object. Throws an exception, if the object can't be traded. + static void registerSelf(); virtual std::string getUpSoundId (const MWWorld::Ptr& ptr, const MWWorld::Environment& environment) const; diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index bafb84bca..171f9d411 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -56,7 +56,7 @@ namespace MWClass boost::shared_ptr Repair::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const { - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); @@ -70,6 +70,14 @@ namespace MWClass return ref->base->script; } + int Repair::getValue (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return ref->base->data.value; + } + void Repair::registerSelf() { boost::shared_ptr instance (new Repair); diff --git a/apps/openmw/mwclass/repair.hpp b/apps/openmw/mwclass/repair.hpp index 3b8260f9d..c4c4c987e 100644 --- a/apps/openmw/mwclass/repair.hpp +++ b/apps/openmw/mwclass/repair.hpp @@ -25,6 +25,9 @@ namespace MWClass virtual std::string getScript (const MWWorld::Ptr& ptr) const; ///< Return name of the script attached to ptr + virtual int getValue (const MWWorld::Ptr& ptr) const; + ///< Return trade value of the object. Throws an exception, if the object can't be traded. + static void registerSelf(); virtual std::string getUpSoundId (const MWWorld::Ptr& ptr, const MWWorld::Environment& environment) const; diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 9d3d40646..216f8a01c 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -57,7 +57,7 @@ namespace MWClass boost::shared_ptr Weapon::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const { - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); @@ -139,6 +139,14 @@ namespace MWClass return -1; } + int Weapon::getValue (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return ref->base->data.value; + } + void Weapon::registerSelf() { boost::shared_ptr instance (new Weapon); diff --git a/apps/openmw/mwclass/weapon.hpp b/apps/openmw/mwclass/weapon.hpp index 31fee9b4b..2f83b25d5 100644 --- a/apps/openmw/mwclass/weapon.hpp +++ b/apps/openmw/mwclass/weapon.hpp @@ -40,6 +40,9 @@ namespace MWClass /// Return the index of the skill this item corresponds to when equiopped or -1, if there is /// no such skill. + virtual int getValue (const MWWorld::Ptr& ptr) const; + ///< Return trade value of the object. Throws an exception, if the object can't be traded. + static void registerSelf(); virtual std::string getUpSoundId (const MWWorld::Ptr& ptr, const MWWorld::Environment& environment) const; diff --git a/apps/openmw/mwdialogue/dialoguemanager.cpp b/apps/openmw/mwdialogue/dialoguemanager.cpp index 50549f4a5..90f0c0231 100644 --- a/apps/openmw/mwdialogue/dialoguemanager.cpp +++ b/apps/openmw/mwdialogue/dialoguemanager.cpp @@ -39,6 +39,9 @@ #include "../mwscript/interpretercontext.hpp" #include +#include "../mwclass/npc.hpp" +#include "../mwmechanics/npcstats.hpp" + namespace { std::string toLower (const std::string& name) @@ -109,16 +112,15 @@ namespace switch (world.getGlobalVariableType (name)) { case 's': - - return selectCompare (comp, value, world.getGlobalVariable (name).mShort); + return selectCompare (comp, world.getGlobalVariable (name).mShort, value); case 'l': - return selectCompare (comp, value, world.getGlobalVariable (name).mLong); + return selectCompare (comp, world.getGlobalVariable (name).mLong, value); case 'f': - return selectCompare (comp, value, world.getGlobalVariable (name).mFloat); + return selectCompare (comp, world.getGlobalVariable (name).mFloat, value); case ' ': @@ -178,7 +180,17 @@ namespace MWDialogue break; case 46://Same faction - if(!selectCompare(comp,0,select.i)) return false; + { + MWMechanics::NpcStats PCstats = MWWorld::Class::get(mEnvironment.mWorld->getPlayer().getPlayer()).getNpcStats(mEnvironment.mWorld->getPlayer().getPlayer()); + MWMechanics::NpcStats NPCstats = MWWorld::Class::get(actor).getNpcStats(actor); + int sameFaction = 0; + if(!NPCstats.mFactionRank.empty()) + { + std::string NPCFaction = NPCstats.mFactionRank.begin()->first; + if(PCstats.mFactionRank.find(NPCFaction) != PCstats.mFactionRank.end()) sameFaction = 1; + } + if(!selectCompare(comp,sameFaction,select.i)) return false; + } break; case 48://Detected @@ -190,7 +202,6 @@ namespace MWDialogue break; case 50://choice - if(choice) { if(!selectCompare(comp,mChoice,select.i)) return false; @@ -270,7 +281,7 @@ namespace MWDialogue { case '1': // function - return true; // TODO implement functions + return true; // Done elsewhere. case '2': // global @@ -444,9 +455,6 @@ namespace MWDialogue if (toLower (info.actor)!=MWWorld::Class::get (actor).getId (actor)) return false; - //PC Faction - if(!info.pcFaction.empty()) return false; - //NPC race if (!info.race.empty()) { @@ -474,26 +482,37 @@ namespace MWDialogue //NPC faction if (!info.npcFaction.empty()) { - ESMS::LiveCellRef *cellRef = actor.get(); - - if (!cellRef) - return false; - - if (toLower (info.npcFaction)!=toLower (cellRef->base->faction)) - return false; - - //check NPC rank - if(cellRef->base->npdt52.gold != -10) + //MWWorld::Class npcClass = MWWorld::Class::get(actor); + MWMechanics::NpcStats stats = MWWorld::Class::get(actor).getNpcStats(actor); + std::map::iterator it = stats.mFactionRank.find(info.npcFaction); + if(it!=stats.mFactionRank.end()) { - if(cellRef->base->npdt52.rank < info.data.rank) return false; + //check rank + if(it->second < (int)info.data.rank) return false; } else { - if(cellRef->base->npdt12.rank < info.data.rank) return false; + //not in the faction + return false; } } // TODO check player faction + if(!info.pcFaction.empty()) + { + MWMechanics::NpcStats stats = MWWorld::Class::get(mEnvironment.mWorld->getPlayer().getPlayer()).getNpcStats(mEnvironment.mWorld->getPlayer().getPlayer()); + std::map::iterator it = stats.mFactionRank.find(info.pcFaction); + if(it!=stats.mFactionRank.end()) + { + //check rank + if(it->second < (int)info.data.PCrank) return false; + } + else + { + //not in the faction + return false; + } + } //check gender ESMS::LiveCellRef* npc = actor.get(); @@ -528,6 +547,13 @@ namespace MWDialogue mChoice = -1; mIsInChoice = false; mCompilerContext.setExtensions (&extensions); + mDialogueMap.clear(); + actorKnownTopics.clear(); + ESMS::RecListT::MapType dialogueList = mEnvironment.mWorld->getStore().dialogs.list; + for(ESMS::RecListT::MapType::iterator it = dialogueList.begin(); it!=dialogueList.end();it++) + { + mDialogueMap[it->first] = it->second; + } } void DialogueManager::addTopic(std::string topic) @@ -563,13 +589,7 @@ namespace MWDialogue mActor = actor; - mDialogueMap.clear(); actorKnownTopics.clear(); - ESMS::RecListT::MapType dialogueList = mEnvironment.mWorld->getStore().dialogs.list; - for(ESMS::RecListT::MapType::iterator it = dialogueList.begin(); it!=dialogueList.end();it++) - { - mDialogueMap[it->first] = it->second; - } //initialise the GUI mEnvironment.mInputManager->setGuiMode(MWGui::GM_Dialogue); @@ -582,6 +602,7 @@ namespace MWDialogue //greeting bool greetingFound = false; //ESMS::RecListT::MapType dialogueList = mEnvironment.mWorld->getStore().dialogs.list; + ESMS::RecListT::MapType dialogueList = mEnvironment.mWorld->getStore().dialogs.list; for(ESMS::RecListT::MapType::iterator it = dialogueList.begin(); it!=dialogueList.end();it++) { ESM::Dialogue ndialogue = it->second; @@ -656,6 +677,7 @@ namespace MWDialogue void DialogueManager::executeScript(std::string script) { + std::cout << script; std::vector code; if(compile(script,code)) { @@ -797,4 +819,19 @@ namespace MWDialogue mChoiceMap[question] = choice; mIsInChoice = true; } + + std::string DialogueManager::getFaction() + { + std::string factionID(""); + MWMechanics::NpcStats stats = MWWorld::Class::get(mActor).getNpcStats(mActor); + if(stats.mFactionRank.empty()) + { + std::cout << "No faction for this actor!"; + } + else + { + factionID = stats.mFactionRank.begin()->first; + } + return factionID; + } } diff --git a/apps/openmw/mwdialogue/dialoguemanager.hpp b/apps/openmw/mwdialogue/dialoguemanager.hpp index 260d8e339..d0380fa71 100644 --- a/apps/openmw/mwdialogue/dialoguemanager.hpp +++ b/apps/openmw/mwdialogue/dialoguemanager.hpp @@ -63,6 +63,9 @@ namespace MWDialogue void askQuestion(std::string question,int choice); + ///get the faction of the actor you are talking with + std::string getFaction(); + //calbacks for the GUI void keywordSelected(std::string keyword); void goodbyeSelected(); diff --git a/apps/openmw/mwgui/cursorreplace.cpp b/apps/openmw/mwgui/cursorreplace.cpp new file mode 100644 index 000000000..2079538fc --- /dev/null +++ b/apps/openmw/mwgui/cursorreplace.cpp @@ -0,0 +1,16 @@ +#include "cursorreplace.hpp" + +#include +#include + +#include +#include + +using namespace MWGui; + +CursorReplace::CursorReplace() +{ + OEngine::Render::ImageRotate::rotate("textures\\tx_cursormove.dds", "mwpointer_vresize.png", 90); + OEngine::Render::ImageRotate::rotate("textures\\tx_cursormove.dds", "mwpointer_dresize1.png", -45); + OEngine::Render::ImageRotate::rotate("textures\\tx_cursormove.dds", "mwpointer_dresize2.png", 45); +} diff --git a/apps/openmw/mwgui/cursorreplace.hpp b/apps/openmw/mwgui/cursorreplace.hpp new file mode 100644 index 000000000..06fe28e39 --- /dev/null +++ b/apps/openmw/mwgui/cursorreplace.hpp @@ -0,0 +1,16 @@ +#ifndef GAME_CURSORREPLACE_H +#define GAME_CURSORREPLACE_H + +#include + +namespace MWGui +{ + /// \brief MyGUI does not support rotating cursors, so we have to do it manually + class CursorReplace + { + public: + CursorReplace(); + }; +} + +#endif diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index d6c4ce4e5..ac6681e27 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -43,9 +43,6 @@ DialogueWindow::DialogueWindow(WindowManager& parWindowManager,MWWorld::Environm // Centre dialog center(); - //WindowManager *wm = environment.mWindowManager; - setText("NpcName", "Name of character"); - //History view getWidget(history, "History"); history->setOverflowToTheLeft(true); @@ -116,7 +113,8 @@ void DialogueWindow::onSelectTopic(MyGUI::ListBox* _sender, size_t _index) void DialogueWindow::startDialogue(std::string npcName) { - setText("NpcName", npcName); + static_cast(mMainWidget)->setCaption(npcName); + adjustWindowCaption(); } void DialogueWindow::setKeywords(std::list keyWords) diff --git a/apps/openmw/mwgui/layouts.cpp b/apps/openmw/mwgui/layouts.cpp index 5c5a977d3..21302d7c1 100644 --- a/apps/openmw/mwgui/layouts.cpp +++ b/apps/openmw/mwgui/layouts.cpp @@ -15,6 +15,27 @@ 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) + , hmsBaseLeft(0) + , weapBoxBaseLeft(0) + , spellBoxBaseLeft(0) + , effectBoxBaseRight(0) + , minimapBoxBaseRight(0) { setCoord(0,0, width, height); @@ -22,32 +43,32 @@ HUD::HUD(int width, int height, int fpsLevel) getWidget(health, "Health"); getWidget(magicka, "Magicka"); getWidget(stamina, "Stamina"); + hmsBaseLeft = health->getLeft(); // Item and spell images and status bars + getWidget(weapBox, "WeapBox"); getWidget(weapImage, "WeapImage"); getWidget(weapStatus, "WeapStatus"); + weapBoxBaseLeft = weapBox->getLeft(); + + getWidget(spellBox, "SpellBox"); getWidget(spellImage, "SpellImage"); getWidget(spellStatus, "SpellStatus"); + spellBoxBaseLeft = spellBox->getLeft(); getWidget(effectBox, "EffectBox"); getWidget(effect1, "Effect1"); + effectBoxBaseRight = effectBox->getRight(); + getWidget(minimapBox, "MiniMapBox"); + minimapBoxBaseRight = minimapBox->getRight(); getWidget(minimap, "MiniMap"); getWidget(compass, "Compass"); getWidget(crosshair, "Crosshair"); - if ( fpsLevel == 2 ){ - getWidget(fpsbox, "FPSBoxAdv"); - fpsbox->setVisible(true); - getWidget(fpscounter, "FPSCounterAdv"); - }else if ( fpsLevel == 1 ){ - getWidget(fpsbox, "FPSBox"); - fpsbox->setVisible(true); - getWidget(fpscounter, "FPSCounter"); - }else{ - getWidget(fpscounter, "FPSCounter"); - } + setFpsLevel(fpsLevel); + getWidget(trianglecounter, "TriangleCounter"); getWidget(batchcounter, "BatchCounter"); @@ -65,6 +86,28 @@ HUD::HUD(int width, int height, int fpsLevel) LocalMapBase::init(minimap, this); } +void HUD::setFpsLevel(int level) +{ + MyGUI::Widget* fps; + getWidget(fps, "FPSBoxAdv"); + fps->setVisible(false); + getWidget(fps, "FPSBox"); + fps->setVisible(false); + + if (level == 2) + { + getWidget(fpsbox, "FPSBoxAdv"); + fpsbox->setVisible(true); + getWidget(fpscounter, "FPSCounterAdv"); + } + else if (level == 1) + { + getWidget(fpsbox, "FPSBox"); + fpsbox->setVisible(true); + getWidget(fpscounter, "FPSCounter"); + } +} + void HUD::setFPS(float fps) { fpscounter->setCaption(boost::lexical_cast((int)fps)); @@ -147,15 +190,21 @@ void HUD::setValue(const std::string& id, const MWMechanics::DynamicStat& v void HUD::setPlayerDir(const float x, const float y) { + if (!minimapBox->getVisible() || (x == mLastPositionX && y == mLastPositionY)) return; + 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); + mLastPositionX = x; + mLastPositionY = y; } void HUD::setPlayerPos(const float x, const float y) { + if (!minimapBox->getVisible() || (x == mLastDirectionX && y == mLastDirectionY)) return; + 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(); @@ -163,110 +212,95 @@ void HUD::setPlayerPos(const float x, const float y) minimap->setViewOffset(pos); compass->setPosition(MyGUI::IntPoint(x*512-16, y*512-16)); + + mLastDirectionX = x; + mLastDirectionY = y; } -MapWindow::MapWindow() - : Layout("openmw_map_window_layout.xml"), mGlobal(false) +void HUD::setBottomLeftVisibility(bool hmsVisible, bool weapVisible, bool spellVisible) { - 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); + int weapDx = 0, spellDx = 0; + if (!hmsVisible) + spellDx = weapDx = weapBoxBaseLeft - hmsBaseLeft; + + if (!weapVisible) + spellDx -= spellBoxBaseLeft - weapBoxBaseLeft; + + health->setVisible(hmsVisible); + stamina->setVisible(hmsVisible); + magicka->setVisible(hmsVisible); + weapBox->setPosition(weapBoxBaseLeft - weapDx, weapBox->getTop()); + weapBox->setVisible(weapVisible); + spellBox->setPosition(spellBoxBaseLeft - spellDx, spellBox->getTop()); + spellBox->setVisible(spellVisible); } -void MapWindow::setVisible(bool b) +void HUD::setBottomRightVisibility(bool effectBoxVisible, bool minimapBoxVisible) { - mMainWidget->setVisible(b); - if (b) - mVisible = true; - else - mVisible = false; + // effect box can have variable width -> variable left coordinate + int effectsDx = 0; + if (!minimapBoxVisible) + effectsDx = minimapBoxBaseRight - effectBoxBaseRight; + + minimapBox->setVisible(minimapBoxVisible); + effectBox->setPosition(effectBoxBaseRight - effectBox->getWidth() + effectsDx, effectBox->getTop()); + effectBox->setVisible(effectBoxVisible); } -void MapWindow::setCellName(const std::string& cellName) +LocalMapBase::LocalMapBase() + : mCurX(0) + , mCurY(0) + , mInterior(false) + , mFogOfWar(true) + , mLocalMap(NULL) + , mPrefix() + , mChanged(true) + , mLayout(NULL) + , mLastPositionX(0.0f) + , mLastPositionY(0.0f) + , mLastDirectionX(0.0f) + , mLastDirectionY(0.0f) { - static_cast(mMainWidget)->setCaption(cellName); } -void MapWindow::setPlayerPos(const float x, const float y) +void LocalMapBase::init(MyGUI::ScrollView* widget, OEngine::GUI::Layout* layout) { - 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)); + mLocalMap = widget; + mLayout = layout; } -void MapWindow::setPlayerDir(const float x, const float y) +void LocalMapBase::setCellPrefix(const std::string& prefix) { - 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); + mPrefix = prefix; + mChanged = true; } -void MapWindow::onDragStart(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) +void LocalMapBase::toggleFogOfWar() { - if (_id!=MyGUI::MouseButton::Left) return; - if (!mGlobal) - mLastDragPos = MyGUI::IntPoint(_left, _top); + mFogOfWar = !mFogOfWar; + applyFogOfWar(); } -void MapWindow::onMouseDrag(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) +void LocalMapBase::applyFogOfWar() { - if (_id!=MyGUI::MouseButton::Left) return; - - if (!mGlobal) + for (int mx=0; mx<3; ++mx) { - MyGUI::IntPoint diff = MyGUI::IntPoint(_left, _top) - mLastDragPos; - mLocalMap->setViewOffset( mLocalMap->getViewOffset() + diff ); - - mLastDragPos = MyGUI::IntPoint(_left, _top); + 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(mCurX + (mx-1)) + "_" + + boost::lexical_cast(mCurY + (mInterior ? (my-1) : -1*(my-1))); + MyGUI::ImageBox* fog; + mLayout->getWidget(fog, name+"_fog"); + fog->setImageTexture(mFogOfWar ? + ((MyGUI::RenderManager::getInstance().getTexture(image+"_fog") != 0) ? image+"_fog" + : "black.png" ) + : ""); + } } } -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 @@ -282,23 +316,17 @@ void LocalMapBase::setActiveCell(const int x, const int y, bool interior) 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; + applyFogOfWar(); } diff --git a/apps/openmw/mwgui/layouts.hpp b/apps/openmw/mwgui/layouts.hpp index 8d9a41a22..19d96d2ef 100644 --- a/apps/openmw/mwgui/layouts.hpp +++ b/apps/openmw/mwgui/layouts.hpp @@ -34,19 +34,30 @@ namespace MWGui class LocalMapBase { public: + LocalMapBase(); 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); + void toggleFogOfWar(); + protected: int mCurX, mCurY; bool mInterior; MyGUI::ScrollView* mLocalMap; std::string mPrefix; bool mChanged; + bool mFogOfWar; + + void applyFogOfWar(); OEngine::GUI::Layout* mLayout; + + float mLastPositionX; + float mLastPositionY; + float mLastDirectionX; + float mLastDirectionY; }; class HUD : public OEngine::GUI::Layout, public LocalMapBase @@ -65,11 +76,15 @@ namespace MWGui void setBatchCount(size_t count); void setPlayerDir(const float x, const float y); void setPlayerPos(const float x, const float y); + void setBottomLeftVisibility(bool hmsVisible, bool weapVisible, bool spellVisible); + void setBottomRightVisibility(bool effectBoxVisible, bool minimapVisible); + void setFpsLevel(const int level); MyGUI::ProgressPtr health, magicka, stamina; + MyGUI::Widget *weapBox, *spellBox; MyGUI::ImageBox *weapImage, *spellImage; MyGUI::ProgressPtr weapStatus, spellStatus; - MyGUI::WidgetPtr effectBox; + MyGUI::Widget *effectBox, *minimapBox; MyGUI::ImageBox* effect1; MyGUI::ScrollView* minimap; MyGUI::ImageBox* compass; @@ -79,29 +94,12 @@ namespace MWGui MyGUI::TextBox* fpscounter; MyGUI::TextBox* trianglecounter; MyGUI::TextBox* batchcounter; - }; - - class MapWindow : public OEngine::GUI::Layout, public LocalMapBase - { - public: - MapWindow(); - 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); - - MyGUI::ScrollView* mGlobalMap; - MyGUI::ImageBox* mPlayerArrow; - MyGUI::Button* mButton; - MyGUI::IntPoint mLastDragPos; - bool mVisible; - bool mGlobal; + // bottom left elements + int hmsBaseLeft, weapBoxBaseLeft, spellBoxBaseLeft; + // bottom right elements + int minimapBoxBaseRight, effectBoxBaseRight; }; class MainMenu : public OEngine::GUI::Layout diff --git a/apps/openmw/mwgui/map_window.cpp b/apps/openmw/mwgui/map_window.cpp new file mode 100644 index 000000000..0e9d57c3c --- /dev/null +++ b/apps/openmw/mwgui/map_window.cpp @@ -0,0 +1,106 @@ +#include "map_window.hpp" +#include "window_manager.hpp" +/* +#include "../mwmechanics/mechanicsmanager.hpp" + +#include +#include +#include + +#undef min +#undef max +*/ +using namespace MWGui; + +MapWindow::MapWindow(WindowManager& parWindowManager) : + MWGui::WindowPinnableBase("openmw_map_window_layout.xml", parWindowManager), + 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::setCellName(const std::string& cellName) +{ + static_cast(mMainWidget)->setCaption(cellName); + adjustWindowCaption(); +} + +void MapWindow::setPlayerPos(const float x, const float y) +{ + if (mGlobal || !mVisible || (x == mLastPositionX && y == mLastPositionY)) 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)); + mLastPositionX = x; + mLastPositionY = y; +} + +void MapWindow::setPlayerDir(const float x, const float y) +{ + if (!mVisible || (x == mLastDirectionX && y == mLastDirectionY)) 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); + + mLastDirectionX = x; + mLastDirectionY = y; +} + +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 MapWindow::onPinToggled() +{ + mWindowManager.setMinimapVisibility(!mPinned); +} diff --git a/apps/openmw/mwgui/map_window.hpp b/apps/openmw/mwgui/map_window.hpp new file mode 100644 index 000000000..d14221a40 --- /dev/null +++ b/apps/openmw/mwgui/map_window.hpp @@ -0,0 +1,34 @@ +#ifndef MWGUI_MAPWINDOW_H +#define MWGUI_MAPWINDOW_H + +#include "layouts.hpp" +#include "window_pinnable_base.hpp" + +namespace MWGui +{ + class MapWindow : public MWGui::WindowPinnableBase, public LocalMapBase + { + public: + MapWindow(WindowManager& parWindowManager); + virtual ~MapWindow(){} + + 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); + + MyGUI::ScrollView* mGlobalMap; + MyGUI::ImageBox* mPlayerArrow; + MyGUI::Button* mButton; + MyGUI::IntPoint mLastDragPos; + bool mGlobal; + + protected: + virtual void onPinToggled(); + }; +} +#endif 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..675e5141f 100644 --- a/apps/openmw/mwgui/stats_window.cpp +++ b/apps/openmw/mwgui/stats_window.cpp @@ -12,10 +12,23 @@ using namespace MWGui; const int StatsWindow::lineHeight = 18; StatsWindow::StatsWindow (WindowManager& parWindowManager) - : WindowBase("openmw_stats_window_layout.xml", parWindowManager) + : WindowPinnableBase("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); @@ -368,3 +381,8 @@ void StatsWindow::updateScroller() skillScrollerWidget->setScrollRange(std::max(clientHeight - skillClientWidget->getHeight(), 0)); skillScrollerWidget->setScrollPage(std::max(skillClientWidget->getHeight() - lineHeight, 0)); } + +void StatsWindow::onPinToggled() +{ + mWindowManager.setHMSVisibility(!mPinned); +} diff --git a/apps/openmw/mwgui/stats_window.hpp b/apps/openmw/mwgui/stats_window.hpp index 2ff170f57..f2731e545 100644 --- a/apps/openmw/mwgui/stats_window.hpp +++ b/apps/openmw/mwgui/stats_window.hpp @@ -9,13 +9,13 @@ #include #include "../mwmechanics/stat.hpp" -#include "window_base.hpp" +#include "window_pinnable_base.hpp" namespace MWGui { class WindowManager; - class StatsWindow : public WindowBase + class StatsWindow : public WindowPinnableBase { public: typedef std::pair Faction; @@ -74,6 +74,9 @@ namespace MWGui std::string birthSignId; int reputation, bounty; std::vector skillWidgets; //< Skills and other information + + protected: + virtual void onPinToggled(); }; } #endif 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 741066896..06bbb3a30 100644 --- a/apps/openmw/mwgui/window_manager.cpp +++ b/apps/openmw/mwgui/window_manager.cpp @@ -4,6 +4,7 @@ #include "review.hpp" #include "dialogue.hpp" #include "dialogue_history.hpp" +#include "map_window.hpp" #include "stats_window.hpp" #include "messagebox.hpp" #include "container.hpp" @@ -15,6 +16,8 @@ #include "journalwindow.hpp" #include "charactercreation.hpp" +#include + #include #include #include @@ -23,15 +26,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); @@ -47,7 +75,7 @@ WindowManager::WindowManager(MWWorld::Environment& environment, hud = new HUD(w,h, showFPSLevel); menu = new MainMenu(w,h); - map = new MapWindow(); + map = new MapWindow(*this); stats = new StatsWindow(*this); console = new Console(w,h, environment, extensions); mJournal = new JournalWindow(*this); @@ -157,71 +185,58 @@ void WindowManager::updateVisible() // Mouse is visible whenever we're not in game mode MyGUI::PointerManager::getInstance().setVisible(isGuiMode()); - // If in game mode, don't show anything. - if(mode == GM_Game) //Use a switch/case structure - { - return; - } - - if(mode == GM_MainMenu) - { - // Enable the main menu - menu->setVisible(true); - return; - } - - if(mode == GM_Console) - { - console->enable(); - return; - } - - //There must be a more elegant solution - if (mode == GM_Name || mode == GM_Race || mode == GM_Class || mode == GM_ClassPick || mode == GM_ClassCreate || mode == GM_Birth || mode == GM_ClassGenerate || mode == GM_Review) - { - mCharGen->spawnDialog(mode); - return; - } - - if(mode == GM_Inventory) - { - // Ah, inventory mode. First, compute the effective set of - // windows to show. This is controlled both by what windows the - // user has opened/closed (the 'shown' variable) and by what - // windows we are allowed to show (the 'allowed' var.) - int eff = shown & allowed; - - // Show the windows we want - map -> setVisible( (eff & GW_Map) != 0 ); - stats -> setVisible( (eff & GW_Stats) != 0 ); - return; - } - - if (mode == GM_Dialogue) - { - dialogueWindow->open(); - return; - } - - if(mode == GM_InterMessageBox) - { - if(!mMessageBoxManager->isInteractiveMessageBox()) { - setGuiMode(GM_Game); + switch(mode) { + case GM_Game: + // If in game mode, don't show anything. + break; + case GM_MainMenu: + menu->setVisible(true); + break; + case GM_Console: + console->enable(); + break; + case GM_Name: + case GM_Race: + case GM_Class: + case GM_ClassPick: + case GM_ClassCreate: + case GM_Birth: + case GM_ClassGenerate: + case GM_Review: + mCharGen->spawnDialog(mode); + break; + case GM_Inventory: + { + // First, compute the effective set of windows to show. + // This is controlled both by what windows the + // user has opened/closed (the 'shown' variable) and by what + // windows we are allowed to show (the 'allowed' var.) + int eff = shown & allowed; + + // Show the windows we want + map -> setVisible( (eff & GW_Map) != 0 ); + stats -> setVisible( (eff & GW_Stats) != 0 ); + break; } - return; - } - - if(mode == GM_Journal) - { - mJournal->setVisible(true); - mJournal->open(); - return; + case GM_Dialogue: + dialogueWindow->open(); + break; + case GM_InterMessageBox: + if(!mMessageBoxManager->isInteractiveMessageBox()) { + setGuiMode(GM_Game); + } + break; + case GM_Journal: + mJournal->setVisible(true); + mJournal->open(); + break; + default: + // Unsupported mode, switch back to game + // Note: The call will eventually end up this method again but + // will stop at the check if mode is GM_Game. + setGuiMode(GM_Game); + break; } - - // Unsupported mode, switch back to game - // Note: The call will eventually end up this method again but - // will stop at the check if(mode == GM_Game) above. - setGuiMode(GM_Game); } void WindowManager::setValue (const std::string& id, const MWMechanics::Stat& value) @@ -348,7 +363,6 @@ void WindowManager::updateSkillArea() void WindowManager::removeDialog(OEngine::GUI::Layout*dialog) { - std::cout << "dialogue a la poubelle"; assert(dialog); if (!dialog) return; @@ -446,3 +460,27 @@ void WindowManager::setPlayerDir(const float x, const float y) map->setPlayerDir(x,y); hud->setPlayerDir(x,y); } + +void WindowManager::setHMSVisibility(bool visible) +{ + hud->setBottomLeftVisibility(visible, hud->weapBox->getVisible(), hud->spellBox->getVisible()); +} + +void WindowManager::setMinimapVisibility(bool visible) +{ + hud->setBottomRightVisibility(hud->effectBox->getVisible(), visible); +} + +void WindowManager::toggleFogOfWar() +{ + map->toggleFogOfWar(); + hud->toggleFogOfWar(); +} + +int WindowManager::toggleFps() +{ + showFPSLevel = (showFPSLevel+1)%3; + hud->setFpsLevel(showFPSLevel); + Settings::Manager::setInt("fps", "HUD", showFPSLevel); + return showFPSLevel; +} diff --git a/apps/openmw/mwgui/window_manager.hpp b/apps/openmw/mwgui/window_manager.hpp index cbe0edad9..4f54b41b8 100644 --- a/apps/openmw/mwgui/window_manager.hpp +++ b/apps/openmw/mwgui/window_manager.hpp @@ -158,10 +158,20 @@ namespace MWGui 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 toggleFogOfWar(); + + int toggleFps(); + ///< toggle fps display @return resulting fps level + void setInteriorMapTexture(const int x, const int y); ///< set the index of the map texture that should be used (for interiors) + // sets the visibility of the hud health/magicka/stamina bars + void setHMSVisibility(bool visible); + // sets the visibility of the hud minimap + void setMinimapVisibility(bool visible); + template void removeDialog(T*& dialog); ///< Casts to OEngine::GUI::Layout and calls removeDialog, then resets pointer to nullptr. void removeDialog(OEngine::GUI::Layout* dialog); ///< Hides dialog and schedules dialog to be deleted. diff --git a/apps/openmw/mwgui/window_pinnable_base.cpp b/apps/openmw/mwgui/window_pinnable_base.cpp new file mode 100644 index 000000000..ecdf311c6 --- /dev/null +++ b/apps/openmw/mwgui/window_pinnable_base.cpp @@ -0,0 +1,33 @@ +#include "window_pinnable_base.hpp" +#include "window_manager.hpp" + +using namespace MWGui; + +WindowPinnableBase::WindowPinnableBase(const std::string& parLayout, WindowManager& parWindowManager) + : WindowBase(parLayout, parWindowManager), mPinned(false), mVisible(false) +{ + MyGUI::WindowPtr t = static_cast(mMainWidget); + t->eventWindowButtonPressed += MyGUI::newDelegate(this, &WindowPinnableBase::onWindowButtonPressed); +} + +void WindowPinnableBase::setVisible(bool b) +{ + // Pinned windows can not be hidden + if (mPinned && !b) + return; + + WindowBase::setVisible(b); + mVisible = b; +} + +void WindowPinnableBase::onWindowButtonPressed(MyGUI::Window* sender, const std::string& eventName) +{ + if ("PinToggle" == eventName) + { + mPinned = !mPinned; + onPinToggled(); + } + + eventDone(this); +} + diff --git a/apps/openmw/mwgui/window_pinnable_base.hpp b/apps/openmw/mwgui/window_pinnable_base.hpp new file mode 100644 index 000000000..8ef38c386 --- /dev/null +++ b/apps/openmw/mwgui/window_pinnable_base.hpp @@ -0,0 +1,28 @@ +#ifndef MWGUI_WINDOW_PINNABLE_BASE_H +#define MWGUI_WINDOW_PINNABLE_BASE_H + +#include "window_base.hpp" + +namespace MWGui +{ + class WindowManager; + + class WindowPinnableBase: public WindowBase + { + public: + WindowPinnableBase(const std::string& parLayout, WindowManager& parWindowManager); + void setVisible(bool b); + + private: + void onWindowButtonPressed(MyGUI::Window* sender, const std::string& eventName); + + protected: + virtual void onPinToggled() = 0; + + bool mPinned; + bool mVisible; + }; +} + +#endif + diff --git a/apps/openmw/mwinput/inputmanager.cpp b/apps/openmw/mwinput/inputmanager.cpp index 88534ddda..9b5a9ae30 100644 --- a/apps/openmw/mwinput/inputmanager.cpp +++ b/apps/openmw/mwinput/inputmanager.cpp @@ -65,12 +65,16 @@ namespace MWInput A_QuickLoad, A_QuickMenu, A_GameMenu, + A_ToggleWeapon, + A_ToggleSpell, + + A_ToggleFps, // Toggle FPS display (this is temporary) A_LAST // Marker for the last item }; // Class that handles all input and key bindings for OpenMW - class InputImpl : public Ogre::FrameListener + class InputImpl { OEngine::Input::DispatcherPtr disp; OEngine::Render::OgreRenderer &ogre; @@ -86,6 +90,43 @@ namespace MWInput /* InputImpl Methods */ + void toggleFps() + { + windows.toggleFps(); + } + + void toggleSpell() + { + DrawState state = player.getDrawState(); + if(state == DrawState_Weapon || state == DrawState_Nothing) + { + player.setDrawState(DrawState_Spell); + std::cout << "Player has now readied his hands for spellcasting!\n"; + } + else + { + player.setDrawState(DrawState_Nothing); + std::cout << "Player does not have any kind of attack ready now.\n"; + } + + } + + void toggleWeapon() + { + DrawState state = player.getDrawState(); + if(state == DrawState_Spell || state == DrawState_Nothing) + { + player.setDrawState(DrawState_Weapon); + std::cout << "Player is now drawing his weapon.\n"; + } + else + { + player.setDrawState(DrawState_Nothing); + std::cout << "Player does not have any kind of attack ready now.\n"; + } + + } + void screenshot() { mEngine.screenshot(); @@ -197,11 +238,14 @@ namespace MWInput "Auto Move"); disp->funcs.bind(A_ToggleWalk, boost::bind(&InputImpl::toggleWalking, this), "Toggle Walk/Run"); - + disp->funcs.bind(A_ToggleWeapon,boost::bind(&InputImpl::toggleWeapon,this), + "Draw Weapon"); + disp->funcs.bind(A_ToggleSpell,boost::bind(&InputImpl::toggleSpell,this), + "Ready hands"); + disp->funcs.bind(A_ToggleFps, boost::bind(&InputImpl::toggleFps, this), + "Toggle FPS display"); // Add the exit listener ogre.getRoot()->addFrameListener(&exit); - // Add ourselves as a frame listener to catch movement keys - ogre.getRoot()->addFrameListener(this); // Set up the mouse handler and tell it about the player camera mouse = MouseLookEventPtr(new MouseLookEvent(player.getRenderer()->getCamera())); @@ -244,6 +288,9 @@ namespace MWInput disp->bind(A_AutoMove, KC_Z); disp->bind(A_ToggleSneak, KC_X); disp->bind(A_ToggleWalk, KC_C); + disp->bind(A_ToggleWeapon,KC_F); + disp->bind(A_ToggleSpell,KC_R); + disp->bind(A_ToggleFps, KC_F10); // Key bindings for polled keys // NOTE: These keys are constantly being polled. Only add keys that must be checked each frame. @@ -262,7 +309,7 @@ namespace MWInput } //NOTE: Used to check for movement keys - bool frameRenderingQueued (const Ogre::FrameEvent &evt) + void update () { // Tell OIS to handle all input events input.capture(); @@ -276,7 +323,7 @@ namespace MWInput windows.update(); // Disable movement in Gui mode - if (windows.isGuiMode()) return true; + if (windows.isGuiMode()) return; // Configure player movement according to keyboard input. Actual movement will // be done in the physics system. @@ -305,8 +352,6 @@ namespace MWInput } else player.setForwardBackward (0); - - return true; } // Switch between gui modes. Besides controlling the Gui windows @@ -358,4 +403,9 @@ namespace MWInput { impl->setGuiMode(mode); } + + void MWInputManager::update() + { + impl->update(); + } } diff --git a/apps/openmw/mwinput/inputmanager.hpp b/apps/openmw/mwinput/inputmanager.hpp index 6b8034c8b..721c77d9f 100644 --- a/apps/openmw/mwinput/inputmanager.hpp +++ b/apps/openmw/mwinput/inputmanager.hpp @@ -48,6 +48,8 @@ namespace MWInput OMW::Engine& engine); ~MWInputManager(); + void update(); + void setGuiMode(MWGui::GuiMode mode); }; } diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp new file mode 100644 index 000000000..7d9f748d4 --- /dev/null +++ b/apps/openmw/mwmechanics/actors.cpp @@ -0,0 +1,77 @@ + +#include "actors.hpp" + +#include + +#include + +#include "../mwworld/class.hpp" +#include "../mwworld/inventorystore.hpp" + +namespace MWMechanics +{ + void Actors::updateActor (const MWWorld::Ptr& ptr, float duration) + { + + } + + void Actors::updateNpc (const MWWorld::Ptr& ptr, float duration, bool paused) + { + if (!paused && ptr.getRefData().getHandle()!="player") + MWWorld::Class::get (ptr).getInventoryStore (ptr).autoEquip ( + MWWorld::Class::get (ptr).getNpcStats (ptr), mEnvironment); + } + + Actors::Actors (MWWorld::Environment& environment) : mEnvironment (environment), mDuration (0) {} + + void Actors::addActor (const MWWorld::Ptr& ptr) + { + mActors.insert (ptr); + } + + void Actors::removeActor (const MWWorld::Ptr& ptr) + { + mActors.erase (ptr); + } + + void Actors::dropActors (const MWWorld::Ptr::CellStore *cellStore) + { + std::set::iterator iter = mActors.begin(); + + while (iter!=mActors.end()) + if (iter->getCell()==cellStore) + { + mActors.erase (iter++); + } + else + ++iter; + } + + void Actors::update (std::vector >& movement, float duration, + bool paused) + { + mDuration += duration; + + if (mDuration>=0.25) + { + for (std::set::iterator iter (mActors.begin()); iter!=mActors.end(); ++iter) + { + updateActor (*iter, mDuration); + + if (iter->getTypeName()==typeid (ESM::NPC).name()) + updateNpc (*iter, mDuration, paused); + } + + mDuration = 0; + } + + for (std::set::iterator iter (mActors.begin()); iter!=mActors.end(); + ++iter) + { + Ogre::Vector3 vector = MWWorld::Class::get (*iter).getMovementVector (*iter); + + if (vector!=Ogre::Vector3::ZERO) + movement.push_back (std::make_pair (iter->getRefData().getHandle(), vector)); + } + } +} diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp new file mode 100644 index 000000000..7ff33b63b --- /dev/null +++ b/apps/openmw/mwmechanics/actors.hpp @@ -0,0 +1,51 @@ +#ifndef GAME_MWMECHANICS_ACTORS_H +#define GAME_MWMECHANICS_ACTORS_H + +#include +#include +#include + +#include "../mwworld/ptr.hpp" + +namespace Ogre +{ + class Vector3; +} + +namespace MWWorld +{ + class Environment; +} + +namespace MWMechanics +{ + class Actors + { + MWWorld::Environment& mEnvironment; + std::set mActors; + float mDuration; + + void updateActor (const MWWorld::Ptr& ptr, float duration); + + void updateNpc (const MWWorld::Ptr& ptr, float duration, bool paused); + + public: + + Actors (MWWorld::Environment& environment); + + void addActor (const MWWorld::Ptr& ptr); + ///< Register an actor for stats management + + void removeActor (const MWWorld::Ptr& ptr); + ///< Deregister an actor for stats management + + void dropActors (const MWWorld::Ptr::CellStore *cellStore); + ///< Deregister all actors in the given cell. + + void update (std::vector >& movement, + float duration, bool paused); + ///< Update actor stats and store desired velocity vectors in \a movement + }; +} + +#endif diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index d2edc031d..ab008da9e 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -6,6 +6,7 @@ #include "stat.hpp" #include "magiceffects.hpp" +#include "spells.hpp" namespace MWMechanics { @@ -14,7 +15,7 @@ namespace MWMechanics Stat mAttributes[8]; DynamicStat mDynamic[3]; // health, magicka, fatigue int mLevel; - std::set mAbilities; + Spells mSpells; MagicEffects mMagicEffects; }; } diff --git a/apps/openmw/mwmechanics/drawstate.hpp b/apps/openmw/mwmechanics/drawstate.hpp new file mode 100644 index 000000000..772086d90 --- /dev/null +++ b/apps/openmw/mwmechanics/drawstate.hpp @@ -0,0 +1,13 @@ +#ifndef GAME_MWMECHANICS_DRAWSTATE_H +#define GAME_MWMECHANICS_DRAWSTATE_H + +#undef DrawState + +enum DrawState +{ + DrawState_Weapon = 0, + DrawState_Spell = 1, + DrawState_Nothing = 2, +}; + +#endif diff --git a/apps/openmw/mwmechanics/mechanicsmanager.cpp b/apps/openmw/mwmechanics/mechanicsmanager.cpp index 7ed81f785..f5711e78e 100644 --- a/apps/openmw/mwmechanics/mechanicsmanager.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanager.cpp @@ -23,7 +23,7 @@ namespace MWMechanics // reset creatureStats.mLevel = player->npdt52.level; - creatureStats.mAbilities.clear(); + creatureStats.mSpells.clear(); creatureStats.mMagicEffects = MagicEffects(); for (int i=0; i<27; ++i) @@ -71,7 +71,7 @@ namespace MWMechanics for (std::vector::const_iterator iter (race->powers.list.begin()); iter!=race->powers.list.end(); ++iter) { - insertSpell (*iter, ptr); + creatureStats.mSpells.add (*iter); } } @@ -85,7 +85,7 @@ namespace MWMechanics for (std::vector::const_iterator iter (sign->powers.list.begin()); iter!=sign->powers.list.end(); ++iter) { - insertSpell (*iter, ptr); + creatureStats.mSpells.add (*iter); } } @@ -159,59 +159,14 @@ namespace MWMechanics creatureStats.mDynamic[i].setCurrent (creatureStats.mDynamic[i].getModified()); } - void MechanicsManager::insertSpell (const std::string& id, MWWorld::Ptr& creature) - { - MWMechanics::CreatureStats& creatureStats = - MWWorld::Class::get (creature).getCreatureStats (creature); - - const ESM::Spell *spell = mEnvironment.mWorld->getStore().spells.find (id); - - switch (spell->data.type) - { - case ESM::Spell::ST_Ability: - - if (creatureStats.mAbilities.find (id)==creatureStats.mAbilities.end()) - { - creatureStats.mAbilities.insert (id); - } - - break; - - // TODO ST_SPELL, ST_Blight, ST_Disease, ST_Curse, ST_Power - - default: - - std::cout - << "adding unsupported spell type (" << spell->data.type - << ") to creature: " << id << std::endl; - } - } - void MechanicsManager::adjustMagicEffects (MWWorld::Ptr& creature) { MWMechanics::CreatureStats& creatureStats = MWWorld::Class::get (creature).getCreatureStats (creature); - MagicEffects now; - - for (std::set::const_iterator iter (creatureStats.mAbilities.begin()); - iter!=creatureStats.mAbilities.end(); ++iter) - { - const ESM::Spell *spell = mEnvironment.mWorld->getStore().spells.find (*iter); + MagicEffects now = creatureStats.mSpells.getMagicEffects (mEnvironment); - for (std::vector::const_iterator iter = spell->effects.list.begin(); - iter!=spell->effects.list.end(); ++iter) - { - if (iter->range==0) // self - { - EffectParam param; - param.mMagnitude = iter->magnMax; // TODO calculate magnitude - now.add (EffectKey (*iter), param); - } - } - } - - // TODO add effects from other spell types, active spells and equipment + /// \todo add effects from active spells and equipment MagicEffects diff = MagicEffects::diff (creatureStats.mMagicEffects, now); @@ -222,14 +177,14 @@ namespace MWMechanics MechanicsManager::MechanicsManager (MWWorld::Environment& environment) : mEnvironment (environment), mUpdatePlayer (true), mClassSelected (false), - mRaceSelected (false) + mRaceSelected (false), mActors (environment) { buildPlayer(); } void MechanicsManager::addActor (const MWWorld::Ptr& ptr) { - mActors.insert (ptr); + mActors.addActor (ptr); } void MechanicsManager::removeActor (const MWWorld::Ptr& ptr) @@ -237,7 +192,7 @@ namespace MWMechanics if (ptr==mWatched) mWatched = MWWorld::Ptr(); - mActors.erase (ptr); + mActors.removeActor (ptr); } void MechanicsManager::dropActors (const MWWorld::Ptr::CellStore *cellStore) @@ -245,16 +200,7 @@ namespace MWMechanics if (!mWatched.isEmpty() && mWatched.getCell()==cellStore) mWatched = MWWorld::Ptr(); - std::set::iterator iter = mActors.begin(); - - while (iter!=mActors.end()) - if (iter->getCell()==cellStore) - { - //std::cout << "Erasing an actor"; - mActors.erase (iter++); - } - else - ++iter; + mActors.dropActors (cellStore); } void MechanicsManager::watchActor (const MWWorld::Ptr& ptr) @@ -262,7 +208,8 @@ namespace MWMechanics mWatched = ptr; } - void MechanicsManager::update (std::vector >& movement) + void MechanicsManager::update (std::vector >& movement, + float duration, bool paused) { if (!mWatched.isEmpty()) { @@ -345,14 +292,7 @@ namespace MWMechanics mEnvironment.mWindowManager->configureSkills (majorSkills, minorSkills); } - for (std::set::iterator iter (mActors.begin()); iter!=mActors.end(); - ++iter) - { - Ogre::Vector3 vector = MWWorld::Class::get (*iter).getMovementVector (*iter); - - if (vector!=Ogre::Vector3::ZERO) - movement.push_back (std::make_pair (iter->getRefData().getHandle(), vector)); - } + mActors.update (movement, duration, paused); } void MechanicsManager::setPlayerName (const std::string& name) diff --git a/apps/openmw/mwmechanics/mechanicsmanager.hpp b/apps/openmw/mwmechanics/mechanicsmanager.hpp index 2e2192638..a121507ce 100644 --- a/apps/openmw/mwmechanics/mechanicsmanager.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanager.hpp @@ -1,7 +1,6 @@ #ifndef GAME_MWMECHANICS_MECHANICSMANAGER_H #define GAME_MWMECHANICS_MECHANICSMANAGER_H -#include #include #include @@ -9,6 +8,7 @@ #include "creaturestats.hpp" #include "npcstats.hpp" +#include "actors.hpp" namespace Ogre { @@ -25,20 +25,18 @@ namespace MWMechanics class MechanicsManager { MWWorld::Environment& mEnvironment; - std::set mActors; MWWorld::Ptr mWatched; CreatureStats mWatchedCreature; NpcStats mWatchedNpc; bool mUpdatePlayer; bool mClassSelected; bool mRaceSelected; + Actors mActors; void buildPlayer(); ///< build player according to stored class/race/birthsign information. Will /// default to the values of the ESM::NPC object, if no explicit information is given. - void insertSpell (const std::string& id, MWWorld::Ptr& creature); - void adjustMagicEffects (MWWorld::Ptr& creature); public: @@ -60,8 +58,12 @@ namespace MWMechanics ///< On each update look for changes in a previously registered actor and update the /// GUI accordingly. - void update (std::vector >& movement); + void update (std::vector >& movement, float duration, + bool paused); ///< Update actor stats and store desired velocity vectors in \a movement + /// + /// \param paused In game type does not currently advance (this usually means some GUI + /// component is up). void setPlayerName (const std::string& name); ///< Set player name. diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index aeb5f56d5..feac5d4d3 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -2,14 +2,19 @@ #define GAME_MWMECHANICS_NPCSTATS_H #include +#include #include "stat.hpp" +#include "drawstate.hpp" namespace MWMechanics { /// \brief Additional stats for NPCs /// /// For non-NPC-specific stats, see the CreatureStats struct. + /// + /// \note For technical reasons the spell list and the currently selected spell is also handled by + /// CreatureStats, even though they are actually NPC stats. struct NpcStats { @@ -24,9 +29,10 @@ namespace MWMechanics bool mRun; bool mSneak; bool mCombat; + DrawState mDrawState; NpcStats() : mForceRun (false), mForceSneak (false), mRun (false), mSneak (false), - mCombat (false) {} + mCombat (false) , mDrawState(DrawState_Nothing) {} }; } diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp new file mode 100644 index 000000000..916239a84 --- /dev/null +++ b/apps/openmw/mwmechanics/spells.cpp @@ -0,0 +1,81 @@ + +#include "spells.hpp" + +#include + +#include "../mwworld/environment.hpp" +#include "../mwworld/world.hpp" + +#include "magiceffects.hpp" + +namespace MWMechanics +{ + void Spells::addSpell (const ESM::Spell *spell, MagicEffects& effects) const + { + for (std::vector::const_iterator iter = spell->effects.list.begin(); + iter!=spell->effects.list.end(); ++iter) + { + EffectParam param; + param.mMagnitude = iter->magnMax; /// \todo calculate magnitude + effects.add (EffectKey (*iter), param); + } + } + + Spells::TIterator Spells::begin() const + { + return mSpells.begin(); + } + + Spells::TIterator Spells::end() const + { + return mSpells.end(); + } + + void Spells::add (const std::string& spellId) + { + if (std::find (mSpells.begin(), mSpells.end(), spellId)!=mSpells.end()) + mSpells.push_back (spellId); + } + + void Spells::remove (const std::string& spellId) + { + TContainer::iterator iter = std::find (mSpells.begin(), mSpells.end(), spellId); + + if (iter!=mSpells.end()) + mSpells.erase (iter); + + if (spellId==mSelectedSpell) + mSelectedSpell.clear(); + } + + MagicEffects Spells::getMagicEffects (const MWWorld::Environment& environment) const + { + MagicEffects effects; + + for (TIterator iter = mSpells.begin(); iter!=mSpells.end(); ++iter) + { + const ESM::Spell *spell = environment.mWorld->getStore().spells.find (*iter); + + if (spell->data.type==ESM::Spell::ST_Ability || spell->data.type==ESM::Spell::ST_Blight || + spell->data.type==ESM::Spell::ST_Disease || spell->data.type==ESM::Spell::ST_Curse) + addSpell (spell, effects); + } + + return effects; + } + + void Spells::clear() + { + mSpells.clear(); + } + + void Spells::setSelectedSpell (const std::string& spellId) + { + mSelectedSpell = spellId; + } + + const std::string Spells::getSelectedSpell() const + { + return mSelectedSpell; + } +} diff --git a/apps/openmw/mwmechanics/spells.hpp b/apps/openmw/mwmechanics/spells.hpp new file mode 100644 index 000000000..f7606c4ac --- /dev/null +++ b/apps/openmw/mwmechanics/spells.hpp @@ -0,0 +1,66 @@ +#ifndef GAME_MWMECHANICS_SPELLS_H +#define GAME_MWMECHANICS_SPELLS_H + +#include +#include + +namespace ESM +{ + struct Spell; +} + +namespace MWWorld +{ + struct Environment; +} + +namespace MWMechanics +{ + class MagicEffects; + + /// \brief Spell list + /// + /// This class manages known spells as well as abilities, powers and permanent negative effects like + /// diseaes. + class Spells + { + public: + + typedef std::vector TContainer; + typedef TContainer::const_iterator TIterator; + + private: + + std::vector mSpells; + std::string mSelectedSpell; + + void addSpell (const ESM::Spell *, MagicEffects& effects) const; + + public: + + TIterator begin() const; + + TIterator end() const; + + void add (const std::string& spell); + ///< Adding a spell that is already listed in *this is a no-op. + + void remove (const std::string& spell); + ///< If the spell to be removed is the selected spell, the selected spell will be changed to + /// no spell (empty string). + + MagicEffects getMagicEffects (const MWWorld::Environment& environment) const; + ///< Return sum of magic effects resulting from abilities, blights, deseases and curses. + + void clear(); + ///< Remove all spells of al types. + + void setSelectedSpell (const std::string& spellId); + ///< This function does not verify, if the spell is available. + + const std::string getSelectedSpell() const; + ///< May return an empty string. + }; +} + +#endif 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..fb710443b 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){ @@ -103,6 +126,11 @@ namespace MWRender{ void Animation::handleShapes(std::vector* allshapes, Ogre::Entity* creaturemodel, Ogre::SkeletonInstance *skel){ shapeNumber = 0; + if (allshapes == NULL || creaturemodel == NULL || skel == NULL) + { + return; + } + std::vector::iterator allshapesiter; for(allshapesiter = allshapes->begin(); allshapesiter != allshapes->end(); allshapesiter++) diff --git a/apps/openmw/mwrender/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/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 4de6453aa..e0eb5ccc2 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -1,4 +1,5 @@ #include "creatureanimation.hpp" +#include "renderconst.hpp" #include "../mwworld/world.hpp" @@ -20,6 +21,28 @@ CreatureAnimation::CreatureAnimation(const MWWorld::Ptr& ptr, MWWorld::Environme std::string meshNumbered = mesh + getUniqueID(mesh) + ">|"; NifOgre::NIFLoader::load(meshNumbered); base = mRend.getScene()->createEntity(meshNumbered); + base->setVisibilityFlags(RV_Actors); + + bool transparent = false; + for (unsigned int i=0; igetNumSubEntities(); ++i) + { + Ogre::MaterialPtr mat = base->getSubEntity(i)->getMaterial(); + Ogre::Material::TechniqueIterator techIt = mat->getTechniqueIterator(); + while (techIt.hasMoreElements()) + { + Ogre::Technique* tech = techIt.getNext(); + Ogre::Technique::PassIterator passIt = tech->getPassIterator(); + while (passIt.hasMoreElements()) + { + Ogre::Pass* pass = passIt.getNext(); + + if (pass->getDepthWriteEnabled() == false) + transparent = true; + } + } + } + base->setRenderQueueGroup(transparent ? RQG_Alpha : RQG_Main); + std::string meshZero = mesh + "0000>|"; if((transformations = (NIFLoader::getSingletonPtr())->getAnim(meshZero))){ diff --git a/apps/openmw/mwrender/debugging.cpp b/apps/openmw/mwrender/debugging.cpp index 60b299acd..d42672179 100644 --- a/apps/openmw/mwrender/debugging.cpp +++ b/apps/openmw/mwrender/debugging.cpp @@ -2,37 +2,283 @@ #include -#include "OgreRoot.h" -#include "OgreRenderWindow.h" -#include "OgreSceneManager.h" -#include "OgreViewport.h" -#include "OgreCamera.h" -#include "OgreTextureManager.h" +#include +#include +#include +#include #include "../mwworld/world.hpp" // these includes can be removed once the static-hack is gone +#include "../mwworld/environment.hpp" #include "../mwworld/ptr.hpp" #include +#include #include "player.hpp" -using namespace MWRender; using namespace Ogre; -Debugging::Debugging(OEngine::Physic::PhysicEngine* engine){ - eng = engine; +namespace MWRender +{ + +static const std::string PATHGRID_POINT_MATERIAL = "pathgridPointMaterial"; +static const std::string PATHGRID_LINE_MATERIAL = "pathgridLineMaterial"; +static const std::string DEBUGGING_GROUP = "debugging"; +static const int POINT_MESH_BASE = 35; + +void Debugging::createGridMaterials() +{ + if (mGridMatsCreated) return; + + if (MaterialManager::getSingleton().getByName(PATHGRID_LINE_MATERIAL, DEBUGGING_GROUP).isNull()) + { + MaterialPtr lineMatPtr = MaterialManager::getSingleton().create(PATHGRID_LINE_MATERIAL, DEBUGGING_GROUP); + lineMatPtr->setReceiveShadows(false); + lineMatPtr->getTechnique(0)->setLightingEnabled(true); + lineMatPtr->getTechnique(0)->getPass(0)->setDiffuse(1,1,0,0); + lineMatPtr->getTechnique(0)->getPass(0)->setAmbient(1,1,0); + lineMatPtr->getTechnique(0)->getPass(0)->setSelfIllumination(1,1,0); + } + + if (MaterialManager::getSingleton().getByName(PATHGRID_POINT_MATERIAL, DEBUGGING_GROUP).isNull()) + { + MaterialPtr pointMatPtr = MaterialManager::getSingleton().create(PATHGRID_POINT_MATERIAL, DEBUGGING_GROUP); + pointMatPtr->setReceiveShadows(false); + pointMatPtr->getTechnique(0)->setLightingEnabled(true); + pointMatPtr->getTechnique(0)->getPass(0)->setDiffuse(1,0,0,0); + pointMatPtr->getTechnique(0)->getPass(0)->setAmbient(1,0,0); + pointMatPtr->getTechnique(0)->getPass(0)->setSelfIllumination(1,0,0); + } + mGridMatsCreated = true; +} + +void Debugging::destroyGridMaterials() +{ + if (mGridMatsCreated) + { + MaterialManager::getSingleton().remove(PATHGRID_POINT_MATERIAL); + MaterialManager::getSingleton().remove(PATHGRID_LINE_MATERIAL); + mGridMatsCreated = false; + } +} + +ManualObject *Debugging::createPathgridLines(const ESM::Pathgrid *pathgrid) +{ + ManualObject *result = mSceneMgr->createManualObject(); + + result->begin(PATHGRID_LINE_MATERIAL, RenderOperation::OT_LINE_LIST); + for(ESM::Pathgrid::EdgeList::const_iterator it = pathgrid->edges.begin(); + it != pathgrid->edges.end(); + it++) + { + const ESM::Pathgrid::Edge &edge = *it; + const ESM::Pathgrid::Point &p1 = pathgrid->points[edge.v0], &p2 = pathgrid->points[edge.v1]; + Vector3 direction = (Vector3(p2.x, p2.y, p2.z) - Vector3(p1.x, p1.y, p1.z)); + Vector3 lineDisplacement = direction.crossProduct(Vector3::UNIT_Z).normalisedCopy(); + lineDisplacement = lineDisplacement * POINT_MESH_BASE + + Vector3(0, 0, 10); // move lines up a little, so they will be less covered by meshes/landscape + result->position(Vector3(p1.x, p1.y, p1.z) + lineDisplacement); + result->position(Vector3(p2.x, p2.y, p2.z) + lineDisplacement); + } + result->end(); + + return result; +} + +ManualObject *Debugging::createPathgridPoints(const ESM::Pathgrid *pathgrid) +{ + ManualObject *result = mSceneMgr->createManualObject(); + const float height = POINT_MESH_BASE * sqrtf(2); + + result->begin(PATHGRID_POINT_MATERIAL, RenderOperation::OT_TRIANGLE_STRIP); + + bool first = true; + uint32 startIndex = 0; + for(ESM::Pathgrid::PointList::const_iterator it = pathgrid->points.begin(); + it != pathgrid->points.end(); + it++, startIndex += 6) + { + Vector3 pointPos(it->x, it->y, it->z); + + if (!first) + { + // degenerate triangle from previous octahedron + result->index(startIndex - 4); // 2nd point of previous octahedron + result->index(startIndex); // start point of current octahedron + } + + result->position(pointPos + Vector3(0, 0, height)); // 0 + result->position(pointPos + Vector3(-POINT_MESH_BASE, -POINT_MESH_BASE, 0)); // 1 + result->position(pointPos + Vector3(POINT_MESH_BASE, -POINT_MESH_BASE, 0)); // 2 + result->position(pointPos + Vector3(POINT_MESH_BASE, POINT_MESH_BASE, 0)); // 3 + result->position(pointPos + Vector3(-POINT_MESH_BASE, POINT_MESH_BASE, 0)); // 4 + result->position(pointPos + Vector3(0, 0, -height)); // 5 + + result->index(startIndex + 0); + result->index(startIndex + 1); + result->index(startIndex + 2); + result->index(startIndex + 5); + result->index(startIndex + 3); + result->index(startIndex + 4); + // degenerates + result->index(startIndex + 4); + result->index(startIndex + 5); + result->index(startIndex + 5); + // end degenerates + result->index(startIndex + 1); + result->index(startIndex + 4); + result->index(startIndex + 0); + result->index(startIndex + 3); + result->index(startIndex + 2); + + first = false; + } + + result->end(); + + return result; +} + +Debugging::Debugging(SceneNode *mwRoot, MWWorld::Environment &env, OEngine::Physic::PhysicEngine *engine) : + mMwRoot(mwRoot), mEnvironment(env), mEngine(engine), + mSceneMgr(mwRoot->getCreator()), + mPathgridEnabled(false), + mInteriorPathgridNode(NULL), mPathGridRoot(NULL), + mGridMatsCreated(false) +{ + ResourceGroupManager::getSingleton().createResourceGroup(DEBUGGING_GROUP); +} + +Debugging::~Debugging() +{ + if (mPathgridEnabled) + { + togglePathgrid(); + } + + ResourceGroupManager::getSingleton().destroyResourceGroup(DEBUGGING_GROUP); } bool Debugging::toggleRenderMode (int mode){ - switch (mode) + switch (mode) { case MWWorld::World::Render_CollisionDebug: - // TODO use a proper function instead of accessing the member variable - // directly. - eng->setDebugRenderingMode (!eng->isDebugCreated); - return eng->isDebugCreated; + return mEngine->toggleDebugRendering(); + + case MWWorld::World::Render_Pathgrid: + togglePathgrid(); + return mPathgridEnabled; } return false; } + +void Debugging::cellAdded(MWWorld::Ptr::CellStore *store) +{ + mActiveCells.push_back(store); + if (mPathgridEnabled) + enableCellPathgrid(store); +} + +void Debugging::cellRemoved(MWWorld::Ptr::CellStore *store) +{ + mActiveCells.erase(std::remove(mActiveCells.begin(), mActiveCells.end(), store), mActiveCells.end()); + if (mPathgridEnabled) + disableCellPathgrid(store); +} + +void Debugging::togglePathgrid() +{ + mPathgridEnabled = !mPathgridEnabled; + if (mPathgridEnabled) + { + createGridMaterials(); + + // add path grid meshes to already loaded cells + mPathGridRoot = mMwRoot->createChildSceneNode(); + for(CellList::iterator it = mActiveCells.begin(); it != mActiveCells.end(); it++) + { + enableCellPathgrid(*it); + } + } + else + { + // remove path grid meshes from already loaded cells + for(CellList::iterator it = mActiveCells.begin(); it != mActiveCells.end(); it++) + { + disableCellPathgrid(*it); + } + mPathGridRoot->removeAndDestroyAllChildren(); + mSceneMgr->destroySceneNode(mPathGridRoot); + mPathGridRoot = NULL; + destroyGridMaterials(); + } +} + +void Debugging::enableCellPathgrid(MWWorld::Ptr::CellStore *store) +{ + ESM::Pathgrid *pathgrid = mEnvironment.mWorld->getStore().pathgrids.search(*store->cell); + if (!pathgrid) return; + + Vector3 cellPathGridPos(0, 0, 0); + if (store->cell->isExterior()) + { + cellPathGridPos.x = store->cell->data.gridX * ESM::Land::REAL_SIZE; + cellPathGridPos.y = store->cell->data.gridY * ESM::Land::REAL_SIZE; + } + SceneNode *cellPathGrid = mPathGridRoot->createChildSceneNode(cellPathGridPos); + cellPathGrid->attachObject(createPathgridLines(pathgrid)); + cellPathGrid->attachObject(createPathgridPoints(pathgrid)); + + if (store->cell->isExterior()) + { + mExteriorPathgridNodes[std::make_pair(store->cell->data.gridX, store->cell->data.gridY)] = cellPathGrid; + } + else + { + assert(mInteriorPathgridNode == NULL); + mInteriorPathgridNode = cellPathGrid; + } +} + +void Debugging::disableCellPathgrid(MWWorld::Ptr::CellStore *store) +{ + if (store->cell->isExterior()) + { + ExteriorPathgridNodes::iterator it = + mExteriorPathgridNodes.find(std::make_pair(store->cell->data.gridX, store->cell->data.gridY)); + if (it != mExteriorPathgridNodes.end()) + { + destroyCellPathgridNode(it->second); + mExteriorPathgridNodes.erase(it); + } + } + else + { + if (mInteriorPathgridNode) + { + destroyCellPathgridNode(mInteriorPathgridNode); + mInteriorPathgridNode = NULL; + } + } +} + +void Debugging::destroyCellPathgridNode(SceneNode *node) +{ + mPathGridRoot->removeChild(node); + destroyAttachedObjects(node); + mSceneMgr->destroySceneNode(node); +} + +void Debugging::destroyAttachedObjects(SceneNode *node) +{ + SceneNode::ObjectIterator objIt = node->getAttachedObjectIterator(); + while (objIt.hasMoreElements()) + { + MovableObject *mesh = static_cast(objIt.getNext()); + mSceneMgr->destroyMovableObject(mesh); + } +} + +} diff --git a/apps/openmw/mwrender/debugging.hpp b/apps/openmw/mwrender/debugging.hpp index b48cfaee2..ebf3884dc 100644 --- a/apps/openmw/mwrender/debugging.hpp +++ b/apps/openmw/mwrender/debugging.hpp @@ -4,6 +4,7 @@ #include #include #include +#include "../mwworld/ptr.hpp" #include #include @@ -22,20 +23,58 @@ namespace Ogre namespace MWWorld { class World; + class Environment; } namespace MWRender { class Player; - class Debugging{ - OEngine::Physic::PhysicEngine* eng; + class Debugging + { + OEngine::Physic::PhysicEngine* mEngine; + Ogre::SceneManager *mSceneMgr; + MWWorld::Environment& mEnvironment; + // Path grid stuff + bool mPathgridEnabled; - public: - Debugging(OEngine::Physic::PhysicEngine* engine); - bool toggleRenderMode (int mode); - }; + void togglePathgrid(); + + typedef std::vector CellList; + CellList mActiveCells; + + Ogre::SceneNode *mMwRoot; + + Ogre::SceneNode *mPathGridRoot; + + typedef std::map, Ogre::SceneNode *> ExteriorPathgridNodes; + ExteriorPathgridNodes mExteriorPathgridNodes; + Ogre::SceneNode *mInteriorPathgridNode; + + void enableCellPathgrid(MWWorld::Ptr::CellStore *store); + void disableCellPathgrid(MWWorld::Ptr::CellStore *store); + + // utility + void destroyCellPathgridNode(Ogre::SceneNode *node); + void destroyAttachedObjects(Ogre::SceneNode *node); + + // materials + bool mGridMatsCreated; + void createGridMaterials(); + void destroyGridMaterials(); + + // path grid meshes + Ogre::ManualObject *createPathgridLines(const ESM::Pathgrid *pathgrid); + Ogre::ManualObject *createPathgridPoints(const ESM::Pathgrid *pathgrid); + public: + Debugging(Ogre::SceneNode* mwRoot, MWWorld::Environment &env, OEngine::Physic::PhysicEngine *engine); + ~Debugging(); + bool toggleRenderMode (int mode); + + void cellAdded(MWWorld::Ptr::CellStore* store); + void cellRemoved(MWWorld::Ptr::CellStore* store); + }; } diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index b83a98220..2cc233a01 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -2,7 +2,9 @@ #include "renderingmanager.hpp" #include "../mwworld/environment.hpp" +#include "../mwworld/world.hpp" #include "../mwgui/window_manager.hpp" +#include "renderconst.hpp" #include #include @@ -10,16 +12,24 @@ using namespace MWRender; using namespace Ogre; -LocalMap::LocalMap(OEngine::Render::OgreRenderer* rend, MWWorld::Environment* env) +LocalMap::LocalMap(OEngine::Render::OgreRenderer* rend, MWRender::RenderingManager* rendering, MWWorld::Environment* env) : + mInterior(false), mCellX(0), mCellY(0) { mRendering = rend; + mRenderingManager = rendering; mEnvironment = env; - + + mCameraPosNode = mRendering->getScene()->getRootSceneNode()->createChildSceneNode(); + mCameraRotNode = mCameraPosNode->createChildSceneNode(); + mCameraNode = mCameraRotNode->createChildSceneNode(); + mCellCamera = mRendering->getScene()->createCamera("CellCamera"); mCellCamera->setProjectionType(PT_ORTHOGRAPHIC); // look down -y const float sqrt0pt5 = 0.707106781; mCellCamera->setOrientation(Quaternion(sqrt0pt5, -sqrt0pt5, 0, 0)); + + mCameraNode->attachObject(mCellCamera); } LocalMap::~LocalMap() @@ -27,13 +37,14 @@ LocalMap::~LocalMap() deleteBuffers(); } +const Ogre::Vector2 LocalMap::rotatePoint(const Ogre::Vector2& p, const Ogre::Vector2& c, const float angle) +{ + return Vector2( Math::Cos(angle) * (p.x - c.x) - Math::Sin(angle) * (p.y - c.y) + c.x, + Math::Sin(angle) * (p.x - c.x) + Math::Cos(angle) * (p.y - c.y) + c.y); +} + void LocalMap::deleteBuffers() { - for (std::map::iterator it=mBuffers.begin(); - it != mBuffers.end(); ++it) - { - delete it->second; - } mBuffers.clear(); } @@ -70,9 +81,6 @@ void LocalMap::saveFogOfWar(MWWorld::Ptr::CellStore* cell) { 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 @@ -95,11 +103,15 @@ void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell) { mInterior = false; + mCameraRotNode->setOrientation(Quaternion::IDENTITY); + std::string name = "Cell_"+coordStr(cell->cell->data.gridX, cell->cell->data.gridY); int x = cell->cell->data.gridX; int y = cell->cell->data.gridY; + mCameraPosNode->setPosition(Vector3(0,0,0)); + render((x+0.5)*sSize, (-y-0.5)*sSize, -10000, 10000, sSize, sSize, name); } @@ -108,17 +120,41 @@ void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell, { 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 z(mBounds.getMaximum().y, mBounds.getMinimum().y); + + const Vector2& north = mEnvironment->mWorld->getNorthVector(cell); + Radian angle(std::atan2(-north.x, -north.y)); + mAngle = angle.valueRadians(); + mCameraRotNode->setOrientation(Quaternion(Math::Cos(angle/2.f), 0, Math::Sin(angle/2.f), 0)); + + // rotate the cell and merge the rotated corners to the bounding box + Vector2 _center(bounds.getCenter().x, bounds.getCenter().z); + Vector3 _c1 = bounds.getCorner(AxisAlignedBox::NEAR_LEFT_BOTTOM); + Vector3 _c2 = bounds.getCorner(AxisAlignedBox::FAR_LEFT_BOTTOM); + Vector3 _c3 = bounds.getCorner(AxisAlignedBox::NEAR_RIGHT_BOTTOM); + Vector3 _c4 = bounds.getCorner(AxisAlignedBox::FAR_RIGHT_BOTTOM); + Vector2 c1(_c1.x, _c1.z); + Vector2 c2(_c2.x, _c2.z); + Vector2 c3(_c3.x, _c3.z); + Vector2 c4(_c4.x, _c4.z); + c1 = rotatePoint(c1, _center, mAngle); + c2 = rotatePoint(c2, _center, mAngle); + c3 = rotatePoint(c3, _center, mAngle); + c4 = rotatePoint(c4, _center, mAngle); + mBounds.merge(Vector3(c1.x, 0, c1.y)); + mBounds.merge(Vector3(c2.x, 0, c2.y)); + mBounds.merge(Vector3(c3.x, 0, c3.y)); + mBounds.merge(Vector3(c4.x, 0, c4.y)); + + Vector2 center(mBounds.getCenter().x, mBounds.getCenter().z); + + Vector2 min(mBounds.getMinimum().x, mBounds.getMinimum().z); + Vector2 max(mBounds.getMaximum().x, mBounds.getMaximum().z); Vector2 length = max-min; - Vector2 center(bounds.getCenter().x, bounds.getCenter().z); + + mCameraPosNode->setPosition(Vector3(center.x, 0, center.y)); // divide into segments const int segsX = std::ceil( length.x / sSize ); @@ -133,7 +169,7 @@ void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell, Vector2 start = min + Vector2(sSize*x,sSize*y); Vector2 newcenter = start + 4096; - render(newcenter.x, newcenter.y, z.y, z.x, sSize, sSize, + render(newcenter.x - center.x, newcenter.y - center.y, z.y, z.x, sSize, sSize, cell->cell->name + "_" + coordStr(x,y)); } } @@ -152,8 +188,9 @@ void LocalMap::render(const float x, const float y, // make everything visible mRendering->getScene()->setAmbientLight(ColourValue(1,1,1)); + mRenderingManager->disableLights(); - mCellCamera->setPosition(Vector3(x, zhigh+100000, y)); + mCameraNode->setPosition(Vector3(x, zhigh+100000, y)); //mCellCamera->setFarClipDistance( (zhigh-zlow) * 1.1 ); mCellCamera->setFarClipDistance(0); // infinite @@ -187,7 +224,10 @@ void LocalMap::render(const float x, const float y, vp->setOverlaysEnabled(false); vp->setShadowsEnabled(false); vp->setBackgroundColour(ColourValue(0, 0, 0)); - //vp->setVisibilityMask( ... ); + vp->setVisibilityMask(RV_Map); + + // use fallback techniques without shadows and without mrt + vp->setMaterialScheme("Fallback"); rtt->update(); @@ -202,16 +242,16 @@ void LocalMap::render(const float x, const float y, TU_DYNAMIC_WRITE_ONLY_DISCARDABLE); // create a buffer to use for dynamic operations - uint32* buffer = new uint32[sFogOfWarResolution*sFogOfWarResolution]; + std::vector buffer; + buffer.resize(sFogOfWarResolution*sFogOfWarResolution); // initialize to (0, 0, 0, 1) - uint32* pointer = buffer; for (int p=0; pgetBuffer()->lock(HardwareBuffer::HBL_DISCARD), buffer, sFogOfWarResolution*sFogOfWarResolution*4); + memcpy(tex2->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), &buffer[0], sFogOfWarResolution*sFogOfWarResolution*4); tex2->getBuffer()->unlock(); mBuffers[texture] = buffer; @@ -220,13 +260,14 @@ void LocalMap::render(const float x, const float y, //rtt->writeContentsToFile("./" + texture + ".jpg"); } } - + + mRenderingManager->enableLights(); // re-enable fog mRendering->getScene()->setFog(FOG_LINEAR, clr, 0, fStart, fEnd); } -void LocalMap::updatePlayer (const Ogre::Vector3& position, const Ogre::Vector3& direction) +void LocalMap::updatePlayer (const Ogre::Vector3& position, const Ogre::Quaternion& orientation) { if (sFogOfWarSkip != 0) { @@ -237,7 +278,19 @@ void LocalMap::updatePlayer (const Ogre::Vector3& position, const Ogre::Vector3& // retrieve the x,y grid coordinates the player is in int x,y; - Vector2 pos(position.x, position.z); + Vector3 _pos(position.x, 0, position.z); + Vector2 pos(_pos.x, _pos.z); + + if (mInterior) + { + pos = rotatePoint(pos, Vector2(mBounds.getCenter().x, mBounds.getCenter().z), mAngle); + } + + + Vector3 playerdirection = -mCameraRotNode->convertWorldToLocalOrientation(orientation).zAxis(); + + Vector2 min(mBounds.getMinimum().x, mBounds.getMinimum().z); + if (!mInterior) { x = std::ceil(pos.x / sSize)-1; @@ -247,9 +300,6 @@ void LocalMap::updatePlayer (const Ogre::Vector3& position, const Ogre::Vector3& } 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; @@ -264,20 +314,17 @@ void LocalMap::updatePlayer (const Ogre::Vector3& position, const Ogre::Vector3& 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); + mEnvironment->mWindowManager->setPlayerDir(playerdirection.x, -playerdirection.z); // explore radius (squared) const float sqrExploreRadius = 0.01 * sFogOfWarResolution*sFogOfWarResolution; @@ -288,25 +335,23 @@ void LocalMap::updatePlayer (const Ogre::Vector3& position, const Ogre::Vector3& { // get its buffer if (mBuffers.find(texName) == mBuffers.end()) return; - uint32* buffer = mBuffers[texName]; - uint32* pointer = buffer; + 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) ); - *((uint32*)pointer) = (alpha << 24); + mBuffers[texName][i] = (uint32) (alpha << 24); - // move to next texel - ++pointer; + ++i; } } // copy to the texture - memcpy(tex->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), buffer, sFogOfWarResolution*sFogOfWarResolution*4); + 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 index 3bef475ea..95685167c 100644 --- a/apps/openmw/mwrender/localmap.hpp +++ b/apps/openmw/mwrender/localmap.hpp @@ -12,13 +12,15 @@ namespace MWWorld namespace MWRender { + class RenderingManager; + /// /// \brief Local map rendering /// class LocalMap { public: - LocalMap(OEngine::Render::OgreRenderer*, MWWorld::Environment* env); + LocalMap(OEngine::Render::OgreRenderer*, MWRender::RenderingManager* rendering, MWWorld::Environment* env); ~LocalMap(); /** @@ -44,9 +46,9 @@ namespace MWRender * @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) + * @param camera orientation (OGRE coordinates) */ - void updatePlayer (const Ogre::Vector3& position, const Ogre::Vector3& direction); + void updatePlayer (const Ogre::Vector3& position, const Ogre::Quaternion& orientation); /** * Save the fog of war for the current cell to disk. @@ -58,6 +60,7 @@ namespace MWRender private: OEngine::Render::OgreRenderer* mRendering; + MWRender::RenderingManager* mRenderingManager; MWWorld::Environment* mEnvironment; // 1024*1024 pixels for a cell @@ -73,6 +76,12 @@ namespace MWRender static const int sSize = 8192; Ogre::Camera* mCellCamera; + Ogre::SceneNode* mCameraNode; + Ogre::SceneNode* mCameraPosNode; + Ogre::SceneNode* mCameraRotNode; + + float mAngle; + const Ogre::Vector2 rotatePoint(const Ogre::Vector2& p, const Ogre::Vector2& c, const float angle); void render(const float x, const float y, const float zlow, const float zhigh, @@ -86,7 +95,7 @@ namespace MWRender // 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; + std::map > mBuffers; void deleteBuffers(); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index c6fe023d6..a231e8fe7 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -1,5 +1,6 @@ #include "npcanimation.hpp" #include "../mwworld/world.hpp" +#include "renderconst.hpp" using namespace Ogre; @@ -65,6 +66,27 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, MWWorld::Environment& _env,O NifOgre::NIFLoader::load(smodel); base = mRend.getScene()->createEntity(smodel); + base->setVisibilityFlags(RV_Actors); + bool transparent = false; + for (unsigned int i=0; igetNumSubEntities(); ++i) + { + Ogre::MaterialPtr mat = base->getSubEntity(i)->getMaterial(); + Ogre::Material::TechniqueIterator techIt = mat->getTechniqueIterator(); + while (techIt.hasMoreElements()) + { + Ogre::Technique* tech = techIt.getNext(); + Ogre::Technique::PassIterator passIt = tech->getPassIterator(); + while (passIt.hasMoreElements()) + { + Ogre::Pass* pass = passIt.getNext(); + + if (pass->getDepthWriteEnabled() == false) + transparent = true; + } + } + } + base->setRenderQueueGroup(transparent ? RQG_Alpha : RQG_Main); + base->setSkipAnimationStateUpdate(true); //Magical line of code, this makes the bones //stay in the same place when we skipanim, or open a gui window @@ -218,6 +240,7 @@ Ogre::Entity* NpcAnimation::insertBoundedPart(const std::string &mesh, std::stri NIFLoader::load(mesh); Entity* ent = mRend.getScene()->createEntity(mesh); + ent->setVisibilityFlags(RV_Actors); base->attachObjectToBone(bonename, ent); return ent; @@ -227,9 +250,7 @@ void NpcAnimation::insertFreePart(const std::string &mesh, const std::string suf NIFLoader::load(meshNumbered); Ogre::Entity* ent = mRend.getScene()->createEntity(meshNumbered); - - - + ent->setVisibilityFlags(RV_Actors); insert->attachObject(ent); entityparts.push_back(ent); diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index e4e721227..eb7e440cb 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -3,6 +3,8 @@ #include #include +#include +#include "renderconst.hpp" using namespace MWRender; @@ -88,38 +90,112 @@ void Objects::insertMesh (const MWWorld::Ptr& ptr, const std::string& mesh) NifOgre::NIFLoader::load(mesh); Ogre::Entity *ent = mRenderer.getScene()->createEntity(mesh); - if(!mIsStatic) + + Ogre::Vector3 extents = ent->getBoundingBox().getSize(); + extents *= insert->getScale(); + float size = std::max(std::max(extents.x, extents.y), extents.z); + + bool small = (size < Settings::Manager::getInt("small object size", "Viewing distance")) && Settings::Manager::getBool("limit small object distance", "Objects"); + + // do not fade out doors. that will cause holes and look stupid + if (ptr.getTypeName().find("Door") != std::string::npos) + small = false; + + if (mBounds.find(ptr.getCell()) == mBounds.end()) + mBounds[ptr.getCell()] = Ogre::AxisAlignedBox::BOX_NULL; + + Ogre::AxisAlignedBox bounds = ent->getBoundingBox(); + bounds = Ogre::AxisAlignedBox( + insert->_getDerivedPosition() + bounds.getMinimum(), + insert->_getDerivedPosition() + bounds.getMaximum() + ); + + bounds.scale(insert->getScale()); + mBounds[ptr.getCell()].merge(bounds); + + bool transparent = false; + for (unsigned int i=0; igetNumSubEntities(); ++i) + { + Ogre::MaterialPtr mat = ent->getSubEntity(i)->getMaterial(); + Ogre::Material::TechniqueIterator techIt = mat->getTechniqueIterator(); + while (techIt.hasMoreElements()) + { + Ogre::Technique* tech = techIt.getNext(); + Ogre::Technique::PassIterator passIt = tech->getPassIterator(); + while (passIt.hasMoreElements()) + { + Ogre::Pass* pass = passIt.getNext(); + + if (pass->getDepthWriteEnabled() == false) + transparent = true; + } + } + } + + if(!mIsStatic || !Settings::Manager::getBool("use static geometry", "Objects")) { insert->attachObject(ent); + + ent->setRenderingDistance(small ? Settings::Manager::getInt("small object distance", "Viewing distance") : 0); + ent->setVisibilityFlags(mIsStatic ? (small ? RV_StaticsSmall : RV_Statics) : RV_Misc); + ent->setRenderQueueGroup(transparent ? RQG_Alpha : RQG_Main); } else { Ogre::StaticGeometry* sg = 0; - if(mStaticGeometry.find(ptr.getCell()) == mStaticGeometry.end()) + +/* if (transparent) + { + if( mStaticGeometryAlpha.find(ptr.getCell()) == mStaticGeometryAlpha.end()) + { + uniqueID = uniqueID +1; + sg = mRenderer.getScene()->createStaticGeometry( "sg" + Ogre::StringConverter::toString(uniqueID)); + mStaticGeometryAlpha[ptr.getCell()] = sg; + } + else + sg = mStaticGeometryAlpha[ptr.getCell()]; + } + else*/ if (small) { - uniqueID = uniqueID +1; - sg = mRenderer.getScene()->createStaticGeometry( "sg" + Ogre::StringConverter::toString(uniqueID)); - //Create the scenenode and put it in the map - mStaticGeometry[ptr.getCell()] = sg; - - // This specifies the size of a single batch region. - // If it is set too high: - // - there will be problems choosing the correct lights - // - the culling will be more inefficient - // If it is set too low: - // - there will be too many batches. - sg->setRegionDimensions(Ogre::Vector3(2500,2500,2500)); - - mBounds[ptr.getCell()] = Ogre::AxisAlignedBox::BOX_NULL; - mBounds[ptr.getCell()].merge(ent->getBoundingBox()); + if( mStaticGeometrySmall.find(ptr.getCell()) == mStaticGeometrySmall.end()) + { + uniqueID = uniqueID +1; + sg = mRenderer.getScene()->createStaticGeometry( "sg" + Ogre::StringConverter::toString(uniqueID)); + mStaticGeometrySmall[ptr.getCell()] = sg; + + sg->setRenderingDistance(Settings::Manager::getInt("small object distance", "Viewing distance")); + } + else + sg = mStaticGeometrySmall[ptr.getCell()]; } else { - sg = mStaticGeometry[ptr.getCell()]; + if( mStaticGeometry.find(ptr.getCell()) == mStaticGeometry.end()) + { + + uniqueID = uniqueID +1; + sg = mRenderer.getScene()->createStaticGeometry( "sg" + Ogre::StringConverter::toString(uniqueID)); + mStaticGeometry[ptr.getCell()] = sg; + } + else + sg = mStaticGeometry[ptr.getCell()]; } + // This specifies the size of a single batch region. + // If it is set too high: + // - there will be problems choosing the correct lights + // - the culling will be more inefficient + // If it is set too low: + // - there will be too many batches. + sg->setRegionDimensions(Ogre::Vector3(2500,2500,2500)); + sg->addEntity(ent,insert->_getDerivedPosition(),insert->_getDerivedOrientation(),insert->_getDerivedScale()); - mBounds[ptr.getCell()].merge(insert->_getDerivedPosition()); + + sg->setVisibilityFlags(small ? RV_StaticsSmall : RV_Statics); + + sg->setCastShadows(true); + + sg->setRenderQueueGroup(transparent ? RQG_Alpha : RQG_Main); mRenderer.getScene()->destroyEntity(ent); } @@ -131,6 +207,7 @@ void Objects::insertLight (const MWWorld::Ptr& ptr, float r, float g, float b, f assert(insert); Ogre::Light *light = mRenderer.getScene()->createLight(); light->setDiffuseColour (r, g, b); + mLights.push_back(light->getName()); float cval=0.0f, lval=0.0f, qval=0.0f; @@ -206,7 +283,21 @@ void Objects::removeCell(MWWorld::Ptr::CellStore* store) mRenderer.getScene()->destroyStaticGeometry (sg); sg = 0; } - + if(mStaticGeometrySmall.find(store) != mStaticGeometrySmall.end()) + { + Ogre::StaticGeometry* sg = mStaticGeometrySmall[store]; + mStaticGeometrySmall.erase(store); + mRenderer.getScene()->destroyStaticGeometry (sg); + sg = 0; + } + /*if(mStaticGeometryAlpha.find(store) != mStaticGeometryAlpha.end()) + { + Ogre::StaticGeometry* sg = mStaticGeometryAlpha[store]; + mStaticGeometryAlpha.erase(store); + mRenderer.getScene()->destroyStaticGeometry (sg); + sg = 0; + }*/ + if(mBounds.find(store) != mBounds.end()) mBounds.erase(store); } @@ -218,9 +309,50 @@ void Objects::buildStaticGeometry(ESMS::CellStore& cell) Ogre::StaticGeometry* sg = mStaticGeometry[&cell]; sg->build(); } + if(mStaticGeometrySmall.find(&cell) != mStaticGeometrySmall.end()) + { + Ogre::StaticGeometry* sg = mStaticGeometrySmall[&cell]; + sg->build(); + } + /*if(mStaticGeometryAlpha.find(&cell) != mStaticGeometryAlpha.end()) + { + Ogre::StaticGeometry* sg = mStaticGeometryAlpha[&cell]; + sg->build(); + }*/ } Ogre::AxisAlignedBox Objects::getDimensions(MWWorld::Ptr::CellStore* cell) { return mBounds[cell]; } + +void Objects::enableLights() +{ + std::vector::iterator it = mLights.begin(); + while (it != mLights.end()) + { + if (mMwRoot->getCreator()->hasLight(*it)) + { + mMwRoot->getCreator()->getLight(*it)->setVisible(true); + ++it; + } + else + it = mLights.erase(it); + } +} + +void Objects::disableLights() +{ + std::vector::iterator it = mLights.begin(); + while (it != mLights.end()) + { + if (mMwRoot->getCreator()->hasLight(*it)) + { + mMwRoot->getCreator()->getLight(*it)->setVisible(false); + ++it; + } + else + it = mLights.erase(it); + } +} + diff --git a/apps/openmw/mwrender/objects.hpp b/apps/openmw/mwrender/objects.hpp index 1ca81331d..0c19f9f33 100644 --- a/apps/openmw/mwrender/objects.hpp +++ b/apps/openmw/mwrender/objects.hpp @@ -14,7 +14,10 @@ class Objects{ OEngine::Render::OgreRenderer &mRenderer; std::map mCellSceneNodes; std::map mStaticGeometry; + std::map mStaticGeometrySmall; + //std::map mStaticGeometryAlpha; std::map mBounds; + std::vector mLights; Ogre::SceneNode* mMwRoot; bool mIsStatic; static int uniqueID; @@ -43,6 +46,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); + void enableLights(); + void disableLights(); + Ogre::AxisAlignedBox getDimensions(MWWorld::Ptr::CellStore*); ///< get a bounding box that encloses all objects in the specified cell diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp new file mode 100644 index 000000000..80b804dce --- /dev/null +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -0,0 +1,304 @@ +#include "occlusionquery.hpp" +#include "renderconst.hpp" + +#include +#include +#include +#include +#include +#include +#include + +using namespace MWRender; +using namespace Ogre; + +OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNode* sunNode) : + mSunTotalAreaQuery(0), mSunVisibleAreaQuery(0), mSingleObjectQuery(0), mActiveQuery(0), + mDoQuery(0), mSunVisibility(0), mQuerySingleObjectStarted(false), mTestResult(false), + mQuerySingleObjectRequested(false), mWasVisible(false), mObjectWasVisible(false), mDoQuery2(false), + mBBNode(0) +{ + mRendering = renderer; + mSunNode = sunNode; + + try { + RenderSystem* renderSystem = Root::getSingleton().getRenderSystem(); + + mSunTotalAreaQuery = renderSystem->createHardwareOcclusionQuery(); + mSunVisibleAreaQuery = renderSystem->createHardwareOcclusionQuery(); + mSingleObjectQuery = renderSystem->createHardwareOcclusionQuery(); + + mSupported = (mSunTotalAreaQuery != 0) && (mSunVisibleAreaQuery != 0) && (mSingleObjectQuery != 0); + } + catch (Ogre::Exception e) + { + mSupported = false; + } + + if (!mSupported) + { + std::cout << "Hardware occlusion queries not supported." << std::endl; + return; + } + + MaterialPtr matBase = MaterialManager::getSingleton().getByName("BaseWhiteNoLighting"); + MaterialPtr matQueryArea = matBase->clone("QueryTotalPixels"); + matQueryArea->setDepthWriteEnabled(false); + matQueryArea->setColourWriteEnabled(false); + matQueryArea->setDepthCheckEnabled(false); // Not occluded by objects + MaterialPtr matQueryVisible = matBase->clone("QueryVisiblePixels"); + matQueryVisible->setDepthWriteEnabled(false); + matQueryVisible->setColourWriteEnabled(false); // Uncomment this to visualize the occlusion query + matQueryVisible->setDepthCheckEnabled(true); // Occluded by objects + matQueryVisible->setCullingMode(CULL_NONE); + matQueryVisible->setManualCullingMode(MANUAL_CULL_NONE); + + if (sunNode) + mBBNode = mSunNode->getParentSceneNode()->createChildSceneNode(); + + mObjectNode = mRendering->getScene()->getRootSceneNode()->createChildSceneNode(); + mBBNodeReal = mRendering->getScene()->getRootSceneNode()->createChildSceneNode(); + + mBBQueryTotal = mRendering->getScene()->createBillboardSet(1); + mBBQueryTotal->setCastShadows(false); + mBBQueryTotal->setDefaultDimensions(150, 150); + mBBQueryTotal->createBillboard(Vector3::ZERO); + mBBQueryTotal->setMaterialName("QueryTotalPixels"); + mBBQueryTotal->setRenderQueueGroup(RQG_OcclusionQuery+1); + mBBNodeReal->attachObject(mBBQueryTotal); + + mBBQueryVisible = mRendering->getScene()->createBillboardSet(1); + mBBQueryVisible->setCastShadows(false); + mBBQueryVisible->setDefaultDimensions(150, 150); + mBBQueryVisible->createBillboard(Vector3::ZERO); + mBBQueryVisible->setMaterialName("QueryVisiblePixels"); + mBBQueryVisible->setRenderQueueGroup(RQG_OcclusionQuery+1); + mBBNodeReal->attachObject(mBBQueryVisible); + + mBBQuerySingleObject = mRendering->getScene()->createBillboardSet(1); + /// \todo ideally this should occupy exactly 1 pixel on the screen + mBBQuerySingleObject->setCastShadows(false); + mBBQuerySingleObject->setDefaultDimensions(0.003, 0.003); + mBBQuerySingleObject->createBillboard(Vector3::ZERO); + mBBQuerySingleObject->setMaterialName("QueryVisiblePixels"); + mBBQuerySingleObject->setRenderQueueGroup(RQG_OcclusionQuery); + mObjectNode->attachObject(mBBQuerySingleObject); + + mRendering->getScene()->addRenderObjectListener(this); + mRendering->getScene()->addRenderQueueListener(this); + mDoQuery = true; +} + +OcclusionQuery::~OcclusionQuery() +{ + RenderSystem* renderSystem = Root::getSingleton().getRenderSystem(); + if (mSunTotalAreaQuery) renderSystem->destroyHardwareOcclusionQuery(mSunTotalAreaQuery); + if (mSunVisibleAreaQuery) renderSystem->destroyHardwareOcclusionQuery(mSunVisibleAreaQuery); + if (mSingleObjectQuery) renderSystem->destroyHardwareOcclusionQuery(mSingleObjectQuery); +} + +bool OcclusionQuery::supported() +{ + return mSupported; +} + +void OcclusionQuery::notifyRenderSingleObject(Renderable* rend, const Pass* pass, const AutoParamDataSource* source, + const LightList* pLightList, bool suppressRenderStateChanges) +{ + // The following code activates and deactivates the occlusion queries + // so that the queries only include the rendering of their intended targets + + // Close the last occlusion query + // Each occlusion query should only last a single rendering + if (mActiveQuery != NULL) + { + mActiveQuery->endOcclusionQuery(); + mActiveQuery = NULL; + } + + // Open a new occlusion query + if (mDoQuery == true) + { + if (rend == mBBQueryTotal) + { + mActiveQuery = mSunTotalAreaQuery; + mWasVisible = true; + } + else if (rend == mBBQueryVisible) + { + mActiveQuery = mSunVisibleAreaQuery; + } + } + if (mDoQuery == true && rend == mBBQuerySingleObject) + { + mQuerySingleObjectStarted = true; + mQuerySingleObjectRequested = false; + mActiveQuery = mSingleObjectQuery; + mObjectWasVisible = true; + } + + if (mActiveQuery != NULL) + mActiveQuery->beginOcclusionQuery(); +} + +void OcclusionQuery::renderQueueEnded(uint8 queueGroupId, const String& invocation, bool& repeatThisInvocation) +{ + if (mActiveQuery != NULL) + { + mActiveQuery->endOcclusionQuery(); + mActiveQuery = NULL; + } + /** + * for every beginOcclusionQuery(), we want a respective pullOcclusionQuery() and vice versa + * this also means that results can be wrong at other places if we pull, but beginOcclusionQuery() was never called + * this can happen for example if the object that is tested is outside of the view frustum + * to prevent this, check if the queries have been performed after everything has been rendered and if not, start them manually + */ + if (queueGroupId == RQG_SkiesLate) + { + if (mWasVisible == false && mDoQuery) + { + mSunTotalAreaQuery->beginOcclusionQuery(); + mSunTotalAreaQuery->endOcclusionQuery(); + mSunVisibleAreaQuery->beginOcclusionQuery(); + mSunVisibleAreaQuery->endOcclusionQuery(); + } + if (mObjectWasVisible == false && mDoQuery) + { + mSingleObjectQuery->beginOcclusionQuery(); + mSingleObjectQuery->endOcclusionQuery(); + mQuerySingleObjectStarted = true; + mQuerySingleObjectRequested = false; + } + } +} + +void OcclusionQuery::update(float duration) +{ + if (!mSupported) return; + + mWasVisible = false; + mObjectWasVisible = false; + + // Adjust the position of the sun billboards according to camera viewing distance + // we need to do this to make sure that _everything_ can occlude the sun + float dist = mRendering->getCamera()->getFarClipDistance(); + if (dist==0) dist = 10000000; + dist -= 1000; // bias + dist /= 1000.f; + if (mBBNode) + { + mBBNode->setPosition(mSunNode->getPosition() * dist); + mBBNode->setScale(dist, dist, dist); + mBBNodeReal->setPosition(mBBNode->_getDerivedPosition()); + mBBNodeReal->setScale(mBBNode->getScale()); + } + + // Stop occlusion queries until we get their information + // (may not happen on the same frame they are requested in) + mDoQuery = false; + + if (!mSunTotalAreaQuery->isStillOutstanding() + && !mSunVisibleAreaQuery->isStillOutstanding() + && !mSingleObjectQuery->isStillOutstanding()) + { + unsigned int totalPixels; + unsigned int visiblePixels; + + mSunTotalAreaQuery->pullOcclusionQuery(&totalPixels); + mSunVisibleAreaQuery->pullOcclusionQuery(&visiblePixels); + + if (totalPixels == 0) + { + // probably outside of the view frustum + mSunVisibility = 0; + } + else + { + mSunVisibility = float(visiblePixels) / float(totalPixels); + if (mSunVisibility > 1) mSunVisibility = 1; + } + + unsigned int result; + + mSingleObjectQuery->pullOcclusionQuery(&result); + + mTestResult = (result != 0); + + mQuerySingleObjectStarted = false; + mQuerySingleObjectRequested = false; + + mDoQuery = true; + } +} + +void OcclusionQuery::occlusionTest(const Ogre::Vector3& position, Ogre::SceneNode* object) +{ + assert( !occlusionTestPending() + && "Occlusion test still pending"); + + mBBQuerySingleObject->setVisible(true); + + mObjectNode->setPosition(position); + // scale proportional to camera distance, in order to always give the billboard the same size in screen-space + mObjectNode->setScale( Vector3(1,1,1)*(position - mRendering->getCamera()->getRealPosition()).length() ); + + mQuerySingleObjectRequested = true; +} + +bool OcclusionQuery::occlusionTestPending() +{ + return (mQuerySingleObjectRequested || mQuerySingleObjectStarted); +} + +void OcclusionQuery::setSunNode(Ogre::SceneNode* node) +{ + mSunNode = node; + if (!mBBNode) + mBBNode = node->getParentSceneNode()->createChildSceneNode(); +} + +bool OcclusionQuery::getTestResult() +{ + assert( !occlusionTestPending() + && "Occlusion test still pending"); + + return mTestResult; +} + +bool OcclusionQuery::isPotentialOccluder(Ogre::SceneNode* node) +{ + bool result = false; + for (unsigned int i=0; i < node->numAttachedObjects(); ++i) + { + MovableObject* ob = node->getAttachedObject(i); + std::string type = ob->getMovableType(); + if (type == "Entity") + { + Entity* ent = static_cast(ob); + for (unsigned int j=0; j < ent->getNumSubEntities(); ++j) + { + // if any sub entity has a material with depth write off, + // consider the object as not an occluder + MaterialPtr mat = ent->getSubEntity(j)->getMaterial(); + + Material::TechniqueIterator techIt = mat->getTechniqueIterator(); + while (techIt.hasMoreElements()) + { + Technique* tech = techIt.getNext(); + Technique::PassIterator passIt = tech->getPassIterator(); + while (passIt.hasMoreElements()) + { + Pass* pass = passIt.getNext(); + + if (pass->getDepthWriteEnabled() == false) + return false; + else + result = true; + } + } + } + } + } + return result; +} diff --git a/apps/openmw/mwrender/occlusionquery.hpp b/apps/openmw/mwrender/occlusionquery.hpp new file mode 100644 index 000000000..c76fcccd0 --- /dev/null +++ b/apps/openmw/mwrender/occlusionquery.hpp @@ -0,0 +1,105 @@ +#ifndef _GAME_OCCLUSION_QUERY_H +#define _GAME_OCCLUSION_QUERY_H + +#include +#include + +namespace Ogre +{ + class HardwareOcclusionQuery; + class Entity; + class SceneNode; +} + +#include + +namespace MWRender +{ + /// + /// \brief Implements hardware occlusion queries on the GPU + /// + class OcclusionQuery : public Ogre::RenderObjectListener, public Ogre::RenderQueueListener + { + public: + OcclusionQuery(OEngine::Render::OgreRenderer*, Ogre::SceneNode* sunNode); + ~OcclusionQuery(); + + /** + * @return true if occlusion queries are supported on the user's hardware + */ + bool supported(); + + /** + * per-frame update + */ + void update(float duration); + + /** + * request occlusion test for a billboard at the given position, omitting an entity + * @param position of the billboard in ogre coordinates + * @param object to exclude from the occluders + */ + void occlusionTest(const Ogre::Vector3& position, Ogre::SceneNode* object); + + /** + * @return true if a request is still outstanding + */ + bool occlusionTestPending(); + + /** + * Checks if the objects held by this scenenode + * can be considered as potential occluders + * (which might not be the case when transparency is involved) + * @param Scene node + */ + bool isPotentialOccluder(Ogre::SceneNode* node); + + /** + * @return true if the object tested in the last request was occluded + */ + bool getTestResult(); + + float getSunVisibility() const {return mSunVisibility;}; + + void setSunNode(Ogre::SceneNode* node); + + private: + Ogre::HardwareOcclusionQuery* mSunTotalAreaQuery; + Ogre::HardwareOcclusionQuery* mSunVisibleAreaQuery; + Ogre::HardwareOcclusionQuery* mSingleObjectQuery; + Ogre::HardwareOcclusionQuery* mActiveQuery; + + Ogre::BillboardSet* mBBQueryVisible; + Ogre::BillboardSet* mBBQueryTotal; + Ogre::BillboardSet* mBBQuerySingleObject; + + Ogre::SceneNode* mSunNode; + Ogre::SceneNode* mBBNode; + Ogre::SceneNode* mBBNodeReal; + float mSunVisibility; + + Ogre::SceneNode* mObjectNode; + + bool mWasVisible; + bool mObjectWasVisible; + + bool mTestResult; + + bool mSupported; + bool mDoQuery; + bool mDoQuery2; + + bool mQuerySingleObjectRequested; + bool mQuerySingleObjectStarted; + + OEngine::Render::OgreRenderer* mRendering; + + protected: + virtual void notifyRenderSingleObject(Ogre::Renderable* rend, const Ogre::Pass* pass, const Ogre::AutoParamDataSource* source, + const Ogre::LightList* pLightList, bool suppressRenderStateChanges); + + virtual void renderQueueEnded(Ogre::uint8 queueGroupId, const Ogre::String& invocation, bool& repeatThisInvocation); + }; +} + +#endif diff --git a/apps/openmw/mwrender/renderconst.hpp b/apps/openmw/mwrender/renderconst.hpp new file mode 100644 index 000000000..2c7f9e9ac --- /dev/null +++ b/apps/openmw/mwrender/renderconst.hpp @@ -0,0 +1,62 @@ +#ifndef GAME_RENDER_CONST_H +#define GAME_RENDER_CONST_H + +#include + +namespace MWRender +{ + +// Render queue groups +enum RenderQueueGroups +{ + // Sky early (atmosphere, clouds, moons) + RQG_SkiesEarly = Ogre::RENDER_QUEUE_SKIES_EARLY, + + RQG_Main = Ogre::RENDER_QUEUE_MAIN, + + RQG_Water = Ogre::RENDER_QUEUE_7+1, + + RQG_Alpha = Ogre::RENDER_QUEUE_MAIN, + + RQG_UnderWater = Ogre::RENDER_QUEUE_7+1, + + RQG_OcclusionQuery = Ogre::RENDER_QUEUE_8, + + // Sky late (sun & sun flare) + RQG_SkiesLate = Ogre::RENDER_QUEUE_SKIES_LATE +}; + +// Visibility flags +enum VisibilityFlags +{ + // Terrain + RV_Terrain = 1, + + // Statics (e.g. trees, houses) + RV_Statics = 2, + + // Small statics + RV_StaticsSmall = 4, + + // Water + RV_Water = 8, + + // Actors (player, npcs, creatures) + RV_Actors = 16, + + // Misc objects (containers, dynamic objects) + RV_Misc = 32, + + RV_Sky = 64, + + // Sun glare (not visible in reflection) + RV_Glare = 128, + + RV_Map = RV_Terrain + RV_Statics + RV_StaticsSmall + RV_Misc + RV_Water, + + /// \todo markers (normally hidden) +}; + +} + +#endif diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index e2aea19c6..c1462807f 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -12,7 +12,12 @@ #include "../mwworld/world.hpp" // these includes can be removed once the static-hack is gone #include "../mwworld/ptr.hpp" #include +#include +#include "shadows.hpp" +#include "shaderhelper.hpp" +#include "localmap.hpp" +#include "water.hpp" using namespace MWRender; using namespace Ogre; @@ -20,16 +25,54 @@ using namespace Ogre; namespace MWRender { RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const boost::filesystem::path& resDir, OEngine::Physic::PhysicEngine* engine, MWWorld::Environment& environment) -:mRendering(_rend), mObjects(mRendering), mActors(mRendering, environment), mAmbientMode(0), mDebugging(engine) + :mRendering(_rend), mObjects(mRendering), mActors(mRendering, environment), mAmbientMode(0), mSunEnabled(0) { - mRendering.createScene("PlayerCam", 55, 5); + mRendering.createScene("PlayerCam", Settings::Manager::getFloat("field of view", "General"), 5); + + mWater = 0; + + //The fog type must be set before any terrain objects are created as if the + //fog type is set to FOG_NONE then the initially created terrain won't have any fog + configureFog(1, ColourValue(1,1,1)); // Set default mipmap level (NB some APIs ignore this) - TextureManager::getSingleton().setDefaultNumMipmaps(5); + TextureManager::getSingleton().setDefaultNumMipmaps(Settings::Manager::getInt("num mipmaps", "General")); + + // Set default texture filtering options + TextureFilterOptions tfo; + std::string filter = Settings::Manager::getString("texture filtering", "General"); + if (filter == "anisotropic") tfo = TFO_ANISOTROPIC; + else if (filter == "trilinear") tfo = TFO_TRILINEAR; + else if (filter == "bilinear") tfo = TFO_BILINEAR; + else if (filter == "none") tfo = TFO_NONE; + + MaterialManager::getSingleton().setDefaultTextureFiltering(tfo); + MaterialManager::getSingleton().setDefaultAnisotropy( (filter == "anisotropic") ? Settings::Manager::getInt("anisotropy", "General") : 1 ); // Load resources ResourceGroupManager::getSingleton().initialiseAllResourceGroups(); + // disable unsupported effects + const RenderSystemCapabilities* caps = Root::getSingleton().getRenderSystem()->getCapabilities(); + if (caps->getNumMultiRenderTargets() < 2) + Settings::Manager::setBool("shader", "Water", false); + if (!caps->isShaderProfileSupported("fp40") && !caps->isShaderProfileSupported("ps_4_0")) + Settings::Manager::setBool("enabled", "Shadows", false); + + // note that the order is important here + if (useMRT()) + { + CompositorManager::getSingleton().addCompositor(mRendering.getViewport(), "gbuffer"); + CompositorManager::getSingleton().setCompositorEnabled(mRendering.getViewport(), "gbuffer", true); + CompositorManager::getSingleton().addCompositor(mRendering.getViewport(), "Underwater"); + CompositorManager::getSingleton().addCompositor(mRendering.getViewport(), "gbufferFinalizer"); + CompositorManager::getSingleton().setCompositorEnabled(mRendering.getViewport(), "gbufferFinalizer", true); + } + else + { + CompositorManager::getSingleton().addCompositor(mRendering.getViewport(), "UnderwaterNoMRT"); + } + // Turn the entire scene (represented by the 'root' node) -90 // degrees around the x axis. This makes Z go upwards, and Y go into // the screen (when x is to the right.) This is the orientation that @@ -40,31 +83,39 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const mMwRoot->pitch(Degree(-90)); mObjects.setMwRoot(mMwRoot); mActors.setMwRoot(mMwRoot); - - //used to obtain ingame information of ogre objects (which are faced or selected) - mRaySceneQuery = mRendering.getScene()->createRayQuery(Ray()); Ogre::SceneNode *playerNode = mMwRoot->createChildSceneNode ("player"); playerNode->pitch(Degree(90)); Ogre::SceneNode *cameraYawNode = playerNode->createChildSceneNode(); Ogre::SceneNode *cameraPitchNode = cameraYawNode->createChildSceneNode(); cameraPitchNode->attachObject(mRendering.getCamera()); - + + mShadows = new Shadows(&mRendering); + mShaderHelper = new ShaderHelper(this); + + mTerrainManager = new TerrainManager(mRendering.getScene(), this, + environment); + //mSkyManager = 0; mSkyManager = new SkyManager(mMwRoot, mRendering.getCamera(), &environment); + mOcclusionQuery = new OcclusionQuery(&mRendering, mSkyManager->getSunNode()); + mPlayer = new MWRender::Player (mRendering.getCamera(), playerNode); mSun = 0; - mLocalMap = new MWRender::LocalMap(&mRendering, &environment); + mDebugging = new Debugging(mMwRoot, environment, engine); + mLocalMap = new MWRender::LocalMap(&mRendering, this, &environment); } RenderingManager::~RenderingManager () { - //TODO: destroy mSun? delete mPlayer; delete mSkyManager; + delete mDebugging; + delete mTerrainManager; delete mLocalMap; + delete mOcclusionQuery; } MWRender::SkyManager* RenderingManager::getSkyManager() @@ -88,14 +139,35 @@ OEngine::Render::Fader* RenderingManager::getFader() return mRendering.getFader(); } -void RenderingManager::removeCell (MWWorld::Ptr::CellStore *store){ +void RenderingManager::removeCell (MWWorld::Ptr::CellStore *store) +{ mObjects.removeCell(store); mActors.removeCell(store); + mDebugging->cellRemoved(store); + if (store->cell->isExterior()) + mTerrainManager->cellRemoved(store); +} + +void RenderingManager::removeWater () +{ + if(mWater){ + mWater->setActive(false); + } +} + +void RenderingManager::toggleWater() +{ + if (mWater) + mWater->toggle(); } void RenderingManager::cellAdded (MWWorld::Ptr::CellStore *store) { mObjects.buildStaticGeometry (*store); + mDebugging->cellAdded(store); + if (store->cell->isExterior()) + mTerrainManager->cellAdded(store); + waterAdded(store); } void RenderingManager::addObject (const MWWorld::Ptr& ptr){ @@ -136,18 +208,44 @@ void RenderingManager::moveObjectToCell (const MWWorld::Ptr& ptr, const Ogre::Ve void RenderingManager::update (float duration){ mActors.update (duration); - + + mOcclusionQuery->update(duration); + mSkyManager->update(duration); - + + mSkyManager->setGlare(mOcclusionQuery->getSunVisibility()); + mRendering.update(duration); - mLocalMap->updatePlayer( mRendering.getCamera()->getRealPosition(), mRendering.getCamera()->getRealDirection() ); + mLocalMap->updatePlayer( mRendering.getCamera()->getRealPosition(), mRendering.getCamera()->getRealOrientation() ); + + checkUnderwater(); +} +void RenderingManager::waterAdded (MWWorld::Ptr::CellStore *store){ + if(store->cell->data.flags & store->cell->HasWater){ + if(mWater == 0) + mWater = new MWRender::Water(mRendering.getCamera(), mSkyManager, store->cell); + else + mWater->changeCell(store->cell); + mWater->setActive(true); + } + else + removeWater(); + +} + +void RenderingManager::setWaterHeight(const float height) +{ + if (mWater) + mWater->setHeight(height); } void RenderingManager::skyEnable () { if(mSkyManager) mSkyManager->enable(); + + mOcclusionQuery->setSunNode(mSkyManager->getSunNode()); } void RenderingManager::skyDisable () @@ -171,7 +269,7 @@ void RenderingManager::skySetDate (int day, int month) int RenderingManager::skyGetMasserPhase() const { - + return mSkyManager->getMasserPhase(); } @@ -187,17 +285,31 @@ void RenderingManager::skySetMoonColour (bool red){ bool RenderingManager::toggleRenderMode(int mode) { - if (mode == MWWorld::World::Render_CollisionDebug) - return mDebugging.toggleRenderMode(mode); + if (mode != MWWorld::World::Render_Wireframe) + return mDebugging->toggleRenderMode(mode); else // if (mode == MWWorld::World::Render_Wireframe) { if (mRendering.getCamera()->getPolygonMode() == PM_SOLID) { + // disable compositors + if (useMRT()) + { + CompositorManager::getSingleton().setCompositorEnabled(mRendering.getViewport(), "gbuffer", false); + CompositorManager::getSingleton().setCompositorEnabled(mRendering.getViewport(), "gbufferFinalizer", false); + } + mRendering.getCamera()->setPolygonMode(PM_WIREFRAME); return true; } else { + // re-enable compositors + if (useMRT()) + { + CompositorManager::getSingleton().setCompositorEnabled(mRendering.getViewport(), "gbuffer", true); + CompositorManager::getSingleton().setCompositorEnabled(mRendering.getViewport(), "gbufferFinalizer", true); + } + mRendering.getCamera()->setPolygonMode(PM_SOLID); return false; } @@ -206,27 +318,29 @@ bool RenderingManager::toggleRenderMode(int mode) void RenderingManager::configureFog(ESMS::CellStore &mCell) { - Ogre::ColourValue color; - color.setAsABGR (mCell.cell->ambi.fog); + Ogre::ColourValue color; + color.setAsABGR (mCell.cell->ambi.fog); - configureFog(mCell.cell->ambi.fogDensity, color); + configureFog(mCell.cell->ambi.fogDensity, color); } void RenderingManager::configureFog(const float density, const Ogre::ColourValue& colour) -{ - /// \todo make the viewing distance and fog start/end configurable +{ + float max = Settings::Manager::getFloat("max viewing distance", "Viewing distance"); - // right now we load 3x3 cells, so the maximum viewing distance we - // can allow (to prevent objects suddenly popping up) equals: - // 8192 * 0.69 - // ^ cell size ^ minimum density value used (clear weather) - float low = 5652.48 / density / 2.f; - float high = 5652.48 / density; + float low = max / (density) * Settings::Manager::getFloat("fog start factor", "Viewing distance"); + float high = max / (density) * Settings::Manager::getFloat("fog end factor", "Viewing distance"); - mRendering.getScene()->setFog (FOG_LINEAR, colour, 0, low, high); - - mRendering.getCamera()->setFarClipDistance ( high ); - mRendering.getViewport()->setBackgroundColour (colour); + mRendering.getScene()->setFog (FOG_LINEAR, colour, 0, low, high); + + mRendering.getCamera()->setFarClipDistance ( max / density ); + mRendering.getViewport()->setBackgroundColour (colour); + + CompositorInstance* inst = CompositorManager::getSingleton().getCompositorChain(mRendering.getViewport())->getCompositor("gbuffer"); + if (inst != 0) + inst->getCompositor()->getTechnique(0)->getTargetPass(0)->getPass(0)->setClearColour(colour); + if (mWater) + mWater->setViewportBackground(colour); } @@ -236,55 +350,62 @@ void RenderingManager::setAmbientMode() { case 0: - mRendering.getScene()->setAmbientLight(mAmbientColor); + setAmbientColour(mAmbientColor); break; case 1: - mRendering.getScene()->setAmbientLight(0.7f*mAmbientColor + 0.3f*ColourValue(1,1,1)); + setAmbientColour(0.7f*mAmbientColor + 0.3f*ColourValue(1,1,1)); break; case 2: - mRendering.getScene()->setAmbientLight(ColourValue(1,1,1)); + setAmbientColour(ColourValue(1,1,1)); break; } } void RenderingManager::configureAmbient(ESMS::CellStore &mCell) { - mAmbientColor.setAsABGR (mCell.cell->ambi.ambient); - setAmbientMode(); + mAmbientColor.setAsABGR (mCell.cell->ambi.ambient); + setAmbientMode(); - // Create a "sun" that shines light downwards. It doesn't look - // completely right, but leave it for now. - if(!mSun) - { - mSun = mRendering.getScene()->createLight(); - } - Ogre::ColourValue colour; - colour.setAsABGR (mCell.cell->ambi.sunlight); - mSun->setDiffuseColour (colour); - mSun->setType(Ogre::Light::LT_DIRECTIONAL); - mSun->setDirection(0,-1,0); + // Create a "sun" that shines light downwards. It doesn't look + // completely right, but leave it for now. + if(!mSun) + { + mSun = mRendering.getScene()->createLight(); + } + Ogre::ColourValue colour; + colour.setAsABGR (mCell.cell->ambi.sunlight); + mSun->setDiffuseColour (colour); + mSun->setType(Ogre::Light::LT_DIRECTIONAL); + mSun->setDirection(0,-1,0); } // Switch through lighting modes. void RenderingManager::toggleLight() { - if (mAmbientMode==2) - mAmbientMode = 0; - else - ++mAmbientMode; + if (mAmbientMode==2) + mAmbientMode = 0; + else + ++mAmbientMode; - switch (mAmbientMode) - { - case 0: std::cout << "Setting lights to normal\n"; break; - case 1: std::cout << "Turning the lights up\n"; break; - case 2: std::cout << "Turning the lights to full\n"; break; - } + switch (mAmbientMode) + { + case 0: std::cout << "Setting lights to normal\n"; break; + case 1: std::cout << "Turning the lights up\n"; break; + case 2: std::cout << "Turning the lights to full\n"; break; + } - setAmbientMode(); + setAmbientMode(); +} +void RenderingManager::checkUnderwater() +{ + if(mWater) + { + mWater->checkUnderwater( mRendering.getCamera()->getRealPosition().y ); + } } void RenderingManager::playAnimationGroup (const MWWorld::Ptr& ptr, const std::string& groupName, @@ -300,30 +421,43 @@ void RenderingManager::skipAnimation (const MWWorld::Ptr& ptr) void RenderingManager::setSunColour(const Ogre::ColourValue& colour) { + if (!mSunEnabled) return; mSun->setDiffuseColour(colour); + mSun->setSpecularColour(colour); + mTerrainManager->setDiffuse(colour); } void RenderingManager::setAmbientColour(const Ogre::ColourValue& colour) { mRendering.getScene()->setAmbientLight(colour); + mTerrainManager->setAmbient(colour); } void RenderingManager::sunEnable() { - if (mSun) mSun->setVisible(true); + // Don't disable the light, as the shaders assume the first light to be directional. + //if (mSun) mSun->setVisible(true); + mSunEnabled = true; } void RenderingManager::sunDisable() { - if (mSun) mSun->setVisible(false); + // Don't disable the light, as the shaders assume the first light to be directional. + //if (mSun) mSun->setVisible(false); + mSunEnabled = false; + if (mSun) + { + mSun->setDiffuseColour(ColourValue(0,0,0)); + mSun->setSpecularColour(ColourValue(0,0,0)); + } } void RenderingManager::setSunDirection(const Ogre::Vector3& direction) { - // direction * -1 (because 'direction' is camera to sun vector and not sun to camera), + // direction * -1 (because 'direction' is camera to sun vector and not sun to camera), // then convert from MW to ogre coordinates (swap y,z and make y negative) if (mSun) mSun->setDirection(Vector3(-direction.x, -direction.z, direction.y)); - + mSkyManager->setSunDirection(direction); } @@ -345,4 +479,26 @@ void RenderingManager::preCellChange(MWWorld::Ptr::CellStore* cell) mLocalMap->saveFogOfWar(cell); } +void RenderingManager::disableLights() +{ + mObjects.disableLights(); + sunDisable(); +} + +void RenderingManager::enableLights() +{ + mObjects.enableLights(); + sunEnable(); +} + +const bool RenderingManager::useMRT() +{ + return Settings::Manager::getBool("shader", "Water"); +} + +Shadows* RenderingManager::getShadows() +{ + return mShadows; +} + } // namespace diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 78a1d2fdb..a563d78c6 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -3,6 +3,7 @@ #include "sky.hpp" +#include "terrain.hpp" #include "debugging.hpp" #include "../mwworld/class.hpp" @@ -24,15 +25,12 @@ #include "objects.hpp" #include "actors.hpp" #include "player.hpp" -#include "localmap.hpp" +#include "occlusionquery.hpp" namespace Ogre { - class Camera; - class Viewport; class SceneManager; class SceneNode; - class RaySceneQuery; class Quaternion; class Vector3; } @@ -45,7 +43,10 @@ namespace MWWorld namespace MWRender { - + class Shadows; + class ShaderHelper; + class LocalMap; + class Water; class RenderingManager: private RenderingInterface { @@ -59,6 +60,8 @@ class RenderingManager: private RenderingInterface { RenderingManager(OEngine::Render::OgreRenderer& _rend, const boost::filesystem::path& resDir, OEngine::Physic::PhysicEngine* engine, MWWorld::Environment& environment); virtual ~RenderingManager(); + + virtual MWRender::Player& getPlayer(); /// \todo move this to private again as soon as /// MWWorld::Player has been rewritten to not need access /// to internal details of the rendering system anymore @@ -67,7 +70,7 @@ class RenderingManager: private RenderingInterface { void toggleLight(); bool toggleRenderMode(int mode); - + OEngine::Render::Fader* getFader(); void removeCell (MWWorld::Ptr::CellStore *store); @@ -75,6 +78,11 @@ class RenderingManager: private RenderingInterface { /// \todo this function should be removed later. Instead the rendering subsystems should track /// when rebatching is needed and update automatically at the end of each frame. void cellAdded (MWWorld::Ptr::CellStore *store); + void waterAdded(MWWorld::Ptr::CellStore *store); + + void removeWater(); + + static const bool useMRT(); void preCellChange (MWWorld::Ptr::CellStore* store); ///< this event is fired immediately before changing cell @@ -86,17 +94,29 @@ class RenderingManager: private RenderingInterface { void scaleObject (const MWWorld::Ptr& ptr, const Ogre::Vector3& scale); void rotateObject (const MWWorld::Ptr& ptr, const::Ogre::Quaternion& orientation); + void checkUnderwater(); + void setWaterHeight(const float height); + void toggleWater(); + /// \param store Cell the object was in previously (\a ptr has already been updated to the new cell). void moveObjectToCell (const MWWorld::Ptr& ptr, const Ogre::Vector3& position, MWWorld::Ptr::CellStore *store); void update (float duration); - + void setAmbientColour(const Ogre::ColourValue& colour); void setSunColour(const Ogre::ColourValue& colour); void setSunDirection(const Ogre::Vector3& direction); void sunEnable(); void sunDisable(); - + + void disableLights(); + void enableLights(); + + bool occlusionQuerySupported() { return mOcclusionQuery->supported(); }; + OcclusionQuery* getOcclusionQuery() { return mOcclusionQuery; }; + + Shadows* getShadows(); + void setGlare(bool glare); void skyEnable (); void skyDisable (); @@ -109,13 +129,13 @@ class RenderingManager: private RenderingInterface { void requestMap (MWWorld::Ptr::CellStore* cell); ///< request the local map for a cell - + /// configure fog according to cell void configureFog(ESMS::CellStore &mCell); - + /// configure fog manually void configureFog(const float density, const Ogre::ColourValue& colour); - + void playAnimationGroup (const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number = 1); ///< Run animation for a MW-reference. Calls to this function for references that are currently not @@ -131,9 +151,17 @@ class RenderingManager: private RenderingInterface { private: void setAmbientMode(); - + + bool mSunEnabled; + SkyManager* mSkyManager; - + + OcclusionQuery* mOcclusionQuery; + + TerrainManager* mTerrainManager; + + MWRender::Water *mWater; + OEngine::Render::OgreRenderer &mRendering; MWRender::Objects mObjects; @@ -149,14 +177,18 @@ class RenderingManager: private RenderingInterface { /// that the OGRE coordinate system matches that used internally in /// Morrowind. Ogre::SceneNode *mMwRoot; - Ogre::RaySceneQuery *mRaySceneQuery; OEngine::Physic::PhysicEngine* mPhysicsEngine; MWRender::Player *mPlayer; - MWRender::Debugging mDebugging; + + MWRender::Debugging *mDebugging; MWRender::LocalMap* mLocalMap; + + MWRender::Shadows* mShadows; + + MWRender::ShaderHelper* mShaderHelper; }; } diff --git a/apps/openmw/mwrender/shaderhelper.cpp b/apps/openmw/mwrender/shaderhelper.cpp new file mode 100644 index 000000000..5354251f8 --- /dev/null +++ b/apps/openmw/mwrender/shaderhelper.cpp @@ -0,0 +1,308 @@ +#include "shaderhelper.hpp" +#include "renderingmanager.hpp" +#include "shadows.hpp" + +#include +#include +#include + +#include + +using namespace Ogre; +using namespace MWRender; + +ShaderHelper::ShaderHelper(RenderingManager* rend) +{ + mRendering = rend; + applyShaders(); +} + +void ShaderHelper::applyShaders() +{ + if (!Settings::Manager::getBool("shaders", "Objects")) return; + + bool mrt = RenderingManager::useMRT(); + bool shadows = Settings::Manager::getBool("enabled", "Shadows"); + bool split = Settings::Manager::getBool("split", "Shadows"); + + // shader for normal rendering + createShader(mrt, shadows, split, "main"); + + // fallback shader without mrt and without shadows + // (useful for reflection and for minimap) + createShader(false, false, false, "main_fallback"); +} + +void ShaderHelper::createShader(const bool mrt, const bool shadows, const bool split, const std::string& name) +{ + HighLevelGpuProgramManager& mgr = HighLevelGpuProgramManager::getSingleton(); + + const int numsplits = 3; + + // the number of lights to support. + // when rendering an object, OGRE automatically picks the lights that are + // closest to the object being rendered. unfortunately this mechanism does + // not work perfectly for objects batched together (they will all use the same + // lights). to work around this, we are simply pushing the maximum number + // of lights here in order to minimize disappearing lights. + int num_lights = Settings::Manager::getInt("num lights", "Objects"); + + { + // vertex + HighLevelGpuProgramPtr vertex; + if (!mgr.getByName(name+"_vp").isNull()) + mgr.remove(name+"_vp"); + + vertex = mgr.createProgram(name+"_vp", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + "cg", GPT_VERTEX_PROGRAM); + vertex->setParameter("profiles", "vs_4_0 vs_2_x vp40 arbvp1"); + vertex->setParameter("entry_point", "main_vp"); + StringUtil::StrStreamType outStream; + outStream << + "void main_vp( \n" + " float4 position : POSITION, \n" + " float4 normal : NORMAL, \n" + " float4 colour : COLOR, \n" + " in float2 uv : TEXCOORD0, \n" + " out float2 oUV : TEXCOORD0, \n" + " out float4 oPosition : POSITION, \n" + " out float4 oPositionObjSpace : TEXCOORD1, \n" + " out float4 oNormal : TEXCOORD2, \n" + " out float oDepth : TEXCOORD3, \n" + " out float4 oVertexColour : TEXCOORD4, \n"; + if (shadows && !split) outStream << + " out float4 oLightSpacePos0 : TEXCOORD5, \n" + " uniform float4x4 worldMatrix, \n" + " uniform float4x4 texViewProjMatrix0, \n"; + else + { + for (int i=0; isetSource(outStream.str()); + vertex->load(); + vertex->getDefaultParameters()->setNamedAutoConstant("worldViewProj", GpuProgramParameters::ACT_WORLDVIEWPROJ_MATRIX); + if (shadows) + { + vertex->getDefaultParameters()->setNamedAutoConstant("worldMatrix", GpuProgramParameters::ACT_WORLD_MATRIX); + if (!split) + vertex->getDefaultParameters()->setNamedAutoConstant("texViewProjMatrix0", GpuProgramParameters::ACT_TEXTURE_VIEWPROJ_MATRIX, 0); + else + { + for (int i=0; igetDefaultParameters()->setNamedAutoConstant("texViewProjMatrix"+StringConverter::toString(i), GpuProgramParameters::ACT_TEXTURE_VIEWPROJ_MATRIX, i); + } + } + } + } + + { + // fragment + HighLevelGpuProgramPtr fragment; + if (!mgr.getByName(name+"_fp").isNull()) + mgr.remove(name+"_fp"); + + fragment = mgr.createProgram(name+"_fp", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + "cg", GPT_FRAGMENT_PROGRAM); + fragment->setParameter("profiles", "ps_4_0 ps_2_x fp40 arbfp1"); + fragment->setParameter("entry_point", "main_fp"); + StringUtil::StrStreamType outStream; + + if (shadows) outStream << + "float depthShadow(sampler2D shadowMap, float4 shadowMapPos, float2 offset) \n" + "{ \n" + " shadowMapPos /= shadowMapPos.w; \n" + " float3 o = float3(offset.xy, -offset.x) * 0.3f; \n" + " float c = (shadowMapPos.z <= tex2D(shadowMap, shadowMapPos.xy - o.xy).r) ? 1 : 0; // top left \n" + " c += (shadowMapPos.z <= tex2D(shadowMap, shadowMapPos.xy + o.xy).r) ? 1 : 0; // bottom right \n" + " c += (shadowMapPos.z <= tex2D(shadowMap, shadowMapPos.xy + o.zy).r) ? 1 : 0; // bottom left \n" + " c += (shadowMapPos.z <= tex2D(shadowMap, shadowMapPos.xy - o.zy).r) ? 1 : 0; // top right \n" + " return c / 4; \n" + "} \n"; + + outStream << + "void main_fp( \n" + " in float2 uv : TEXCOORD0, \n" + " out float4 oColor : COLOR, \n" + " uniform sampler2D texture : register(s0), \n" + " float4 positionObjSpace : TEXCOORD1, \n" + " float4 normal : TEXCOORD2, \n" + " float iDepth : TEXCOORD3, \n" + " float4 vertexColour : TEXCOORD4, \n" + " uniform float4 fogColour, \n" + " uniform float4 fogParams, \n"; + + if (shadows) outStream << + " uniform float4 shadowFar_fadeStart, \n"; + + if (shadows && !split) outStream << + " uniform sampler2D shadowMap : register(s1), \n" + " float4 lightSpacePos0 : TEXCOORD5, \n" + " uniform float4 invShadowmapSize0, \n"; + else + { + outStream << + " uniform float4 pssmSplitPoints, \n"; + for (int i=0; i shadowFar_fadeStart.x) ? 1 : ((iDepth > shadowFar_fadeStart.y) ? 1-((1-shadow)*fade) : shadow); \n" + " lightColour.xyz += shadow * lit(dot(normalize(lightDir), normalize(normal)), 0, 0).y * lightDiffuse"<setSource(outStream.str()); + fragment->load(); + + for (int i=0; igetDefaultParameters()->setNamedAutoConstant("lightPositionObjSpace"+StringConverter::toString(i), GpuProgramParameters::ACT_LIGHT_POSITION_OBJECT_SPACE, i); + fragment->getDefaultParameters()->setNamedAutoConstant("lightDiffuse"+StringConverter::toString(i), GpuProgramParameters::ACT_LIGHT_DIFFUSE_COLOUR, i); + fragment->getDefaultParameters()->setNamedAutoConstant("lightAttenuation"+StringConverter::toString(i), GpuProgramParameters::ACT_LIGHT_ATTENUATION, i); + } + fragment->getDefaultParameters()->setNamedAutoConstant("emissive", GpuProgramParameters::ACT_SURFACE_EMISSIVE_COLOUR); + fragment->getDefaultParameters()->setNamedAutoConstant("diffuse", GpuProgramParameters::ACT_SURFACE_DIFFUSE_COLOUR); + fragment->getDefaultParameters()->setNamedAutoConstant("ambient", GpuProgramParameters::ACT_SURFACE_AMBIENT_COLOUR); + fragment->getDefaultParameters()->setNamedAutoConstant("lightAmbient", GpuProgramParameters::ACT_AMBIENT_LIGHT_COLOUR); + fragment->getDefaultParameters()->setNamedAutoConstant("fogColour", GpuProgramParameters::ACT_FOG_COLOUR); + fragment->getDefaultParameters()->setNamedAutoConstant("fogParams", GpuProgramParameters::ACT_FOG_PARAMS); + + if (shadows) + { + fragment->getDefaultParameters()->setNamedConstant("shadowFar_fadeStart", Vector4(mRendering->getShadows()->getShadowFar(), mRendering->getShadows()->getFadeStart()*mRendering->getShadows()->getShadowFar(), 0, 0)); + for (int i=0; i < (split ? numsplits : 1); ++i) + { + fragment->getDefaultParameters()->setNamedAutoConstant("invShadowmapSize" + StringConverter::toString(i), GpuProgramParameters::ACT_INVERSE_TEXTURE_SIZE, i+1); + } + if (split) + { + Vector4 splitPoints; + const PSSMShadowCameraSetup::SplitPointList& splitPointList = mRendering->getShadows()->getPSSMSetup()->getSplitPoints(); + // Populate from split point 1, not 0, since split 0 isn't useful (usually 0) + for (int i = 1; i < numsplits; ++i) + { + splitPoints[i-1] = splitPointList[i]; + } + fragment->getDefaultParameters()->setNamedConstant("pssmSplitPoints", splitPoints); + } + } + + if (mrt) + fragment->getDefaultParameters()->setNamedAutoConstant("far", GpuProgramParameters::ACT_FAR_CLIP_DISTANCE); + } +} diff --git a/apps/openmw/mwrender/shaderhelper.hpp b/apps/openmw/mwrender/shaderhelper.hpp new file mode 100644 index 000000000..356d345de --- /dev/null +++ b/apps/openmw/mwrender/shaderhelper.hpp @@ -0,0 +1,29 @@ +#ifndef GAME_SHADERHELPER_H +#define GAME_SHADERHELPER_H + +#include + +namespace MWRender +{ + class RenderingManager; + + /// + /// \brief manages the main shader + /// + class ShaderHelper + { + public: + ShaderHelper(RenderingManager* rend); + + void applyShaders(); + ///< apply new settings + + private: + RenderingManager* mRendering; + + void createShader(const bool mrt, const bool shadows, const bool split, const std::string& name); + }; + +} + +#endif diff --git a/apps/openmw/mwrender/shadows.cpp b/apps/openmw/mwrender/shadows.cpp new file mode 100644 index 000000000..bf5602f43 --- /dev/null +++ b/apps/openmw/mwrender/shadows.cpp @@ -0,0 +1,173 @@ +#include "shadows.hpp" + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include "renderconst.hpp" + +using namespace Ogre; +using namespace MWRender; + +Shadows::Shadows(OEngine::Render::OgreRenderer* rend) : + mShadowFar(1000), mFadeStart(0.9) +{ + mRendering = rend; + mSceneMgr = mRendering->getScene(); + recreate(); +} + +void Shadows::recreate() +{ + bool enabled = Settings::Manager::getBool("enabled", "Shadows"); + + // Split shadow maps are currently disabled because the terrain cannot cope with them + // (Too many texture units) Solution would be a multi-pass terrain material + bool split = Settings::Manager::getBool("split", "Shadows"); + //const bool split = false; + + if (!enabled) + { + mSceneMgr->setShadowTechnique(SHADOWTYPE_NONE); + return; + } + + int texsize = Settings::Manager::getInt("texture size", "Shadows"); + mSceneMgr->setShadowTextureSize(texsize); + + mSceneMgr->setShadowTechnique(SHADOWTYPE_TEXTURE_MODULATIVE_INTEGRATED); + + // no point light shadows, i'm afraid. might revisit this with Deferred Shading + mSceneMgr->setShadowTextureCountPerLightType(Light::LT_POINT, 0); + + mSceneMgr->setShadowTextureCountPerLightType(Light::LT_DIRECTIONAL, split ? 3 : 1); + mSceneMgr->setShadowTextureCount(split ? 3 : 1); + + mSceneMgr->setShadowTextureSelfShadow(true); + mSceneMgr->setShadowCasterRenderBackFaces(true); + mSceneMgr->setShadowTextureCasterMaterial("depth_shadow_caster"); + mSceneMgr->setShadowTexturePixelFormat(PF_FLOAT32_R); + mSceneMgr->setShadowDirectionalLightExtrusionDistance(1000000); + + mShadowFar = split ? Settings::Manager::getInt("split shadow distance", "Shadows") : Settings::Manager::getInt("shadow distance", "Shadows"); + mSceneMgr->setShadowFarDistance(mShadowFar); + + mFadeStart = Settings::Manager::getFloat("fade start", "Shadows"); + + ShadowCameraSetupPtr shadowCameraSetup; + if (split) + { + mPSSMSetup = new PSSMShadowCameraSetup(); + mPSSMSetup->setSplitPadding(5); + mPSSMSetup->calculateSplitPoints(3, mRendering->getCamera()->getNearClipDistance(), mShadowFar); + + const Real adjustFactors[3] = {64, 64, 64}; + for (int i=0; i < 3; ++i) + { + mPSSMSetup->setOptimalAdjustFactor(i, adjustFactors[i]); + /*if (i==0) + mSceneMgr->setShadowTextureConfig(i, texsize, texsize, Ogre::PF_FLOAT32_R); + else if (i ==1) + mSceneMgr->setShadowTextureConfig(i, texsize/2, texsize/2, Ogre::PF_FLOAT32_R); + else if (i ==2) + mSceneMgr->setShadowTextureConfig(i, texsize/4, texsize/4, Ogre::PF_FLOAT32_R);*/ + } + + shadowCameraSetup = ShadowCameraSetupPtr(mPSSMSetup); + } + else + { + LiSPSMShadowCameraSetup* lispsmSetup = new LiSPSMShadowCameraSetup(); + lispsmSetup->setOptimalAdjustFactor(2); + //lispsmSetup->setCameraLightDirectionThreshold(Degree(0)); + //lispsmSetup->setUseAggressiveFocusRegion(false); + shadowCameraSetup = ShadowCameraSetupPtr(lispsmSetup); + } + mSceneMgr->setShadowCameraSetup(shadowCameraSetup); + + // Set visibility mask for the shadow render textures + int visibilityMask = RV_Actors * Settings::Manager::getBool("actor shadows", "Shadows") + + (RV_Statics + RV_StaticsSmall) * Settings::Manager::getBool("statics shadows", "Shadows") + + RV_Misc * Settings::Manager::getBool("misc shadows", "Shadows"); + + for (int i = 0; i < (split ? 3 : 1); ++i) + { + TexturePtr shadowTexture = mSceneMgr->getShadowTexture(i); + Viewport* vp = shadowTexture->getBuffer()->getRenderTarget()->getViewport(0); + vp->setVisibilityMask(visibilityMask); + } + + // -------------------------------------------------------------------------------------------------------------------- + // --------------------------- Debug overlays to display the content of shadow maps ----------------------------------- + // -------------------------------------------------------------------------------------------------------------------- + /* + OverlayManager& mgr = OverlayManager::getSingleton(); + Overlay* overlay; + + // destroy if already exists + if (overlay = mgr.getByName("DebugOverlay")) + mgr.destroy(overlay); + + overlay = mgr.create("DebugOverlay"); + for (size_t i = 0; i < (split ? 3 : 1); ++i) { + TexturePtr tex = mRendering->getScene()->getShadowTexture(i); + + // Set up a debug panel to display the shadow + + if (MaterialManager::getSingleton().resourceExists("Ogre/DebugTexture" + StringConverter::toString(i))) + MaterialManager::getSingleton().remove("Ogre/DebugTexture" + StringConverter::toString(i)); + MaterialPtr debugMat = MaterialManager::getSingleton().create( + "Ogre/DebugTexture" + StringConverter::toString(i), + ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); + + debugMat->getTechnique(0)->getPass(0)->setLightingEnabled(false); + TextureUnitState *t = debugMat->getTechnique(0)->getPass(0)->createTextureUnitState(tex->getName()); + t->setTextureAddressingMode(TextureUnitState::TAM_CLAMP); + + OverlayContainer* debugPanel; + + // destroy container if exists + try + { + if (debugPanel = + static_cast( + mgr.getOverlayElement("Ogre/DebugTexPanel" + StringConverter::toString(i) + ))) + mgr.destroyOverlayElement(debugPanel); + } + catch (Ogre::Exception&) {} + + debugPanel = (OverlayContainer*) + (OverlayManager::getSingleton().createOverlayElement("Panel", "Ogre/DebugTexPanel" + StringConverter::toString(i))); + debugPanel->_setPosition(0.8, i*0.25); + debugPanel->_setDimensions(0.2, 0.24); + debugPanel->setMaterialName(debugMat->getName()); + debugPanel->show(); + overlay->add2D(debugPanel); + overlay->show(); + } + */ +} + +PSSMShadowCameraSetup* Shadows::getPSSMSetup() +{ + return mPSSMSetup; +} + +float Shadows::getShadowFar() const +{ + return mShadowFar; +} + +float Shadows::getFadeStart() const +{ + return mFadeStart; +} diff --git a/apps/openmw/mwrender/shadows.hpp b/apps/openmw/mwrender/shadows.hpp new file mode 100644 index 000000000..bc2b141f7 --- /dev/null +++ b/apps/openmw/mwrender/shadows.hpp @@ -0,0 +1,39 @@ +#ifndef GAME_SHADOWS_H +#define GAME_SHADOWS_H + +// forward declares +namespace Ogre +{ + class SceneManager; + class PSSMShadowCameraSetup; +} +namespace OEngine{ + namespace Render{ + class OgreRenderer; + } +} + +namespace MWRender +{ + class Shadows + { + public: + Shadows(OEngine::Render::OgreRenderer* rend); + + void recreate(); + + Ogre::PSSMShadowCameraSetup* getPSSMSetup(); + float getShadowFar() const; + float getFadeStart() const; + + protected: + OEngine::Render::OgreRenderer* mRendering; + Ogre::SceneManager* mSceneMgr; + + Ogre::PSSMShadowCameraSetup* mPSSMSetup; + float mShadowFar; + float mFadeStart; + }; +} + +#endif diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 5e8578002..859da2dc1 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -12,6 +12,8 @@ #include "../mwworld/environment.hpp" #include "../mwworld/world.hpp" +#include "renderconst.hpp" +#include "renderingmanager.hpp" using namespace MWRender; using namespace Ogre; @@ -30,7 +32,7 @@ BillboardObject::BillboardObject() void BillboardObject::setVisible(const bool visible) { - mNode->setVisible(visible); + mBBSet->setVisible(visible); } void BillboardObject::setSize(const float size) @@ -59,6 +61,11 @@ Vector3 BillboardObject::getPosition() const return Vector3(p.x, -p.z, p.y); } +void BillboardObject::setVisibilityFlags(int flags) +{ + mBBSet->setVisibilityFlags(flags); +} + void BillboardObject::setColour(const ColourValue& pColour) { mMaterial->getTechnique(0)->getPass(0)->setSelfIllumination(pColour); @@ -88,13 +95,14 @@ void BillboardObject::init(const String& textureName, /// \todo These billboards are not 100% correct, might want to revisit them later mBBSet = sceneMgr->createBillboardSet("SkyBillboardSet"+StringConverter::toString(bodyCount), 1); mBBSet->setDefaultDimensions(550.f*initialSize, 550.f*initialSize); - mBBSet->setRenderQueueGroup(RENDER_QUEUE_SKIES_EARLY+2); mBBSet->setBillboardType(BBT_PERPENDICULAR_COMMON); mBBSet->setCommonDirection( -position.normalisedCopy() ); + mBBSet->setVisibilityFlags(RV_Sky); mNode = rootNode->createChildSceneNode(); mNode->setPosition(finalPosition); mNode->attachObject(mBBSet); mBBSet->createBillboard(0,0,0); + mBBSet->setCastShadows(false); mMaterial = MaterialManager::getSingleton().create("BillboardMaterial"+StringConverter::toString(bodyCount), ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); mMaterial->removeAllTechniques(); @@ -108,6 +116,65 @@ void BillboardObject::init(const String& textureName, p->createTextureUnitState(textureName); mBBSet->setMaterialName("BillboardMaterial"+StringConverter::toString(bodyCount)); + HighLevelGpuProgramManager& mgr = HighLevelGpuProgramManager::getSingleton(); + HighLevelGpuProgramPtr vshader; + if (mgr.resourceExists("BBO_VP")) + vshader = mgr.getByName("BBO_VP"); + else + vshader = mgr.createProgram("BBO_VP", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, "cg", GPT_VERTEX_PROGRAM); + vshader->setParameter("profiles", "vs_2_x arbvp1"); + vshader->setParameter("entry_point", "main_vp"); + StringUtil::StrStreamType outStream; + outStream << + "void main_vp( \n" + " float4 position : POSITION, \n" + " in float2 uv : TEXCOORD0, \n" + " out float2 oUV : TEXCOORD0, \n" + " out float4 oPosition : POSITION, \n" + " uniform float4x4 worldViewProj \n" + ") \n" + "{ \n" + " oUV = uv; \n" + " oPosition = mul( worldViewProj, position ); \n" + "}"; + vshader->setSource(outStream.str()); + vshader->load(); + vshader->getDefaultParameters()->setNamedAutoConstant("worldViewProj", GpuProgramParameters::ACT_WORLDVIEWPROJ_MATRIX); + mMaterial->getTechnique(0)->getPass(0)->setVertexProgram(vshader->getName()); + + HighLevelGpuProgramPtr fshader; + if (mgr.resourceExists("BBO_FP")) + fshader = mgr.getByName("BBO_FP"); + else + fshader = mgr.createProgram("BBO_FP", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, "cg", GPT_FRAGMENT_PROGRAM); + + fshader->setParameter("profiles", "ps_2_x arbfp1"); + fshader->setParameter("entry_point", "main_fp"); + StringUtil::StrStreamType outStream2; + outStream2 << + "void main_fp( \n" + " in float2 uv : TEXCOORD0, \n" + " out float4 oColor : COLOR, \n"; + if (RenderingManager::useMRT()) outStream2 << + " out float4 oColor1 : COLOR1, \n"; + outStream2 << + " uniform sampler2D texture : TEXUNIT0, \n" + " uniform float4 diffuse, \n" + " uniform float4 emissive \n" + ") \n" + "{ \n" + " float4 tex = tex2D(texture, uv); \n" + " oColor = float4(emissive.xyz,1) * tex * float4(1,1,1,diffuse.a); \n"; + if (RenderingManager::useMRT()) outStream2 << + " oColor1 = float4(1, 0, 0, 1); \n"; + outStream2 << + "}"; + fshader->setSource(outStream2.str()); + fshader->load(); + fshader->getDefaultParameters()->setNamedAutoConstant("diffuse", GpuProgramParameters::ACT_SURFACE_DIFFUSE_COLOUR); + fshader->getDefaultParameters()->setNamedAutoConstant("emissive", GpuProgramParameters::ACT_SURFACE_EMISSIVE_COLOUR); + mMaterial->getTechnique(0)->getPass(0)->setFragmentProgram(fshader->getName()); + bodyCount++; } @@ -156,7 +223,10 @@ Moon::Moon( const String& textureName, outStream2 << "void main_fp( \n" " in float2 uv : TEXCOORD0, \n" - " out float4 oColor : COLOR, \n" + " out float4 oColor : COLOR, \n"; + if (RenderingManager::useMRT()) outStream2 << + " out float4 oColor1 : COLOR1, \n"; + outStream2 << " uniform sampler2D texture : TEXUNIT0, \n" " uniform float4 skyColour, \n" " uniform float4 diffuse, \n" @@ -164,7 +234,10 @@ Moon::Moon( const String& textureName, ") \n" "{ \n" " float4 tex = tex2D(texture, uv); \n" - " oColor = float4(emissive.xyz,1) * tex; \n" + " oColor = float4(emissive.xyz,1) * tex; \n"; + if (RenderingManager::useMRT()) outStream2 << + " oColor1 = float4(1, 0, 0, 1); \n"; + outStream2 << // use a circle for the alpha (compute UV distance to center) // looks a bit bad because its not filtered on the edges, // but it's cheaper than a seperate alpha texture. @@ -254,7 +327,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,16 +365,49 @@ 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) + , mGlare(0.0f) + , mEnabled(true) + , mSunEnabled(true) + , mMasserEnabled(true) + , mSecundaEnabled(true) + , mCreated(false) { - mEnvironment = env; mViewport = pCamera->getViewport(); mSceneMgr = pMwRoot->getCreator(); mRootNode = pCamera->getParentSceneNode()->createChildSceneNode(); mRootNode->pitch(Degree(-90)); // convert MW to ogre coordinates mRootNode->setInheritOrientation(false); +} +void SkyManager::create() +{ /// \todo preload all the textures and meshes that are used for sky rendering // Create overlay used for thunderstorm @@ -324,15 +430,17 @@ SkyManager::SkyManager (SceneNode* pMwRoot, Camera* pCamera, MWWorld::Environmen mSecunda = new Moon("textures\\tx_secunda_full.dds", 0.5, Vector3(-0.4, 0.4, 0.5), mRootNode); mSecunda->setType(Moon::Type_Secunda); - mSecunda->setRenderQueue(RENDER_QUEUE_SKIES_EARLY+4); + mSecunda->setRenderQueue(RQG_SkiesEarly+4); mMasser = new Moon("textures\\tx_masser_full.dds", 0.75, Vector3(-0.4, 0.4, 0.5), mRootNode); - mMasser->setRenderQueue(RENDER_QUEUE_SKIES_EARLY+3); + mMasser->setRenderQueue(RQG_SkiesEarly+3); mMasser->setType(Moon::Type_Masser); mSun = new BillboardObject("textures\\tx_sun_05.dds", 1, Vector3(0.4, 0.4, 0.4), mRootNode); + mSun->setRenderQueue(RQG_SkiesEarly+4); mSunGlare = new BillboardObject("textures\\tx_sun_flash_grey_05.dds", 3, Vector3(0.4, 0.4, 0.4), mRootNode); - mSunGlare->setRenderQueue(RENDER_QUEUE_SKIES_LATE); + mSunGlare->setRenderQueue(RQG_SkiesLate); + mSunGlare->setVisibilityFlags(RV_Glare); HighLevelGpuProgramManager& mgr = HighLevelGpuProgramManager::getSingleton(); @@ -341,7 +449,9 @@ SkyManager::SkyManager (SceneNode* pMwRoot, Camera* pCamera, MWWorld::Environmen /// \todo sky_night_02.nif (available in Bloodmoon) MeshPtr mesh = NifOgre::NIFLoader::load("meshes\\sky_night_01.nif"); Entity* night1_ent = mSceneMgr->createEntity("meshes\\sky_night_01.nif"); - night1_ent->setRenderQueueGroup(RENDER_QUEUE_SKIES_EARLY+1); + night1_ent->setRenderQueueGroup(RQG_SkiesEarly+1); + night1_ent->setVisibilityFlags(RV_Sky); + night1_ent->setCastShadows(false); mAtmosphereNight = mRootNode->createChildSceneNode(); mAtmosphereNight->attachObject(night1_ent); @@ -379,7 +489,10 @@ SkyManager::SkyManager (SceneNode* pMwRoot, Camera* pCamera, MWWorld::Environmen outStream5 << "void main_fp( \n" " in float2 uv : TEXCOORD0, \n" - " out float4 oColor : COLOR, \n" + " out float4 oColor : COLOR, \n"; + if (RenderingManager::useMRT()) outStream5 << + " out float4 oColor1 : COLOR1, \n"; + outStream5 << " in float fade : TEXCOORD1, \n" " uniform sampler2D texture : TEXUNIT0, \n" " uniform float opacity, \n" @@ -387,7 +500,10 @@ SkyManager::SkyManager (SceneNode* pMwRoot, Camera* pCamera, MWWorld::Environmen " uniform float4 emissive \n" ") \n" "{ \n" - " oColor = tex2D(texture, uv) * float4(emissive.xyz, 1) * float4(1,1,1,fade*diffuse.a); \n" + " oColor = tex2D(texture, uv) * float4(emissive.xyz, 1) * float4(1,1,1,fade*diffuse.a); \n"; + if (RenderingManager::useMRT()) outStream5 << + " oColor1 = float4(1, 0, 0, 1); \n"; + outStream5 << "}"; stars_fp->setSource(outStream5.str()); stars_fp->load(); @@ -411,10 +527,12 @@ SkyManager::SkyManager (SceneNode* pMwRoot, Camera* pCamera, MWWorld::Environmen // Atmosphere (day) mesh = NifOgre::NIFLoader::load("meshes\\sky_atmosphere.nif"); Entity* atmosphere_ent = mSceneMgr->createEntity("meshes\\sky_atmosphere.nif"); + atmosphere_ent->setCastShadows(false); ModVertexAlpha(atmosphere_ent, 0); - atmosphere_ent->setRenderQueueGroup(RENDER_QUEUE_SKIES_EARLY); + atmosphere_ent->setRenderQueueGroup(RQG_SkiesEarly); + atmosphere_ent->setVisibilityFlags(RV_Sky); mAtmosphereDay = mRootNode->createChildSceneNode(); mAtmosphereDay->attachObject(atmosphere_ent); mAtmosphereMaterial = atmosphere_ent->getSubEntity(0)->getMaterial(); @@ -432,29 +550,56 @@ SkyManager::SkyManager (SceneNode* pMwRoot, Camera* pCamera, MWWorld::Environmen " float4 position : POSITION, \n" " in float4 color : COLOR, \n" " out float4 oPosition : POSITION, \n" - " out float4 oColor : COLOR, \n" - " uniform float4 emissive, \n" + " out float4 oVertexColor : TEXCOORD0, \n" " uniform float4x4 worldViewProj \n" ") \n" "{ \n" " oPosition = mul( worldViewProj, position ); \n" - " oColor = color * emissive; \n" + " oVertexColor = color; \n" "}"; vshader->setSource(outStream.str()); vshader->load(); 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(""); + + HighLevelGpuProgramPtr fshader = mgr.createProgram("Atmosphere_FP", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + "cg", GPT_FRAGMENT_PROGRAM); + + fshader->setParameter("profiles", "ps_2_x arbfp1"); + fshader->setParameter("entry_point", "main_fp"); + + StringUtil::StrStreamType _outStream; + _outStream << + "void main_fp( \n" + " in float4 iVertexColor : TEXCOORD0, \n" + " out float4 oColor : COLOR, \n"; + if (RenderingManager::useMRT()) _outStream << + " out float4 oColor1 : COLOR1, \n"; + _outStream << + " uniform float4 emissive \n" + ") \n" + "{ \n" + " oColor = iVertexColor * emissive; \n"; + if (RenderingManager::useMRT()) _outStream << + " oColor1 = float4(1, 0, 0, 1); \n"; + _outStream << + "}"; + fshader->setSource(_outStream.str()); + fshader->load(); + + fshader->getDefaultParameters()->setNamedAutoConstant("emissive", GpuProgramParameters::ACT_SURFACE_EMISSIVE_COLOUR); + mAtmosphereMaterial->getTechnique(0)->getPass(0)->setFragmentProgram(fshader->getName()); // Clouds NifOgre::NIFLoader::load("meshes\\sky_clouds_01.nif"); Entity* clouds_ent = mSceneMgr->createEntity("meshes\\sky_clouds_01.nif"); - clouds_ent->setRenderQueueGroup(RENDER_QUEUE_SKIES_EARLY+5); + clouds_ent->setVisibilityFlags(RV_Sky); + clouds_ent->setRenderQueueGroup(RQG_SkiesEarly+5); SceneNode* clouds_node = mRootNode->createChildSceneNode(); clouds_node->attachObject(clouds_ent); mCloudMaterial = clouds_ent->getSubEntity(0)->getMaterial(); + clouds_ent->setCastShadows(false); // Clouds vertex shader HighLevelGpuProgramPtr vshader2 = mgr.createProgram("Clouds_VP", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, @@ -491,8 +636,11 @@ SkyManager::SkyManager (SceneNode* pMwRoot, Camera* pCamera, MWWorld::Environmen outStream2 << "void main_fp( \n" " in float2 uv : TEXCOORD0, \n" - " out float4 oColor : COLOR, \n" " in float4 color : TEXCOORD1, \n" + " out float4 oColor : COLOR, \n"; + if (RenderingManager::useMRT()) outStream2 << + " out float4 oColor1 : COLOR1, \n"; + outStream2 << " uniform sampler2D texture : TEXUNIT0, \n" " uniform sampler2D secondTexture : TEXUNIT1, \n" " uniform float transitionFactor, \n" @@ -502,9 +650,12 @@ SkyManager::SkyManager (SceneNode* pMwRoot, Camera* pCamera, MWWorld::Environmen " uniform float4 emissive \n" ") \n" "{ \n" - " uv += float2(1,0) * time * speed * 0.003; \n" // Scroll in x direction + " uv += float2(0,1) * time * speed * 0.003; \n" // Scroll in y direction " float4 tex = lerp(tex2D(texture, uv), tex2D(secondTexture, uv), transitionFactor); \n" - " oColor = color * float4(emissive.xyz,1) * tex * float4(1,1,1,opacity); \n" + " oColor = color * float4(emissive.xyz,1) * tex * float4(1,1,1,opacity); \n"; + if (RenderingManager::useMRT()) outStream2 << + " oColor1 = float4(1, 0, 0, 1); \n"; + outStream2 << "}"; mCloudFragmentShader->setSource(outStream2.str()); mCloudFragmentShader->load(); @@ -530,7 +681,11 @@ SkyManager::SkyManager (SceneNode* pMwRoot, Camera* pCamera, MWWorld::Environmen mAtmosphereMaterial->getTechnique(0)->getPass(0)->setSceneBlending(SBT_TRANSPARENT_ALPHA); mCloudMaterial->getTechnique(0)->getPass(0)->setSceneBlending(SBT_TRANSPARENT_ALPHA); + mCloudMaterial->getTechnique(0)->getPass(0)->removeAllTextureUnitStates(); + mCloudMaterial->getTechnique(0)->getPass(0)->createTextureUnitState("textures\\tx_sky_cloudy.dds"); mCloudMaterial->getTechnique(0)->getPass(0)->createTextureUnitState(""); + + mCreated = true; } SkyManager::~SkyManager() @@ -543,11 +698,13 @@ SkyManager::~SkyManager() int SkyManager::getMasserPhase() const { + if (!mCreated) return 0; return mMasser->getPhaseInt(); } int SkyManager::getSecundaPhase() const { + if (!mCreated) return 0; return mSecunda->getPhaseInt(); } @@ -562,10 +719,23 @@ void SkyManager::update(float duration) mMasser->setPhase( static_cast( (int) ((mDay % 32)/4.f)) ); mSecunda->setPhase ( static_cast( (int) ((mDay % 32)/4.f)) ); - // increase the strength of the sun glare effect depending - // on how directly the player is looking at the sun + if (mSunEnabled) { + // take 1/5 sec for fading the glare effect from invisible to full + if (mGlareFade > mGlare) + { + mGlareFade -= duration*5; + if (mGlareFade < mGlare) mGlareFade = mGlare; + } + else if (mGlareFade < mGlare) + { + mGlareFade += duration*5; + if (mGlareFade > mGlare) mGlareFade = mGlare; + } + + // increase the strength of the sun glare effect depending + // on how directly the player is looking at the sun Vector3 sun = mSunGlare->getPosition(); sun = Vector3(sun.x, sun.z, -sun.y); Vector3 cam = mViewport->getCamera()->getRealDirection(); @@ -573,21 +743,10 @@ void SkyManager::update(float duration) float val = 1- (angle.valueDegrees() / 180.f); val = (val*val*val*val)*2; - if (mGlareEnabled) - { - mGlareFade += duration*3; - if (mGlareFade > 1) mGlareFade = 1; - } - else - { - mGlareFade -= duration*3; - if (mGlareFade < 0.3) mGlareFade = 0; - } - - mSunGlare->setSize(val * (mGlareFade)); + mSunGlare->setSize(val * mGlareFade); } - mSunGlare->setVisible(mGlareFade>0 && mSunEnabled); + mSunGlare->setVisible(mSunEnabled); mSun->setVisible(mSunEnabled); mMasser->setVisible(mMasserEnabled); mSecunda->setVisible(mSecundaEnabled); @@ -598,6 +757,9 @@ void SkyManager::update(float duration) void SkyManager::enable() { + if (!mCreated) + create(); + mRootNode->setVisible(true); mEnabled = true; } @@ -610,17 +772,20 @@ void SkyManager::disable() void SkyManager::setMoonColour (bool red) { + if (!mCreated) return; mSecunda->setColour( red ? ColourValue(1.0, 0.0784, 0.0784) : ColourValue(1.0, 1.0, 1.0)); } void SkyManager::setCloudsOpacity(float opacity) { + if (!mCreated) return; mCloudMaterial->getTechnique(0)->getPass(0)->getFragmentProgramParameters()->setNamedConstant("opacity", Real(opacity)); } void SkyManager::setWeather(const MWWorld::WeatherResult& weather) { + if (!mCreated) return; if (mClouds != weather.mCloudTexture) { mCloudMaterial->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName("textures\\"+weather.mCloudTexture); @@ -689,19 +854,21 @@ void SkyManager::setWeather(const MWWorld::WeatherResult& weather) else strength = 1.f; - mSunGlare->setVisibility(weather.mGlareView * strength); - mSun->setVisibility(strength); + mSunGlare->setVisibility(weather.mGlareView * mGlareFade * strength); + + mSun->setVisibility(weather.mGlareView * strength); mAtmosphereNight->setVisible(weather.mNight && mEnabled); } -void SkyManager::setGlare(bool glare) +void SkyManager::setGlare(const float glare) { - mGlareEnabled = glare; + mGlare = glare; } Vector3 SkyManager::getRealSunPos() { + if (!mCreated) return Vector3(0,0,0); return mSun->getNode()->_getDerivedPosition(); } @@ -717,17 +884,20 @@ void SkyManager::sunDisable() void SkyManager::setSunDirection(const Vector3& direction) { + if (!mCreated) return; mSun->setPosition(direction); mSunGlare->setPosition(direction); } void SkyManager::setMasserDirection(const Vector3& direction) { + if (!mCreated) return; mMasser->setPosition(direction); } void SkyManager::setSecundaDirection(const Vector3& direction) { + if (!mCreated) return; mSecunda->setPosition(direction); } @@ -753,6 +923,7 @@ void SkyManager::secundaDisable() void SkyManager::setThunder(const float factor) { + if (!mCreated) return; if (factor > 0.f) { mThunderOverlay->show(); @@ -764,11 +935,13 @@ void SkyManager::setThunder(const float factor) void SkyManager::setMasserFade(const float fade) { + if (!mCreated) return; mMasser->setVisibility(fade); } void SkyManager::setSecundaFade(const float fade) { + if (!mCreated) return; mSecunda->setVisibility(fade); } @@ -782,3 +955,24 @@ void SkyManager::setDate(int day, int month) mDay = day; mMonth = month; } + +Ogre::SceneNode* SkyManager::getSunNode() +{ + if (!mCreated) return 0; + return mSun->getNode(); +} + +void SkyManager::setSkyPosition(const Ogre::Vector3& position) +{ + mRootNode->_setDerivedPosition(position); +} + +void SkyManager::resetSkyPosition() +{ + mRootNode->setPosition(0,0,0); +} + +void SkyManager::scaleSky(float scale) +{ + mRootNode->setScale(scale, scale, scale); +} diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index bf52afd8d..64d5c16a0 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -36,24 +36,25 @@ namespace MWRender BillboardObject(); virtual ~BillboardObject() {} - + void setColour(const Ogre::ColourValue& pColour); void setPosition(const Ogre::Vector3& pPosition); void setVisible(const bool visible); void setRenderQueue(unsigned int id); + void setVisibilityFlags(int flags); void setSize(const float size); Ogre::Vector3 getPosition() const; - + void setVisibility(const float visibility); - + Ogre::SceneNode* getNode(); - + protected: virtual void init(const Ogre::String& textureName, const float size, const Ogre::Vector3& position, Ogre::SceneNode* rootNode); - + Ogre::SceneNode* mNode; Ogre::MaterialPtr mMaterial; Ogre::BillboardSet* mBBSet; @@ -71,9 +72,9 @@ namespace MWRender const Ogre::Vector3& position, Ogre::SceneNode* rootNode ); - + virtual ~Moon() {} - + enum Phase { Phase_New = 0, @@ -85,20 +86,20 @@ namespace MWRender Phase_WaningHalf, Phase_WaningCrescent }; - + enum Type { Type_Masser = 0, Type_Secunda }; - + void setPhase(const Phase& phase); void setType(const Type& type); void setSkyColour(const Ogre::ColourValue& colour); - + Phase getPhase() const; unsigned int getPhaseInt() const; - + private: Type mType; Phase mPhase; @@ -109,61 +110,72 @@ namespace MWRender public: SkyManager(Ogre::SceneNode* pMwRoot, Ogre::Camera* pCamera, MWWorld::Environment* env); ~SkyManager(); - + void update(float duration); - + + void create(); + ///< no need to call this, automatically done on first enable() + void enable(); - + void disable(); - + void setHour (double hour); ///< will be called even when sky is disabled. - + void setDate (int day, int month); ///< will be called even when sky is disabled. - + int getMasserPhase() const; ///< 0 new moon, 1 waxing or waning cresecent, 2 waxing or waning half, /// 3 waxing or waning gibbous, 4 full moon - + int getSecundaPhase() const; ///< 0 new moon, 1 waxing or waning cresecent, 2 waxing or waning half, /// 3 waxing or waning gibbous, 4 full moon - + void setMoonColour (bool red); ///< change Secunda colour to red - + void setCloudsOpacity(float opacity); ///< change opacity of the clouds - + void setWeather(const MWWorld::WeatherResult& weather); - + + Ogre::SceneNode* getSunNode(); + void sunEnable(); - + void sunDisable(); - + void setSunDirection(const Ogre::Vector3& direction); - + void setMasserDirection(const Ogre::Vector3& direction); - + void setSecundaDirection(const Ogre::Vector3& direction); - + void setMasserFade(const float fade); - + void setSecundaFade(const float fade); - + void masserEnable(); void masserDisable(); void secundaEnable(); void secundaDisable(); - + void setThunder(const float factor); - - void setGlare(bool glare); + + void setGlare(const float glare); Ogre::Vector3 getRealSunPos(); - + + void setSkyPosition(const Ogre::Vector3& position); + void resetSkyPosition(); + void scaleSky(float scale); + private: + bool mCreated; + MWWorld::Environment* mEnvironment; float mHour; int mDay; @@ -173,21 +185,21 @@ namespace MWRender BillboardObject* mSunGlare; Moon* mMasser; Moon* mSecunda; - + Ogre::Viewport* mViewport; Ogre::SceneNode* mRootNode; Ogre::SceneManager* mSceneMgr; - + Ogre::SceneNode* mAtmosphereDay; Ogre::SceneNode* mAtmosphereNight; - + Ogre::MaterialPtr mCloudMaterial; Ogre::MaterialPtr mAtmosphereMaterial; - + Ogre::MaterialPtr mStarsMaterials[7]; - + Ogre::HighLevelGpuProgramPtr mCloudFragmentShader; - + // remember some settings so we don't have to apply them again if they didnt change Ogre::String mClouds; Ogre::String mNextClouds; @@ -197,18 +209,18 @@ namespace MWRender float mStarsOpacity; Ogre::ColourValue mCloudColour; Ogre::ColourValue mSkyColour; - + Ogre::Overlay* mThunderOverlay; Ogre::TextureUnitState* mThunderTextureUnit; - + float mRemainingTransitionTime; - - float mGlareFade; - + + float mGlare; // target + float mGlareFade; // actual + void ModVertexAlpha(Ogre::Entity* ent, unsigned int meshType); - + bool mEnabled; - bool mGlareEnabled; bool mSunEnabled; bool mMasserEnabled; bool mSecundaEnabled; diff --git a/apps/openmw/mwrender/terrain.cpp b/apps/openmw/mwrender/terrain.cpp new file mode 100644 index 000000000..f9b43655b --- /dev/null +++ b/apps/openmw/mwrender/terrain.cpp @@ -0,0 +1,531 @@ +#include +#include +#include + +#include "../mwworld/world.hpp" + +#include "terrainmaterial.hpp" +#include "terrain.hpp" +#include "renderconst.hpp" +#include "shadows.hpp" +#include + +using namespace Ogre; + +namespace MWRender +{ + + //---------------------------------------------------------------------------------------------- + + TerrainManager::TerrainManager(Ogre::SceneManager* mgr, RenderingManager* rend, const MWWorld::Environment& evn) : + mEnvironment(evn), mTerrainGroup(TerrainGroup(mgr, Terrain::ALIGN_X_Z, mLandSize, mWorldSize)), mRendering(rend) + { + + TerrainMaterialGeneratorPtr matGen; + TerrainMaterialGeneratorB* matGenP = new TerrainMaterialGeneratorB(); + matGen.bind(matGenP); + mTerrainGlobals.setDefaultMaterialGenerator(matGen); + + TerrainMaterialGenerator::Profile* const activeProfile = + mTerrainGlobals.getDefaultMaterialGenerator() + ->getActiveProfile(); + mActiveProfile = static_cast(activeProfile); + + //The pixel error should be as high as possible without it being noticed + //as it governs how fast mesh quality decreases. + mTerrainGlobals.setMaxPixelError(8); + + mTerrainGlobals.setLayerBlendMapSize(32); + mTerrainGlobals.setDefaultGlobalColourMapSize(65); + + //10 (default) didn't seem to be quite enough + mTerrainGlobals.setSkirtSize(128); + + //due to the sudden flick between composite and non composite textures, + //this seemed the distance where it wasn't too noticeable + mTerrainGlobals.setCompositeMapDistance(mWorldSize*2); + + mActiveProfile->setLightmapEnabled(false); + mActiveProfile->setLayerSpecularMappingEnabled(false); + mActiveProfile->setLayerNormalMappingEnabled(false); + mActiveProfile->setLayerParallaxMappingEnabled(false); + + bool shadows = Settings::Manager::getBool("enabled", "Shadows"); + mActiveProfile->setReceiveDynamicShadowsEnabled(shadows); + mActiveProfile->setReceiveDynamicShadowsDepth(shadows); + if (Settings::Manager::getBool("split", "Shadows")) + mActiveProfile->setReceiveDynamicShadowsPSSM(mRendering->getShadows()->getPSSMSetup()); + else + mActiveProfile->setReceiveDynamicShadowsPSSM(0); + + mActiveProfile->setShadowFar(mRendering->getShadows()->getShadowFar()); + mActiveProfile->setShadowFadeStart(mRendering->getShadows()->getFadeStart()); + + //composite maps lead to a drastic increase in loading time so are + //disabled + mActiveProfile->setCompositeMapEnabled(false); + + mTerrainGroup.setOrigin(Vector3(mWorldSize/2, + 0, + -mWorldSize/2)); + + Terrain::ImportData& importSettings = mTerrainGroup.getDefaultImportSettings(); + + importSettings.inputBias = 0; + importSettings.terrainSize = mLandSize; + importSettings.worldSize = mWorldSize; + importSettings.minBatchSize = 9; + importSettings.maxBatchSize = mLandSize; + + importSettings.deleteInputData = true; + } + + //---------------------------------------------------------------------------------------------- + + TerrainManager::~TerrainManager() + { + } + + //---------------------------------------------------------------------------------------------- + + void TerrainManager::setDiffuse(const ColourValue& diffuse) + { + mTerrainGlobals.setCompositeMapDiffuse(diffuse); + } + + //---------------------------------------------------------------------------------------------- + + void TerrainManager::setAmbient(const ColourValue& ambient) + { + mTerrainGlobals.setCompositeMapAmbient(ambient); + } + + //---------------------------------------------------------------------------------------------- + + void TerrainManager::cellAdded(MWWorld::Ptr::CellStore *store) + { + const int cellX = store->cell->getGridX(); + const int cellY = store->cell->getGridY(); + + ESM::Land* land = mEnvironment.mWorld->getStore().lands.search(cellX, cellY); + if ( land != NULL ) + { + if (!land->dataLoaded) + { + land->loadData(); + } + } + + //split the cell terrain into four segments + const int numTextures = ESM::Land::LAND_TEXTURE_SIZE/2; + + for ( int x = 0; x < 2; x++ ) + { + for ( int y = 0; y < 2; y++ ) + { + Terrain::ImportData terrainData = + mTerrainGroup.getDefaultImportSettings(); + + const int terrainX = cellX * 2 + x; + const int terrainY = cellY * 2 + y; + + //it makes far more sense to reallocate the memory here, + //and let Ogre deal with it due to the issues with deleting + //it at the wrong time if using threads (Which Terrain does) + terrainData.inputFloat = OGRE_ALLOC_T(float, + mLandSize*mLandSize, + MEMCATEGORY_GEOMETRY); + + if ( land != NULL ) + { + //copy the height data row by row + for ( int terrainCopyY = 0; terrainCopyY < mLandSize; terrainCopyY++ ) + { + //the offset of the current segment + const size_t yOffset = y * (mLandSize-1) * ESM::Land::LAND_SIZE + + //offset of the row + terrainCopyY * ESM::Land::LAND_SIZE; + const size_t xOffset = x * (mLandSize-1); + + memcpy(&terrainData.inputFloat[terrainCopyY*mLandSize], + &land->landData->heights[yOffset + xOffset], + mLandSize*sizeof(float)); + } + } + else + { + memset(terrainData.inputFloat, 0, mLandSize*mLandSize*sizeof(float)); + } + + std::map indexes; + initTerrainTextures(&terrainData, cellX, cellY, + x * numTextures, y * numTextures, + numTextures, indexes); + + if (mTerrainGroup.getTerrain(terrainX, terrainY) == NULL) + { + mTerrainGroup.defineTerrain(terrainX, terrainY, &terrainData); + + mTerrainGroup.loadTerrain(terrainX, terrainY, true); + + Terrain* terrain = mTerrainGroup.getTerrain(terrainX, terrainY); + initTerrainBlendMaps(terrain, + cellX, cellY, + x * numTextures, y * numTextures, + numTextures, + indexes); + terrain->setVisibilityFlags(RV_Terrain); + terrain->setRenderQueueGroup(RQG_Main); + + if ( land && land->landData->usingColours ) + { + // disable or enable global colour map (depends on available vertex colours) + mActiveProfile->setGlobalColourMapEnabled(true); + TexturePtr vertex = getVertexColours(land, + cellX, cellY, + x*(mLandSize-1), + y*(mLandSize-1), + mLandSize); + + //this is a hack to get around the fact that Ogre seems to + //corrupt the global colour map leading to rendering errors + MaterialPtr mat = terrain->getMaterial(); + mat->getTechnique(0)->getPass(0)->getTextureUnitState(1)->setTextureName( vertex->getName() ); + //mat = terrain->_getCompositeMapMaterial(); + //mat->getTechnique(0)->getPass(0)->getTextureUnitState(1)->setTextureName( vertex->getName() ); + } + else + { + mActiveProfile->setGlobalColourMapEnabled(false); + } + } + } + } + + mTerrainGroup.freeTemporaryResources(); + } + + //---------------------------------------------------------------------------------------------- + + void TerrainManager::cellRemoved(MWWorld::Ptr::CellStore *store) + { + for ( int x = 0; x < 2; x++ ) + { + for ( int y = 0; y < 2; y++ ) + { + mTerrainGroup.unloadTerrain(store->cell->getGridX() * 2 + x, + store->cell->getGridY() * 2 + y); + } + } + } + + //---------------------------------------------------------------------------------------------- + + void TerrainManager::initTerrainTextures(Terrain::ImportData* terrainData, + int cellX, int cellY, + int fromX, int fromY, int size, + std::map& indexes) + { + assert(terrainData != NULL && "Must have valid terrain data"); + assert(fromX >= 0 && fromY >= 0 && + "Can't get a terrain texture on terrain outside the current cell"); + assert(fromX+size <= ESM::Land::LAND_TEXTURE_SIZE && + fromY+size <= ESM::Land::LAND_TEXTURE_SIZE && + "Can't get a terrain texture on terrain outside the current cell"); + + //this ensures that the ltex indexes are sorted (or retrived as sorted + //which simplifies shading between cells). + // + //If we don't sort the ltex indexes, the splatting order may differ between + //cells which may lead to inconsistent results when shading between cells + std::set ltexIndexes; + for ( int y = fromY - 1; y < fromY + size + 1; y++ ) + { + for ( int x = fromX - 1; x < fromX + size + 1; x++ ) + { + ltexIndexes.insert(getLtexIndexAt(cellX, cellY, x, y)); + } + } + + //there is one texture that we want to use as a base (i.e. it won't have + //a blend map). This holds the ltex index of that base texture so that + //we know not to include it in the output map + int baseTexture = -1; + for ( std::set::iterator iter = ltexIndexes.begin(); + iter != ltexIndexes.end(); + ++iter ) + { + const uint16_t ltexIndex = *iter; + //this is the base texture, so we can ignore this at present + if ( ltexIndex == baseTexture ) + { + continue; + } + + const std::map::const_iterator it = indexes.find(ltexIndex); + + if ( it == indexes.end() ) + { + //NB: All vtex ids are +1 compared to the ltex ids + + assert( (int)mEnvironment.mWorld->getStore().landTexts.getSize() >= (int)ltexIndex - 1 && + "LAND.VTEX must be within the bounds of the LTEX array"); + + std::string texture; + if ( ltexIndex == 0 ) + { + texture = "_land_default.dds"; + } + else + { + texture = mEnvironment.mWorld->getStore().landTexts.search(ltexIndex-1)->texture; + //TODO this is needed due to MWs messed up texture handling + texture = texture.substr(0, texture.rfind(".")) + ".dds"; + } + + const size_t position = terrainData->layerList.size(); + terrainData->layerList.push_back(Terrain::LayerInstance()); + + terrainData->layerList[position].worldSize = 256; + terrainData->layerList[position].textureNames.push_back("textures\\" + texture); + + if ( baseTexture == -1 ) + { + baseTexture = ltexIndex; + } + else + { + indexes[ltexIndex] = position; + } + } + } + } + + //---------------------------------------------------------------------------------------------- + + void TerrainManager::initTerrainBlendMaps(Terrain* terrain, + int cellX, int cellY, + int fromX, int fromY, int size, + const std::map& indexes) + { + assert(terrain != NULL && "Must have valid terrain"); + assert(fromX >= 0 && fromY >= 0 && + "Can't get a terrain texture on terrain outside the current cell"); + assert(fromX+size <= ESM::Land::LAND_TEXTURE_SIZE && + fromY+size <= ESM::Land::LAND_TEXTURE_SIZE && + "Can't get a terrain texture on terrain outside the current cell"); + + //size must be a power of 2 as we do divisions with a power of 2 number + //that need to result in an integer for correct splatting + assert( (size & (size - 1)) == 0 && "Size must be a power of 2"); + + const int blendMapSize = terrain->getLayerBlendMapSize(); + const int splatSize = blendMapSize / size; + + //zero out every map + std::map::const_iterator iter; + for ( iter = indexes.begin(); iter != indexes.end(); ++iter ) + { + float* pBlend = terrain->getLayerBlendMap(iter->second) + ->getBlendPointer(); + memset(pBlend, 0, sizeof(float) * blendMapSize * blendMapSize); + } + + //covert the ltex data into a set of blend maps + for ( int texY = fromY - 1; texY < fromY + size + 1; texY++ ) + { + for ( int texX = fromX - 1; texX < fromX + size + 1; texX++ ) + { + const uint16_t ltexIndex = getLtexIndexAt(cellX, cellY, texX, texY); + + //check if it is the base texture (which isn't in the map) and + //if it is don't bother altering the blend map for it + if ( indexes.find(ltexIndex) == indexes.end() ) + { + continue; + } + + //while texX is the splat index relative to the entire cell, + //relX is relative to the current segment we are splatting + const int relX = texX - fromX; + const int relY = texY - fromY; + + const int layerIndex = indexes.find(ltexIndex)->second; + + float* const pBlend = terrain->getLayerBlendMap(layerIndex) + ->getBlendPointer(); + + for ( int y = -1; y < splatSize + 1; y++ ) + { + for ( int x = -1; x < splatSize + 1; x++ ) + { + + //Note: Y is reversed + const int splatY = blendMapSize - 1 - relY * splatSize - y; + const int splatX = relX * splatSize + x; + + if ( splatX >= 0 && splatX < blendMapSize && + splatY >= 0 && splatY < blendMapSize ) + { + const int index = (splatY)*blendMapSize + splatX; + + if ( y >= 0 && y < splatSize && + x >= 0 && x < splatSize ) + { + pBlend[index] = 1; + } + else + { + //this provides a transition shading but also + //rounds off the corners slightly + pBlend[index] = std::min(1.0f, pBlend[index] + 0.5f); + } + } + + } + } + } + } + + for ( int i = 1; i < terrain->getLayerCount(); i++ ) + { + TerrainLayerBlendMap* blend = terrain->getLayerBlendMap(i); + blend->dirty(); + blend->update(); + } + + } + + //---------------------------------------------------------------------------------------------- + + int TerrainManager::getLtexIndexAt(int cellX, int cellY, + int x, int y) + { + //check texture index falls within the 9 cell bounds + //as this function can't cope with anything above that + assert(x >= -ESM::Land::LAND_TEXTURE_SIZE && + y >= -ESM::Land::LAND_TEXTURE_SIZE && + "Trying to get land textures that are out of bounds"); + + assert(x < 2*ESM::Land::LAND_TEXTURE_SIZE && + y < 2*ESM::Land::LAND_TEXTURE_SIZE && + "Trying to get land textures that are out of bounds"); + + if ( x < 0 ) + { + cellX--; + x += ESM::Land::LAND_TEXTURE_SIZE; + } + else if ( x >= ESM::Land::LAND_TEXTURE_SIZE ) + { + cellX++; + x -= ESM::Land::LAND_TEXTURE_SIZE; + } + + if ( y < 0 ) + { + cellY--; + y += ESM::Land::LAND_TEXTURE_SIZE; + } + else if ( y >= ESM::Land::LAND_TEXTURE_SIZE ) + { + cellY++; + y -= ESM::Land::LAND_TEXTURE_SIZE; + } + + + ESM::Land* land = mEnvironment.mWorld->getStore().lands.search(cellX, cellY); + if ( land != NULL ) + { + if (!land->dataLoaded) + { + land->loadData(); + } + + return land->landData + ->textures[y * ESM::Land::LAND_TEXTURE_SIZE + x]; + } + else + { + return 0; + } + } + + //---------------------------------------------------------------------------------------------- + + TexturePtr TerrainManager::getVertexColours(ESM::Land* land, + int cellX, int cellY, + int fromX, int fromY, int size) + { + TextureManager* const texMgr = TextureManager::getSingletonPtr(); + + const std::string colourTextureName = "VtexColours_" + + boost::lexical_cast(cellX) + + "_" + + boost::lexical_cast(cellY) + + "_" + + boost::lexical_cast(fromX) + + "_" + + boost::lexical_cast(fromY); + + TexturePtr tex = texMgr->getByName(colourTextureName); + if ( !tex.isNull() ) + { + return tex; + } + + tex = texMgr->createManual(colourTextureName, + ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + TEX_TYPE_2D, size, size, 0, PF_BYTE_BGR); + + HardwarePixelBufferSharedPtr pixelBuffer = tex->getBuffer(); + + pixelBuffer->lock(HardwareBuffer::HBL_DISCARD); + const PixelBox& pixelBox = pixelBuffer->getCurrentLock(); + + uint8* pDest = static_cast(pixelBox.data); + + if ( land != NULL ) + { + const char* const colours = land->landData->colours; + for ( int y = 0; y < size; y++ ) + { + for ( int x = 0; x < size; x++ ) + { + const size_t colourOffset = (y+fromY)*3*65 + (x+fromX)*3; + + assert( colourOffset < 65*65*3 && + "Colour offset is out of the expected bounds of record" ); + + const unsigned char r = colours[colourOffset + 0]; + const unsigned char g = colours[colourOffset + 1]; + const unsigned char b = colours[colourOffset + 2]; + + //as is the case elsewhere we need to flip the y + const size_t imageOffset = (size - 1 - y)*size*4 + x*4; + pDest[imageOffset + 0] = b; + pDest[imageOffset + 1] = g; + pDest[imageOffset + 2] = r; + } + } + } + else + { + for ( int y = 0; y < size; y++ ) + { + for ( int x = 0; x < size; x++ ) + { + for ( int k = 0; k < 3; k++ ) + { + *pDest++ = 0; + } + } + } + } + + pixelBuffer->unlock(); + + return tex; + } + +} diff --git a/apps/openmw/mwrender/terrain.hpp b/apps/openmw/mwrender/terrain.hpp new file mode 100644 index 000000000..dc4a2388c --- /dev/null +++ b/apps/openmw/mwrender/terrain.hpp @@ -0,0 +1,118 @@ +#ifndef _GAME_RENDER_TERRAIN_H +#define _GAME_RENDER_TERRAIN_H + +#include +#include +#include "terrainmaterial.hpp" + +#include "../mwworld/ptr.hpp" + +namespace Ogre{ + class SceneManager; + class TerrainGroup; + class TerrainGlobalOptions; + class Terrain; +} + +namespace MWRender{ + + /** + * Implements the Morrowind terrain using the Ogre Terrain Component + * + * Each terrain cell is split into four blocks as this leads to an increase + * in performance and means we don't hit splat limits quite as much + */ + class TerrainManager{ + public: + TerrainManager(Ogre::SceneManager* mgr, RenderingManager* rend, const MWWorld::Environment& env); + virtual ~TerrainManager(); + + void setDiffuse(const Ogre::ColourValue& diffuse); + void setAmbient(const Ogre::ColourValue& ambient); + + void cellAdded(MWWorld::Ptr::CellStore* store); + void cellRemoved(MWWorld::Ptr::CellStore* store); + private: + Ogre::TerrainGlobalOptions mTerrainGlobals; + Ogre::TerrainGroup mTerrainGroup; + + const MWWorld::Environment& mEnvironment; + RenderingManager* mRendering; + + Ogre::TerrainMaterialGeneratorB::SM2Profile* mActiveProfile; + + /** + * The length in verticies of a single terrain block. + */ + static const int mLandSize = (ESM::Land::LAND_SIZE - 1)/2 + 1; + + /** + * The length in game units of a single terrain block. + */ + static const int mWorldSize = ESM::Land::REAL_SIZE/2; + + /** + * Setups up the list of textures for part of a cell, using indexes as + * an output to create a mapping of MW LtexIndex to the relevant terrain + * layer + * + * @param terrainData the terrain data to setup the textures for + * @param cellX the coord of the cell + * @param cellY the coord of the cell + * @param fromX the ltex index in the current cell to start making the texture from + * @param fromY the ltex index in the current cell to start making the texture from + * @param size the size (number of splats) to get + * @param indexes a mapping of ltex index to the terrain texture layer that + * can be used by initTerrainBlendMaps + */ + void initTerrainTextures(Ogre::Terrain::ImportData* terrainData, + int cellX, int cellY, + int fromX, int fromY, int size, + std::map& indexes); + + /** + * Creates the blend (splatting maps) for the given terrain from the ltex data. + * + * @param terrain the terrain object for the current cell + * @param cellX the coord of the cell + * @param cellY the coord of the cell + * @param fromX the ltex index in the current cell to start making the texture from + * @param fromY the ltex index in the current cell to start making the texture from + * @param size the size (number of splats) to get + * @param indexes the mapping of ltex to blend map produced by initTerrainTextures + */ + void initTerrainBlendMaps(Ogre::Terrain* terrain, + int cellX, int cellY, + int fromX, int fromY, int size, + const std::map& indexes); + + /** + * Gets a LTEX index at the given point, assuming the current cell + * starts at (0,0). This supports getting values from the surrounding + * cells so negative x, y is acceptable + * + * @param cellX the coord of the cell + * @param cellY the coord of the cell + * @param x, y the splat position of the ltex index to get relative to the + * first splat of the current cell + */ + int getLtexIndexAt(int cellX, int cellY, int x, int y); + + /** + * Due to the fact that Ogre terrain doesn't support vertex colours + * we have to generate them manually + * + * @param cellX the coord of the cell + * @param cellY the coord of the cell + * @param fromX the *vertex* index in the current cell to start making texture from + * @param fromY the *vertex* index in the current cell to start making the texture from + * @param size the size (number of vertexes) to get + */ + Ogre::TexturePtr getVertexColours(ESM::Land* land, + int cellX, int cellY, + int fromX, int fromY, int size); + }; + +} + +#endif // _GAME_RENDER_TERRAIN_H diff --git a/apps/openmw/mwrender/terrainmaterial.cpp b/apps/openmw/mwrender/terrainmaterial.cpp new file mode 100644 index 000000000..9785ec903 --- /dev/null +++ b/apps/openmw/mwrender/terrainmaterial.cpp @@ -0,0 +1,1746 @@ +/* +----------------------------------------------------------------------------- +This source file is part of OGRE +(Object-oriented Graphics Rendering Engine) +For the latest info, see http://www.ogre3d.org/ + +Copyright (c) 2000-2011 Torus Knot Software Ltd + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +----------------------------------------------------------------------------- +*/ +#include "terrainmaterial.hpp" +#include "OgreTerrain.h" +#include "OgreMaterialManager.h" +#include "OgreTechnique.h" +#include "OgrePass.h" +#include "OgreTextureUnitState.h" +#include "OgreGpuProgramManager.h" +#include "OgreHighLevelGpuProgramManager.h" +#include "OgreHardwarePixelBuffer.h" +#include "OgreShadowCameraSetupPSSM.h" + +#include +#include "renderingmanager.hpp" + +#undef far + +namespace Ogre +{ + //--------------------------------------------------------------------- + TerrainMaterialGeneratorB::TerrainMaterialGeneratorB() + { + // define the layers + // We expect terrain textures to have no alpha, so we use the alpha channel + // in the albedo texture to store specular reflection + // similarly we double-up the normal and height (for parallax) + mLayerDecl.samplers.push_back(TerrainLayerSampler("albedo_specular", PF_BYTE_RGBA)); + //mLayerDecl.samplers.push_back(TerrainLayerSampler("normal_height", PF_BYTE_RGBA)); + + mLayerDecl.elements.push_back( + TerrainLayerSamplerElement(0, TLSS_ALBEDO, 0, 3)); + //mLayerDecl.elements.push_back( + // TerrainLayerSamplerElement(0, TLSS_SPECULAR, 3, 1)); + //mLayerDecl.elements.push_back( + // TerrainLayerSamplerElement(1, TLSS_NORMAL, 0, 3)); + //mLayerDecl.elements.push_back( + // TerrainLayerSamplerElement(1, TLSS_HEIGHT, 3, 1)); + + + mProfiles.push_back(OGRE_NEW SM2Profile(this, "SM2", "Profile for rendering on Shader Model 2 capable cards")); + // TODO - check hardware capabilities & use fallbacks if required (more profiles needed) + setActiveProfile("SM2"); + + } + //--------------------------------------------------------------------- + TerrainMaterialGeneratorB::~TerrainMaterialGeneratorB() + { + + } + //--------------------------------------------------------------------- + //--------------------------------------------------------------------- + TerrainMaterialGeneratorB::SM2Profile::SM2Profile(TerrainMaterialGenerator* parent, const String& name, const String& desc) + : Profile(parent, name, desc) + , mShaderGen(0) + , mLayerNormalMappingEnabled(true) + , mLayerParallaxMappingEnabled(true) + , mLayerSpecularMappingEnabled(true) + , mGlobalColourMapEnabled(true) + , mLightmapEnabled(true) + , mCompositeMapEnabled(true) + , mReceiveDynamicShadows(true) + , mPSSM(0) + , mDepthShadows(false) + , mLowLodShadows(false) + , mShadowFar(1300) + { + + } + //--------------------------------------------------------------------- + TerrainMaterialGeneratorB::SM2Profile::~SM2Profile() + { + OGRE_DELETE mShaderGen; + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::requestOptions(Terrain* terrain) + { + terrain->_setMorphRequired(true); + terrain->_setNormalMapRequired(true); + terrain->_setLightMapRequired(mLightmapEnabled, true); + terrain->_setCompositeMapRequired(mCompositeMapEnabled); + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::setShadowFar(float far) + { + if (mShadowFar != far) + { + mShadowFar = far; + mParent->_markChanged(); + } + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::setShadowFadeStart(float fadestart) + { + if (mShadowFadeStart != fadestart) + { + mShadowFadeStart = fadestart; + mParent->_markChanged(); + } + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::setLayerNormalMappingEnabled(bool enabled) + { + if (enabled != mLayerNormalMappingEnabled) + { + mLayerNormalMappingEnabled = enabled; + mParent->_markChanged(); + } + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::setLayerParallaxMappingEnabled(bool enabled) + { + if (enabled != mLayerParallaxMappingEnabled) + { + mLayerParallaxMappingEnabled = enabled; + mParent->_markChanged(); + } + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::setLayerSpecularMappingEnabled(bool enabled) + { + if (enabled != mLayerSpecularMappingEnabled) + { + mLayerSpecularMappingEnabled = enabled; + mParent->_markChanged(); + } + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::setGlobalColourMapEnabled(bool enabled) + { + if (enabled != mGlobalColourMapEnabled) + { + mGlobalColourMapEnabled = enabled; + //mParent->_markChanged(); + } + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::setLightmapEnabled(bool enabled) + { + if (enabled != mLightmapEnabled) + { + mLightmapEnabled = enabled; + mParent->_markChanged(); + } + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::setCompositeMapEnabled(bool enabled) + { + if (enabled != mCompositeMapEnabled) + { + mCompositeMapEnabled = enabled; + mParent->_markChanged(); + } + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::setReceiveDynamicShadowsEnabled(bool enabled) + { + if (enabled != mReceiveDynamicShadows) + { + mReceiveDynamicShadows = enabled; + mParent->_markChanged(); + } + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::setReceiveDynamicShadowsPSSM(PSSMShadowCameraSetup* pssmSettings) + { + if (pssmSettings != mPSSM) + { + mPSSM = pssmSettings; + mParent->_markChanged(); + } + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::setReceiveDynamicShadowsDepth(bool enabled) + { + if (enabled != mDepthShadows) + { + mDepthShadows = enabled; + mParent->_markChanged(); + } + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::setReceiveDynamicShadowsLowLod(bool enabled) + { + if (enabled != mLowLodShadows) + { + mLowLodShadows = enabled; + mParent->_markChanged(); + } + } + //--------------------------------------------------------------------- + uint8 TerrainMaterialGeneratorB::SM2Profile::getMaxLayers(const Terrain* terrain) const + { + // count the texture units free + uint8 freeTextureUnits = 16; + // lightmap + if (mLightmapEnabled) + --freeTextureUnits; + // normalmap + --freeTextureUnits; + // colourmap + //if (terrain->getGlobalColourMapEnabled()) + --freeTextureUnits; + if (isShadowingEnabled(HIGH_LOD, terrain)) + { + uint numShadowTextures = 1; + if (getReceiveDynamicShadowsPSSM()) + { + numShadowTextures = getReceiveDynamicShadowsPSSM()->getSplitCount(); + } + freeTextureUnits -= numShadowTextures; + } + + // each layer needs 2.25 units (1xdiffusespec, (1xnormalheight), 0.25xblend) + return static_cast(freeTextureUnits / (1.25f + (mLayerNormalMappingEnabled||mLayerParallaxMappingEnabled))); + + + } + int TerrainMaterialGeneratorB::SM2Profile::getNumberOfLightsSupported() const + { + return Settings::Manager::getInt("num lights", "Terrain"); + } + //--------------------------------------------------------------------- + MaterialPtr TerrainMaterialGeneratorB::SM2Profile::generate(const Terrain* terrain) + { + // re-use old material if exists + MaterialPtr mat = terrain->_getMaterial(); + if (mat.isNull()) + { + MaterialManager& matMgr = MaterialManager::getSingleton(); + + // it's important that the names are deterministic for a given terrain, so + // use the terrain pointer as an ID + const String& matName = terrain->getMaterialName(); + mat = matMgr.getByName(matName); + if (mat.isNull()) + { + mat = matMgr.create(matName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); + } + } + // clear everything + mat->removeAllTechniques(); + + // Automatically disable normal & parallax mapping if card cannot handle it + // We do this rather than having a specific technique for it since it's simpler + GpuProgramManager& gmgr = GpuProgramManager::getSingleton(); + if (!gmgr.isSyntaxSupported("ps_3_0") && !gmgr.isSyntaxSupported("ps_2_x") + && !gmgr.isSyntaxSupported("fp40") && !gmgr.isSyntaxSupported("arbfp1")) + { + setLayerNormalMappingEnabled(false); + setLayerParallaxMappingEnabled(false); + } + + addTechnique(mat, terrain, HIGH_LOD); + + // LOD + if(mCompositeMapEnabled) + { + addTechnique(mat, terrain, LOW_LOD); + Material::LodValueList lodValues; + lodValues.push_back(TerrainGlobalOptions::getSingleton().getCompositeMapDistance()); + mat->setLodLevels(lodValues); + Technique* lowLodTechnique = mat->getTechnique(1); + lowLodTechnique->setLodIndex(1); + } + + updateParams(mat, terrain); + + return mat; + + } + //--------------------------------------------------------------------- + MaterialPtr TerrainMaterialGeneratorB::SM2Profile::generateForCompositeMap(const Terrain* terrain) + { + // re-use old material if exists + MaterialPtr mat = terrain->_getCompositeMapMaterial(); + if (mat.isNull()) + { + MaterialManager& matMgr = MaterialManager::getSingleton(); + + // it's important that the names are deterministic for a given terrain, so + // use the terrain pointer as an ID + const String& matName = terrain->getMaterialName() + "/comp"; + mat = matMgr.getByName(matName); + if (mat.isNull()) + { + mat = matMgr.create(matName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); + } + } + // clear everything + mat->removeAllTechniques(); + + addTechnique(mat, terrain, RENDER_COMPOSITE_MAP); + + updateParamsForCompositeMap(mat, terrain); + + return mat; + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::addTechnique( + const MaterialPtr& mat, const Terrain* terrain, TechniqueType tt) + { + + Technique* tech = mat->createTechnique(); + + // Only supporting one pass + Pass* pass = tech->createPass(); + + GpuProgramManager& gmgr = GpuProgramManager::getSingleton(); + HighLevelGpuProgramManager& hmgr = HighLevelGpuProgramManager::getSingleton(); + if (!mShaderGen) + { + bool check2x = mLayerNormalMappingEnabled || mLayerParallaxMappingEnabled; + if (hmgr.isLanguageSupported("cg")) + mShaderGen = OGRE_NEW ShaderHelperCg(); + else if (hmgr.isLanguageSupported("hlsl") && + ((check2x && gmgr.isSyntaxSupported("ps_2_x")) || + (!check2x && gmgr.isSyntaxSupported("ps_2_0")))) + mShaderGen = OGRE_NEW ShaderHelperHLSL(); + else if (hmgr.isLanguageSupported("glsl")) + mShaderGen = OGRE_NEW ShaderHelperGLSL(); + else + { + // todo + } + + // check SM3 features + mSM3Available = GpuProgramManager::getSingleton().isSyntaxSupported("ps_3_0"); + + } + HighLevelGpuProgramPtr vprog = mShaderGen->generateVertexProgram(this, terrain, tt); + HighLevelGpuProgramPtr fprog = mShaderGen->generateFragmentProgram(this, terrain, tt); + + pass->setVertexProgram(vprog->getName()); + pass->setFragmentProgram(fprog->getName()); + + if (tt == HIGH_LOD || tt == RENDER_COMPOSITE_MAP) + { + // global normal map + TextureUnitState* tu = pass->createTextureUnitState(); + tu->setTextureName(terrain->getTerrainNormalMap()->getName()); + tu->setTextureAddressingMode(TextureUnitState::TAM_CLAMP); + + // global colour map + if (isGlobalColourMapEnabled()) + { + tu = pass->createTextureUnitState(""); + tu->setTextureAddressingMode(TextureUnitState::TAM_CLAMP); + } + + // light map + if (isLightmapEnabled()) + { + tu = pass->createTextureUnitState(terrain->getLightmap()->getName()); + tu->setTextureAddressingMode(TextureUnitState::TAM_CLAMP); + } + + // blend maps + uint maxLayers = getMaxLayers(terrain); + uint numBlendTextures = std::min(terrain->getBlendTextureCount(maxLayers), terrain->getBlendTextureCount()); + uint numLayers = std::min(maxLayers, static_cast(terrain->getLayerCount())); + for (uint i = 0; i < numBlendTextures; ++i) + { + tu = pass->createTextureUnitState(terrain->getBlendTextureName(i)); + tu->setTextureAddressingMode(TextureUnitState::TAM_CLAMP); + } + + // layer textures + for (uint i = 0; i < numLayers; ++i) + { + // diffuse / specular + tu = pass->createTextureUnitState(terrain->getLayerTextureName(i, 0)); + + // normal / height + if (mLayerNormalMappingEnabled || mLayerParallaxMappingEnabled) + tu = pass->createTextureUnitState(terrain->getLayerTextureName(i, 1)); + } + + } + else + { + // LOW_LOD textures + // composite map + TextureUnitState* tu = pass->createTextureUnitState(); + tu->setTextureName(terrain->getCompositeMap()->getName()); + tu->setTextureAddressingMode(TextureUnitState::TAM_CLAMP); + + // That's it! + + } + + // Add shadow textures (always at the end) + if (isShadowingEnabled(tt, terrain)) + { + uint numTextures = 1; + if (getReceiveDynamicShadowsPSSM()) + { + numTextures = getReceiveDynamicShadowsPSSM()->getSplitCount(); + } + for (uint i = 0; i < numTextures; ++i) + { + TextureUnitState* tu = pass->createTextureUnitState(); + tu->setContentType(TextureUnitState::CONTENT_SHADOW); + tu->setTextureAddressingMode(TextureUnitState::TAM_BORDER); + tu->setTextureBorderColour(ColourValue::White); + } + } + + } + //--------------------------------------------------------------------- + bool TerrainMaterialGeneratorB::SM2Profile::isShadowingEnabled(TechniqueType tt, const Terrain* terrain) const + { + return getReceiveDynamicShadowsEnabled() && tt != RENDER_COMPOSITE_MAP && + (tt != LOW_LOD || mLowLodShadows) && + terrain->getSceneManager()->isShadowTechniqueTextureBased(); + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::updateParams(const MaterialPtr& mat, const Terrain* terrain) + { + mShaderGen->updateParams(this, mat, terrain, false); + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::updateParamsForCompositeMap(const MaterialPtr& mat, const Terrain* terrain) + { + mShaderGen->updateParams(this, mat, terrain, true); + } + //--------------------------------------------------------------------- + //--------------------------------------------------------------------- + HighLevelGpuProgramPtr + TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::generateVertexProgram( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) + { + HighLevelGpuProgramPtr ret = createVertexProgram(prof, terrain, tt); + + StringUtil::StrStreamType sourceStr; + generateVertexProgramSource(prof, terrain, tt, sourceStr); + ret->setSource(sourceStr.str()); + ret->load(); + defaultVpParams(prof, terrain, tt, ret); +#if OGRE_DEBUG_MODE + LogManager::getSingleton().stream(LML_TRIVIAL) << "*** Terrain Vertex Program: " + << ret->getName() << " ***\n" << ret->getSource() << "\n*** ***"; +#endif + + return ret; + + } + //--------------------------------------------------------------------- + HighLevelGpuProgramPtr + TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::generateFragmentProgram( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) + { + HighLevelGpuProgramPtr ret = createFragmentProgram(prof, terrain, tt); + + StringUtil::StrStreamType sourceStr; + generateFragmentProgramSource(prof, terrain, tt, sourceStr); + + ret->setSource(sourceStr.str()); + ret->load(); + defaultFpParams(prof, terrain, tt, ret); + +#if OGRE_DEBUG_MODE + LogManager::getSingleton().stream(LML_TRIVIAL) << "*** Terrain Fragment Program: " + << ret->getName() << " ***\n" << ret->getSource() << "\n*** ***"; +#endif + + return ret; + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::generateVertexProgramSource( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) + { + generateVpHeader(prof, terrain, tt, outStream); + + if (tt != LOW_LOD) + { + uint maxLayers = prof->getMaxLayers(terrain); + uint numLayers = std::min(maxLayers, static_cast(terrain->getLayerCount())); + + for (uint i = 0; i < numLayers; ++i) + generateVpLayer(prof, terrain, tt, i, outStream); + } + + generateVpFooter(prof, terrain, tt, outStream); + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::generateFragmentProgramSource( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) + { + generateFpHeader(prof, terrain, tt, outStream); + + if (tt != LOW_LOD) + { + uint maxLayers = prof->getMaxLayers(terrain); + uint numLayers = std::min(maxLayers, static_cast(terrain->getLayerCount())); + + for (uint i = 0; i < numLayers; ++i) + generateFpLayer(prof, terrain, tt, i, outStream); + } + + generateFpFooter(prof, terrain, tt, outStream); + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::defaultVpParams( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, const HighLevelGpuProgramPtr& prog) + { + GpuProgramParametersSharedPtr params = prog->getDefaultParameters(); + params->setIgnoreMissingParams(true); + params->setNamedAutoConstant("worldMatrix", GpuProgramParameters::ACT_WORLD_MATRIX); + params->setNamedAutoConstant("viewProjMatrix", GpuProgramParameters::ACT_VIEWPROJ_MATRIX); + params->setNamedAutoConstant("lodMorph", GpuProgramParameters::ACT_CUSTOM, + Terrain::LOD_MORPH_CUSTOM_PARAM); + + if (prof->isShadowingEnabled(tt, terrain)) + { + uint numTextures = 1; + if (prof->getReceiveDynamicShadowsPSSM()) + { + numTextures = prof->getReceiveDynamicShadowsPSSM()->getSplitCount(); + } + for (uint i = 0; i < numTextures; ++i) + { + params->setNamedAutoConstant("texViewProjMatrix" + StringConverter::toString(i), + GpuProgramParameters::ACT_TEXTURE_VIEWPROJ_MATRIX, i); + if (prof->getReceiveDynamicShadowsDepth()) + { + //params->setNamedAutoConstant("depthRange" + StringConverter::toString(i), + //GpuProgramParameters::ACT_SHADOW_SCENE_DEPTH_RANGE, i); + } + } + } + + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::defaultFpParams( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, const HighLevelGpuProgramPtr& prog) + { + GpuProgramParametersSharedPtr params = prog->getDefaultParameters(); + params->setIgnoreMissingParams(true); + + params->setNamedAutoConstant("ambient", GpuProgramParameters::ACT_AMBIENT_LIGHT_COLOUR); + + for (int i=0; igetNumberOfLightsSupported(); ++i) + { + params->setNamedAutoConstant("lightPosObjSpace"+StringConverter::toString(i), GpuProgramParameters::ACT_LIGHT_POSITION_OBJECT_SPACE, i); + params->setNamedAutoConstant("lightDiffuseColour"+StringConverter::toString(i), GpuProgramParameters::ACT_LIGHT_DIFFUSE_COLOUR, i); + if (i > 0) + params->setNamedAutoConstant("lightAttenuation"+StringConverter::toString(i), GpuProgramParameters::ACT_LIGHT_ATTENUATION, i); + //params->setNamedAutoConstant("lightSpecularColour"+StringConverter::toString(i), GpuProgramParameters::ACT_LIGHT_SPECULAR_COLOUR, i); + } + + if (MWRender::RenderingManager::useMRT()) + params->setNamedAutoConstant("far", GpuProgramParameters::ACT_FAR_CLIP_DISTANCE); + + params->setNamedAutoConstant("eyePosObjSpace", GpuProgramParameters::ACT_CAMERA_POSITION_OBJECT_SPACE); + params->setNamedAutoConstant("fogColour", GpuProgramParameters::ACT_FOG_COLOUR); + params->setNamedAutoConstant("fogParams", GpuProgramParameters::ACT_FOG_PARAMS); + + if (prof->isShadowingEnabled(tt, terrain)) + { + params->setNamedConstant("shadowFar_fadeStart", Vector4(prof->mShadowFar, prof->mShadowFadeStart * prof->mShadowFar, 0, 0)); + uint numTextures = 1; + if (prof->getReceiveDynamicShadowsPSSM()) + { + PSSMShadowCameraSetup* pssm = prof->getReceiveDynamicShadowsPSSM(); + numTextures = pssm->getSplitCount(); + Vector4 splitPoints; + const PSSMShadowCameraSetup::SplitPointList& splitPointList = pssm->getSplitPoints(); + // Populate from split point 1, not 0, since split 0 isn't useful (usually 0) + for (uint i = 1; i < numTextures; ++i) + { + splitPoints[i-1] = splitPointList[i]; + } + params->setNamedConstant("pssmSplitPoints", splitPoints); + } + + if (prof->getReceiveDynamicShadowsDepth()) + { + size_t samplerOffset = (tt == HIGH_LOD) ? mShadowSamplerStartHi : mShadowSamplerStartLo; + for (uint i = 0; i < numTextures; ++i) + { + params->setNamedAutoConstant("inverseShadowmapSize" + StringConverter::toString(i), + GpuProgramParameters::ACT_INVERSE_TEXTURE_SIZE, i + samplerOffset); + } + } + } + + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::updateParams( + const SM2Profile* prof, const MaterialPtr& mat, const Terrain* terrain, bool compositeMap) + { + Pass* p = mat->getTechnique(0)->getPass(0); + if (compositeMap) + { + updateVpParams(prof, terrain, RENDER_COMPOSITE_MAP, p->getVertexProgramParameters()); + updateFpParams(prof, terrain, RENDER_COMPOSITE_MAP, p->getFragmentProgramParameters()); + } + else + { + // high lod + updateVpParams(prof, terrain, HIGH_LOD, p->getVertexProgramParameters()); + updateFpParams(prof, terrain, HIGH_LOD, p->getFragmentProgramParameters()); + + if(prof->isCompositeMapEnabled()) + { + // low lod + p = mat->getTechnique(1)->getPass(0); + updateVpParams(prof, terrain, LOW_LOD, p->getVertexProgramParameters()); + updateFpParams(prof, terrain, LOW_LOD, p->getFragmentProgramParameters()); + } + } + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::updateVpParams( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, const GpuProgramParametersSharedPtr& params) + { + params->setIgnoreMissingParams(true); + uint maxLayers = prof->getMaxLayers(terrain); + uint numLayers = std::min(maxLayers, static_cast(terrain->getLayerCount())); + uint numUVMul = numLayers / 4; + if (numLayers % 4) + ++numUVMul; + for (uint i = 0; i < numUVMul; ++i) + { + Vector4 uvMul( + terrain->getLayerUVMultiplier(i * 4), + terrain->getLayerUVMultiplier(i * 4 + 1), + terrain->getLayerUVMultiplier(i * 4 + 2), + terrain->getLayerUVMultiplier(i * 4 + 3) + ); + params->setNamedConstant("uvMul" + StringConverter::toString(i), uvMul); + } + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::updateFpParams( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, const GpuProgramParametersSharedPtr& params) + { + params->setIgnoreMissingParams(true); + // TODO - parameterise this? + Vector4 scaleBiasSpecular(0.03, -0.04, 32, 1); + params->setNamedConstant("scaleBiasSpecular", scaleBiasSpecular); + + } + //--------------------------------------------------------------------- + String TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::getChannel(uint idx) + { + uint rem = idx % 4; + switch(rem) + { + case 0: + default: + return "r"; + case 1: + return "g"; + case 2: + return "b"; + case 3: + return "a"; + }; + } + //--------------------------------------------------------------------- + String TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::getVertexProgramName( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) + { + String progName = terrain->getMaterialName() + "/sm2/vp"; + + switch(tt) + { + case HIGH_LOD: + progName += "/hlod"; + break; + case LOW_LOD: + progName += "/llod"; + break; + case RENDER_COMPOSITE_MAP: + progName += "/comp"; + break; + } + + return progName; + + } + //--------------------------------------------------------------------- + String TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::getFragmentProgramName( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) + { + + String progName = terrain->getMaterialName() + "/sm2/fp"; + + switch(tt) + { + case HIGH_LOD: + progName += "/hlod"; + break; + case LOW_LOD: + progName += "/llod"; + break; + case RENDER_COMPOSITE_MAP: + progName += "/comp"; + break; + } + + return progName; + } + //--------------------------------------------------------------------- + //--------------------------------------------------------------------- + HighLevelGpuProgramPtr + TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::createVertexProgram( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) + { + HighLevelGpuProgramManager& mgr = HighLevelGpuProgramManager::getSingleton(); + String progName = getVertexProgramName(prof, terrain, tt); + HighLevelGpuProgramPtr ret = mgr.getByName(progName); + if (ret.isNull()) + { + ret = mgr.createProgram(progName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + "cg", GPT_VERTEX_PROGRAM); + } + else + { + ret->unload(); + } + + ret->setParameter("profiles", "vs_3_0 vs_2_0 vp40 arbvp1"); + ret->setParameter("entry_point", "main_vp"); + + return ret; + + } + //--------------------------------------------------------------------- + HighLevelGpuProgramPtr + TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::createFragmentProgram( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) + { + HighLevelGpuProgramManager& mgr = HighLevelGpuProgramManager::getSingleton(); + String progName = getFragmentProgramName(prof, terrain, tt); + + HighLevelGpuProgramPtr ret = mgr.getByName(progName); + if (ret.isNull()) + { + ret = mgr.createProgram(progName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + "cg", GPT_FRAGMENT_PROGRAM); + } + else + { + ret->unload(); + } + + ret->setParameter("profiles", "ps_3_0 ps_2_x fp40 arbfp1"); + ret->setParameter("entry_point", "main_fp"); + + return ret; + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::generateVpHeader( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) + { + outStream << + "void main_vp(\n" + "float4 pos : POSITION,\n" + "float2 uv : TEXCOORD0,\n"; + if (tt != RENDER_COMPOSITE_MAP) + outStream << "float2 delta : TEXCOORD1,\n"; // lodDelta, lodThreshold + + outStream << + "uniform float4x4 worldMatrix,\n" + "uniform float4x4 viewProjMatrix,\n" + "uniform float2 lodMorph,\n"; // morph amount, morph LOD target + + // uv multipliers + uint maxLayers = prof->getMaxLayers(terrain); + uint numLayers = std::min(maxLayers, static_cast(terrain->getLayerCount())); + uint numUVMultipliers = (numLayers / 4); + if (numLayers % 4) + ++numUVMultipliers; + for (uint i = 0; i < numUVMultipliers; ++i) + outStream << "uniform float4 uvMul" << i << ", \n"; + + outStream << + "out float4 oPos : POSITION,\n" + "out float4 oPosObj : TEXCOORD0 \n"; + + uint texCoordSet = 1; + outStream << + ", out float4 oUVMisc : COLOR0 // xy = uv, z = camDepth\n"; + + // layer UV's premultiplied, packed as xy/zw + uint numUVSets = numLayers / 2; + if (numLayers % 2) + ++numUVSets; + if (tt != LOW_LOD) + { + for (uint i = 0; i < numUVSets; ++i) + { + outStream << + ", out float4 oUV" << i << " : TEXCOORD" << texCoordSet++ << "\n"; + } + } + + if (prof->getParent()->getDebugLevel() && tt != RENDER_COMPOSITE_MAP) + { + outStream << ", out float2 lodInfo : TEXCOORD" << texCoordSet++ << "\n"; + } + + if (prof->isShadowingEnabled(tt, terrain)) + { + texCoordSet = generateVpDynamicShadowsParams(texCoordSet, prof, terrain, tt, outStream); + } + + // check we haven't exceeded texture coordinates + if (texCoordSet > 8) + { + OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, + "Requested options require too many texture coordinate sets! Try reducing the number of layers. requested: " + StringConverter::toString(texCoordSet), + __FUNCTION__); + } + + outStream << + ")\n" + "{\n" + " float4 worldPos = mul(worldMatrix, pos);\n" + " oPosObj = pos;\n"; + + if (tt != RENDER_COMPOSITE_MAP) + { + // determine whether to apply the LOD morph to this vertex + // we store the deltas against all vertices so we only want to apply + // the morph to the ones which would disappear. The target LOD which is + // being morphed to is stored in lodMorph.y, and the LOD at which + // the vertex should be morphed is stored in uv.w. If we subtract + // the former from the latter, and arrange to only morph if the + // result is negative (it will only be -1 in fact, since after that + // the vertex will never be indexed), we will achieve our aim. + // sign(vertexLOD - targetLOD) == -1 is to morph + outStream << + " float toMorph = -min(0, sign(delta.y - lodMorph.y));\n"; + // this will either be 1 (morph) or 0 (don't morph) + if (prof->getParent()->getDebugLevel()) + { + // x == LOD level (-1 since value is target level, we want to display actual) + outStream << "lodInfo.x = (lodMorph.y - 1) / " << terrain->getNumLodLevels() << ";\n"; + // y == LOD morph + outStream << "lodInfo.y = toMorph * lodMorph.x;\n"; + } + + // morph + switch (terrain->getAlignment()) + { + case Terrain::ALIGN_X_Y: + outStream << " worldPos.z += delta.x * toMorph * lodMorph.x;\n"; + break; + case Terrain::ALIGN_X_Z: + outStream << " worldPos.y += delta.x * toMorph * lodMorph.x;\n"; + break; + case Terrain::ALIGN_Y_Z: + outStream << " worldPos.x += delta.x * toMorph * lodMorph.x;\n"; + break; + }; + } + + + // generate UVs + if (tt != LOW_LOD) + { + for (uint i = 0; i < numUVSets; ++i) + { + uint layer = i * 2; + uint uvMulIdx = layer / 4; + + outStream << + " oUV" << i << ".xy = " << " uv.xy * uvMul" << uvMulIdx << "." << getChannel(layer) << ";\n"; + outStream << + " oUV" << i << ".zw = " << " uv.xy * uvMul" << uvMulIdx << "." << getChannel(layer+1) << ";\n"; + + } + + } + + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::generateFpHeader( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) + { + + // Main header + outStream << + // helpers + "float4 expand(float4 v)\n" + "{ \n" + " return v * 2 - 1;\n" + "}\n\n\n"; + + if (prof->isShadowingEnabled(tt, terrain)) + generateFpDynamicShadowsHelpers(prof, terrain, tt, outStream); + + + outStream << + "void main_fp(\n" + "float4 position : TEXCOORD0,\n"; + + uint texCoordSet = 1; + outStream << + "float4 uvMisc : COLOR0,\n"; + + // UV's premultiplied, packed as xy/zw + uint maxLayers = prof->getMaxLayers(terrain); + uint numBlendTextures = std::min(terrain->getBlendTextureCount(maxLayers), terrain->getBlendTextureCount()); + uint numLayers = std::min(maxLayers, static_cast(terrain->getLayerCount())); + uint numUVSets = numLayers / 2; + if (numLayers % 2) + ++numUVSets; + if (tt != LOW_LOD) + { + for (uint i = 0; i < numUVSets; ++i) + { + outStream << + "float4 layerUV" << i << " : TEXCOORD" << texCoordSet++ << ", \n"; + } + + } + if (prof->getParent()->getDebugLevel() && tt != RENDER_COMPOSITE_MAP) + { + outStream << "float2 lodInfo : TEXCOORD" << texCoordSet++ << ", \n"; + } + + bool fog = terrain->getSceneManager()->getFogMode() != FOG_NONE && tt != RENDER_COMPOSITE_MAP; + if (fog) + { + outStream << + "uniform float4 fogParams, \n" + "uniform float3 fogColour, \n"; + } + + uint currentSamplerIdx = 0; + + outStream << + // Only 1 light supported in this version + // deferred shading profile / generator later, ok? :) + "uniform float3 ambient,\n"; + + + for (int i=0; igetNumberOfLightsSupported(); ++i) + { + outStream << + "uniform float4 lightPosObjSpace"< 0) + outStream << + "uniform float4 lightAttenuation"<isGlobalColourMapEnabled()) + { + outStream << ", uniform sampler2D globalColourMap : register(s" + << currentSamplerIdx++ << ")\n"; + } + if (prof->isLightmapEnabled()) + { + outStream << ", uniform sampler2D lightMap : register(s" + << currentSamplerIdx++ << ")\n"; + } + // Blend textures - sampler definitions + for (uint i = 0; i < numBlendTextures; ++i) + { + outStream << ", uniform sampler2D blendTex" << i + << " : register(s" << currentSamplerIdx++ << ")\n"; + } + + // Layer textures - sampler definitions & UV multipliers + for (uint i = 0; i < numLayers; ++i) + { + outStream << ", uniform sampler2D difftex" << i + << " : register(s" << currentSamplerIdx++ << ")\n"; + + if (prof->mLayerNormalMappingEnabled || prof->mLayerParallaxMappingEnabled) + outStream << ", uniform sampler2D normtex" << i + << " : register(s" << currentSamplerIdx++ << ")\n"; + } + } + + if (prof->isShadowingEnabled(tt, terrain)) + { + generateFpDynamicShadowsParams(&texCoordSet, ¤tSamplerIdx, prof, terrain, tt, outStream); + } + + // check we haven't exceeded samplers + if (currentSamplerIdx > 16) + { + OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, + "Requested options require too many texture samplers! Try reducing the number of layers.", + __FUNCTION__); + } + + if (MWRender::RenderingManager::useMRT()) outStream << + " , out float4 oColor : COLOR \n" + " , out float4 oColor1 : COLOR1 \n" + " , uniform float far \n"; + else outStream << + " , out float4 oColor : COLOR \n"; + + outStream << + ")\n" + "{\n" + " float4 outputCol;\n" + " float shadow = 1.0;\n" + " float2 uv = uvMisc.xy;\n" + // base colour + " outputCol = float4(0,0,0,1);\n"; + + if (tt != LOW_LOD) + { + outStream << + // global normal + " float3 normal = expand(tex2D(globalNormal, uv)).rgb;\n"; + + // not needed anymore apparently + //" normal = float3(normal.x, normal.z, -normal.y); \n"; // convert Ogre to MW coordinate system + + } + + for (int i=0; igetNumberOfLightsSupported(); ++i) + outStream << + " float3 lightDir"<isLayerNormalMappingEnabled()) + { + // derive the tangent space basis + // we do this in the pixel shader because we don't have per-vertex normals + // because of the LOD, we use a normal map + // tangent is always +x or -z in object space depending on alignment + switch(terrain->getAlignment()) + { + case Terrain::ALIGN_X_Y: + case Terrain::ALIGN_X_Z: + outStream << " float3 tangent = float3(1, 0, 0);\n"; + break; + case Terrain::ALIGN_Y_Z: + outStream << " float3 tangent = float3(0, 0, -1);\n"; + break; + }; + + outStream << " float3 binormal = normalize(cross(tangent, normal));\n"; + // note, now we need to re-cross to derive tangent again because it wasn't orthonormal + outStream << " tangent = normalize(cross(normal, binormal));\n"; + // derive final matrix + outStream << " float3x3 TBN = float3x3(tangent, binormal, normal);\n"; + + // set up lighting result placeholders for interpolation + outStream << " float4 litRes, litResLayer;\n"; + outStream << " float3 TSlightDir, TSeyeDir, TShalfAngle, TSnormal;\n"; + if (prof->isLayerParallaxMappingEnabled()) + outStream << " float displacement;\n"; + // move + outStream << " TSlightDir = normalize(mul(TBN, lightDir));\n"; + outStream << " TSeyeDir = normalize(mul(TBN, eyeDir));\n"; + + } + else + { + if (prof->getNumberOfLightsSupported() > 1) + outStream << "float d; \n" + "float attn; \n"; + + outStream << + " eyeDir = normalize(eyeDir); \n"; + + // simple per-pixel lighting with no normal mapping + for (int i=0; igetNumberOfLightsSupported(); ++i) + { + outStream << " float3 halfAngle"< 0) + outStream << + // pre-multiply light color with attenuation factor + "d = length( lightDir"<_isSM3Available()) + outStream << " if (" << blendWeightStr << " > 0.0003)\n { \n"; + + + // generate UV + outStream << " float2 uv" << layer << " = layerUV" << uvIdx << uvChannels << ";\n"; + + // calculate lighting here if normal mapping + if (prof->isLayerNormalMappingEnabled()) + { + if (prof->isLayerParallaxMappingEnabled() && tt != RENDER_COMPOSITE_MAP) + { + // modify UV - note we have to sample an extra time + outStream << " displacement = tex2D(normtex" << layer << ", uv" << layer << ").a\n" + " * scaleBiasSpecular.x + scaleBiasSpecular.y;\n"; + outStream << " uv" << layer << " += TSeyeDir.xy * displacement;\n"; + } + + // access TS normal map + outStream << " TSnormal = expand(tex2D(normtex" << layer << ", uv" << layer << ")).rgb;\n"; + outStream << " TShalfAngle = normalize(TSlightDir + TSeyeDir);\n"; + outStream << " litResLayer = lit(dot(TSlightDir, TSnormal), dot(TShalfAngle, TSnormal), scaleBiasSpecular.z);\n"; + if (!layer) + outStream << " litRes = litResLayer;\n"; + else + outStream << " litRes = lerp(litRes, litResLayer, " << blendWeightStr << ");\n"; + + } + + // sample diffuse texture + outStream << " float4 diffuseSpecTex" << layer + << " = tex2D(difftex" << layer << ", uv" << layer << ");\n"; + + // apply to common + if (!layer) + { + outStream << " diffuse = diffuseSpecTex0.rgb;\n"; + if (prof->isLayerSpecularMappingEnabled()) + outStream << " specular = diffuseSpecTex0.a;\n"; + } + else + { + outStream << " diffuse = lerp(diffuse, diffuseSpecTex" << layer + << ".rgb, " << blendWeightStr << ");\n"; + if (prof->isLayerSpecularMappingEnabled()) + outStream << " specular = lerp(specular, diffuseSpecTex" << layer + << ".a, " << blendWeightStr << ");\n"; + + } + + // End early-out + // Disable - causing some issues even when trying to force the use of texldd + + // comment by scrawl: + // on a NVIDIA card in opengl mode, didn't produce any problems, + // while increasing FPS from 170 to 185 (!!!) in the same area + // so let's try this out - if something does cause problems for + // someone else (with a different card / renderer) we can just + // add a vendor-specific check here + if (layer && prof->_isSM3Available()) + outStream << " } // early-out blend value\n"; + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::generateVpFooter( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) + { + + outStream << + " oPos = mul(viewProjMatrix, worldPos);\n" + " oUVMisc.xy = uv.xy;\n"; + + outStream << + " // pass cam depth\n" + " oPosObj.w = oPos.z;\n"; + + if (prof->isShadowingEnabled(tt, terrain)) + generateVpDynamicShadows(prof, terrain, tt, outStream); + + outStream << + "}\n"; + + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::generateFpFooter( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) + { + + if (tt == LOW_LOD) + { + if (prof->isShadowingEnabled(tt, terrain)) + { + generateFpDynamicShadows(prof, terrain, tt, outStream); + outStream << + " outputCol.rgb = diffuse * rtshadow;\n"; + } + else + { + outStream << + " outputCol.rgb = diffuse;\n"; + } + } + else + { + if (prof->isGlobalColourMapEnabled()) + { + // sample colour map and apply to diffuse + outStream << " diffuse *= tex2D(globalColourMap, uv).rgb;\n"; + } + if (prof->isLightmapEnabled()) + { + // sample lightmap + outStream << " shadow = tex2D(lightMap, uv).r;\n"; + } + + if (prof->isShadowingEnabled(tt, terrain)) + { + generateFpDynamicShadows(prof, terrain, tt, outStream); + } + + outStream << " outputCol.rgb += ambient * diffuse; \n"; + + // diffuse lighting + for (int i=0; igetNumberOfLightsSupported(); ++i) + { + // shadows only for first light (directional) + if (i==0) + outStream << " outputCol.rgb += litRes"<isLayerSpecularMappingEnabled()) + outStream << " specular = 0.0;\n"; + + if (tt == RENDER_COMPOSITE_MAP) + { + // Lighting embedded in alpha + outStream << + " outputCol.a = shadow;\n"; + + } + else + { + // Apply specular + //outStream << " outputCol.rgb += litRes.z * lightSpecularColour * specular * shadow;\n"; + + if (prof->getParent()->getDebugLevel()) + { + outStream << " outputCol.rg += lodInfo.xy;\n"; + } + } + } + + bool fog = terrain->getSceneManager()->getFogMode() != FOG_NONE && tt != RENDER_COMPOSITE_MAP; + if (fog) + { + if (terrain->getSceneManager()->getFogMode() == FOG_LINEAR) + { + outStream << + " float fogVal = saturate((position.w - fogParams.y) * fogParams.w);\n"; + } + else + { + outStream << + " float fogVal = saturate(1 / (exp(position.w * fogParams.x)));\n"; + } + outStream << " outputCol.rgb = lerp(outputCol.rgb, fogColour, fogVal);\n"; + } + + // Final return + outStream << " oColor = outputCol;\n"; + + if (MWRender::RenderingManager::useMRT()) outStream << + " oColor1 = float4(position.w / far, 0, 0, 1); \n"; + + outStream + << "}\n"; + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::generateFpDynamicShadowsHelpers( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) + { + // TODO make filtering configurable + outStream << + "// Simple PCF \n" + "// Number of samples in one dimension (square for total samples) \n" + "#define NUM_SHADOW_SAMPLES_1D 1.0 \n" + "#define SHADOW_FILTER_SCALE 1 \n" + + "#define SHADOW_SAMPLES NUM_SHADOW_SAMPLES_1D*NUM_SHADOW_SAMPLES_1D \n" + + "float4 offsetSample(float4 uv, float2 offset, float invMapSize) \n" + "{ \n" + " return float4(uv.xy + offset * invMapSize * uv.w, uv.z, uv.w); \n" + "} \n"; + + if (prof->getReceiveDynamicShadowsDepth()) + { + outStream << + "float calcDepthShadow(sampler2D shadowMap, float4 shadowMapPos, float2 offset) \n" + " { \n" + " shadowMapPos = shadowMapPos / shadowMapPos.w; \n" + " float2 uv = shadowMapPos.xy; \n" + " float3 o = float3(offset, -offset.x) * 0.3f; \n" + " // Note: We using 2x2 PCF. Good enough and is alot faster. \n" + " float c = (shadowMapPos.z <= tex2D(shadowMap, uv.xy - o.xy).r) ? 1 : 0; // top left \n" + " c += (shadowMapPos.z <= tex2D(shadowMap, uv.xy + o.xy).r) ? 1 : 0; // bottom right \n" + " c += (shadowMapPos.z <= tex2D(shadowMap, uv.xy + o.zy).r) ? 1 : 0; // bottom left \n" + " c += (shadowMapPos.z <= tex2D(shadowMap, uv.xy - o.zy).r) ? 1 : 0; // top right \n" + " return c / 4; \n" + " } \n"; + } + else + { + outStream << + "float calcSimpleShadow(sampler2D shadowMap, float4 shadowMapPos) \n" + "{ \n" + " return tex2Dproj(shadowMap, shadowMapPos).x; \n" + "} \n"; + + } + + if (prof->getReceiveDynamicShadowsPSSM()) + { + uint numTextures = prof->getReceiveDynamicShadowsPSSM()->getSplitCount(); + + + if (prof->getReceiveDynamicShadowsDepth()) + { + outStream << + "float calcPSSMDepthShadow("; + } + else + { + outStream << + "float calcPSSMSimpleShadow("; + } + + outStream << "\n "; + for (uint i = 0; i < numTextures; ++i) + outStream << "sampler2D shadowMap" << i << ", "; + outStream << "\n "; + for (uint i = 0; i < numTextures; ++i) + outStream << "float4 lsPos" << i << ", "; + if (prof->getReceiveDynamicShadowsDepth()) + { + outStream << "\n "; + for (uint i = 0; i < numTextures; ++i) + outStream << "float2 invShadowmapSize" << i << ", "; + } + outStream << "\n" + " float4 pssmSplitPoints, float camDepth) \n" + "{ \n" + " float shadow; \n" + " // calculate shadow \n"; + + for (uint i = 0; i < numTextures; ++i) + { + if (!i) + outStream << " if (camDepth <= pssmSplitPoints." << ShaderHelper::getChannel(i) << ") \n"; + else if (i < numTextures - 1) + outStream << " else if (camDepth <= pssmSplitPoints." << ShaderHelper::getChannel(i) << ") \n"; + else + outStream << " else \n"; + + outStream << + " { \n"; + if (prof->getReceiveDynamicShadowsDepth()) + { + outStream << + " shadow = calcDepthShadow(shadowMap" << i << ", lsPos" << i << ", invShadowmapSize" << i << ".xy); \n"; + } + else + { + outStream << + " shadow = calcSimpleShadow(shadowMap" << i << ", lsPos" << i << "); \n"; + } + outStream << + " } \n"; + + } + + outStream << + " return shadow; \n" + "} \n\n\n"; + } + + + } + //--------------------------------------------------------------------- + uint TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::generateVpDynamicShadowsParams( + uint texCoord, const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) + { + // out semantics & params + uint numTextures = 1; + if (prof->getReceiveDynamicShadowsPSSM()) + { + numTextures = prof->getReceiveDynamicShadowsPSSM()->getSplitCount(); + } + for (uint i = 0; i < numTextures; ++i) + { + outStream << + ", out float4 oLightSpacePos" << i << " : TEXCOORD" << texCoord++ << " \n" << + ", uniform float4x4 texViewProjMatrix" << i << " \n"; + if (prof->getReceiveDynamicShadowsDepth()) + { + outStream << + ", uniform float4 depthRange" << i << " // x = min, y = max, z = range, w = 1/range \n"; + } + } + + return texCoord; + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::generateVpDynamicShadows( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) + { + uint numTextures = 1; + if (prof->getReceiveDynamicShadowsPSSM()) + { + numTextures = prof->getReceiveDynamicShadowsPSSM()->getSplitCount(); + } + + // Calculate the position of vertex in light space + for (uint i = 0; i < numTextures; ++i) + { + outStream << + " oLightSpacePos" << i << " = mul(texViewProjMatrix" << i << ", worldPos); \n"; + if (prof->getReceiveDynamicShadowsDepth()) + { + // make linear + //outStream << + // "oLightSpacePos" << i << ".z = (oLightSpacePos" << i << ".z - depthRange" << i << ".x) * depthRange" << i << ".w;\n"; + + } + } + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::generateFpDynamicShadowsParams( + uint* texCoord, uint* sampler, const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) + { + if (tt == HIGH_LOD) + mShadowSamplerStartHi = *sampler; + else if (tt == LOW_LOD) + mShadowSamplerStartLo = *sampler; + + // in semantics & params + uint numTextures = 1; + outStream << + ", uniform float4 shadowFar_fadeStart \n"; + if (prof->getReceiveDynamicShadowsPSSM()) + { + numTextures = prof->getReceiveDynamicShadowsPSSM()->getSplitCount(); + outStream << + ", uniform float4 pssmSplitPoints \n"; + } + for (uint i = 0; i < numTextures; ++i) + { + outStream << + ", float4 lightSpacePos" << i << " : TEXCOORD" << *texCoord << " \n" << + ", uniform sampler2D shadowMap" << i << " : register(s" << *sampler << ") \n"; + *sampler = *sampler + 1; + *texCoord = *texCoord + 1; + if (prof->getReceiveDynamicShadowsDepth()) + { + outStream << + ", uniform float4 inverseShadowmapSize" << i << " \n"; + } + } + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::generateFpDynamicShadows( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) + { + if (prof->getReceiveDynamicShadowsPSSM()) + { + uint numTextures = prof->getReceiveDynamicShadowsPSSM()->getSplitCount(); + outStream << + " float camDepth = position.w;\n"; + + if (prof->getReceiveDynamicShadowsDepth()) + { + outStream << + " float rtshadow = calcPSSMDepthShadow("; + } + else + { + outStream << + " float rtshadow = calcPSSMSimpleShadow("; + } + for (uint i = 0; i < numTextures; ++i) + outStream << "shadowMap" << i << ", "; + outStream << "\n "; + + for (uint i = 0; i < numTextures; ++i) + outStream << "lightSpacePos" << i << ", "; + if (prof->getReceiveDynamicShadowsDepth()) + { + outStream << "\n "; + for (uint i = 0; i < numTextures; ++i) + outStream << "inverseShadowmapSize" << i << ".xy, "; + } + outStream << "\n" << + " pssmSplitPoints, camDepth);\n"; + + } + else + { + if (prof->getReceiveDynamicShadowsDepth()) + { + outStream << + " float rtshadow = calcDepthShadow(shadowMap0, lightSpacePos0, inverseShadowmapSize0.xy);"; + } + else + { + outStream << + " float rtshadow = calcSimpleShadow(shadowMap0, lightSpacePos0);"; + } + } + + outStream << + " float fadeRange = shadowFar_fadeStart.x - shadowFar_fadeStart.y; \n" + " float fade = 1-((position.w - shadowFar_fadeStart.y) / fadeRange); \n" + " rtshadow = (position.w > shadowFar_fadeStart.x) ? 1 : ((position.w > shadowFar_fadeStart.y) ? 1-((1-rtshadow)*fade) : rtshadow); \n" + " rtshadow = (1-(1-rtshadow)*0.6); \n" // make the shadow a little less intensive + " shadow = min(shadow, rtshadow);\n"; + + } + //--------------------------------------------------------------------- + //--------------------------------------------------------------------- + HighLevelGpuProgramPtr + TerrainMaterialGeneratorB::SM2Profile::ShaderHelperHLSL::createVertexProgram( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) + { + HighLevelGpuProgramManager& mgr = HighLevelGpuProgramManager::getSingleton(); + String progName = getVertexProgramName(prof, terrain, tt); + + HighLevelGpuProgramPtr ret = mgr.getByName(progName); + if (ret.isNull()) + { + ret = mgr.createProgram(progName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + "hlsl", GPT_VERTEX_PROGRAM); + } + else + { + ret->unload(); + } + + if (prof->_isSM3Available()) + ret->setParameter("target", "vs_3_0"); + else + ret->setParameter("target", "vs_2_0"); + ret->setParameter("entry_point", "main_vp"); + + return ret; + + } + //--------------------------------------------------------------------- + HighLevelGpuProgramPtr + TerrainMaterialGeneratorB::SM2Profile::ShaderHelperHLSL::createFragmentProgram( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) + { + HighLevelGpuProgramManager& mgr = HighLevelGpuProgramManager::getSingleton(); + String progName = getFragmentProgramName(prof, terrain, tt); + + + HighLevelGpuProgramPtr ret = mgr.getByName(progName); + if (ret.isNull()) + { + ret = mgr.createProgram(progName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + "hlsl", GPT_FRAGMENT_PROGRAM); + } + else + { + ret->unload(); + } + + if (prof->_isSM3Available()) + ret->setParameter("target", "ps_3_0"); + else + ret->setParameter("target", "ps_2_x"); + ret->setParameter("entry_point", "main_fp"); + + return ret; + + } + //--------------------------------------------------------------------- + //--------------------------------------------------------------------- + HighLevelGpuProgramPtr + TerrainMaterialGeneratorB::SM2Profile::ShaderHelperGLSL::createVertexProgram( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) + { + HighLevelGpuProgramManager& mgr = HighLevelGpuProgramManager::getSingleton(); + String progName = getVertexProgramName(prof, terrain, tt); + + switch(tt) + { + case HIGH_LOD: + progName += "/hlod"; + break; + case LOW_LOD: + progName += "/llod"; + break; + case RENDER_COMPOSITE_MAP: + progName += "/comp"; + break; + } + + HighLevelGpuProgramPtr ret = mgr.getByName(progName); + if (ret.isNull()) + { + ret = mgr.createProgram(progName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + "glsl", GPT_VERTEX_PROGRAM); + } + else + { + ret->unload(); + } + + return ret; + + } + //--------------------------------------------------------------------- + HighLevelGpuProgramPtr + TerrainMaterialGeneratorB::SM2Profile::ShaderHelperGLSL::createFragmentProgram( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) + { + HighLevelGpuProgramManager& mgr = HighLevelGpuProgramManager::getSingleton(); + String progName = getFragmentProgramName(prof, terrain, tt); + + HighLevelGpuProgramPtr ret = mgr.getByName(progName); + if (ret.isNull()) + { + ret = mgr.createProgram(progName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + "glsl", GPT_FRAGMENT_PROGRAM); + } + else + { + ret->unload(); + } + + return ret; + + } + + +} diff --git a/apps/openmw/mwrender/terrainmaterial.hpp b/apps/openmw/mwrender/terrainmaterial.hpp new file mode 100644 index 000000000..db916bf25 --- /dev/null +++ b/apps/openmw/mwrender/terrainmaterial.hpp @@ -0,0 +1,271 @@ +/* +----------------------------------------------------------------------------- +This source file is part of OGRE +(Object-oriented Graphics Rendering Engine) +For the latest info, see http://www.ogre3d.org/ + +Copyright (c) 2000-2011 Torus Knot Software Ltd + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +----------------------------------------------------------------------------- +*/ + +#ifndef __Ogre_TerrainMaterialGeneratorB_H__ +#define __Ogre_TerrainMaterialGeneratorB_H__ + +#include "OgreTerrainPrerequisites.h" +#include "OgreTerrainMaterialGenerator.h" +#include "OgreGpuProgramParams.h" + +namespace Ogre +{ + class PSSMShadowCameraSetup; + + /** \addtogroup Optional Components + * @{ + */ + /** \addtogroup Terrain + * Some details on the terrain component + * @{ + */ + + + /** A TerrainMaterialGenerator which can cope with normal mapped, specular mapped + terrain. + @note Requires the Cg plugin to render correctly + */ + class TerrainMaterialGeneratorB : public TerrainMaterialGenerator + { + public: + TerrainMaterialGeneratorB(); + ~TerrainMaterialGeneratorB(); + + /** Shader model 2 profile target. + */ + class SM2Profile : public TerrainMaterialGenerator::Profile + { + public: + SM2Profile(TerrainMaterialGenerator* parent, const String& name, const String& desc); + ~SM2Profile(); + + bool isVertexCompressionSupported() const {return false;} + + MaterialPtr generate(const Terrain* terrain); + MaterialPtr generateForCompositeMap(const Terrain* terrain); + uint8 getMaxLayers(const Terrain* terrain) const; + void updateParams(const MaterialPtr& mat, const Terrain* terrain); + void updateParamsForCompositeMap(const MaterialPtr& mat, const Terrain* terrain); + void requestOptions(Terrain* terrain); + + void setShadowFar(float far); + void setShadowFadeStart(float fadestart); + + /** Whether to support normal mapping per layer in the shader (default true). + */ + bool isLayerNormalMappingEnabled() const { return mLayerNormalMappingEnabled; } + /** Whether to support normal mapping per layer in the shader (default true). + */ + void setLayerNormalMappingEnabled(bool enabled); + /** Whether to support parallax mapping per layer in the shader (default true). + */ + bool isLayerParallaxMappingEnabled() const { return mLayerParallaxMappingEnabled; } + /** Whether to support parallax mapping per layer in the shader (default true). + */ + void setLayerParallaxMappingEnabled(bool enabled); + /** Whether to support specular mapping per layer in the shader (default true). + */ + bool isLayerSpecularMappingEnabled() const { return mLayerSpecularMappingEnabled; } + /** Whether to support specular mapping per layer in the shader (default true). + */ + void setLayerSpecularMappingEnabled(bool enabled); + /** Whether to support a global colour map over the terrain in the shader, + if it's present (default true). + */ + bool isGlobalColourMapEnabled() const { return mGlobalColourMapEnabled; } + /** Whether to support a global colour map over the terrain in the shader, + if it's present (default true). + */ + void setGlobalColourMapEnabled(bool enabled); + /** Whether to support a light map over the terrain in the shader, + if it's present (default true). + */ + bool isLightmapEnabled() const { return mLightmapEnabled; } + /** Whether to support a light map over the terrain in the shader, + if it's present (default true). + */ + void setLightmapEnabled(bool enabled); + /** Whether to use the composite map to provide a lower LOD technique + in the distance (default true). + */ + bool isCompositeMapEnabled() const { return mCompositeMapEnabled; } + /** Whether to use the composite map to provide a lower LOD technique + in the distance (default true). + */ + void setCompositeMapEnabled(bool enabled); + /** Whether to support dynamic texture shadows received from other + objects, on the terrain (default true). + */ + bool getReceiveDynamicShadowsEnabled() const { return mReceiveDynamicShadows; } + /** Whether to support dynamic texture shadows received from other + objects, on the terrain (default true). + */ + void setReceiveDynamicShadowsEnabled(bool enabled); + + /** Whether to use PSSM support dynamic texture shadows, and if so the + settings to use (default 0). + */ + void setReceiveDynamicShadowsPSSM(PSSMShadowCameraSetup* pssmSettings); + /** Whether to use PSSM support dynamic texture shadows, and if so the + settings to use (default 0). + */ + PSSMShadowCameraSetup* getReceiveDynamicShadowsPSSM() const { return mPSSM; } + /** Whether to use depth shadows (default false). + */ + void setReceiveDynamicShadowsDepth(bool enabled); + /** Whether to use depth shadows (default false). + */ + bool getReceiveDynamicShadowsDepth() const { return mDepthShadows; } + /** Whether to use shadows on low LOD material rendering (when using composite map) (default false). + */ + void setReceiveDynamicShadowsLowLod(bool enabled); + /** Whether to use shadows on low LOD material rendering (when using composite map) (default false). + */ + bool getReceiveDynamicShadowsLowLod() const { return mLowLodShadows; } + + int getNumberOfLightsSupported() const; + + /// Internal + bool _isSM3Available() const { return mSM3Available; } + + protected: + + enum TechniqueType + { + HIGH_LOD, + LOW_LOD, + RENDER_COMPOSITE_MAP + }; + void addTechnique(const MaterialPtr& mat, const Terrain* terrain, TechniqueType tt); + + /// Interface definition for helper class to generate shaders + class ShaderHelper : public TerrainAlloc + { + public: + ShaderHelper() {} + virtual ~ShaderHelper() {} + virtual HighLevelGpuProgramPtr generateVertexProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); + virtual HighLevelGpuProgramPtr generateFragmentProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); + virtual void updateParams(const SM2Profile* prof, const MaterialPtr& mat, const Terrain* terrain, bool compositeMap); + protected: + virtual String getVertexProgramName(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); + virtual String getFragmentProgramName(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); + virtual HighLevelGpuProgramPtr createVertexProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) = 0; + virtual HighLevelGpuProgramPtr createFragmentProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) = 0; + virtual void generateVertexProgramSource(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); + virtual void generateFragmentProgramSource(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); + virtual void generateVpHeader(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) = 0; + virtual void generateFpHeader(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) = 0; + virtual void generateVpLayer(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, uint layer, StringUtil::StrStreamType& outStream) = 0; + virtual void generateFpLayer(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, uint layer, StringUtil::StrStreamType& outStream) = 0; + virtual void generateVpFooter(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) = 0; + virtual void generateFpFooter(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) = 0; + virtual void defaultVpParams(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, const HighLevelGpuProgramPtr& prog); + virtual void defaultFpParams(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, const HighLevelGpuProgramPtr& prog); + virtual void updateVpParams(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, const GpuProgramParametersSharedPtr& params); + virtual void updateFpParams(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, const GpuProgramParametersSharedPtr& params); + static String getChannel(uint idx); + + size_t mShadowSamplerStartHi; + size_t mShadowSamplerStartLo; + + }; + + /// Utility class to help with generating shaders for Cg / HLSL. + class ShaderHelperCg : public ShaderHelper + { + protected: + HighLevelGpuProgramPtr createVertexProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); + HighLevelGpuProgramPtr createFragmentProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); + void generateVpHeader(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); + void generateFpHeader(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); + void generateVpLayer(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, uint layer, StringUtil::StrStreamType& outStream); + void generateFpLayer(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, uint layer, StringUtil::StrStreamType& outStream); + void generateVpFooter(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); + void generateFpFooter(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); + uint generateVpDynamicShadowsParams(uint texCoordStart, const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); + void generateVpDynamicShadows(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); + void generateFpDynamicShadowsHelpers(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); + void generateFpDynamicShadowsParams(uint* texCoord, uint* sampler, const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); + void generateFpDynamicShadows(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); + }; + + class ShaderHelperHLSL : public ShaderHelperCg + { + protected: + HighLevelGpuProgramPtr createVertexProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); + HighLevelGpuProgramPtr createFragmentProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); + }; + + /// Utility class to help with generating shaders for GLSL. + class ShaderHelperGLSL : public ShaderHelper + { + protected: + HighLevelGpuProgramPtr createVertexProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); + HighLevelGpuProgramPtr createFragmentProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); + void generateVpHeader(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) {} + void generateFpHeader(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) {} + void generateVpLayer(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, uint layer, StringUtil::StrStreamType& outStream) {} + void generateFpLayer(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, uint layer, StringUtil::StrStreamType& outStream) {} + void generateVpFooter(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) {} + void generateFpFooter(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) {} + }; + + ShaderHelper* mShaderGen; + bool mLayerNormalMappingEnabled; + bool mLayerParallaxMappingEnabled; + bool mLayerSpecularMappingEnabled; + bool mGlobalColourMapEnabled; + bool mLightmapEnabled; + bool mCompositeMapEnabled; + bool mReceiveDynamicShadows; + PSSMShadowCameraSetup* mPSSM; + bool mDepthShadows; + bool mLowLodShadows; + bool mSM3Available; + float mShadowFar; + float mShadowFadeStart; + + bool isShadowingEnabled(TechniqueType tt, const Terrain* terrain) const; + + }; + + + + + }; + + + + /** @} */ + /** @} */ + + +} + +#endif diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp new file mode 100644 index 000000000..71cf56dfd --- /dev/null +++ b/apps/openmw/mwrender/water.cpp @@ -0,0 +1,293 @@ +#include "water.hpp" +#include +#include "sky.hpp" +#include "renderingmanager.hpp" + +using namespace Ogre; + +namespace MWRender +{ + +Water::Water (Ogre::Camera *camera, SkyManager* sky, const ESM::Cell* cell) : + mCamera (camera), mViewport (camera->getViewport()), mSceneManager (camera->getSceneManager()), + mIsUnderwater(false), mVisibilityFlags(0), + mReflectionTarget(0), mActive(1), mToggled(1) +{ + mSky = sky; + + try + { + CompositorManager::getSingleton().setCompositorEnabled(mViewport, "Water", false); + } catch(...) {} + + mTop = cell->water; + + mIsUnderwater = false; + + mWaterPlane = Plane(Vector3::UNIT_Y, 0); + + MeshManager::getSingleton().createPlane("water", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, mWaterPlane, CELL_SIZE*5, CELL_SIZE * 5, 10, 10, true, 1, 3,3, Vector3::UNIT_Z); + + mWater = mSceneManager->createEntity("water"); + mWater->setVisibilityFlags(RV_Water); + mWater->setRenderQueueGroup(RQG_Water); + mWater->setCastShadows(false); + + mVisibilityFlags = RV_Terrain * Settings::Manager::getBool("reflect terrain", "Water") + + RV_Statics * Settings::Manager::getBool("reflect statics", "Water") + + RV_StaticsSmall * Settings::Manager::getBool("reflect small statics", "Water") + + RV_Actors * Settings::Manager::getBool("reflect actors", "Water") + + RV_Misc * Settings::Manager::getBool("reflect misc", "Water") + + RV_Sky; + + mWaterNode = mSceneManager->getRootSceneNode()->createChildSceneNode(); + mWaterNode->setPosition(0, mTop, 0); + + mReflectionCamera = mSceneManager->createCamera("ReflectionCamera"); + + if(!(cell->data.flags & cell->Interior)) + { + mWaterNode->setPosition(getSceneNodeCoordinates(cell->data.gridX, cell->data.gridY)); + } + mWaterNode->attachObject(mWater); + + // Create rendertarget for reflection + int rttsize = Settings::Manager::getInt("rtt size", "Water"); + + TexturePtr tex; + if (Settings::Manager::getBool("shader", "Water")) + { + tex = TextureManager::getSingleton().createManual("WaterReflection", + ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, TEX_TYPE_2D, rttsize, rttsize, 0, PF_FLOAT16_RGBA, TU_RENDERTARGET); + + RenderTarget* rtt = tex->getBuffer()->getRenderTarget(); + Viewport* vp = rtt->addViewport(mReflectionCamera); + vp->setOverlaysEnabled(false); + vp->setBackgroundColour(ColourValue(0.8f, 0.9f, 1.0f)); + vp->setShadowsEnabled(false); + vp->setVisibilityMask( mVisibilityFlags ); + // use fallback techniques without shadows and without mrt (currently not implemented for sky and terrain) + //vp->setMaterialScheme("Fallback"); + rtt->addListener(this); + rtt->setActive(true); + + mReflectionTarget = rtt; + } + + mCompositorName = RenderingManager::useMRT() ? "Underwater" : "UnderwaterNoMRT"; + + createMaterial(); + mWater->setMaterial(mMaterial); + + mUnderwaterEffect = Settings::Manager::getBool("underwater effect", "Water"); + + + // ---------------------------------------------------------------------------------------------- + // ---------------------------------- reflection debug overlay ---------------------------------- + // ---------------------------------------------------------------------------------------------- + /* + if (Settings::Manager::getBool("shader", "Water")) + { + OverlayManager& mgr = OverlayManager::getSingleton(); + Overlay* overlay; + // destroy if already exists + if (overlay = mgr.getByName("ReflectionDebugOverlay")) + mgr.destroy(overlay); + + overlay = mgr.create("ReflectionDebugOverlay"); + + if (MaterialManager::getSingleton().resourceExists("Ogre/ReflectionDebugTexture")) + MaterialManager::getSingleton().remove("Ogre/ReflectionDebugTexture"); + MaterialPtr debugMat = MaterialManager::getSingleton().create( + "Ogre/ReflectionDebugTexture", + ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); + + debugMat->getTechnique(0)->getPass(0)->setLightingEnabled(false); + TextureUnitState *t = debugMat->getTechnique(0)->getPass(0)->createTextureUnitState(tex->getName()); + t->setTextureAddressingMode(TextureUnitState::TAM_CLAMP); + + OverlayContainer* debugPanel; + + // destroy container if exists + try + { + if (debugPanel = + static_cast( + mgr.getOverlayElement("Ogre/ReflectionDebugTexPanel" + ))) + mgr.destroyOverlayElement(debugPanel); + } + catch (Ogre::Exception&) {} + + debugPanel = (OverlayContainer*) + (OverlayManager::getSingleton().createOverlayElement("Panel", "Ogre/ReflectionDebugTexPanel")); + debugPanel->_setPosition(0, 0.55); + debugPanel->_setDimensions(0.3, 0.3); + debugPanel->setMaterialName(debugMat->getName()); + debugPanel->show(); + overlay->add2D(debugPanel); + overlay->show(); + } + */ +} + +void Water::setActive(bool active) +{ + mActive = active; + updateVisible(); +} + +Water::~Water() +{ + MeshManager::getSingleton().remove("water"); + + mWaterNode->detachObject(mWater); + mSceneManager->destroyEntity(mWater); + mSceneManager->destroySceneNode(mWaterNode); + + CompositorManager::getSingleton().removeCompositorChain(mViewport); +} + +void Water::changeCell(const ESM::Cell* cell) +{ + mTop = cell->water; + + if(!(cell->data.flags & cell->Interior)) + mWaterNode->setPosition(getSceneNodeCoordinates(cell->data.gridX, cell->data.gridY)); + else + setHeight(mTop); +} + +void Water::setHeight(const float height) +{ + mTop = height; + mWaterNode->setPosition(0, height, 0); +} + +void Water::toggle() +{ + mToggled = !mToggled; + updateVisible(); +} + +void Water::checkUnderwater(float y) +{ + if (!mActive) return; + if ((mIsUnderwater && y > mTop) || !mWater->isVisible() || mCamera->getPolygonMode() != Ogre::PM_SOLID) + { + CompositorManager::getSingleton().setCompositorEnabled(mViewport, mCompositorName, false); + + // tell the shader we are not underwater + Ogre::Pass* pass = mMaterial->getTechnique(0)->getPass(0); + if (pass->hasFragmentProgram() && pass->getFragmentProgramParameters()->_findNamedConstantDefinition("isUnderwater", false)) + pass->getFragmentProgramParameters()->setNamedConstant("isUnderwater", Real(0)); + + mWater->setRenderQueueGroup(RQG_Water); + + mIsUnderwater = false; + } + + if (!mIsUnderwater && y < mTop && mWater->isVisible() && mCamera->getPolygonMode() == Ogre::PM_SOLID) + { + if (mUnderwaterEffect) + CompositorManager::getSingleton().setCompositorEnabled(mViewport, mCompositorName, true); + + // tell the shader we are underwater + Ogre::Pass* pass = mMaterial->getTechnique(0)->getPass(0); + if (pass->hasFragmentProgram() && pass->getFragmentProgramParameters()->_findNamedConstantDefinition("isUnderwater", false)) + pass->getFragmentProgramParameters()->setNamedConstant("isUnderwater", Real(1)); + + mWater->setRenderQueueGroup(RQG_UnderWater); + + mIsUnderwater = true; + } + + updateVisible(); +} + +Vector3 Water::getSceneNodeCoordinates(int gridX, int gridY) +{ + return Vector3(gridX * CELL_SIZE + (CELL_SIZE / 2), mTop, -gridY * CELL_SIZE - (CELL_SIZE / 2)); +} + +void Water::preRenderTargetUpdate(const RenderTargetEvent& evt) +{ + if (evt.source == mReflectionTarget) + { + mReflectionCamera->setOrientation(mCamera->getDerivedOrientation()); + mReflectionCamera->setPosition(mCamera->getDerivedPosition()); + mReflectionCamera->setNearClipDistance(mCamera->getNearClipDistance()); + mReflectionCamera->setFarClipDistance(mCamera->getFarClipDistance()); + mReflectionCamera->setAspectRatio(mCamera->getAspectRatio()); + mReflectionCamera->setFOVy(mCamera->getFOVy()); + + // Some messy code to get the skybox to show up at all + // The problem here is that it gets clipped by the water plane + // Therefore scale it up a bit + Vector3 pos = mCamera->getRealPosition(); + pos.y = mTop*2 - pos.y; + mSky->setSkyPosition(pos); + mSky->scaleSky(mCamera->getFarClipDistance() / 1000.f); + + mReflectionCamera->enableCustomNearClipPlane(Plane(Vector3::UNIT_Y, mTop)); + mReflectionCamera->enableReflection(Plane(Vector3::UNIT_Y, mTop)); + } +} + +void Water::postRenderTargetUpdate(const RenderTargetEvent& evt) +{ + if (evt.source == mReflectionTarget) + { + mSky->resetSkyPosition(); + mSky->scaleSky(1); + mReflectionCamera->disableCustomNearClipPlane(); + mReflectionCamera->disableReflection(); + } +} + +void Water::createMaterial() +{ + mMaterial = MaterialManager::getSingleton().getByName("Water"); + + // these have to be set in code + std::string textureNames[32]; + for (int i=0; i<32; ++i) + { + textureNames[i] = "textures\\water\\water" + StringConverter::toString(i, 2, '0') + ".dds"; + } + mMaterial->getTechnique(1)->getPass(0)->getTextureUnitState(0)->setAnimatedTextureName(textureNames, 32, 2); + + // use technique without shaders if reflection is disabled + if (mReflectionTarget == 0) + mMaterial->removeTechnique(0); + + if (Settings::Manager::getBool("shader", "Water")) + { + CompositorInstance* compositor = CompositorManager::getSingleton().getCompositorChain(mViewport)->getCompositor("gbuffer"); + + TexturePtr colorTexture = compositor->getTextureInstance("mrt_output", 0); + TextureUnitState* tus = mMaterial->getTechnique(0)->getPass(0)->getTextureUnitState("refractionMap"); + if (tus != 0) + tus->setTexture(colorTexture); + + TexturePtr depthTexture = compositor->getTextureInstance("mrt_output", 1); + tus = mMaterial->getTechnique(0)->getPass(0)->getTextureUnitState("depthMap"); + if (tus != 0) + tus->setTexture(depthTexture); + } +} + +void Water::setViewportBackground(const ColourValue& bg) +{ + if (mReflectionTarget) + mReflectionTarget->getViewport(0)->setBackgroundColour(bg); +} + +void Water::updateVisible() +{ + mWater->setVisible(mToggled && mActive); + if (mReflectionTarget) + mReflectionTarget->setActive(mToggled && mActive && !mIsUnderwater); +} + +} // namespace diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp new file mode 100644 index 000000000..f14482e2b --- /dev/null +++ b/apps/openmw/mwrender/water.hpp @@ -0,0 +1,69 @@ +#ifndef GAME_MWRENDER_WATER_H +#define GAME_MWRENDER_WATER_H + +#include +#include + +#include "renderconst.hpp" + +namespace MWRender { + + class SkyManager; + + /// Water rendering + class Water : public Ogre::RenderTargetListener + { + static const int CELL_SIZE = 8192; + Ogre::Camera *mCamera; + Ogre::SceneManager *mSceneManager; + Ogre::Viewport *mViewport; + + Ogre::Plane mWaterPlane; + Ogre::SceneNode *mWaterNode; + Ogre::Entity *mWater; + + bool mIsUnderwater; + bool mActive; + bool mToggled; + int mTop; + + Ogre::Vector3 getSceneNodeCoordinates(int gridX, int gridY); + + protected: + void preRenderTargetUpdate(const Ogre::RenderTargetEvent& evt); + void postRenderTargetUpdate(const Ogre::RenderTargetEvent& evt); + void updateVisible(); + + SkyManager* mSky; + + std::string mCompositorName; + + void createMaterial(); + Ogre::MaterialPtr mMaterial; + + Ogre::Camera* mReflectionCamera; + + Ogre::RenderTarget* mReflectionTarget; + + bool mUnderwaterEffect; + int mVisibilityFlags; + + public: + Water (Ogre::Camera *camera, SkyManager* sky, const ESM::Cell* cell); + ~Water(); + + void setActive(bool active); + + void toggle(); + + void setViewportBackground(const Ogre::ColourValue& bg); + + void checkUnderwater(float y); + void changeCell(const ESM::Cell* cell); + void setHeight(const float height); + + }; + +} + +#endif diff --git a/apps/openmw/mwscript/cellextensions.cpp b/apps/openmw/mwscript/cellextensions.cpp index bb5263203..d69c42ab3 100644 --- a/apps/openmw/mwscript/cellextensions.cpp +++ b/apps/openmw/mwscript/cellextensions.cpp @@ -133,11 +133,70 @@ namespace MWScript } }; + class OpGetWaterLevel : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + InterpreterContext& context + = static_cast (runtime.getContext()); + + MWWorld::Ptr::CellStore *cell = context.getWorld().getPlayer().getPlayer().getCell(); + runtime.push (cell->mWaterLevel); + } + }; + + class OpSetWaterLevel : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + InterpreterContext& context + = static_cast (runtime.getContext()); + + Interpreter::Type_Float level = runtime[0].mFloat; + + MWWorld::Ptr::CellStore *cell = context.getWorld().getPlayer().getPlayer().getCell(); + + if (!(cell->cell->data.flags & ESM::Cell::Interior)) + throw std::runtime_error("Can't set water level in exterior cell"); + + cell->mWaterLevel = level; + context.getEnvironment().mWorld->setWaterHeight(cell->mWaterLevel); + } + }; + + class OpModWaterLevel : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + InterpreterContext& context + = static_cast (runtime.getContext()); + + Interpreter::Type_Float level = runtime[0].mFloat; + + MWWorld::Ptr::CellStore *cell = context.getWorld().getPlayer().getPlayer().getCell(); + + if (!(cell->cell->data.flags & ESM::Cell::Interior)) + throw std::runtime_error("Can't set water level in exterior cell"); + + cell->mWaterLevel +=level; + context.getEnvironment().mWorld->setWaterHeight(cell->mWaterLevel); + } + }; + const int opcodeCellChanged = 0x2000000; const int opcodeCOC = 0x2000026; const int opcodeCOE = 0x200008e; const int opcodeGetInterior = 0x2000131; const int opcodeGetPCCell = 0x2000136; + const int opcodeGetWaterLevel = 0x2000141; + const int opcodeSetWaterLevel = 0x2000142; + const int opcodeModWaterLevel = 0x2000143; void registerExtensions (Compiler::Extensions& extensions) { @@ -146,8 +205,11 @@ namespace MWScript extensions.registerInstruction ("centeroncell", "S", opcodeCOC); extensions.registerInstruction ("coe", "ll", opcodeCOE); extensions.registerInstruction ("centeronexterior", "ll", opcodeCOE); + extensions.registerInstruction ("setwaterlevel", "f", opcodeSetWaterLevel); + extensions.registerInstruction ("modwaterlevel", "f", opcodeModWaterLevel); extensions.registerFunction ("getinterior", 'l', "", opcodeGetInterior); extensions.registerFunction ("getpccell", 'l', "c", opcodeGetPCCell); + extensions.registerFunction ("getwaterlevel", 'f', "", opcodeGetWaterLevel); } void installOpcodes (Interpreter::Interpreter& interpreter) @@ -157,6 +219,9 @@ namespace MWScript interpreter.installSegment5 (opcodeCOE, new OpCOE); interpreter.installSegment5 (opcodeGetInterior, new OpGetInterior); interpreter.installSegment5 (opcodeGetPCCell, new OpGetPCCell); + interpreter.installSegment5 (opcodeGetWaterLevel, new OpGetWaterLevel); + interpreter.installSegment5 (opcodeSetWaterLevel, new OpSetWaterLevel); + interpreter.installSegment5 (opcodeModWaterLevel, new OpModWaterLevel); } } } diff --git a/apps/openmw/mwscript/dialogueextensions.cpp b/apps/openmw/mwscript/dialogueextensions.cpp index 0cca028e2..fec539d3e 100644 --- a/apps/openmw/mwscript/dialogueextensions.cpp +++ b/apps/openmw/mwscript/dialogueextensions.cpp @@ -11,6 +11,7 @@ #include "../mwdialogue/dialoguemanager.hpp" #include "interpretercontext.hpp" +#include "ref.hpp" namespace MWScript { @@ -115,12 +116,27 @@ namespace MWScript } }; + template + class OpForceGreeting : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + MWScript::InterpreterContext& context + = static_cast (runtime.getContext()); + context.getEnvironment().mDialogueManager->startDialogue (ptr); + } + }; const int opcodeJournal = 0x2000133; const int opcodeSetJournalIndex = 0x2000134; const int opcodeGetJournalIndex = 0x2000135; const int opcodeAddTopic = 0x200013a; const int opcodeChoice = 0x2000a; + const int opcodeForceGreeting = 0x200014f; + const int opcodeForceGreetingExplicit = 0x2000150; void registerExtensions (Compiler::Extensions& extensions) { @@ -129,6 +145,9 @@ namespace MWScript extensions.registerFunction ("getjournalindex", 'l', "c", opcodeGetJournalIndex); extensions.registerInstruction ("addtopic", "S" , opcodeAddTopic); extensions.registerInstruction ("choice", "/SlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSl", opcodeChoice); + extensions.registerInstruction("forcegreeting","",opcodeForceGreeting); + extensions.registerInstruction("forcegreeting","",opcodeForceGreeting, + opcodeForceGreetingExplicit); } void installOpcodes (Interpreter::Interpreter& interpreter) @@ -138,6 +157,8 @@ namespace MWScript interpreter.installSegment5 (opcodeGetJournalIndex, new OpGetJournalIndex); interpreter.installSegment5 (opcodeAddTopic, new OpAddTopic); interpreter.installSegment3 (opcodeChoice,new OpChoice); + interpreter.installSegment5 (opcodeForceGreeting, new OpForceGreeting); + interpreter.installSegment5 (opcodeForceGreetingExplicit, new OpForceGreeting); } } diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index eab5bf846..58960aac4 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -24,7 +24,12 @@ op 0x20007: PlayAnim, explicit reference op 0x20008: LoopAnim op 0x20009: LoopAnim, explicit reference op 0x2000a: Choice -opcodes 0x2000b-0x3ffff unused +op 0x2000b: PCRaiseRank +op 0x2000c: PCLowerRank +op 0x2000d: PCJoinFaction +op 0x2000e: PCGetRank implicit +op 0x2000f: PCGetRank explicit +opcodes 0x20010-0x3ffff unused Segment 4: (not implemented yet) @@ -123,4 +128,20 @@ op 0x200013d: FadeOut op 0x200013e: FadeTo op 0x200013f: GetCurrentWeather op 0x2000140: ChangeWeather -opcodes 0x2000141-0x3ffffff unused +op 0x2000141: GetWaterLevel +op 0x2000142: SetWaterLevel +op 0x2000143: ModWaterLevel +op 0x2000144: ToggleWater, twa +op 0x2000145: ToggleFogOfWar (tfow) +op 0x2000146: TogglePathgrid +op 0x2000147: AddSpell +op 0x2000148: AddSpell, explicit reference +op 0x2000149: RemoveSpell +op 0x200014a: RemoveSpell, explicit reference +op 0x200014b: GetSpell +op 0x200014c: GetSpell, explicit reference +op 0x200014d: ModDisposition +op 0x200014e: ModDisposition, explicit reference +op 0x200014f: ForceGreeting +op 0x2000150: ForceGreeting, explicit reference +opcodes 0x2000151-0x3ffffff unused diff --git a/apps/openmw/mwscript/guiextensions.cpp b/apps/openmw/mwscript/guiextensions.cpp index 484c0d3ab..426378efc 100644 --- a/apps/openmw/mwscript/guiextensions.cpp +++ b/apps/openmw/mwscript/guiextensions.cpp @@ -67,6 +67,19 @@ namespace MWScript } }; + class OpToggleFogOfWar : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + InterpreterContext& context = + static_cast (runtime.getContext()); + + context.getEnvironment().mWindowManager->toggleFogOfWar(); + } + }; + const int opcodeEnableBirthMenu = 0x200000e; const int opcodeEnableClassMenu = 0x200000f; const int opcodeEnableNameMenu = 0x2000010; @@ -79,6 +92,7 @@ namespace MWScript const int opcodeEnableRest = 0x2000017; const int opcodeShowRestMenu = 0x2000018; const int opcodeGetButtonPressed = 0x2000137; + const int opcodeToggleFogOfWar = 0x2000145; void registerExtensions (Compiler::Extensions& extensions) { @@ -100,6 +114,9 @@ namespace MWScript extensions.registerInstruction ("showrestmenu", "", opcodeShowRestMenu); extensions.registerFunction ("getbuttonpressed", 'l', "", opcodeGetButtonPressed); + + extensions.registerInstruction ("togglefogofwar", "", opcodeToggleFogOfWar); + extensions.registerInstruction ("tfow", "", opcodeToggleFogOfWar); } void installOpcodes (Interpreter::Interpreter& interpreter) @@ -135,6 +152,8 @@ namespace MWScript new OpShowDialogue (MWGui::GM_Rest)); interpreter.installSegment5 (opcodeGetButtonPressed, new OpGetButtonPressed); + + interpreter.installSegment5 (opcodeToggleFogOfWar, new OpToggleFogOfWar); } } } diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 5bfffd3a2..a0770b9a8 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -106,7 +106,7 @@ namespace MWScript "Collision Mesh Rendering -> On" : "Collision Mesh Rendering -> Off"); } }; - + class OpToggleWireframe : public Interpreter::Opcode0 { public: @@ -123,7 +123,23 @@ namespace MWScript "Wireframe Rendering -> On" : "Wireframe Rendering -> Off"); } }; - + + class OpTogglePathgrid : public Interpreter::Opcode0 + { + public: + virtual void execute (Interpreter::Runtime& runtime) + { + InterpreterContext& context = + static_cast (runtime.getContext()); + + bool enabled = + context.getWorld().toggleRenderMode (MWWorld::World::Render_Pathgrid); + + context.report (enabled ? + "Path Grid rendering -> On" : "Path Grid Rendering -> Off"); + } + }; + class OpFadeIn : public Interpreter::Opcode0 { public: @@ -135,11 +151,11 @@ namespace MWScript Interpreter::Type_Float time = runtime[0].mFloat; runtime.pop(); - + context.getWorld().getFader()->fadeIn(time); } }; - + class OpFadeOut : public Interpreter::Opcode0 { public: @@ -151,11 +167,11 @@ namespace MWScript Interpreter::Type_Float time = runtime[0].mFloat; runtime.pop(); - + context.getWorld().getFader()->fadeOut(time); } }; - + class OpFadeTo : public Interpreter::Opcode0 { public: @@ -167,14 +183,27 @@ namespace MWScript Interpreter::Type_Float alpha = runtime[0].mFloat; runtime.pop(); - + Interpreter::Type_Float time = runtime[0].mFloat; runtime.pop(); - + context.getWorld().getFader()->fadeTo(alpha, time); } }; + class OpToggleWater : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + InterpreterContext& context = + static_cast (runtime.getContext()); + + context.getWorld().toggleWater(); + } + }; + const int opcodeXBox = 0x200000c; const int opcodeOnActivate = 0x200000d; const int opcodeActivate = 0x2000075; @@ -187,6 +216,8 @@ namespace MWScript const int opcodeFadeIn = 0x200013c; const int opcodeFadeOut = 0x200013d; const int opcodeFadeTo = 0x200013e; + const int opcodeToggleWater = 0x2000144; + const int opcodeTogglePathgrid = 0x2000146; void registerExtensions (Compiler::Extensions& extensions) { @@ -204,6 +235,10 @@ namespace MWScript extensions.registerInstruction ("fadein", "f", opcodeFadeIn); extensions.registerInstruction ("fadeout", "f", opcodeFadeOut); extensions.registerInstruction ("fadeto", "ff", opcodeFadeTo); + extensions.registerInstruction ("togglewater", "", opcodeToggleWater); + extensions.registerInstruction ("twa", "", opcodeToggleWater); + extensions.registerInstruction ("togglepathgrid", "", opcodeTogglePathgrid); + extensions.registerInstruction ("tpg", "", opcodeTogglePathgrid); } void installOpcodes (Interpreter::Interpreter& interpreter) @@ -220,6 +255,8 @@ namespace MWScript interpreter.installSegment5 (opcodeFadeIn, new OpFadeIn); interpreter.installSegment5 (opcodeFadeOut, new OpFadeOut); interpreter.installSegment5 (opcodeFadeTo, new OpFadeTo); + interpreter.installSegment5 (opcodeTogglePathgrid, new OpTogglePathgrid); + interpreter.installSegment5 (opcodeToggleWater, new OpToggleWater); } } } diff --git a/apps/openmw/mwscript/soundextensions.cpp b/apps/openmw/mwscript/soundextensions.cpp index d5cc41b76..b4386a8a0 100644 --- a/apps/openmw/mwscript/soundextensions.cpp +++ b/apps/openmw/mwscript/soundextensions.cpp @@ -130,7 +130,7 @@ namespace MWScript std::string sound = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - context.getSoundManager().playSound3D (ptr, sound, 1.0, 1.0, mLoop); + context.getSoundManager().playSound3D (ptr, sound, 1.0, 1.0, mLoop ? MWSound::Play_Loop : 0); } }; @@ -159,7 +159,7 @@ namespace MWScript Interpreter::Type_Float pitch = runtime[0].mFloat; runtime.pop(); - context.getSoundManager().playSound3D (ptr, sound, volume, pitch, mLoop); + context.getSoundManager().playSound3D (ptr, sound, volume, pitch, mLoop ? MWSound::Play_Loop : 0); } }; diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 0e97a39cf..239f8d768 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -8,6 +8,8 @@ #include #include "../mwworld/class.hpp" +#include "../mwworld/environment.hpp" +#include "../mwworld/player.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" @@ -15,6 +17,8 @@ #include "interpretercontext.hpp" #include "ref.hpp" +#include "../mwdialogue/dialoguemanager.hpp" + namespace MWScript { namespace Stats @@ -280,6 +284,220 @@ namespace MWScript } }; + template + class OpAddSpell : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + std::string id = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + MWWorld::Class::get (ptr).getCreatureStats (ptr).mSpells.add (id); + } + }; + + template + class OpRemoveSpell : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + std::string id = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + MWWorld::Class::get (ptr).getCreatureStats (ptr).mSpells.remove (id); + } + }; + + template + class OpGetSpell : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + + MWWorld::Ptr ptr = R()(runtime); + + std::string id = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + Interpreter::Type_Integer value = 0; + + for (MWMechanics::Spells::TIterator iter ( + MWWorld::Class::get (ptr).getCreatureStats (ptr).mSpells.begin()); + iter!=MWWorld::Class::get (ptr).getCreatureStats (ptr).mSpells.end(); ++iter) + if (*iter==id) + { + value = 1; + break; + } + + runtime.push (value); + } + }; + + class OpPCJoinFaction : public Interpreter::Opcode1 + { + public: + + virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) + { + std::string factionID = ""; + MWScript::InterpreterContext& context + = static_cast (runtime.getContext()); + if(arg0==0) + { + factionID = context.getEnvironment().mDialogueManager->getFaction(); + } + else + { + factionID = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + } + if(factionID != "") + { + MWWorld::Ptr player = context.getEnvironment().mWorld->getPlayer().getPlayer(); + if(MWWorld::Class::get(player).getNpcStats(player).mFactionRank.find(factionID) == MWWorld::Class::get(player).getNpcStats(player).mFactionRank.end()) + { + MWWorld::Class::get(player).getNpcStats(player).mFactionRank[factionID] = 0; + } + } + } + }; + + class OpPCRaiseRank : public Interpreter::Opcode1 + { + public: + + virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) + { + std::string factionID = ""; + MWScript::InterpreterContext& context + = static_cast (runtime.getContext()); + if(arg0==0) + { + factionID = context.getEnvironment().mDialogueManager->getFaction(); + } + else + { + factionID = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + } + if(factionID != "") + { + MWWorld::Ptr player = context.getEnvironment().mWorld->getPlayer().getPlayer(); + if(MWWorld::Class::get(player).getNpcStats(player).mFactionRank.find(factionID) == MWWorld::Class::get(player).getNpcStats(player).mFactionRank.end()) + { + MWWorld::Class::get(player).getNpcStats(player).mFactionRank[factionID] = 0; + } + else + { + MWWorld::Class::get(player).getNpcStats(player).mFactionRank[factionID] = MWWorld::Class::get(player).getNpcStats(player).mFactionRank[factionID] +1; + } + } + } + }; + + class OpPCLowerRank : public Interpreter::Opcode1 + { + public: + + virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) + { + std::string factionID = ""; + MWScript::InterpreterContext& context + = static_cast (runtime.getContext()); + if(arg0==0) + { + factionID = context.getEnvironment().mDialogueManager->getFaction(); + } + else + { + factionID = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + } + if(factionID != "") + { + MWWorld::Ptr player = context.getEnvironment().mWorld->getPlayer().getPlayer(); + if(MWWorld::Class::get(player).getNpcStats(player).mFactionRank.find(factionID) != MWWorld::Class::get(player).getNpcStats(player).mFactionRank.end()) + { + MWWorld::Class::get(player).getNpcStats(player).mFactionRank[factionID] = MWWorld::Class::get(player).getNpcStats(player).mFactionRank[factionID] -1; + } + } + } + }; + + template + class OpGetPCRank : public Interpreter::Opcode1 + { + public: + + virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) + { + MWWorld::Ptr ptr = R()(runtime); + + std::string factionID = ""; + if(arg0 >0) + { + factionID = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + } + else + { + if(MWWorld::Class::get(ptr).getNpcStats(ptr).mFactionRank.empty()) + { + //throw exception? + } + else + { + factionID = MWWorld::Class::get(ptr).getNpcStats(ptr).mFactionRank.begin()->first; + } + } + MWScript::InterpreterContext& context + = static_cast (runtime.getContext()); + MWWorld::Ptr player = context.getEnvironment().mWorld->getPlayer().getPlayer(); + if(factionID!="") + { + if(MWWorld::Class::get(player).getNpcStats(player).mFactionRank.find(factionID) != MWWorld::Class::get(player).getNpcStats(player).mFactionRank.end()) + { + runtime.push(MWWorld::Class::get(player).getNpcStats(player).mFactionRank[factionID]); + } + else + { + runtime.push(-1); + } + } + else + { + runtime.push(-1); + } + } + }; + + template + class OpModDisposition : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + +// Interpreter::Type_Integer value = runtime[0].mInteger; + runtime.pop(); + + /// \todo modify disposition towards the player + } + }; + const int numberOfAttributes = 8; const int opcodeGetAttribute = 0x2000027; @@ -311,6 +529,21 @@ namespace MWScript const int opcodeModSkill = 0x20000fa; const int opcodeModSkillExplicit = 0x2000115; + const int opcodeAddSpell = 0x2000147; + const int opcodeAddSpellExplicit = 0x2000148; + const int opcodeRemoveSpell = 0x2000149; + const int opcodeRemoveSpellExplicit = 0x200014a; + const int opcodeGetSpell = 0x200014b; + const int opcodeGetSpellExplicit = 0x200014c; + + const int opcodePCRaiseRank = 0x2000b; + const int opcodePCLowerRank = 0x2000c; + const int opcodePCJoinFaction = 0x2000d; + const int opcodeGetPCRank = 0x2000e; + const int opcodeGetPCRankExplicit = 0x2000f; + const int opcodeModDisposition = 0x200014d; + const int opcodeModDispositionExplicit = 0x200014e; + void registerExtensions (Compiler::Extensions& extensions) { static const char *attributes[numberOfAttributes] = @@ -381,6 +614,18 @@ namespace MWScript extensions.registerInstruction (mod + skills[i], "l", opcodeModSkill+i, opcodeModSkillExplicit+i); } + + extensions.registerInstruction ("addspell", "c", opcodeAddSpell, opcodeAddSpellExplicit); + extensions.registerInstruction ("removespell", "c", opcodeRemoveSpell, + opcodeRemoveSpellExplicit); + extensions.registerFunction ("getspell", 'l', "c", opcodeGetSpell, opcodeGetSpellExplicit); + + extensions.registerInstruction("pcraiserank","/S",opcodePCRaiseRank); + extensions.registerInstruction("pclowerrank","/S",opcodePCLowerRank); + extensions.registerInstruction("pcjoinfaction","/S",opcodePCJoinFaction); + extensions.registerInstruction("moddisposition","l",opcodeModDisposition, + opcodeModDispositionExplicit); + extensions.registerFunction("getpcrank",'l',"/S",opcodeGetPCRank,opcodeGetPCRankExplicit); } void installOpcodes (Interpreter::Interpreter& interpreter) @@ -436,6 +681,22 @@ namespace MWScript interpreter.installSegment5 (opcodeModSkill+i, new OpModSkill (i)); interpreter.installSegment5 (opcodeModSkillExplicit+i, new OpModSkill (i)); } + + interpreter.installSegment5 (opcodeAddSpell, new OpAddSpell); + interpreter.installSegment5 (opcodeAddSpellExplicit, new OpAddSpell); + interpreter.installSegment5 (opcodeRemoveSpell, new OpRemoveSpell); + interpreter.installSegment5 (opcodeRemoveSpellExplicit, + new OpRemoveSpell); + interpreter.installSegment5 (opcodeGetSpell, new OpGetSpell); + interpreter.installSegment5 (opcodeGetSpellExplicit, new OpGetSpell); + + interpreter.installSegment3(opcodePCRaiseRank,new OpPCRaiseRank); + interpreter.installSegment3(opcodePCLowerRank,new OpPCLowerRank); + interpreter.installSegment3(opcodePCJoinFaction,new OpPCJoinFaction); + interpreter.installSegment5(opcodeModDisposition,new OpModDisposition); + interpreter.installSegment5(opcodeModDispositionExplicit,new OpModDisposition); + interpreter.installSegment3(opcodeGetPCRank,new OpGetPCRank); + interpreter.installSegment3(opcodeGetPCRankExplicit,new OpGetPCRank); } } } diff --git a/apps/openmw/mwsound/audiere_decoder.cpp b/apps/openmw/mwsound/audiere_decoder.cpp new file mode 100644 index 000000000..acc2e5283 --- /dev/null +++ b/apps/openmw/mwsound/audiere_decoder.cpp @@ -0,0 +1,122 @@ +#ifdef OPENMW_USE_AUDIERE + +#include +#include + +#include "audiere_decoder.hpp" + + +static void fail(const std::string &msg) +{ throw std::runtime_error("Audiere exception: "+msg); } + +namespace MWSound +{ + +class OgreFile : public audiere::File +{ + Ogre::DataStreamPtr mStream; + + ADR_METHOD(int) read(void* buffer, int size) + { + return mStream->read(buffer, size); + } + + ADR_METHOD(bool) seek(int position, SeekMode mode) + { + if(mode == CURRENT) + mStream->seek(mStream->tell()+position); + else if(mode == BEGIN) + mStream->seek(position); + else if(mode == END) + mStream->seek(mStream->size()+position); + else + return false; + + return true; + } + + ADR_METHOD(int) tell() + { + return mStream->tell(); + } + + size_t refs; + virtual void ref() { ++refs; } + virtual void unref() + { + if(--refs == 0) + delete this; + } + +public: + OgreFile(const Ogre::DataStreamPtr &stream) + : mStream(stream), refs(1) + { } + virtual ~OgreFile() { } +}; + + +void Audiere_Decoder::open(const std::string &fname) +{ + close(); + + audiere::FilePtr file(new OgreFile(mResourceMgr.openResource(fname))); + mSoundSource = audiere::OpenSampleSource(file); + + int channels, srate; + audiere::SampleFormat format; + + mSoundSource->getFormat(channels, srate, format); + if(format == audiere::SF_S16) + mSampleType = SampleType_Int16; + else if(format == audiere::SF_U8) + mSampleType = SampleType_UInt8; + else + fail("Unsupported sample type"); + + if(channels == 1) + mChannelConfig = ChannelConfig_Mono; + else if(channels == 2) + mChannelConfig = ChannelConfig_Stereo; + else + fail("Unsupported channel count"); + + mSampleRate = srate; +} + +void Audiere_Decoder::close() +{ + mSoundSource = NULL; +} + +void Audiere_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) +{ + *samplerate = mSampleRate; + *chans = mChannelConfig; + *type = mSampleType; +} + +size_t Audiere_Decoder::read(char *buffer, size_t bytes) +{ + int size = bytesToFrames(bytes, mChannelConfig, mSampleType); + size = mSoundSource->read(size, buffer); + return framesToBytes(size, mChannelConfig, mSampleType); +} + +void Audiere_Decoder::rewind() +{ + mSoundSource->reset(); +} + +Audiere_Decoder::Audiere_Decoder() +{ +} + +Audiere_Decoder::~Audiere_Decoder() +{ + close(); +} + +} + +#endif diff --git a/apps/openmw/mwsound/audiere_decoder.hpp b/apps/openmw/mwsound/audiere_decoder.hpp new file mode 100644 index 000000000..0ad026d51 --- /dev/null +++ b/apps/openmw/mwsound/audiere_decoder.hpp @@ -0,0 +1,42 @@ +#ifndef GAME_SOUND_AUDIERE_DECODER_H +#define GAME_SOUND_AUDIERE_DECODER_H + +#include + +#include "audiere.h" + +#include "sound_decoder.hpp" + + +namespace MWSound +{ + class Audiere_Decoder : public Sound_Decoder + { + audiere::SampleSourcePtr mSoundSource; + int mSampleRate; + SampleType mSampleType; + ChannelConfig mChannelConfig; + + 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 rewind(); + + Audiere_Decoder& operator=(const Audiere_Decoder &rhs); + Audiere_Decoder(const Audiere_Decoder &rhs); + + Audiere_Decoder(); + public: + virtual ~Audiere_Decoder(); + + friend class SoundManager; + }; +#ifndef DEFAULT_DECODER +#define DEFAULT_DECODER (::MWSound::Audiere_Decoder) +#endif +}; + +#endif 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..615def701 --- /dev/null +++ b/apps/openmw/mwsound/openal_output.cpp @@ -0,0 +1,828 @@ +#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) + { + const ALCchar *errstring = alcGetString(device, err); + fail(errstring ? errstring : ""); + } +} + +static void throwALerror() +{ + ALenum err = alGetError(); + if(err != AL_NO_ERROR) + { + const ALchar *errstring = alGetString(err); + fail(errstring ? errstring : ""); + } +} + + +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 update(); + + 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) + { + 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::update() +{ + ALfloat gain = mVolume*mBaseVolume; + ALfloat pitch = mPitch; + if(!(mFlags&Play_NoEnv) && mOutput.mLastEnvironment == Env_Underwater) + { + gain *= 0.9f; + pitch *= 0.7f; + } + + alSourcef(mSource, AL_GAIN, gain); + alSourcef(mSource, AL_PITCH, pitch); + alSource3f(mSource, AL_POSITION, mPos[0], mPos[2], -mPos[1]); + alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); + alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f); + throwALerror(); +} + +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 2D OpenAL sound +// +class OpenAL_Sound : public Sound +{ +protected: + OpenAL_Output &mOutput; + + ALuint mSource; + ALuint mBuffer; + +private: + OpenAL_Sound(const OpenAL_Sound &rhs); + OpenAL_Sound& operator=(const OpenAL_Sound &rhs); + +public: + OpenAL_Sound(OpenAL_Output &output, ALuint src, ALuint buf); + virtual ~OpenAL_Sound(); + + virtual void stop(); + virtual bool isPlaying(); + virtual void update(); +}; + +// +// A regular 3D OpenAL sound +// +class OpenAL_Sound3D : public OpenAL_Sound +{ + OpenAL_Sound3D(const OpenAL_Sound &rhs); + OpenAL_Sound3D& operator=(const OpenAL_Sound &rhs); + +public: + OpenAL_Sound3D(OpenAL_Output &output, ALuint src, ALuint buf) + : OpenAL_Sound(output, src, buf) + { } + + virtual void update(); +}; + +OpenAL_Sound::OpenAL_Sound(OpenAL_Output &output, ALuint src, ALuint buf) + : 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::update() +{ + ALfloat gain = mVolume*mBaseVolume; + ALfloat pitch = mPitch; + if(!(mFlags&Play_NoEnv) && mOutput.mLastEnvironment == Env_Underwater) + { + gain *= 0.9f; + pitch *= 0.7f; + } + + alSourcef(mSource, AL_GAIN, gain); + alSourcef(mSource, AL_PITCH, pitch); + alSource3f(mSource, AL_POSITION, mPos[0], mPos[2], -mPos[1]); + alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); + alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f); + throwALerror(); +} + +void OpenAL_Sound3D::update() +{ + ALfloat gain = mVolume*mBaseVolume; + ALfloat pitch = mPitch; + if(mPos.squaredDistance(mOutput.mPos) > mMaxDistance*mMaxDistance) + gain = 0.0f; + else if(!(mFlags&Play_NoEnv) && mOutput.mLastEnvironment == Env_Underwater) + { + gain *= 0.9f; + pitch *= 0.7f; + } + + alSourcef(mSource, AL_GAIN, gain); + alSourcef(mSource, AL_PITCH, pitch); + alSource3f(mSource, AL_POSITION, mPos[0], mPos[2], -mPos[1]); + alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); + alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f); + throwALerror(); +} + + +// +// 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) +{ + deinit(); + + mDevice = alcOpenDevice(devname.c_str()); + if(!mDevice) + { + if(devname.empty()) + fail("Failed to open default device"); + else + fail("Failed to open \""+devname+"\""); + } + else + { + const ALCchar *name = NULL; + if(alcIsExtensionPresent(mDevice, "ALC_ENUMERATE_ALL_EXT")) + name = alcGetString(mDevice, ALC_ALL_DEVICES_SPECIFIER); + if(alcGetError(mDevice) != AL_NO_ERROR || !name) + name = alcGetString(mDevice, ALC_DEVICE_SPECIFIER); + std::cout << "Opened \""<(maxmono+maxstereo, 256); + if (maxtotal == 0) // workaround for broken implementations + maxtotal = 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; + + mInitialized = false; +} + + +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, int flags) +{ + 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); + + if(!(flags&Play_NoEnv) && mLastEnvironment == Env_Underwater) + { + volume *= 0.9f; + pitch *= 0.7f; + } + alSourcef(src, AL_GAIN, volume); + alSourcef(src, AL_PITCH, pitch); + + alSourcei(src, AL_SOURCE_RELATIVE, AL_TRUE); + alSourcei(src, AL_LOOPING, (flags&Play_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 Ogre::Vector3 &pos, float volume, float pitch, + float min, float max, int flags) +{ + 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_Sound3D(*this, src, buf)); + } + catch(std::exception &e) + { + mFreeSources.push_back(src); + if(buf && alIsBuffer(buf)) + bufferFinished(buf); + alGetError(); + 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&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&Play_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, int flags) +{ + boost::shared_ptr sound; + ALuint src; + + if(mFreeSources.empty()) + fail("No free sources"); + src = mFreeSources.front(); + mFreeSources.pop_front(); + + try + { + if((flags&Play_Loop)) + std::cout <<"Warning: cannot loop stream "<open(fname); + sound.reset(new OpenAL_SoundStream(*this, src, decoder)); + } + 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); + + if(!(flags&Play_NoEnv) && mLastEnvironment == Env_Underwater) + { + volume *= 0.9f; + pitch *= 0.7f; + } + alSourcef(src, AL_GAIN, volume); + alSourcef(src, AL_PITCH, pitch); + + alSourcei(src, AL_SOURCE_RELATIVE, AL_TRUE); + alSourcei(src, AL_LOOPING, AL_FALSE); + throwALerror(); + + sound->play(); + return sound; +} + + +void OpenAL_Output::updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir, Environment env) +{ + mPos = pos; + mLastEnvironment = env; + + if(mContext) + { + ALfloat orient[6] = { + atdir.x, atdir.z, -atdir.y, + updir.x, updir.z, -updir.y + }; + alListener3f(AL_POSITION, mPos.x, mPos.z, -mPos.y); + alListenerfv(AL_ORIENTATION, orient); + throwALerror(); + } +} + + +OpenAL_Output::OpenAL_Output(SoundManager &mgr) + : Sound_Output(mgr), mDevice(0), mContext(0), mBufferCacheMemSize(0), + mLastEnvironment(Env_Normal), 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..d62d20286 --- /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 +#include + +#include "alc.h" +#include "al.h" + +#include "sound_output.hpp" + +namespace MWSound +{ + class SoundManager; + class Sound; + + class OpenAL_Output : public Sound_Output + { + ALCdevice *mDevice; + ALCcontext *mContext; + + typedef std::deque IDDq; + IDDq mFreeSources; + IDDq mUnusedBuffers; + + typedef std::map NameMap; + NameMap mBufferCache; + + typedef std::map IDRefMap; + IDRefMap mBufferRefs; + + uint64_t mBufferCacheMemSize; + + ALuint getBuffer(const std::string &fname); + void bufferFinished(ALuint buffer); + + Environment mLastEnvironment; + + virtual std::vector enumerate(); + virtual void init(const std::string &devname=""); + virtual void deinit(); + + virtual SoundPtr playSound(const std::string &fname, float volume, float pitch, int flags); + virtual SoundPtr playSound3D(const std::string &fname, const Ogre::Vector3 &pos, + float volume, float pitch, float min, float max, int flags); + virtual SoundPtr streamSound(const std::string &fname, float volume, float pitch, int flags); + + virtual void updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir, Environment env); + + OpenAL_Output& operator=(const OpenAL_Output &rhs); + OpenAL_Output(const OpenAL_Output &rhs); + + OpenAL_Output(SoundManager &mgr); + virtual ~OpenAL_Output(); + + class StreamThread; + std::auto_ptr mStreamThread; + + friend class OpenAL_Sound; + friend class OpenAL_Sound3D; + friend class OpenAL_SoundStream; + friend class SoundManager; + }; +#ifndef DEFAULT_OUTPUT +#define DEFAULT_OUTPUT (::MWSound::OpenAL_Output) +#endif +}; + +#endif diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp new file mode 100644 index 000000000..a33892548 --- /dev/null +++ b/apps/openmw/mwsound/sound.hpp @@ -0,0 +1,45 @@ +#ifndef GAME_SOUND_SOUND_H +#define GAME_SOUND_SOUND_H + +#include + +namespace MWSound +{ + class Sound + { + virtual void update() = 0; + + Sound& operator=(const Sound &rhs); + Sound(const Sound &rhs); + + protected: + Ogre::Vector3 mPos; + float mVolume; /* NOTE: Real volume = mVolume*mBaseVolume */ + float mBaseVolume; + float mPitch; + float mMinDistance; + float mMaxDistance; + int mFlags; + + public: + virtual void stop() = 0; + virtual bool isPlaying() = 0; + void setPosition(const Ogre::Vector3 &pos) { mPos = pos; } + void setVolume(float volume) { mVolume = volume; } + + Sound() : mPos(0.0f, 0.0f, 0.0f) + , mVolume(1.0f) + , mBaseVolume(1.0f) + , mPitch(1.0f) + , mMinDistance(20.0f) /* 1 * min_range_scale */ + , mMaxDistance(12750.0f) /* 255 * max_range_scale */ + , mFlags(Play_Normal) + { } + virtual ~Sound() { } + + friend class OpenAL_Output; + friend class SoundManager; + }; +} + +#endif diff --git a/apps/openmw/mwsound/sound_decoder.hpp b/apps/openmw/mwsound/sound_decoder.hpp new file mode 100644 index 000000000..9c28d5ff5 --- /dev/null +++ b/apps/openmw/mwsound/sound_decoder.hpp @@ -0,0 +1,48 @@ +#ifndef GAME_SOUND_SOUND_DECODER_H +#define GAME_SOUND_SOUND_DECODER_H + +#include + +#include + +namespace MWSound +{ + enum SampleType { + SampleType_UInt8, + SampleType_Int16 + }; + const char *getSampleTypeName(SampleType type); + + enum ChannelConfig { + ChannelConfig_Mono, + ChannelConfig_Stereo + }; + const char *getChannelConfigName(ChannelConfig config); + + size_t framesToBytes(size_t frames, ChannelConfig config, SampleType type); + size_t bytesToFrames(size_t bytes, ChannelConfig config, SampleType type); + + struct Sound_Decoder + { + Ogre::ResourceGroupManager &mResourceMgr; + + virtual void open(const std::string &fname) = 0; + virtual void close() = 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; + + Sound_Decoder() : mResourceMgr(Ogre::ResourceGroupManager::getSingleton()) + { } + virtual ~Sound_Decoder() { } + + private: + Sound_Decoder(const Sound_Decoder &rhs); + Sound_Decoder& operator=(const Sound_Decoder &rhs); + }; +} + +#endif diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp new file mode 100644 index 000000000..7efed8129 --- /dev/null +++ b/apps/openmw/mwsound/sound_output.hpp @@ -0,0 +1,56 @@ +#ifndef GAME_SOUND_SOUND_OUTPUT_H +#define GAME_SOUND_SOUND_OUTPUT_H + +#include +#include + +#include + +#include "soundmanager.hpp" + +#include "../mwworld/ptr.hpp" + +namespace MWSound +{ + class SoundManager; + class Sound_Decoder; + class Sound; + + class Sound_Output + { + SoundManager &mManager; + + virtual std::vector enumerate() = 0; + virtual void init(const std::string &devname="") = 0; + virtual void deinit() = 0; + + virtual SoundPtr playSound(const std::string &fname, float volume, float pitch, int flags) = 0; + virtual SoundPtr playSound3D(const std::string &fname, const Ogre::Vector3 &pos, + float volume, float pitch, float min, float max, int flags) = 0; + virtual SoundPtr streamSound(const std::string &fname, float volume, float pitch, int flags) = 0; + + virtual void updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir, Environment env) = 0; + + Sound_Output& operator=(const Sound_Output &rhs); + Sound_Output(const Sound_Output &rhs); + + protected: + bool mInitialized; + Ogre::Vector3 mPos; + + Sound_Output(SoundManager &mgr) + : mManager(mgr) + , mInitialized(false) + , mPos(0.0f, 0.0f, 0.0f) + { } + public: + virtual ~Sound_Output() { } + + bool isInitialized() { return mInitialized; } + + friend class OpenAL_Output; + friend class SoundManager; + }; +} + +#endif diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 226796603..730d9d9b2 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -6,111 +6,105 @@ #include -#include -#include -#include - #include +#include #include "../mwworld/environment.hpp" #include "../mwworld/world.hpp" #include "../mwworld/player.hpp" -/* Set up the sound manager to use Audiere, FFMPEG or - MPG123/libsndfile for input. The OPENMW_USE_x macros are set in - CMakeLists.txt. +#include "sound_output.hpp" +#include "sound_decoder.hpp" +#include "sound.hpp" + +#include "openal_output.hpp" +#define SOUND_OUT "OpenAL" +/* Set up the sound manager to use FFMPEG, MPG123+libsndfile, or Audiere for + * input. The OPENMW_USE_x macros are set in CMakeLists.txt. */ +#ifdef OPENMW_USE_FFMPEG +#include "ffmpeg_decoder.hpp" +#ifndef SOUND_IN +#define SOUND_IN "FFmpeg" +#endif +#endif + #ifdef OPENMW_USE_AUDIERE -#include -#define SOUND_FACTORY OpenAL_Audiere_Factory -#define SOUND_OUT "OpenAL" +#include "audiere_decoder.hpp" +#ifndef SOUND_IN #define SOUND_IN "Audiere" #endif - -#ifdef OPENMW_USE_FFMPEG -#include -#define SOUND_FACTORY OpenAL_FFMpeg_Factory -#define SOUND_OUT "OpenAL" -#define SOUND_IN "FFmpeg" #endif #ifdef OPENMW_USE_MPG123 -#include -#define SOUND_FACTORY OpenAL_SndFile_Mpg123_Factory -#define SOUND_OUT "OpenAL" +#include "mpgsnd_decoder.hpp" +#ifndef SOUND_IN #define SOUND_IN "mpg123,sndfile" #endif +#endif -using namespace Mangle::Sound; -typedef OEngine::Sound::SoundManager OEManager; - -// Set the position on a sound based on a Ptr. -static void setPos(SoundPtr &snd, const MWWorld::Ptr ref) -{ - // Get sound position from the reference - const float *pos = ref.getCellRef().pos.pos; - - // Move the sound, converting from MW coordinates to Ogre - // coordinates. - snd->setPos(pos[0], pos[2], -pos[1]); -} namespace MWSound { - - SoundManager::SoundManager(Ogre::Root *root, Ogre::Camera *camera, - const Files::PathContainer& dataDirs, - bool useSound, bool fsstrict, MWWorld::Environment& environment) - : mFSStrict(fsstrict) + SoundManager::SoundManager(bool useSound, MWWorld::Environment& environment) + : mResourceMgr(Ogre::ResourceGroupManager::getSingleton()) , mEnvironment(environment) - , mgr(new OEManager(SoundFactoryPtr(new SOUND_FACTORY))) - , updater(mgr) - , cameraTracker(mgr) - , mCurrentPlaylist(NULL) - , mUsingSound(useSound) + , mOutput(new DEFAULT_OUTPUT(*this)) + , mMasterVolume(1.0f) + , mSFXVolume(1.0f) + , mMusicVolume(1.0f) { - if(useSound) + if(!useSound) + return; + + mMasterVolume = Settings::Manager::getFloat("master volume", "Sound"); + mMasterVolume = std::min(std::max(mMasterVolume, 0.0f), 1.0f); + mSFXVolume = Settings::Manager::getFloat("sfx volume", "Sound"); + mSFXVolume = std::min(std::max(mSFXVolume, 0.0f), 1.0f); + mMusicVolume = Settings::Manager::getFloat("music volume", "Sound"); + mMusicVolume = std::min(std::max(mMusicVolume, 0.0f), 1.0f); + + std::cout << "Sound output: " << SOUND_OUT << std::endl; + std::cout << "Sound decoder: " << SOUND_IN << std::endl; + + try { - // The music library will accept these filetypes - // If none is given then it will accept all filetypes - std::vector acceptableExtensions; - acceptableExtensions.push_back(".mp3"); - acceptableExtensions.push_back(".wav"); - acceptableExtensions.push_back(".ogg"); - acceptableExtensions.push_back(".flac"); - - // Makes a list of all sound files, searches in reverse for priority reasons - for (Files::PathContainer::const_reverse_iterator it = dataDirs.rbegin(); it != dataDirs.rend(); ++it) + std::vector names = mOutput->enumerate(); + std::cout <<"Enumerated output devices:"<< std::endl; + for(size_t i = 0;i < names.size();i++) + std::cout <<" "<init(devname); } - - // Makes a FileLibrary of all music files, searches in reverse for priority reasons - for (Files::PathContainer::const_reverse_iterator it = dataDirs.rbegin(); it != dataDirs.rend(); ++it) + catch(std::exception &e) { - mMusicLibrary.add(*it / std::string("Music"), true, mFSStrict, acceptableExtensions); + if(devname.empty()) + throw; + std::cout <<"Failed to open device \""<init(); + Settings::Manager::setString("device", "Sound", ""); } - - std::string anything = "anything"; // anything is better that a segfault - mCurrentPlaylist = mMusicLibrary.section(anything, mFSStrict); // now points to an empty path - - std::cout << "Sound output: " << SOUND_OUT << std::endl; - std::cout << "Sound decoder: " << SOUND_IN << std::endl; - // Attach the camera to the camera tracker - cameraTracker.followCamera(camera); - - // Tell Ogre to update the sound system each frame - root->addFrameListener(&updater); } - } + catch(std::exception &e) + { + std::cout <<"Sound init failed: "<getStore().sounds.search(soundId); - if(snd == NULL) return ""; - - if(snd->data.volume == 0) - volume = 0.0f; - else - volume *= pow(10.0, (snd->data.volume/255.0f*3348.0 - 3348.0) / 2000.0); - - if(snd->data.minRange == 0 && snd->data.maxRange == 0) - { - min = 100.0f; - max = 2000.0f; - } - else - { - min = snd->data.minRange * 20.0f; - max = snd->data.maxRange * 50.0f; - min = std::max(min, 1.0f); - max = std::max(min, max); - } - - return Files::FileListLocator(mSoundFiles, snd->sound, mFSStrict, false); - } + const ESM::Sound *snd = mEnvironment.mWorld->getStore().sounds.search(soundId); + if(snd == NULL) + throw std::runtime_error(std::string("Failed to lookup sound ")+soundId); - // Add a sound to the list and play it - void SoundManager::add(const std::string &file, - MWWorld::Ptr ptr, - const std::string &id, - float volume, float pitch, - float min, float max, - bool loop, bool untracked) - { - try - { - SoundPtr snd = mgr->load(file); - snd->setRepeat(loop); - snd->setVolume(volume); - snd->setPitch(pitch); - snd->setRange(min,max); - setPos(snd, ptr); - snd->play(); - - if (!untracked) - { - sounds[ptr][id] = WSoundPtr(snd); - } - } - catch(...) + volume *= pow(10.0, (snd->data.volume/255.0*3348.0 - 3348.0) / 2000.0); + + if(snd->data.minRange == 0 && snd->data.maxRange == 0) { - std::cout << "Error loading " << file << ", skipping.\n"; + min = 100.0f; + max = 2000.0f; } - } - - // Clears all the sub-elements of a given iterator, and then - // removes it from 'sounds'. - void SoundManager::clearAll(PtrMap::iterator& it) - { - IDMap::iterator sit = it->second.begin(); - - while(sit != it->second.end()) + else { - // Get sound pointer, if any - SoundPtr snd = sit->second.lock(); - - // Stop the sound - if(snd) snd->stop(); - - sit++; + min = snd->data.minRange * 20.0f; + max = snd->data.maxRange * 50.0f; + min = std::max(min, 1.0f); + max = std::max(min, max); } - // Remove the ptr reference - sounds.erase(it); + return "Sound/"+snd->sound; } - // Stop a sound and remove it from the list. If id="" then - // remove the entire object and stop all its sounds. - void SoundManager::remove(MWWorld::Ptr ptr, const std::string &id) - { - PtrMap::iterator it = sounds.find(ptr); - if(it != sounds.end()) - { - if(id == "") - // Kill all references to 'ptr' - clearAll(it); - else - { - // Only find the id we're looking for - IDMap::iterator it2 = it->second.find(id); - if(it2 != it->second.end()) - { - // Stop the sound and remove it from the list - SoundPtr snd = it2->second.lock(); - if(snd) snd->stop(); - it->second.erase(it2); - } - } - } - } bool SoundManager::isPlaying(MWWorld::Ptr ptr, const std::string &id) const { - PtrMap::const_iterator it = sounds.find(ptr); - if(it != sounds.end()) + SoundMap::const_iterator snditer = mActiveSounds.begin(); + while(snditer != mActiveSounds.end()) { - IDMap::const_iterator it2 = it->second.find(id); - if(it2 != it->second.end()) - { - // Get a shared_ptr from the weak_ptr - SoundPtr snd = it2->second.lock();; - - // Is it still alive? - if(snd) - { - // Then return its status! - return snd->isPlaying(); - } - } + if(snditer->second.first == ptr && snditer->second.second == id) + return snditer->first->isPlaying(); + snditer++; } - // Nothing found, sound is not playing - return false; + return false; } - // Remove all references to objects belonging to a given cell - void SoundManager::removeCell(const MWWorld::Ptr::CellStore *cell) + + void SoundManager::stopMusic() { - PtrMap::iterator it2, it = sounds.begin(); - while(it != sounds.end()) - { - // Make sure to increase the iterator before we erase it. - it2 = it++; - if(it2->first.getCell() == cell) - clearAll(it2); - } + if(mMusic) + mMusic->stop(); + mMusic.reset(); } - void SoundManager::updatePositions(MWWorld::Ptr ptr) + void SoundManager::streamMusicFull(const std::string& filename) { - // Find the reference (if any) - PtrMap::iterator it = sounds.find(ptr); - if(it != sounds.end()) + if(!mOutput->isInitialized()) + return; + std::cout <<"Playing "<second.begin(); - for(;it2 != it->second.end(); it2++) - { - // Get the sound (if it still exists) - SoundPtr snd = it2->second.lock(); - if(snd) - // Update position - setPos(snd, ptr); - } + float basevol = mMasterVolume * mMusicVolume; + stopMusic(); + mMusic = mOutput->streamSound(filename, basevol, 1.0f, Play_NoEnv); + mMusic->mBaseVolume = basevol; + mMusic->mFlags = Play_NoEnv; + } + catch(std::exception &e) + { + std::cout << "Music Error: " << e.what() << "\n"; } } - void SoundManager::stopMusic() + void SoundManager::streamMusic(const std::string& filename) { - if (music) - music->stop(); - setPlaylist(); + streamMusicFull("Music/"+filename); } + void SoundManager::startRandomTitle() + { + Ogre::StringVectorPtr filelist; + filelist = mResourceMgr.findResourceNames(Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + "Music/"+mCurrentPlaylist+"/*"); + if(!filelist->size()) + return; - void SoundManager::streamMusicFull(const std::string& filename) - { - // Play the sound and tell it to stream, if possible. TODO: - // Store the reference, the jukebox will need to check status, - // control volume etc. - if (music) - music->stop(); - music = mgr->load(filename); - music->setStreaming(true); - music->setVolume(0.4); - music->play(); - - } + int i = rand()%filelist->size(); + streamMusicFull((*filelist)[i]); + } - void SoundManager::streamMusic(const std::string& filename) + bool SoundManager::isMusicPlaying() { - std::string filePath = mMusicLibrary.locate(filename, mFSStrict, true).string(); - if(!filePath.empty()) - { - streamMusicFull(filePath); - } + return mMusic && mMusic->isPlaying(); } - void SoundManager::startRandomTitle() - { - if(mCurrentPlaylist && !mCurrentPlaylist->empty()) + void SoundManager::playPlaylist(const std::string &playlist) { - Files::PathContainer::const_iterator fileIter = mCurrentPlaylist->begin(); - srand( time(NULL) ); - int r = rand() % mCurrentPlaylist->size() + 1; //old random code - - std::advance(fileIter, r - 1); - std::string music = fileIter->string(); - std::cout << "Playing " << music << "\n"; + mCurrentPlaylist = playlist; + startRandomTitle(); + } + void SoundManager::say(MWWorld::Ptr ptr, const std::string& filename) + { + if(!mOutput->isInitialized()) + return; try { - streamMusicFull(music); + // The range values are not tested + float basevol = mMasterVolume * mSFXVolume; + std::string filePath = "Sound/"+filename; + const ESM::Position &pos = ptr.getCellRef().pos; + const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]); + + SoundPtr sound = mOutput->playSound3D(filePath, objpos, basevol, 1.0f, + 20.0f, 12750.0f, Play_Normal); + sound->mPos = objpos; + sound->mBaseVolume = basevol; + + mActiveSounds[sound] = std::make_pair(ptr, std::string("_say_sound")); } - catch (std::exception &e) + catch(std::exception &e) { - std::cout << " Music Error: " << e.what() << "\n"; + std::cout <<"Sound Error: "<isPlaying(); - } - return test; + return !isPlaying(ptr, "_say_sound"); } - bool SoundManager::setPlaylist(std::string playlist) + + SoundPtr SoundManager::playSound(const std::string& soundId, float volume, float pitch, int mode) { - const Files::PathContainer* previousPlaylist; - previousPlaylist = mCurrentPlaylist; - if (playlist == "") + SoundPtr sound; + if(!mOutput->isInitialized()) + return sound; + try { - mCurrentPlaylist = mMusicLibrary.section(playlist, mFSStrict); + float basevol = mMasterVolume * mSFXVolume; + float min, max; + std::string file = lookup(soundId, basevol, min, max); + + sound = mOutput->playSound(file, volume*basevol, pitch, mode); + sound->mVolume = volume; + sound->mBaseVolume = basevol; + sound->mPitch = pitch; + sound->mMinDistance = min; + sound->mMaxDistance = max; + sound->mFlags = mode; + + mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), soundId); } - else if(mMusicLibrary.containsSection(playlist, mFSStrict)) + catch(std::exception &e) { - mCurrentPlaylist = mMusicLibrary.section(playlist, mFSStrict); + std::cout <<"Sound Error: "<isInitialized()) + return sound; + try { - std::cout << "Warning: playlist named " << playlist << " does not exist.\n"; + // Look up the sound in the ESM data + float basevol = mMasterVolume * mSFXVolume; + float min, max; + std::string file = lookup(soundId, basevol, min, max); + const ESM::Position &pos = ptr.getCellRef().pos; + const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]); + + sound = mOutput->playSound3D(file, objpos, volume*basevol, pitch, min, max, mode); + sound->mPos = objpos; + sound->mVolume = volume; + sound->mBaseVolume = basevol; + sound->mPitch = pitch; + sound->mMinDistance = min; + sound->mMaxDistance = max; + sound->mFlags = mode; + + if((mode&Play_NoTrack)) + mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), soundId); + else + mActiveSounds[sound] = std::make_pair(ptr, soundId); } - return previousPlaylist == mCurrentPlaylist; + catch(std::exception &e) + { + std::cout <<"Sound Error: "<second.first == ptr && snditer->second.second == soundId) { - startRandomTitle(); + snditer->first->stop(); + mActiveSounds.erase(snditer++); } - return; + else + snditer++; } + } - if(!setPlaylist(playlist)) - { - startRandomTitle(); - } - else if (!isMusicPlaying()) + void SoundManager::stopSound3D(MWWorld::Ptr ptr) + { + SoundMap::iterator snditer = mActiveSounds.begin(); + while(snditer != mActiveSounds.end()) { - startRandomTitle(); + if(snditer->second.first == ptr) + { + snditer->first->stop(); + mActiveSounds.erase(snditer++); + } + else + snditer++; } } - void SoundManager::say (MWWorld::Ptr ptr, const std::string& filename) - { - if (!mUsingSound) - return; - - // The range values are not tested - std::string filePath = Files::FileListLocator(mSoundFiles, filename, mFSStrict, true); - if(!filePath.empty()) - add(filePath, ptr, "_say_sound", 1, 1, 100, 20000, false); - else - std::cout << "Sound file " << filename << " not found, skipping.\n"; - } - - bool SoundManager::sayDone (MWWorld::Ptr ptr) const - { - return !isPlaying(ptr, "_say_sound"); - } - - - void SoundManager::playSound(const std::string& soundId, float volume, float pitch, bool loop) - { - float min, max; - const std::string &file = lookup(soundId, volume, min, max); - if (file != "") + void SoundManager::stopSound(const MWWorld::Ptr::CellStore *cell) { - SoundPtr snd = mgr->load(file); - snd->setRepeat(loop); - snd->setVolume(volume); - snd->setRange(min,max); - snd->setPitch(pitch); - snd->setRelative(true); - snd->play(); - - if (loop) + SoundMap::iterator snditer = mActiveSounds.begin(); + while(snditer != mActiveSounds.end()) { - // Only add the looping sound once - IDMap::iterator it = mLoopedSounds.find(soundId); - if(it == mLoopedSounds.end()) + if(snditer->second.first != MWWorld::Ptr() && + snditer->second.first.getCell() == cell) { - mLoopedSounds[soundId] = WSoundPtr(snd); + snditer->first->stop(); + mActiveSounds.erase(snditer++); } + else + snditer++; } } - } - - void SoundManager::playSound3D (MWWorld::Ptr ptr, const std::string& soundId, - float volume, float pitch, bool loop, bool untracked) - { - // Look up the sound in the ESM data - float min, max; - const std::string &file = lookup(soundId, volume, min, max); - if (file != "") - add(file, ptr, soundId, volume, pitch, min, max, loop, untracked); - } - - void SoundManager::stopSound3D (MWWorld::Ptr ptr, const std::string& soundId) - { - remove(ptr, soundId); - } - - void SoundManager::stopSound (MWWorld::Ptr::CellStore *cell) - { - removeCell(cell); - } void SoundManager::stopSound(const std::string& soundId) { - IDMap::iterator it = mLoopedSounds.find(soundId); - if(it != mLoopedSounds.end()) + SoundMap::iterator snditer = mActiveSounds.begin(); + while(snditer != mActiveSounds.end()) { - SoundPtr snd = it->second.lock(); - if(snd) snd->stop(); - mLoopedSounds.erase(it); + if(snditer->second.first == MWWorld::Ptr() && + snditer->second.second == soundId) + { + snditer->first->stop(); + mActiveSounds.erase(snditer++); + } + else + snditer++; } } - bool SoundManager::getSoundPlaying (MWWorld::Ptr ptr, const std::string& soundId) const - { - // Mark all sounds as playing, otherwise the scripts will just - // keep trying to play them every frame. - - return isPlaying(ptr, soundId); - } + bool SoundManager::getSoundPlaying(MWWorld::Ptr ptr, const std::string& soundId) const + { + return isPlaying(ptr, soundId); + } - void SoundManager::updateObject(MWWorld::Ptr ptr) - { - updatePositions(ptr); - } + void SoundManager::updateObject(MWWorld::Ptr ptr) + { + const ESM::Position &pos = ptr.getCellRef().pos; + const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]); + SoundMap::iterator snditer = mActiveSounds.begin(); + while(snditer != mActiveSounds.end()) + { + if(snditer->second.first == ptr) + snditer->first->setPosition(objpos); + snditer++; + } + } - void SoundManager::update (float duration) - { + void SoundManager::updateRegionSound(float duration) + { MWWorld::Ptr::CellStore *current = mEnvironment.mWorld->getPlayer().getPlayer().getCell(); static int total = 0; static std::string regionName = ""; static float timePassed = 0.0; - timePassed += duration; //If the region has changed - if(!(current->cell->data.flags & current->cell->Interior) && timePassed >= 10) + timePassed += duration; + if((current->cell->data.flags & current->cell->Interior) || timePassed < 10) + return; + timePassed = 0; + + if(regionName != current->cell->region) + { + regionName = current->cell->region; + total = 0; + } + + const ESM::Region *regn = mEnvironment.mWorld->getStore().regions.find(regionName); + std::vector::const_iterator soundIter; + if(total == 0) { + soundIter = regn->soundList.begin(); + while(soundIter != regn->soundList.end()) + { + total += (int)soundIter->chance; + soundIter++; + } + if(total == 0) + return; + } - ESM::Region test = (ESM::Region) *(mEnvironment.mWorld->getStore().regions.find(current->cell->region)); + int r = (int)(rand()/((double)RAND_MAX+1) * total); + int pos = 0; - timePassed = 0; - if (regionName != current->cell->region) + soundIter = regn->soundList.begin(); + while(soundIter != regn->soundList.end()) + { + const std::string go = soundIter->sound.toString(); + int chance = (int) soundIter->chance; + //std::cout << "Sound: " << go.name <<" Chance:" << chance << "\n"; + soundIter++; + if(r - pos < chance) { - regionName = current->cell->region; - total = 0; + //play sound + std::cout << "Sound: " << go <<" Chance:" << chance << "\n"; + playSound(go, 1.0f, 1.0f); + break; } + pos += chance; + } + } + + void SoundManager::updateSounds(float duration) + { + static float timePassed = 0.0; - if(test.soundList.size() > 0) + timePassed += duration; + if(timePassed < (1.0f/30.0f)) + return; + timePassed = 0.0f; + + // Make sure music is still playing + if(!isMusicPlaying()) + startRandomTitle(); + + const ESM::Cell *cell = mEnvironment.mWorld->getPlayer().getPlayer().getCell()->cell; + Ogre::Camera *cam = mEnvironment.mWorld->getPlayer().getRenderer()->getCamera(); + Ogre::Vector3 nPos, nDir, nUp; + nPos = cam->getRealPosition(); + nDir = cam->getRealDirection(); + nUp = cam->getRealUp(); + + Environment env = Env_Normal; + if((cell->data.flags&cell->HasWater) && nPos.y < cell->water) + env = Env_Underwater; + + // The output handler is expecting vectors oriented like the game + // (that is, -Z goes down, +Y goes forward), but that's not what we + // get from Ogre's camera, so we have to convert. + const Ogre::Vector3 pos(nPos[0], -nPos[2], nPos[1]); + const Ogre::Vector3 at(nDir[0], -nDir[2], nDir[1]); + const Ogre::Vector3 up(nUp[0], -nUp[2], nUp[1]); + + mOutput->updateListener(pos, at, up, env); + + // Check if any sounds are finished playing, and trash them + SoundMap::iterator snditer = mActiveSounds.begin(); + while(snditer != mActiveSounds.end()) + { + if(!snditer->first->isPlaying()) + mActiveSounds.erase(snditer++); + else { - std::vector::iterator soundIter = test.soundList.begin(); - //mEnvironment.mSoundManager - if(total == 0) - { - while (soundIter != test.soundList.end()) - { - int chance = (int) soundIter->chance; - //ESM::NAME32 go = soundIter->sound; - //std::cout << "Sound: " << go.name <<" Chance:" << chance << "\n"; - soundIter++; - total += chance; - } - } - - int r = rand() % total; //old random code - int pos = 0; - soundIter = test.soundList.begin(); - while (soundIter != test.soundList.end()) - { - const std::string go = soundIter->sound.toString(); - int chance = (int) soundIter->chance; - //std::cout << "Sound: " << go.name <<" Chance:" << chance << "\n"; - soundIter++; - if( r - pos < chance) - { - //play sound - std::cout << "Sound: " << go <<" Chance:" << chance << "\n"; - mEnvironment.mSoundManager->playSound(go, 20.0, 1.0); - - break; - } - pos += chance; - } + snditer->first->update(); + snditer++; } } - else if(current->cell->data.flags & current->cell->Interior) + } + + void SoundManager::update(float duration) + { + if(!mOutput->isInitialized()) + return; + updateSounds(duration); + updateRegionSound(duration); + } + + + // Default readAll implementation, for decoders that can't do anything + // better + void Sound_Decoder::readAll(std::vector &output) + { + size_t total = output.size(); + size_t got; + + output.resize(total+32768); + while((got=read(&output[total], output.size()-total)) > 0) { - regionName = ""; + total += got; + output.resize(total*2); } + output.resize(total); + } - } + + const char *getSampleTypeName(SampleType type) + { + switch(type) + { + case SampleType_UInt8: return "U8"; + case SampleType_Int16: return "S16"; + } + return "(unknown sample type)"; + } + + const char *getChannelConfigName(ChannelConfig config) + { + switch(config) + { + case ChannelConfig_Mono: return "Mono"; + case ChannelConfig_Stereo: return "Stereo"; + } + return "(unknown channel config)"; + } + + size_t framesToBytes(size_t frames, ChannelConfig config, SampleType type) + { + switch(config) + { + case ChannelConfig_Mono: frames *= 1; break; + case ChannelConfig_Stereo: frames *= 2; break; + } + switch(type) + { + case SampleType_UInt8: frames *= 1; break; + case SampleType_Int16: frames *= 2; break; + } + return frames; + } + + size_t bytesToFrames(size_t bytes, ChannelConfig config, SampleType type) + { + return bytes / framesToBytes(1, config, type); + } } diff --git a/apps/openmw/mwsound/soundmanager.hpp b/apps/openmw/mwsound/soundmanager.hpp index dcf64b90c..d64db299b 100644 --- a/apps/openmw/mwsound/soundmanager.hpp +++ b/apps/openmw/mwsound/soundmanager.hpp @@ -2,13 +2,10 @@ #define GAME_SOUND_SOUNDMANAGER_H #include +#include +#include -#include -#include - -#include - -#include +#include #include "../mwworld/ptr.hpp" @@ -19,16 +16,6 @@ namespace Ogre class Camera; } -namespace Mangle -{ - namespace Sound - { - typedef boost::shared_ptr SoundPtr; - } -} - -typedef OEngine::Sound::SoundManagerPtr OEManagerPtr; - namespace MWWorld { struct Environment; @@ -36,128 +23,118 @@ namespace MWWorld namespace MWSound { + class Sound_Output; + struct Sound_Decoder; + class Sound; + + typedef boost::shared_ptr DecoderPtr; + typedef boost::shared_ptr SoundPtr; + + enum PlayMode { + Play_Normal = 0, /* tracked, non-looping, multi-instance, environment */ + Play_Loop = 1<<0, /* Sound will continually loop until explicitly stopped */ + Play_NoEnv = 1<<1, /* Do not apply environment effects (eg, underwater filters) */ + Play_NoTrack = 1<<2, /* (3D only) Play the sound at the given object's position + * but do not keep it updated (the sound will not move with + * the object and will not stop when the object is deleted. */ + }; + static inline int operator|(const PlayMode &a, const PlayMode &b) + { return (int)a | (int)b; } + static inline int operator&(const PlayMode &a, const PlayMode &b) + { return (int)a & (int)b; } + + enum Environment { + Env_Normal, + Env_Underwater, + }; + class SoundManager { + Ogre::ResourceGroupManager& mResourceMgr; - // This is used for case insensitive and slash-type agnostic file - // finding. It takes DOS paths (any case, \\ slashes or / slashes) - // relative to the sound dir, and translates them into full paths - // of existing files in the filesystem, if they exist. - bool mFSStrict; - - MWWorld::Environment& mEnvironment; - - void streamMusicFull (const std::string& filename); - ///< Play a soundifle - /// \param absolute filename - - /* This is the sound manager. It loades, stores and deletes - sounds based on the sound factory it is given. - */ - OEManagerPtr mgr; - Mangle::Sound::SoundPtr music; - - /* This class calls update() on the sound manager each frame - using and Ogre::FrameListener - */ - Mangle::Sound::OgreOutputUpdater updater; - - /* This class tracks the movement of an Ogre::Camera and moves - a sound listener automatically to follow it. - */ - Mangle::Sound::OgreListenerMover cameraTracker; - - typedef std::map IDMap; - typedef std::map PtrMap; - PtrMap sounds; + MWWorld::Environment& mEnvironment; - // A list of all sound files used to lookup paths - Files::PathContainer mSoundFiles; + std::auto_ptr mOutput; - // A library of all Music file paths stored by the folder they are contained in - Files::FileLibrary mMusicLibrary; + float mMasterVolume; + float mSFXVolume; + float mMusicVolume; - // Points to the current playlist of music files stored in the music library - const Files::PathContainer* mCurrentPlaylist; + boost::shared_ptr mMusic; + std::string mCurrentPlaylist; - IDMap mLoopedSounds; + typedef std::pair PtrIDPair; + typedef std::map SoundMap; + SoundMap mActiveSounds; - bool mUsingSound; + std::string lookup(const std::string &soundId, + float &volume, float &min, float &max); + void streamMusicFull(const std::string& filename); + bool isPlaying(MWWorld::Ptr ptr, const std::string &id) const; + void updateSounds(float duration); + void updateRegionSound(float duration); - std::string lookup(const std::string &soundId, - float &volume, float &min, float &max); - void add(const std::string &file, - MWWorld::Ptr ptr, const std::string &id, - float volume, float pitch, float min, float max, - bool loop, bool untracked=false); - void clearAll(PtrMap::iterator& it); - void remove(MWWorld::Ptr ptr, const std::string &id = ""); - bool isPlaying(MWWorld::Ptr ptr, const std::string &id) const; - void removeCell(const MWWorld::Ptr::CellStore *cell); - void updatePositions(MWWorld::Ptr ptr); + SoundManager(const SoundManager &rhs); + SoundManager& operator=(const SoundManager &rhs); - public: + protected: + DecoderPtr getDecoder(); + friend class OpenAL_Output; - SoundManager(Ogre::Root*, Ogre::Camera*, - const Files::PathContainer& dataDir, bool useSound, bool fsstrict, - MWWorld::Environment& environment); - ~SoundManager(); + public: + SoundManager(bool useSound, MWWorld::Environment& environment); + ~SoundManager(); - void stopMusic(); - ///< Stops music if it's playing + void stopMusic(); + ///< Stops music if it's playing - void streamMusic(const std::string& filename); - ///< Play a soundifle - /// \param filename name of a sound file in "Music/" in the data directory. + void streamMusic(const std::string& filename); + ///< Play a soundifle + /// \param filename name of a sound file in "Music/" in the data directory. - void startRandomTitle(); - ///< Starts a random track from the current playlist + void startRandomTitle(); + ///< Starts a random track from the current playlist - bool isMusicPlaying(); - ///< Returns true if music is playing + bool isMusicPlaying(); + ///< Returns true if music is playing - bool setPlaylist(std::string playlist=""); - ///< Set the playlist to an existing folder - /// \param name of the folder that contains the playlist - /// if none is set then it is set to an empty playlist - /// \return Return true if the previous playlist was the same + void playPlaylist(const std::string &playlist); + ///< Start playing music from the selected folder + /// \param name of the folder that contains the playlist - void playPlaylist(std::string playlist=""); - ///< Start playing music from the selected folder - /// \param name of the folder that contains the playlist - /// if none is set then it plays from the current playlist + void say(MWWorld::Ptr reference, const std::string& filename); + ///< Make an actor say some text. + /// \param filename name of a sound file in "Sound/Vo/" in the data directory. - void say (MWWorld::Ptr reference, const std::string& filename); - ///< Make an actor say some text. - /// \param filename name of a sound file in "Sound/Vo/" in the data directory. + bool sayDone(MWWorld::Ptr reference) const; + ///< Is actor not speaking? - bool sayDone (MWWorld::Ptr reference) const; - ///< Is actor not speaking? + SoundPtr playSound(const std::string& soundId, float volume, float pitch, int mode=Play_Normal); + ///< Play a sound, independently of 3D-position - void playSound (const std::string& soundId, float volume, float pitch, bool loop=false); - ///< Play a sound, independently of 3D-position + SoundPtr playSound3D(MWWorld::Ptr reference, const std::string& soundId, + float volume, float pitch, int mode=Play_Normal); + ///< Play a sound from an object - void playSound3D (MWWorld::Ptr reference, const std::string& soundId, - float volume, float pitch, bool loop, bool untracked=false); - ///< Play a sound from an object + void stopSound3D(MWWorld::Ptr reference, const std::string& soundId); + ///< Stop the given object from playing the given sound, - void stopSound3D (MWWorld::Ptr reference, const std::string& soundId = ""); - ///< Stop the given object from playing the given sound, If no soundId is given, - /// all sounds for this reference will stop. + void stopSound3D(MWWorld::Ptr reference); + ///< Stop the given object from playing all sounds. - void stopSound (MWWorld::Ptr::CellStore *cell); - ///< Stop all sounds for the given cell. + void stopSound(const MWWorld::Ptr::CellStore *cell); + ///< Stop all sounds for the given cell. - void stopSound(const std::string& soundId); - ///< Stop a non-3d looping sound + void stopSound(const std::string& soundId); + ///< Stop a non-3d looping sound - bool getSoundPlaying (MWWorld::Ptr reference, const std::string& soundId) const; - ///< Is the given sound currently playing on the given object? + bool getSoundPlaying(MWWorld::Ptr reference, const std::string& soundId) const; + ///< Is the given sound currently playing on the given object? - void updateObject(MWWorld::Ptr reference); - ///< Update the position of all sounds connected to the given object. + void updateObject(MWWorld::Ptr reference); + ///< Update the position of all sounds connected to the given object. - void update (float duration); + void update(float duration); }; } diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 9d766909f..d49b98d0f 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -26,7 +26,9 @@ namespace MWWorld { } - void Class::insertObject(const Ptr& ptr, MWWorld::PhysicsSystem& physics, MWWorld::Environment& environment) const{ + + void Class::insertObject(const Ptr& ptr, MWWorld::PhysicsSystem& physics, MWWorld::Environment& environment) const + { } @@ -137,6 +139,11 @@ namespace MWWorld return -1; } + int Class::getValue (const Ptr& ptr) const + { + throw std::logic_error ("value not supported by this class"); + } + const Class& Class::get (const std::string& key) { std::map >::const_iterator iter = sClasses.find (key); diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 67320b3e0..e474e9b92 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -155,6 +155,10 @@ namespace MWWorld /// no such skill. /// (default implementation: return -1) + virtual int getValue (const Ptr& ptr) const; + ///< Return trade value of the object. Throws an exception, if the object can't be traded. + /// (default implementation: throws an exception) + static const Class& get (const std::string& key); ///< If there is no class for this \a key, an exception is thrown. diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index e64c9785f..650418201 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -4,8 +4,12 @@ #include #include +#include "../mwmechanics/npcstats.hpp" + #include "class.hpp" +#include /// \todo remove after rendering is implemented + void MWWorld::InventoryStore::copySlots (const InventoryStore& store) { // some const-trickery, required because of a flaw in the handling of MW-references and the @@ -24,10 +28,15 @@ void MWWorld::InventoryStore::copySlots (const InventoryStore& store) } } -MWWorld::InventoryStore::InventoryStore() +void MWWorld::InventoryStore::initSlots (TSlots& slots) { for (int i=0; i, bool> itemsSlots = + MWWorld::Class::get (*iter).getEquipmentSlots (*iter); + + for (std::vector::const_iterator iter2 (itemsSlots.first.begin()); + iter2!=itemsSlots.first.end(); ++iter2) + { + bool use = false; + + if (slots.at (*iter2)==end()) + use = true; // slot was empty before -> skill all further checks + else + { + Ptr old = *slots.at (*iter2); + + if (!use) + { + // check skill + int oldSkill = + MWWorld::Class::get (old).getEquipmentSkill (old, environment); + + if (testSkill!=-1 || oldSkill!=-1 || testSkill!=oldSkill) + { + if (stats.mSkill[oldSkill].getModified()>stats.mSkill[testSkill].getModified()) + continue; // rejected, because old item better matched the NPC's skills. + + if (stats.mSkill[oldSkill].getModified()= + MWWorld::Class::get (test).getValue (test)) + { + continue; + } + + use = true; + } + } + + /// \todo unstack, if reqquired (itemsSlots.second) + + slots[*iter2] = iter; + break; + } + } + + bool changed = false; + + for (std::size_t i=0; i mSlots; + typedef std::vector TSlots; + + mutable TSlots mSlots; void copySlots (const InventoryStore& store); + void initSlots (TSlots& slots); + public: InventoryStore(); @@ -52,6 +63,9 @@ namespace MWWorld ///< \note \a iteartor can be an end-iterator ContainerStoreIterator getSlot (int slot); + + void autoEquip (const MWMechanics::NpcStats& stats, const Environment& environment); + ///< Auto equip items according to stats and item value. }; } diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index bb2f9f8a9..83c3ef2ba 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -50,6 +50,28 @@ namespace MWWorld return mEngine->rayTest(from,to); } + + std::vector < std::pair > PhysicsSystem::getFacedObjects () + { + //get a ray pointing to the center of the viewport + Ray centerRay = mRender.getCamera()->getCameraToViewportRay( + mRender.getViewport()->getWidth()/2, + mRender.getViewport()->getHeight()/2); + btVector3 from(centerRay.getOrigin().x,-centerRay.getOrigin().z,centerRay.getOrigin().y); + btVector3 to(centerRay.getPoint(500).x,-centerRay.getPoint(500).z,centerRay.getPoint(500).y); + + return mEngine->rayTest2(from,to); + } + + btVector3 PhysicsSystem::getRayPoint(float extent) + { + //get a ray pointing to the center of the viewport + Ray centerRay = mRender.getCamera()->getCameraToViewportRay( + mRender.getViewport()->getWidth()/2, + mRender.getViewport()->getHeight()/2); + btVector3 result(centerRay.getPoint(500*extent).x,-centerRay.getPoint(500*extent).z,centerRay.getPoint(500*extent).y); + return result; + } bool PhysicsSystem::castRay(const Vector3& from, const Vector3& to) { diff --git a/apps/openmw/mwworld/physicssystem.hpp b/apps/openmw/mwworld/physicssystem.hpp index 78cbde083..7b2d77325 100644 --- a/apps/openmw/mwworld/physicssystem.hpp +++ b/apps/openmw/mwworld/physicssystem.hpp @@ -35,7 +35,11 @@ namespace MWWorld bool toggleCollisionMode(); std::pair getFacedHandle (MWWorld::World& world); - + + btVector3 getRayPoint(float extent); + + std::vector < std::pair > getFacedObjects (); + // cast ray, return true if it hit something bool castRay(const Ogre::Vector3& from, const Ogre::Vector3& to); diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 5bfb82138..d24780ec1 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -4,6 +4,7 @@ #include "../mwrender/player.hpp" #include "../mwmechanics/movement.hpp" +#include "../mwmechanics/npcstats.hpp" #include "world.hpp" #include "class.hpp" @@ -48,6 +49,12 @@ namespace MWWorld mClass = new_class; } + void Player::setDrawState(const DrawState& value) + { + MWWorld::Ptr ptr = getPlayer(); + MWWorld::Class::get(ptr).getNpcStats(ptr).mDrawState = value; + } + void Player::setAutoMove (bool enable) { MWWorld::Ptr ptr = getPlayer(); @@ -89,4 +96,10 @@ namespace MWWorld MWWorld::Class::get (ptr).setStance (ptr, MWWorld::Class::Run, !running); } + + DrawState Player::getDrawState() + { + MWWorld::Ptr ptr = getPlayer(); + return MWWorld::Class::get(ptr).getNpcStats(ptr).mDrawState; + } } diff --git a/apps/openmw/mwworld/player.hpp b/apps/openmw/mwworld/player.hpp index 01c71da43..8dcd9fcc6 100644 --- a/apps/openmw/mwworld/player.hpp +++ b/apps/openmw/mwworld/player.hpp @@ -8,6 +8,8 @@ #include "../mwworld/refdata.hpp" #include "../mwworld/ptr.hpp" +#include "../mwmechanics/drawstate.hpp" + namespace MWRender { class Player; @@ -18,7 +20,7 @@ namespace MWWorld class World; /// \brief NPC object representing the player and additional player data - class Player + class Player { ESMS::LiveCellRef mPlayer; MWWorld::Ptr::CellStore *mCellStore; @@ -31,7 +33,6 @@ namespace MWWorld ESM::Class *mClass; bool mAutoMove; int mForwardBackward; - public: Player(MWRender::Player *renderer, const ESM::NPC *player, MWWorld::World& world); @@ -76,6 +77,8 @@ namespace MWWorld void setClass (const ESM::Class& class_); + void setDrawState(const DrawState& state); + std::string getName() const { return mName; @@ -106,6 +109,8 @@ namespace MWWorld return mAutoMove; } + DrawState getDrawState(); + void setAutoMove (bool enable); void setLeftRight (int value); diff --git a/apps/openmw/mwworld/ptr.hpp b/apps/openmw/mwworld/ptr.hpp index c31c53122..d6e485f41 100644 --- a/apps/openmw/mwworld/ptr.hpp +++ b/apps/openmw/mwworld/ptr.hpp @@ -39,7 +39,7 @@ namespace MWWorld return mPtr.empty(); } - const std::type_info& getType() + const std::type_info& getType() const { assert (!mPtr.empty()); return mPtr.type(); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 22955bf32..2123b4799 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -54,9 +54,11 @@ void insertCellRefList(MWRender::RenderingManager& rendering, MWWorld::Environme namespace MWWorld { + void Scene::update (float duration){ mRendering.update (duration); } + void Scene::unloadCell (CellStoreCollection::iterator iter) { std::cout << "Unloading cell\n"; @@ -79,6 +81,7 @@ namespace MWWorld mPhysics->removeObject (node->getName()); } } + mRendering.removeCell(*iter); //mPhysics->removeObject("Unnamed_43"); @@ -87,6 +90,7 @@ namespace MWWorld mEnvironment.mSoundManager->stopSound (*iter); mActiveCells.erase(*iter); + } @@ -101,7 +105,7 @@ namespace MWWorld mActiveCells.insert(cell); if(result.second){ insertCell(*cell, mEnvironment); - mRendering.cellAdded (cell); + mRendering.cellAdded(cell); mRendering.configureAmbient(*cell); mRendering.requestMap(cell); mRendering.configureAmbient(*cell); @@ -192,6 +196,7 @@ namespace MWWorld mCurrentCell = *iter; + // adjust player playerCellChange (mWorld->getExterior(X, Y), position, adjustPlayerPos); @@ -238,6 +243,7 @@ namespace MWWorld Ptr::CellStore *cell = mWorld->getInterior(cellName); loadCell (cell); + // adjust player mCurrentCell = cell; diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 9a918c2fb..fb0480171 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -34,7 +34,7 @@ const float WeatherGlobals::mThunderFrequency = .4; const float WeatherGlobals::mThunderThreshold = 0.6; const float WeatherGlobals::mThunderSoundDelay = 0.25; -WeatherManager::WeatherManager(MWRender::RenderingManager* rendering, Environment* env) : +WeatherManager::WeatherManager(MWRender::RenderingManager* rendering, MWWorld::Environment* env) : mHour(14), mCurrentWeather("clear"), mFirstUpdate(true), mWeatherUpdateTime(0), mThunderFlash(0), mThunderChance(0), mThunderChanceNeeded(50), mThunderSoundDelay(0) { @@ -334,14 +334,16 @@ WeatherManager::WeatherManager(MWRender::RenderingManager* rendering, Environmen void WeatherManager::setWeather(const String& weather, bool instant) { - if (weather == mCurrentWeather && mNextWeather == "") + if (weather == mCurrentWeather && mNextWeather == "") + { + mFirstUpdate = false; return; + } if (instant || mFirstUpdate) { mNextWeather = ""; mCurrentWeather = weather; - mFirstUpdate = false; } else { @@ -355,6 +357,7 @@ void WeatherManager::setWeather(const String& weather, bool instant) mNextWeather = weather; mRemainingTransitionTime = mWeatherSettings[mCurrentWeather].mTransitionDelta*24.f*3600; } + mFirstUpdate = false; } WeatherResult WeatherManager::getResult(const String& weather) @@ -472,6 +475,7 @@ WeatherResult WeatherManager::transition(float factor) result.mCloudSpeed = current.mCloudSpeed; result.mCloudOpacity = lerp(current.mCloudOpacity, other.mCloudOpacity); result.mGlareView = lerp(current.mGlareView, other.mGlareView); + result.mNightFade = lerp(current.mNightFade, other.mNightFade); result.mNight = current.mNight; @@ -520,23 +524,23 @@ void WeatherManager::update(float duration) srand(time(NULL)); float random = ((rand()%100)/100.f) * total; - //if (random > snow+blight+ash+thunder+rain+overcast+foggy+cloudy+clear) + //if (random >= snow+blight+ash+thunder+rain+overcast+foggy+cloudy+clear) // weather = "blizzard"; - //else if (random > blight+ash+thunder+rain+overcast+foggy+cloudy+clear) + //else if (random >= blight+ash+thunder+rain+overcast+foggy+cloudy+clear) // weather = "snow"; - /*else*/ if (random > ash+thunder+rain+overcast+foggy+cloudy+clear) + /*else*/ if (random >= ash+thunder+rain+overcast+foggy+cloudy+clear) weather = "blight"; - else if (random > thunder+rain+overcast+foggy+cloudy+clear) + else if (random >= thunder+rain+overcast+foggy+cloudy+clear) weather = "ashstorm"; - else if (random > rain+overcast+foggy+cloudy+clear) + else if (random >= rain+overcast+foggy+cloudy+clear) weather = "thunderstorm"; - else if (random > overcast+foggy+cloudy+clear) + else if (random >= overcast+foggy+cloudy+clear) weather = "rain"; - else if (random > foggy+cloudy+clear) + else if (random >= foggy+cloudy+clear) weather = "overcast"; - else if (random > cloudy+clear) + else if (random >= cloudy+clear) weather = "foggy"; - else if (random > clear) + else if (random >= clear) weather = "cloudy"; else weather = "clear"; @@ -584,8 +588,8 @@ void WeatherManager::update(float duration) int facing = (mHour > 13.f) ? 1 : -1; Vector3 final( - (1-height)*facing, - (1-height)*facing, + -(1-height)*facing, + -(1-height)*facing, height); mRendering->setSunDirection(final); diff --git a/apps/openmw/mwworld/world.cpp b/apps/openmw/mwworld/world.cpp index a636ce288..1c64039d4 100644 --- a/apps/openmw/mwworld/world.cpp +++ b/apps/openmw/mwworld/world.cpp @@ -146,18 +146,39 @@ namespace MWWorld mRendering->skySetDate (mGlobalVariables->getInt ("day"), mGlobalVariables->getInt ("month")); - mRendering->getSkyManager()->enable(); + mRendering->skyEnable(); } else - mRendering->getSkyManager()->disable(); + mRendering->skyDisable(); + } + + void World::setFallbackValues(std::map fallbackMap) + { + mFallback = fallbackMap; + } + + std::string World::getFallback(std::string key) + { + return getFallback(key, ""); + } + + std::string World::getFallback(std::string key, std::string def) + { + std::map::iterator it; + if((it = mFallback.find(key)) == mFallback.end()) + { + return def; + } + return it->second; } World::World (OEngine::Render::OgreRenderer& renderer, const Files::Collections& fileCollections, const std::string& master, const boost::filesystem::path& resDir, - bool newGame, Environment& environment, const std::string& encoding) + bool newGame, Environment& environment, const std::string& encoding, std::map fallbackMap) : mPlayer (0), mLocalScripts (mStore), mGlobalVariables (0), - mSky (true), mEnvironment (environment), mNextDynamicRecord (0), mCells (mStore, mEsm, *this) + mSky (true), mEnvironment (environment), mNextDynamicRecord (0), mCells (mStore, mEsm, *this), + mNumFacing(0) { mPhysics = new PhysicsSystem(renderer); mPhysEngine = mPhysics->getEngine(); @@ -190,6 +211,8 @@ namespace MWWorld mWorldScene = new Scene(environment, this, *mRendering, mPhysics); + setFallbackValues(fallbackMap); + } @@ -498,13 +521,21 @@ namespace MWWorld std::string World::getFacedHandle() { - std::pair result = mPhysics->getFacedHandle (*this); + if (!mRendering->occlusionQuerySupported()) + { + std::pair result = mPhysics->getFacedHandle (*this); - if (result.first.empty() || - result.second>getStore().gameSettings.find ("iMaxActivateDist")->i) - return ""; + if (result.first.empty() || + result.second>getStore().gameSettings.find ("iMaxActivateDist")->i) + return ""; - return result.first; + return result.first; + } + else + { + // updated every few frames in update() + return mFacedHandle; + } } void World::deleteObject (Ptr ptr) @@ -531,9 +562,10 @@ namespace MWWorld ptr.getRefData().getPosition().pos[0] = x; ptr.getRefData().getPosition().pos[1] = y; ptr.getRefData().getPosition().pos[2] = z; - if (ptr==mPlayer->getPlayer()) { + //std::cout << "X:" << ptr.getRefData().getPosition().pos[0] << " Z: " << ptr.getRefData().getPosition().pos[1] << "\n"; + Ptr::CellStore *currentCell = mWorldScene->getCurrentCell(); if (currentCell) { @@ -705,13 +737,112 @@ namespace MWWorld mWeatherManager->update (duration); - // cast a ray from player to sun to detect if the sun is visible - // this is temporary until we find a better place to put this code - // currently its here because we need to access the physics system - float* p = mPlayer->getPlayer().getRefData().getPosition().pos; - Vector3 sun = mRendering->getSkyManager()->getRealSunPos(); - sun = Vector3(sun.x, -sun.z, sun.y); - mRendering->getSkyManager()->setGlare(!mPhysics->castRay(Ogre::Vector3(p[0], p[1], p[2]), sun)); + if (!mRendering->occlusionQuerySupported()) + { + // cast a ray from player to sun to detect if the sun is visible + // this is temporary until we find a better place to put this code + // currently its here because we need to access the physics system + float* p = mPlayer->getPlayer().getRefData().getPosition().pos; + Vector3 sun = mRendering->getSkyManager()->getRealSunPos(); + sun = Vector3(sun.x, -sun.z, sun.y); + mRendering->getSkyManager()->setGlare(!mPhysics->castRay(Ogre::Vector3(p[0], p[1], p[2]), sun)); + } + + // update faced handle (object the player is looking at) + // this uses a mixture of raycasts and occlusion queries. + else // if (mRendering->occlusionQuerySupported()) + { + MWRender::OcclusionQuery* query = mRendering->getOcclusionQuery(); + if (!query->occlusionTestPending()) + { + // get result of last query + if (mNumFacing == 0) mFacedHandle = ""; + else if (mNumFacing == 1) + { + bool result = query->getTestResult(); + mFacedHandle = result ? mFaced1Name : ""; + } + else if (mNumFacing == 2) + { + bool result = query->getTestResult(); + mFacedHandle = result ? mFaced2Name : mFaced1Name; + } + + // send new query + // figure out which object we want to test against + std::vector < std::pair < float, std::string > > results = mPhysics->getFacedObjects(); + + // ignore the player and other things we're not interested in + std::vector < std::pair < float, std::string > >::iterator it = results.begin(); + while (it != results.end()) + { + if ( getPtrViaHandle((*it).second) == mPlayer->getPlayer() ) + { + it = results.erase(it); + } + else + ++it; + } + + if (results.size() == 0) + { + mNumFacing = 0; + } + else if (results.size() == 1) + { + mFaced1 = getPtrViaHandle(results.front().second); + mFaced1Name = results.front().second; + mNumFacing = 1; + + btVector3 p = mPhysics->getRayPoint(results.front().first); + Ogre::Vector3 pos(p.x(), p.z(), -p.y()); + Ogre::SceneNode* node = mFaced1.getRefData().getBaseNode(); + + //std::cout << "Num facing 1 : " << mFaced1Name << std::endl; + //std::cout << "Type 1 " << mFaced1.getTypeName() << std::endl; + + query->occlusionTest(pos, node); + } + else + { + mFaced1Name = results.front().second; + mFaced2Name = results[1].second; + mFaced1 = getPtrViaHandle(results.front().second); + mFaced2 = getPtrViaHandle(results[1].second); + mNumFacing = 2; + + btVector3 p = mPhysics->getRayPoint(results[1].first); + Ogre::Vector3 pos(p.x(), p.z(), -p.y()); + Ogre::SceneNode* node1 = mFaced1.getRefData().getBaseNode(); + Ogre::SceneNode* node2 = mFaced2.getRefData().getBaseNode(); + + // no need to test if the first node is not occluder + if (!query->isPotentialOccluder(node1) && (mFaced1.getTypeName().find("Static") == std::string::npos)) + { + mFacedHandle = mFaced1Name; + //std::cout << "node1 Not an occluder" << std::endl; + return; + } + + // no need to test if the second object is static (thus cannot be activated) + if (mFaced2.getTypeName().find("Static") != std::string::npos) + { + mFacedHandle = mFaced1Name; + return; + } + + // work around door problems + if (mFaced1.getTypeName().find("Static") != std::string::npos + && mFaced2.getTypeName().find("Door") != std::string::npos) + { + mFacedHandle = mFaced2Name; + return; + } + + query->occlusionTest(pos, node2); + } + } + } } bool World::isCellExterior() const @@ -754,4 +885,27 @@ namespace MWWorld { return mRendering->getFader(); } + + Ogre::Vector2 World::getNorthVector(Ptr::CellStore* cell) + { + ESMS::CellRefList statics = cell->statics; + ESMS::LiveCellRef* ref = statics.find("northmarker"); + if (!ref) + return Vector2(0, 1); + Ogre::SceneNode* node = ref->mData.getBaseNode(); + Vector3 dir = node->_getDerivedOrientation().yAxis(); + Vector2 d = Vector2(dir.x, dir.z); + return d; + } + + void World::setWaterHeight(const float height) + { + mRendering->setWaterHeight(height); + } + + void World::toggleWater() + { + mRendering->toggleWater(); + } + } diff --git a/apps/openmw/mwworld/world.hpp b/apps/openmw/mwworld/world.hpp index 71cca3545..8dd370ad7 100644 --- a/apps/openmw/mwworld/world.hpp +++ b/apps/openmw/mwworld/world.hpp @@ -63,13 +63,14 @@ namespace MWWorld enum RenderMode { Render_CollisionDebug, - Render_Wireframe + Render_Wireframe, + Render_Pathgrid }; private: MWRender::RenderingManager* mRendering; - + MWWorld::WeatherManager* mWeatherManager; MWWorld::Scene *mWorldScene; @@ -93,6 +94,13 @@ namespace MWWorld Ptr getPtrViaHandle (const std::string& handle, Ptr::CellStore& cellStore); + std::string mFacedHandle; + Ptr mFaced1; + Ptr mFaced2; + std::string mFaced1Name; + std::string mFaced2Name; + int mNumFacing; + std::map mFallback; int getDaysPerMonth (int month) const; @@ -103,18 +111,27 @@ namespace MWWorld World (OEngine::Render::OgreRenderer& renderer, const Files::Collections& fileCollections, const std::string& master, const boost::filesystem::path& resDir, bool newGame, - Environment& environment, const std::string& encoding); + Environment& environment, const std::string& encoding, std::map fallbackMap); ~World(); - + OEngine::Render::Fader* getFader(); Ptr::CellStore *getExterior (int x, int y); Ptr::CellStore *getInterior (const std::string& name); - + + void setWaterHeight(const float height); + void toggleWater(); + void adjustSky(); + void setFallbackValues(std::map fallbackMap); + + std::string getFallback(std::string key); + + std::string getFallback(std::string key, std::string def); + MWWorld::Player& getPlayer(); const ESMS::ESMStore& getStore() const; @@ -125,10 +142,13 @@ namespace MWWorld bool hasCellChanged() const; ///< Has the player moved to a different cell, since the last frame? - + bool isCellExterior() const; bool isCellQuasiExterior() const; + Ogre::Vector2 getNorthVector(Ptr::CellStore* cell); + ///< get north vector (OGRE coordinates) for given interior cell + Globals::Data& getGlobalVariable (const std::string& name); Globals::Data getGlobalVariable (const std::string& name) const; @@ -163,9 +183,9 @@ namespace MWWorld bool toggleSky(); ///< \return Resulting mode - + void changeWeather(const std::string& region, const unsigned int id); - + int getCurrentWeather() const; int getMasserPhase() const; diff --git a/cmake/FindBullet.cmake b/cmake/FindBullet.cmake index 7bdf75a0a..552a0651a 100644 --- a/cmake/FindBullet.cmake +++ b/cmake/FindBullet.cmake @@ -51,13 +51,13 @@ find_path(BULLET_INCLUDE_DIR NAMES btBulletCollisionCommon.h # Find the libraries _FIND_BULLET_LIBRARY(BULLET_DYNAMICS_LIBRARY BulletDynamics) -_FIND_BULLET_LIBRARY(BULLET_DYNAMICS_LIBRARY_DEBUG BulletDynamics_d) +_FIND_BULLET_LIBRARY(BULLET_DYNAMICS_LIBRARY_DEBUG BulletDynamics_Debug BulletDynamics_d) _FIND_BULLET_LIBRARY(BULLET_COLLISION_LIBRARY BulletCollision) -_FIND_BULLET_LIBRARY(BULLET_COLLISION_LIBRARY_DEBUG BulletCollision_d) -_FIND_BULLET_LIBRARY(BULLET_MATH_LIBRARY LinearMath BulletMath) -_FIND_BULLET_LIBRARY(BULLET_MATH_LIBRARY_DEBUG LinearMath_d BulletMath_d) +_FIND_BULLET_LIBRARY(BULLET_COLLISION_LIBRARY_DEBUG BulletCollision_Debug BulletCollision_d) +_FIND_BULLET_LIBRARY(BULLET_MATH_LIBRARY BulletMath LinearMath) +_FIND_BULLET_LIBRARY(BULLET_MATH_LIBRARY_DEBUG BulletMath_Debug BulletMath_d LinearMath_debug LinearMath_d) _FIND_BULLET_LIBRARY(BULLET_SOFTBODY_LIBRARY BulletSoftBody) -_FIND_BULLET_LIBRARY(BULLET_SOFTBODY_LIBRARY_DEBUG BulletSoftBody_d) +_FIND_BULLET_LIBRARY(BULLET_SOFTBODY_LIBRARY_DEBUG BulletSoftBody_Debug BulletSoftBody_d) # handle the QUIETLY and REQUIRED arguments and set BULLET_FOUND to TRUE if diff --git a/cmake/FindFFMPEG.cmake b/cmake/FindFFMPEG.cmake index ff6d0c598..2e755d047 100644 --- a/cmake/FindFFMPEG.cmake +++ b/cmake/FindFFMPEG.cmake @@ -9,9 +9,23 @@ 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 @@ -25,6 +39,7 @@ FIND_PATH( FFMPEG_avcodec_INCLUDE_DIR avcodec.h FIND_PATH( FFMPEG_avformat_INCLUDE_DIR avformat.h HINTS PATHS + ${FFMPEG_general_INCLUDE_DIR}/libavformat /usr/include /usr/local/include /usr/include/ffmpeg @@ -35,7 +50,7 @@ FIND_PATH( FFMPEG_avformat_INCLUDE_DIR avformat.h /usr/local/include/libavformat ) -set(FFMPEG_INCLUDE_DIR ${FFMPEG_avcodec_INCLUDE_DIR} ${FFMPEG_avformat_INCLUDE_DIR}) +set(FFMPEG_INCLUDE_DIR ${FFMPEG_general_INCLUDE_DIR} ${FFMPEG_avcodec_INCLUDE_DIR} ${FFMPEG_avformat_INCLUDE_DIR}) IF( FFMPEG_INCLUDE_DIR ) diff --git a/cmake/FindMyGUI.cmake b/cmake/FindMyGUI.cmake index 339f494dd..6731d584c 100644 --- a/cmake/FindMyGUI.cmake +++ b/cmake/FindMyGUI.cmake @@ -19,57 +19,57 @@ include(FindPkgMacros) IF (WIN32) #Windows MESSAGE(STATUS "Looking for MyGUI") - SET(MYGUISDK $ENV{MYGUI_HOME}) +SET(MYGUISDK $ENV{MYGUI_HOME}) IF (MYGUISDK) - findpkg_begin ( "MYGUI" ) +findpkg_begin ( "MYGUI" ) MESSAGE(STATUS "Using MyGUI in OGRE SDK") - STRING(REGEX REPLACE "[\\]" "/" MYGUISDK "${MYGUISDK}" ) - - find_path ( MYGUI_INCLUDE_DIRS - MyGUI.h - "${MYGUISDK}/MyGUIEngine/include" - NO_DEFAULT_PATH ) - - find_path ( MYGUI_PLATFORM_INCLUDE_DIRS - MyGUI_OgrePlatform.h - "${MYGUISDK}/Platforms/Ogre/OgrePlatform/include" - NO_DEFAULT_PATH ) - - SET ( MYGUI_LIB_DIR ${MYGUISDK}/*/lib ) - - find_library ( MYGUI_LIBRARIES_REL NAMES - MyGUIEngine.lib - MyGUI.OgrePlatform.lib - HINTS - ${MYGUI_LIB_DIR} - PATH_SUFFIXES "" release relwithdebinfo minsizerel ) - - find_library ( MYGUI_LIBRARIES_DBG NAMES - MyGUIEngine_d.lib - MyGUI.OgrePlatform_d.lib - HINTS - ${MYGUI_LIB_DIR} - PATH_SUFFIXES "" debug ) - - find_library ( MYGUI_PLATFORM_LIBRARIES_REL NAMES - MyGUI.OgrePlatform.lib - HINTS - ${MYGUI_LIB_DIR} - PATH_SUFFIXES "" release relwithdebinfo minsizerel ) - - find_library ( MYGUI_PLATFORM_LIBRARIES_DBG NAMES - MyGUI.OgrePlatform_d.lib - HINTS - ${MYGUI_LIB_DIR} - PATH_SUFFIXES "" debug ) - - make_library_set ( MYGUI_LIBRARIES ) - make_library_set ( MYGUI_PLATFORM_LIBRARIES ) - - MESSAGE ("${MYGUI_LIBRARIES}") - MESSAGE ("${MYGUI_PLATFORM_LIBRARIES}") - - findpkg_finish ( "MYGUI" ) +STRING(REGEX REPLACE "[\\]" "/" MYGUISDK "${MYGUISDK}" ) + +find_path ( MYGUI_INCLUDE_DIRS +MyGUI.h +"${MYGUISDK}/MyGUIEngine/include" +NO_DEFAULT_PATH ) + +find_path ( MYGUI_PLATFORM_INCLUDE_DIRS +MyGUI_OgrePlatform.h +"${MYGUISDK}/Platforms/Ogre/OgrePlatform/include" +NO_DEFAULT_PATH ) + +SET ( MYGUI_LIB_DIR ${MYGUISDK}/*/lib ) + +find_library ( MYGUI_LIBRARIES_REL NAMES +MyGUIEngine.lib +MyGUI.OgrePlatform.lib +HINTS +${MYGUI_LIB_DIR} +PATH_SUFFIXES "" release relwithdebinfo minsizerel ) + +find_library ( MYGUI_LIBRARIES_DBG NAMES +MyGUIEngine_d.lib +MyGUI.OgrePlatform_d.lib +HINTS +${MYGUI_LIB_DIR} +PATH_SUFFIXES "" debug ) + +find_library ( MYGUI_PLATFORM_LIBRARIES_REL NAMES +MyGUI.OgrePlatform.lib +HINTS +${MYGUI_LIB_DIR} +PATH_SUFFIXES "" release relwithdebinfo minsizerel ) + +find_library ( MYGUI_PLATFORM_LIBRARIES_DBG NAMES +MyGUI.OgrePlatform_d.lib +HINTS +${MYGUI_LIB_DIR} +PATH_SUFFIXES "" debug ) + +make_library_set ( MYGUI_LIBRARIES ) +make_library_set ( MYGUI_PLATFORM_LIBRARIES ) + +MESSAGE ("${MYGUI_LIBRARIES}") +MESSAGE ("${MYGUI_PLATFORM_LIBRARIES}") + +findpkg_finish ( "MYGUI" ) ENDIF (MYGUISDK) IF (OGRESOURCE) @@ -82,18 +82,37 @@ IF (WIN32) #Windows ELSE (WIN32) #Unix CMAKE_MINIMUM_REQUIRED(VERSION 2.4.7 FATAL_ERROR) FIND_PACKAGE(PkgConfig) - PKG_SEARCH_MODULE(MYGUI MYGUI MyGUI) - IF (MYGUI_INCLUDE_DIRS) - SET(MYGUI_INCLUDE_DIRS ${MYGUI_INCLUDE_DIRS}) - SET(MYGUI_LIB_DIR ${MYGUI_LIBDIR}) - SET(MYGUI_LIBRARIES ${MYGUI_LIBRARIES} CACHE STRING "") - ELSE (MYGUI_INCLUDE_DIRS) - FIND_PATH(MYGUI_INCLUDE_DIRS MyGUI.h PATHS /usr/local/include /usr/include PATH_SUFFIXES MyGUI MYGUI) - FIND_LIBRARY(MYGUI_LIBRARIES mygui PATHS /usr/lib /usr/local/lib) - SET(MYGUI_LIB_DIR ${MYGUI_LIBRARIES}) - STRING(REGEX REPLACE "(.*)/.*" "\\1" MYGUI_LIB_DIR "${MYGUI_LIB_DIR}") - STRING(REGEX REPLACE ".*/" "" MYGUI_LIBRARIES "${MYGUI_LIBRARIES}") - ENDIF (MYGUI_INCLUDE_DIRS) + IF(MYGUI_STATIC) + PKG_SEARCH_MODULE(MYGUI MYGUIStatic MyGUIStatic) + IF (MYGUI_INCLUDE_DIRS) + SET(MYGUI_INCLUDE_DIRS ${MYGUI_INCLUDE_DIRS}) + SET(MYGUI_LIB_DIR ${MYGUI_LIBDIR}) + SET(MYGUI_LIBRARIES ${MYGUI_LIBRARIES} CACHE STRING "") + SET(MYGUI_PLATFORM_LIBRARIES "MyGUI.OgrePlatform") + ELSE (MYGUI_INCLUDE_DIRS) + FIND_PATH(MYGUI_INCLUDE_DIRS MyGUI.h PATHS /usr/local/include /usr/include PATH_SUFFIXES MyGUI MYGUI) + FIND_LIBRARY(MYGUI_LIBRARIES myguistatic PATHS /usr/lib /usr/local/lib) + SET(MYGUI_PLATFORM_LIBRARIES "MyGUI.OgrePlatform") + SET(MYGUI_LIB_DIR ${MYGUI_LIBRARIES}) + STRING(REGEX REPLACE "(.*)/.*" "\\1" MYGUI_LIB_DIR "${MYGUI_LIB_DIR}") + STRING(REGEX REPLACE ".*/" "" MYGUI_LIBRARIES "${MYGUI_LIBRARIES}") + ENDIF (MYGUI_INCLUDE_DIRS) + ELSE(MYGUI_STATIC) + PKG_SEARCH_MODULE(MYGUI MYGUI MyGUI) + IF (MYGUI_INCLUDE_DIRS) + SET(MYGUI_INCLUDE_DIRS ${MYGUI_INCLUDE_DIRS}) + SET(MYGUI_LIB_DIR ${MYGUI_LIBDIR}) + SET(MYGUI_LIBRARIES ${MYGUI_LIBRARIES} CACHE STRING "") + SET(MYGUI_PLATFORM_LIBRARIES "MyGUI.OgrePlatform") + ELSE (MYGUI_INCLUDE_DIRS) + FIND_PATH(MYGUI_INCLUDE_DIRS MyGUI.h PATHS /usr/local/include /usr/include PATH_SUFFIXES MyGUI MYGUI) + FIND_LIBRARY(MYGUI_LIBRARIES mygui PATHS /usr/lib /usr/local/lib) + SET(MYGUI_PLATFORM_LIBRARIES "MyGUI.OgrePlatform") + SET(MYGUI_LIB_DIR ${MYGUI_LIBRARIES}) + STRING(REGEX REPLACE "(.*)/.*" "\\1" MYGUI_LIB_DIR "${MYGUI_LIB_DIR}") + STRING(REGEX REPLACE ".*/" "" MYGUI_LIBRARIES "${MYGUI_LIBRARIES}") + ENDIF (MYGUI_INCLUDE_DIRS) + ENDIF(MYGUI_STATIC) ENDIF (WIN32) #Do some preparation @@ -103,7 +122,7 @@ SEPARATE_ARGUMENTS(MYGUI_PLATFORM_LIBRARIES) SET(MYGUI_INCLUDE_DIRS ${MYGUI_INCLUDE_DIRS} CACHE PATH "") SET(MYGUI_LIBRARIES ${MYGUI_LIBRARIES} CACHE STRING "") -SET(MYGUI_LIBRARIES ${MYGUI_PLATFORM_LIBRARIES} CACHE STRING "") +SET(MYGUI_PLATFORM_LIBRARIES ${MYGUI_PLATFORM_LIBRARIES} CACHE STRING "") SET(MYGUI_LIB_DIR ${MYGUI_LIB_DIR} CACHE PATH "") IF (MYGUI_INCLUDE_DIRS AND MYGUI_LIBRARIES) @@ -111,7 +130,7 @@ IF (MYGUI_INCLUDE_DIRS AND MYGUI_LIBRARIES) ENDIF (MYGUI_INCLUDE_DIRS AND MYGUI_LIBRARIES) IF (MYGUI_FOUND) - MARK_AS_ADVANCED(MYGUI_LIB_DIR) +MARK_AS_ADVANCED(MYGUI_LIB_DIR) IF (NOT MYGUI_FIND_QUIETLY) MESSAGE(STATUS " libraries : ${MYGUI_LIBRARIES} from ${MYGUI_LIB_DIR}") MESSAGE(STATUS " includes : ${MYGUI_INCLUDE_DIRS}") @@ -122,4 +141,4 @@ ELSE (MYGUI_FOUND) ENDIF (MYGUI_FIND_REQUIRED) ENDIF (MYGUI_FOUND) -CMAKE_POLICY(POP) \ No newline at end of file +CMAKE_POLICY(POP) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index c95efb37d..8a1875d0f 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -2,6 +2,14 @@ project (Components) # source files +add_component_dir (settings + settings + ) + +add_component_dir (nifoverrides + nifoverrides + ) + add_component_dir (bsa bsa_archive bsa_file ) diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index 72d15944d..0e3563b26 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -41,7 +41,21 @@ struct ciLessBoost : std::binary_function { bool operator() (const std::string & s1, const std::string & s2) const { //case insensitive version of is_less - return lexicographical_compare(s1, s2, boost::algorithm::is_iless()); + return boost::ilexicographical_compare(s1, s2); + } +}; + +struct pathComparer +{ +private: + std::string find; + +public: + pathComparer(const std::string& toFind) : find(toFind) { } + + bool operator() (const std::string& other) + { + return boost::iequals(find, other); } }; @@ -55,16 +69,62 @@ class DirArchive: public Ogre::FileSystemArchive std::map, ciLessBoost> m; unsigned int cutoff; - bool comparePortion(std::string file1, std::string file2, int start, int size) const + bool findFile(const String& filename, std::string& copy) const { - for(int i = start; i < start+size; i++) { - char one = file1.at(i); - char two = file2.at(i); - if(tolower(one) != tolower(two) ) + String passed = filename; + if(filename.at(filename.length() - 1) == '*' || filename.at(filename.length() - 1) == '?' || filename.at(filename.length() - 1) == '<' + || filename.at(filename.length() - 1) == '"' || filename.at(filename.length() - 1) == '>' || filename.at(filename.length() - 1) == ':' + || filename.at(filename.length() - 1) == '|') + { + passed = filename.substr(0, filename.length() - 2); + } + if(filename.at(filename.length() - 2) == '>') + passed = filename.substr(0, filename.length() - 6); + copy = passed; + } + + std::replace(copy.begin(), copy.end(), '\\', '/'); + + if(copy.at(0) == '/') + copy.erase(0, 1); + + if(fsstrict == true) + return true; + + std::string folder; + int delimiter = 0; + size_t lastSlash = copy.rfind('/'); + if (lastSlash != std::string::npos) + { + folder = copy.substr(0, lastSlash); + delimiter = lastSlash+1; + } + + std::vector current; + { + std::map,ciLessBoost>::const_iterator found = m.find(folder); + + if (found == m.end()) + { return false; + } + else + current = found->second; + } + + std::vector::iterator find = std::lower_bound(current.begin(), current.end(), copy, ciLessBoost()); + if (find != current.end() && !ciLessBoost()(copy, current.front())) + { + if (!boost::iequals(copy, *find)) + if ((find = std::find_if(current.begin(), current.end(), pathComparer(copy))) == current.end()) //\todo Check if this line is actually needed + return false; + + copy = *find; + return true; } - return true; + + return false; } public: @@ -83,16 +143,14 @@ class DirArchive: public Ogre::FileSystemArchive //need to cut off first boost::filesystem::directory_iterator dir_iter(d), dir_end; std::vector filesind; - boost::filesystem::path f; for(;dir_iter != dir_end; dir_iter++) { if(boost::filesystem::is_directory(*dir_iter)) populateMap(*dir_iter); else { - - f = *dir_iter; - std::string s = f.string(); + std::string s = dir_iter->path().string(); + std::replace(s.begin(), s.end(), '\\', '/'); std::string small; if(cutoff < s.size()) @@ -103,14 +161,17 @@ class DirArchive: public Ogre::FileSystemArchive filesind.push_back(small); } } + std::sort(filesind.begin(), filesind.end(), ciLessBoost()); + std::string small; std::string original = d.string(); + std::replace(original.begin(), original.end(), '\\', '/'); if(cutoff < original.size()) small = original.substr(cutoff, original.size() - cutoff); else small = original.substr(cutoff - 1, original.size() - cutoff); - m[small] = filesind; + m[small] = filesind; } bool isCaseSensitive() const { return fsstrict; } @@ -120,97 +181,21 @@ class DirArchive: public Ogre::FileSystemArchive void unload() {} bool exists(const String& filename) { - std::string copy = filename; - - - - for (unsigned int i = 0; i < filename.size(); i++) - { - if(copy.at(i) == '\\' ){ - copy.replace(i, 1, "/"); - } - } + std::string copy; - - if(copy.at(0) == '\\' || copy.at(0) == '/') - { - copy.erase(0, 1); - } - if(fsstrict == true) - { - //std::cout << "fsstrict " << copy << "\n"; + if (findFile(filename, copy)) return FileSystemArchive::exists(copy); - } - - - int last = copy.size() - 1; - int i = last; - - for (;last >= 0; i--) - { - if(copy.at(i) == '/' || copy.at(i) == '\\') - break; - } - - std::string folder = copy.substr(0, i); //folder with no slash - - std::vector& current = m[folder]; - - for(std::vector::iterator iter = current.begin(); iter != current.end(); iter++) - { - if(comparePortion(*iter, copy, i + 1, copy.size() - i -1) == true){ - return FileSystemArchive::exists(*iter); - } - } - return false; } DataStreamPtr open(const String& filename, bool readonly = true) const { - std::map, ciLessBoost> mlocal = m; - std::string copy = filename; - - - - for (unsigned int i = 0; i < filename.size(); i++) - { - if(copy.at(i) == '\\' ){ - copy.replace(i, 1, "/"); - } - } - - - if(copy.at(0) == '\\' || copy.at(0) == '/') - { - copy.erase(0, 1); - } + std::string copy; - if(fsstrict == true) - { + if (findFile(filename, copy)) return FileSystemArchive::open(copy, readonly); - } - - - int last = copy.size() - 1; - int i = last; - - for (;last >= 0; i--) - { - if(copy.at(i) == '/' || copy.at(i) == '\\') - break; - } - - std::string folder = copy.substr(0, i); //folder with no slash - std::vector current = mlocal[folder]; - for(std::vector::iterator iter = current.begin(); iter != current.end(); iter++) - { - if(comparePortion(*iter, copy, i + 1, copy.size() - i -1) == true){ - return FileSystemArchive::open(*iter, readonly); - } - } DataStreamPtr p; return p; } @@ -256,8 +241,12 @@ public: return DataStreamPtr(new Mangle2OgreStream(strm)); } +bool exists(const String& filename) { + return cexists(filename); +} + // Check if the file exists. - bool exists(const String& filename) { + bool cexists(const String& filename) const { String passed = filename; if(filename.at(filename.length() - 1) == '*' || filename.at(filename.length() - 1) == '?' || filename.at(filename.length() - 1) == '<' || filename.at(filename.length() - 1) == '"' || filename.at(filename.length() - 1) == '>' || filename.at(filename.length() - 1) == ':' @@ -268,7 +257,7 @@ public: if(filename.at(filename.length() - 2) == '>') passed = filename.substr(0, filename.length() - 6); -return arc.exists(passed.c_str()); +return arc.exists(passed.c_str()); } time_t getModifiedTime(const String&) { return 0; } @@ -308,6 +297,29 @@ return arc.exists(passed.c_str()); located in BSAs. So instead we channel it through exists() and set up a single-element result list if the file is found. */ + FileInfoListPtr findFileInfo(const String& pattern, bool recursive = true, + bool dirs = false) const + { + FileInfoListPtr ptr = FileInfoListPtr(new FileInfoList()); + + // Check if the file exists (only works for single files - wild + // cards and recursive search isn't implemented.) + if(cexists(pattern)) + { + FileInfo fi; + fi.archive = this; + fi.filename = pattern; + // It apparently doesn't matter that we return bogus + // information + fi.path = ""; + fi.compressedSize = fi.uncompressedSize = 0; + + ptr->push_back(fi); + } + + return ptr; + } + FileInfoListPtr findFileInfo(const String& pattern, bool recursive = true, bool dirs = false) { @@ -315,7 +327,7 @@ return arc.exists(passed.c_str()); // Check if the file exists (only works for single files - wild // cards and recursive search isn't implemented.) - if(exists(pattern)) + if(cexists(pattern)) { FileInfo fi; fi.archive = this; diff --git a/components/bsa/bsa_file.cpp b/components/bsa/bsa_file.cpp index 95358a362..f19606703 100644 --- a/components/bsa/bsa_file.cpp +++ b/components/bsa/bsa_file.cpp @@ -148,9 +148,9 @@ void BSAFile::readHeader() } /// Get the index of a given file name, or -1 if not found -int BSAFile::getIndex(const char *str) +int BSAFile::getIndex(const char *str) const { - Lookup::iterator it; + Lookup::const_iterator it; it = lookup.find(str); if(it == lookup.end()) return -1; diff --git a/components/bsa/bsa_file.hpp b/components/bsa/bsa_file.hpp index f54a64d2a..95fac0f4d 100644 --- a/components/bsa/bsa_file.hpp +++ b/components/bsa/bsa_file.hpp @@ -93,7 +93,7 @@ class BSAFile void readHeader(); /// Get the index of a given file name, or -1 if not found - int getIndex(const char *str); + int getIndex(const char *str) const; public: @@ -119,7 +119,7 @@ class BSAFile */ /// Check if a file exists - bool exists(const char *file) { return getIndex(file) != -1; } + bool exists(const char *file) const { return getIndex(file) != -1; } /** Open a file contained in the archive. Throws an exception if the file doesn't exist. diff --git a/components/compiler/parser.cpp b/components/compiler/parser.cpp index 73cadfeba..90368eee0 100644 --- a/components/compiler/parser.cpp +++ b/components/compiler/parser.cpp @@ -7,6 +7,7 @@ #include "errorhandler.hpp" #include "exception.hpp" +#include "scanner.hpp" namespace Compiler { @@ -81,6 +82,8 @@ namespace Compiler { if (!(mOptional && mEmpty)) reportSeriousError ("Unexpected numeric value", loc); + else + scanner.putbackInt (value, loc); return false; } @@ -94,6 +97,8 @@ namespace Compiler { if (!(mOptional && mEmpty)) reportSeriousError ("Unexpected floating point value", loc); + else + scanner.putbackFloat (value, loc); return false; } @@ -108,6 +113,8 @@ namespace Compiler { if (!(mOptional && mEmpty)) reportSeriousError ("Unexpected name", loc); + else + scanner.putbackName (name, loc); return false; } @@ -121,6 +128,8 @@ namespace Compiler { if (!(mOptional && mEmpty)) reportSeriousError ("Unexpected keyword", loc); + else + scanner.putbackKeyword (keyword, loc); return false; } @@ -134,6 +143,8 @@ namespace Compiler { if (!(mOptional && mEmpty)) reportSeriousError ("Unexpected special token", loc); + else + scanner.putbackSpecial (code, loc); return false; } diff --git a/components/compiler/stringparser.cpp b/components/compiler/stringparser.cpp index fe7bd30b9..396a88c78 100644 --- a/components/compiler/stringparser.cpp +++ b/components/compiler/stringparser.cpp @@ -39,11 +39,6 @@ namespace Compiler mState = CommaState; return true; } - else if (code==Scanner::S_newline && mState==StartState) - { - scanner.putbackSpecial (code, loc); - return false; - } return Parser::parseSpecial (code, loc, scanner); } diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp index 80a0f3e5a..158cc0867 100644 --- a/components/esm/loadcell.cpp +++ b/components/esm/loadcell.cpp @@ -21,8 +21,13 @@ void Cell::load(ESMReader &esm) if (data.flags & Interior) { // Interior cells - - if (esm.isNextSub("INTV") || esm.isNextSub("WHGT")) + if (esm.isNextSub("INTV")) + { + int waterl; + esm.getHT(waterl); + water = (float) waterl; + } + else if (esm.isNextSub("WHGT")) esm.getHT(water); // Quasi-exterior cells have a region (which determines the diff --git a/components/esm/loadcell.hpp b/components/esm/loadcell.hpp index 671f702ca..8070f9c03 100644 --- a/components/esm/loadcell.hpp +++ b/components/esm/loadcell.hpp @@ -114,11 +114,26 @@ struct Cell ESM_Context context; // File position DATAstruct data; AMBIstruct ambi; - int water; // Water level + float water; // Water level int mapColor; void load(ESMReader &esm); + bool isExterior() const + { + return !(data.flags & Interior); + } + + int getGridX() const + { + return data.gridX; + } + + int getGridY() const + { + return data.gridY; + } + // Restore the given reader to the stored position. Will try to open // the file matching the stored file name. If you want to read from // somewhere other than the file system, you need to pre-open the diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp index 740d15a40..96afdf831 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -2,8 +2,28 @@ namespace ESM { + +Land::Land() + : flags(0) + , X(0) + , Y(0) + , mEsm(NULL) + , hasData(false) + , dataLoaded(false) + , landData(NULL) +{ +} + +Land::~Land() +{ + delete landData; +} + + void Land::load(ESMReader &esm) { + mEsm = &esm; + // Get the grid location esm.getSubNameIs("INTV"); esm.getSubHeaderIs(8); @@ -19,14 +39,117 @@ void Land::load(ESMReader &esm) int cnt = 0; // Skip these here. Load the actual data when the cell is loaded. - if(esm.isNextSub("VNML")) {esm.skipHSubSize(12675);cnt++;} - if(esm.isNextSub("VHGT")) {esm.skipHSubSize(4232);cnt++;} - if(esm.isNextSub("WNAM")) esm.skipHSubSize(81); - if(esm.isNextSub("VCLR")) esm.skipHSubSize(12675); - if(esm.isNextSub("VTEX")) {esm.skipHSubSize(512);cnt++;} + if (esm.isNextSub("VNML")) + { + esm.skipHSubSize(12675); + cnt++; + } + if (esm.isNextSub("VHGT")) + { + esm.skipHSubSize(4232); + cnt++; + } + if (esm.isNextSub("WNAM")) + { + esm.skipHSubSize(81); + } + if (esm.isNextSub("VCLR")) + { + esm.skipHSubSize(12675); + } + if (esm.isNextSub("VTEX")) + { + esm.skipHSubSize(512); + cnt++; + } // We need all three of VNML, VHGT and VTEX in order to use the // landscape. hasData = (cnt == 3); + + dataLoaded = false; + landData = NULL; +} + +void Land::loadData() +{ + if (dataLoaded) + { + return; + } + + landData = new LandData; + + if (hasData) + { + mEsm->restoreContext(context); + + //esm.getHNExact(landData->normals, sizeof(VNML), "VNML"); + if (mEsm->isNextSub("VNML")) + { + mEsm->skipHSubSize(12675); + } + + VHGT rawHeights; + + mEsm->getHNExact(&rawHeights, sizeof(VHGT), "VHGT"); + int currentHeightOffset = rawHeights.heightOffset; + for (int y = 0; y < LAND_SIZE; y++) + { + currentHeightOffset += rawHeights.heightData[y * LAND_SIZE]; + landData->heights[y * LAND_SIZE] = currentHeightOffset * HEIGHT_SCALE; + + int tempOffset = currentHeightOffset; + for (int x = 1; x < LAND_SIZE; x++) + { + tempOffset += rawHeights.heightData[y * LAND_SIZE + x]; + landData->heights[x + y * LAND_SIZE] = tempOffset * HEIGHT_SCALE; + } + } + + if (mEsm->isNextSub("WNAM")) + { + mEsm->skipHSubSize(81); + } + if (mEsm->isNextSub("VCLR")) + { + landData->usingColours = true; + mEsm->getHExact(&landData->colours, 3*LAND_NUM_VERTS); + }else{ + landData->usingColours = false; + } + //TODO fix magic numbers + uint16_t vtex[512]; + mEsm->getHNExact(&vtex, 512, "VTEX"); + + int readPos = 0; //bit ugly, but it works + for ( int y1 = 0; y1 < 4; y1++ ) + for ( int x1 = 0; x1 < 4; x1++ ) + for ( int y2 = 0; y2 < 4; y2++) + for ( int x2 = 0; x2 < 4; x2++ ) + landData->textures[(y1*4+y2)*16+(x1*4+x2)] = vtex[readPos++]; + } + else + { + landData->usingColours = false; + memset(&landData->textures, 0, 512 * sizeof(uint16_t)); + for (int i = 0; i < LAND_NUM_VERTS; i++) + { + landData->heights[i] = -256.0f * HEIGHT_SCALE; + } + } + + dataLoaded = true; +} + +void Land::unloadData() +{ + if (dataLoaded) + { + delete landData; + landData = NULL; + dataLoaded = false; + } } + } diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp index af91850ac..ebc314a28 100644 --- a/components/esm/loadland.hpp +++ b/components/esm/loadland.hpp @@ -11,17 +11,80 @@ namespace ESM struct Land { + Land(); + ~Land(); + int flags; // Only first four bits seem to be used, don't know what // they mean. int X, Y; // Map coordinates. // File context. This allows the ESM reader to be 'reset' to this // location later when we are ready to load the full data set. + ESMReader* mEsm; ESM_Context context; bool hasData; + bool dataLoaded; + + // number of vertices per side + static const int LAND_SIZE = 65; + + // cell terrain size in world coords + static const int REAL_SIZE = 8192; + + // total number of vertices + static const int LAND_NUM_VERTS = LAND_SIZE * LAND_SIZE; + + static const int HEIGHT_SCALE = 8; + + //number of textures per side of land + static const int LAND_TEXTURE_SIZE = 16; + + //total number of textures per land + static const int LAND_NUM_TEXTURES = LAND_TEXTURE_SIZE * LAND_TEXTURE_SIZE; + +#pragma pack(push,1) + struct VHGT + { + float heightOffset; + int8_t heightData[LAND_NUM_VERTS]; + short unknown1; + char unknown2; + }; +#pragma pack(pop) + + typedef uint8_t VNML[LAND_NUM_VERTS * 3]; + + struct LandData + { + float heightOffset; + float heights[LAND_NUM_VERTS]; + //float normals[LAND_NUM_VERTS * 3]; + uint16_t textures[LAND_NUM_TEXTURES]; + + bool usingColours; + char colours[3 * LAND_NUM_VERTS]; + }; + + LandData *landData; + void load(ESMReader &esm); + + /** + * Actually loads data + */ + void loadData(); + + /** + * Frees memory allocated for land data + */ + void unloadData(); + + private: + Land(const Land& land); + Land& operator=(const Land& land); }; + } #endif diff --git a/components/esm_store/cell_store.hpp b/components/esm_store/cell_store.hpp index c4bcf84d8..024412291 100644 --- a/components/esm_store/cell_store.hpp +++ b/components/esm_store/cell_store.hpp @@ -95,12 +95,17 @@ namespace ESMS State_Unloaded, State_Preloaded, State_Loaded }; - CellStore (const ESM::Cell *cell_) : cell (cell_), mState (State_Unloaded) {} + CellStore (const ESM::Cell *cell_) : cell (cell_), mState (State_Unloaded) + { + mWaterLevel = cell->water; + } const ESM::Cell *cell; State mState; std::vector mIds; + float mWaterLevel; + // Lists for each individual object type CellRefList activators; CellRefList potions; diff --git a/components/esm_store/reclists.hpp b/components/esm_store/reclists.hpp index 678f794c8..d7a4100aa 100644 --- a/components/esm_store/reclists.hpp +++ b/components/esm_store/reclists.hpp @@ -201,15 +201,21 @@ namespace ESMS // TODO: For multiple ESM/ESP files we need one list per file. std::vector ltex; - int count; - LTexList() : count(0) + LTexList() { // More than enough to hold Morrowind.esm. ltex.reserve(128); } - int getSize() { return count; } + const LandTexture* search(size_t index) const + { + assert(index < ltex.size()); + return <ex.at(index); + } + + int getSize() { return ltex.size(); } + int getSize() const { return ltex.size(); } virtual void listIdentifier (std::vector& identifier) const {} @@ -233,12 +239,18 @@ namespace ESMS */ struct LandList : RecList { - virtual ~LandList() {} + virtual ~LandList() + { + for ( LandMap::iterator itr = lands.begin(); itr != lands.end(); ++itr ) + { + delete itr->second; + } + } // Map containing all landscapes - typedef std::map LandsCol; - typedef std::map Lands; - Lands lands; + typedef std::pair LandCoord; + typedef std::map LandMap; + LandMap lands; int count; LandList() : count(0) {} @@ -247,17 +259,15 @@ namespace ESMS virtual void listIdentifier (std::vector& identifier) const {} // Find land for the given coordinates. Return null if no data. - const Land *search(int x, int y) const + Land *search(int x, int y) const { - Lands::const_iterator it = lands.find(x); - if(it==lands.end()) - return NULL; - - LandsCol::const_iterator it2 = it->second.find(y); - if(it2 == it->second.end()) + LandMap::const_iterator itr = lands.find(std::make_pair(x, y)); + if ( itr == lands.end() ) + { return NULL; + } - return it2->second; + return itr->second; } void load(ESMReader &esm, const std::string &id) @@ -266,11 +276,11 @@ namespace ESMS // Create the structure and load it. This actually skips the // landscape data and remembers the file position for later. - Land *land = new Land; + Land *land = new Land(); land->load(esm); // Store the structure - lands[land->X][land->Y] = land; + lands[std::make_pair(land->X, land->Y)] = land; } }; @@ -447,7 +457,7 @@ namespace ESMS } } - Pathgrid *find(int cellX, int cellY, std::string cellName) const + Pathgrid *find(int cellX, int cellY, const std::string &cellName) const { Pathgrid *result = search(cellX, cellY, cellName); if (!result) @@ -457,7 +467,7 @@ namespace ESMS return result; } - Pathgrid *search(int cellX, int cellY, std::string cellName) const + Pathgrid *search(int cellX, int cellY, const std::string &cellName) const { Pathgrid *result = NULL; if (cellX == 0 && cellY == 0) // possibly interior diff --git a/components/esm_store/store.hpp b/components/esm_store/store.hpp index fab04d3e9..857682089 100644 --- a/components/esm_store/store.hpp +++ b/components/esm_store/store.hpp @@ -116,7 +116,7 @@ namespace ESMS recLists[REC_GLOB] = &globals; recLists[REC_GMST] = &gameSettings; recLists[REC_INGR] = &ingreds; - //recLists[REC_LAND] = &lands; + recLists[REC_LAND] = &lands; recLists[REC_LEVC] = &creatureLists; recLists[REC_LEVI] = &itemLists; recLists[REC_LIGH] = &lights; diff --git a/components/files/collections.cpp b/components/files/collections.cpp index 424b558e6..50340dca4 100644 --- a/components/files/collections.cpp +++ b/components/files/collections.cpp @@ -30,4 +30,9 @@ namespace Files return iter->second; } + + const Files::PathContainer& Collections::getPaths() const + { + return mDirectories; + } } diff --git a/components/files/collections.hpp b/components/files/collections.hpp index 1ddca9a5b..70aaec55e 100644 --- a/components/files/collections.hpp +++ b/components/files/collections.hpp @@ -21,6 +21,8 @@ namespace Files /// leading dot and must be all lower-case. const MultiDirCollection& getCollection(const std::string& extension) const; + const Files::PathContainer& getPaths() const; + private: typedef std::map MultiDirCollectionContainer; Files::PathContainer mDirectories; diff --git a/components/nif/nif_file.cpp b/components/nif/nif_file.cpp index 52a37ba5c..80ea7a0b7 100644 --- a/components/nif/nif_file.cpp +++ b/components/nif/nif_file.cpp @@ -162,6 +162,15 @@ void NIFFile::parse() r->recName = rec; records[i] = r; r->read(this); + + // Discard tranformations for the root node, otherwise some meshes + // occasionally get wrong orientation. Only for NiNode-s for now, but + // can be expanded if needed. + // This should be rewritten when the method is cleaned up. + if (0 == i && rec == "NiNode") + { + static_cast(r)->trafo = Nif::Transformation::getIdentity(); + } } /* After the data, the nif contains an int N and then a list of N diff --git a/components/nif/nif_types.hpp b/components/nif/nif_types.hpp index 17cec19bc..ee796cc99 100644 --- a/components/nif/nif_types.hpp +++ b/components/nif/nif_types.hpp @@ -55,10 +55,26 @@ struct Matrix struct Transformation { - Vector pos; - Matrix rotation; - float scale; - Vector velocity; + Vector pos; + Matrix rotation; + float scale; + Vector velocity; + + static const Transformation* getIdentity() + { + static Transformation identity; + static bool iset = false; + if (!iset) + { + identity.scale = 1.0f; + identity.rotation.v[0].array[0] = 1.0f; + identity.rotation.v[1].array[1] = 1.0f; + identity.rotation.v[2].array[2] = 1.0f; + iset = true; + } + + return &identity; + } }; #pragma pack(pop) diff --git a/components/nifbullet/bullet_nif_loader.cpp b/components/nifbullet/bullet_nif_loader.cpp index 82e9d7adc..e9aa626db 100644 --- a/components/nifbullet/bullet_nif_loader.cpp +++ b/components/nifbullet/bullet_nif_loader.cpp @@ -51,7 +51,11 @@ using namespace Mangle::VFS; using namespace NifBullet; -//==================================================================================================== +ManualBulletShapeLoader::~ManualBulletShapeLoader() +{ + delete vfs; +} + Ogre::Matrix3 ManualBulletShapeLoader::getMatrix(Nif::Transformation* tr) { Ogre::Matrix3 rot(tr->rotation.v[0].array[0],tr->rotation.v[0].array[1],tr->rotation.v[0].array[2], @@ -135,7 +139,21 @@ void ManualBulletShapeLoader::loadResource(Ogre::Resource *resource) handleNode(node,0,Ogre::Matrix3::IDENTITY,Ogre::Vector3::ZERO,1,hasCollisionNode,false,true); } - currentShape = new btBvhTriangleMeshShape(mTriMesh,true); + struct TriangleMeshShape : public btBvhTriangleMeshShape + { + TriangleMeshShape(btStridingMeshInterface* meshInterface, bool useQuantizedAabbCompression) + : btBvhTriangleMeshShape(meshInterface, useQuantizedAabbCompression) + { + } + + virtual ~TriangleMeshShape() + { + delete getTriangleInfoMap(); + delete m_meshInterface; + } + }; + + currentShape = new TriangleMeshShape(mTriMesh,true); cShape->Shape = currentShape; } diff --git a/components/nifbullet/bullet_nif_loader.hpp b/components/nifbullet/bullet_nif_loader.hpp index 1fa2b6aa5..ed3aceac4 100644 --- a/components/nifbullet/bullet_nif_loader.hpp +++ b/components/nifbullet/bullet_nif_loader.hpp @@ -69,7 +69,7 @@ class ManualBulletShapeLoader : public BulletShapeLoader public: ManualBulletShapeLoader():resourceGroup("General"){vfs = 0;} - virtual ~ManualBulletShapeLoader() {} + virtual ~ManualBulletShapeLoader(); void warn(std::string msg) { diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 2e68cfe90..ab6a708c0 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -25,6 +25,8 @@ #include "ogre_nif_loader.hpp" +#include +#include typedef unsigned char ubyte; @@ -281,156 +283,69 @@ void NIFLoader::createMaterial(const String &name, // other values. 237 basically means normal transparencly. if (alphaFlags == 237) { - // Enable transparency - pass->setSceneBlending(SBT_TRANSPARENT_ALPHA); + NifOverrides::TransparencyResult result = NifOverrides::Overrides::getTransparencyOverride(texName); + if (result.first) + { + pass->setAlphaRejectFunction(CMPF_GREATER_EQUAL); + pass->setAlphaRejectValue(result.second); + } + else + { + // Enable transparency + pass->setSceneBlending(SBT_TRANSPARENT_ALPHA); - //pass->setDepthCheckEnabled(false); - pass->setDepthWriteEnabled(false); + //pass->setDepthCheckEnabled(false); + pass->setDepthWriteEnabled(false); + //std::cout << "alpha 237; material: " << name << " texName: " << texName << std::endl; + } } else warn("Unhandled alpha setting for texture " + texName); } + else + { + material->getTechnique(0)->setShadowCasterMaterial("depth_shadow_caster_noalpha"); + } } - // Add material bells and whistles - material->setAmbient(ambient.array[0], ambient.array[1], ambient.array[2]); - material->setDiffuse(diffuse.array[0], diffuse.array[1], diffuse.array[2], alpha); - material->setSpecular(specular.array[0], specular.array[1], specular.array[2], alpha); - material->setSelfIllumination(emissive.array[0], emissive.array[1], emissive.array[2]); - material->setShininess(glossiness); - - // Create shader for the material - // vertex - HighLevelGpuProgramManager& mgr = HighLevelGpuProgramManager::getSingleton(); - - HighLevelGpuProgramPtr vertex; - if (mgr.getByName("main_vp").isNull()) + if (Settings::Manager::getBool("enabled", "Shadows")) { - vertex = mgr.createProgram("main_vp", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - "cg", GPT_VERTEX_PROGRAM); - vertex->setParameter("profiles", "vs_4_0 vs_2_x vp40 arbvp1"); - vertex->setParameter("entry_point", "main_vp"); - StringUtil::StrStreamType outStream; - outStream << - "void main_vp( \n" - " float4 position : POSITION, \n" - " float4 normal : NORMAL, \n" - " float4 colour : COLOR, \n" - " in float2 uv : TEXCOORD0, \n" - " out float2 oUV : TEXCOORD0, \n" - " out float4 oPosition : POSITION, \n" - " out float4 oPositionObjSpace : TEXCOORD1, \n" - " out float4 oNormal : TEXCOORD2, \n" - " out float oFogValue : TEXCOORD3, \n" - " out float4 oVertexColour : TEXCOORD4, \n" - " uniform float4 fogParams, \n" - " uniform float4x4 worldViewProj \n" - ") \n" - "{ \n" - " oVertexColour = colour; \n" - " oUV = uv; \n" - " oNormal = normal; \n" - " oPosition = mul( worldViewProj, position ); \n" - " oFogValue = saturate((oPosition.z - fogParams.y) * fogParams.w); \n" - " oPositionObjSpace = position; \n" - "}"; - vertex->setSource(outStream.str()); - vertex->load(); - vertex->getDefaultParameters()->setNamedAutoConstant("worldViewProj", GpuProgramParameters::ACT_WORLDVIEWPROJ_MATRIX); - vertex->getDefaultParameters()->setNamedAutoConstant("fogParams", GpuProgramParameters::ACT_FOG_PARAMS); + bool split = Settings::Manager::getBool("split", "Shadows"); + const int numsplits = 3; + for (int i = 0; i < (split ? numsplits : 1); ++i) + { + TextureUnitState* tu = material->getTechnique(0)->getPass(0)->createTextureUnitState(); + tu->setName("shadowMap" + StringConverter::toString(i)); + tu->setContentType(TextureUnitState::CONTENT_SHADOW); + tu->setTextureAddressingMode(TextureUnitState::TAM_BORDER); + tu->setTextureBorderColour(ColourValue::White); + } } - else - vertex = mgr.getByName("main_vp"); - material->getTechnique(0)->getPass(0)->setVertexProgram(vertex->getName()); - - // the number of lights to support. - // when rendering an object, OGRE automatically picks the lights that are - // closest to the object being rendered. unfortunately this mechanism does - // not work perfectly for objects batched together (they will all use the same - // lights). to work around this, we are simply pushing the maximum number - // of lights here in order to minimize disappearing lights. - float num_lights; - if (GpuProgramManager::getSingleton().isSyntaxSupported("fp40") || - GpuProgramManager::getSingleton().isSyntaxSupported("ps_4_0")) - num_lights = 8 /* 32 */; - else - num_lights = 8; - // fragment - HighLevelGpuProgramPtr fragment; - if (mgr.getByName("main_fp").isNull()) + if (Settings::Manager::getBool("shaders", "Objects")) { - fragment = mgr.createProgram("main_fp", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - "cg", GPT_FRAGMENT_PROGRAM); - fragment->setParameter("profiles", "ps_4_0 ps_2_x fp40 arbfp1"); - fragment->setParameter("entry_point", "main_fp"); - StringUtil::StrStreamType outStream; - outStream << - "void main_fp( \n" - " in float2 uv : TEXCOORD0, \n" - " out float4 oColor : COLOR, \n" - " uniform sampler2D texture : TEXUNIT0, \n" - " float4 positionObjSpace : TEXCOORD1, \n" - " float4 normal : TEXCOORD2, \n" - " float fogValue : TEXCOORD3, \n" - " float4 vertexColour : TEXCOORD4, \n" - " uniform float4 fogColour, \n"; - - for (int i=0; igetTechnique(0)->getPass(0)->setVertexProgram("main_vp"); + material->getTechnique(0)->getPass(0)->setFragmentProgram("main_fp"); + } - " lightColour.xyz += lit(dot(normalize(lightDir"<setSource(outStream.str()); - fragment->load(); - - for (int i=0; igetDefaultParameters()->setNamedAutoConstant("lightPositionObjSpace"+StringConverter::toString(i), GpuProgramParameters::ACT_LIGHT_POSITION_OBJECT_SPACE, i); - fragment->getDefaultParameters()->setNamedAutoConstant("lightDiffuse"+StringConverter::toString(i), GpuProgramParameters::ACT_LIGHT_DIFFUSE_COLOUR, i); - fragment->getDefaultParameters()->setNamedAutoConstant("lightAttenuation"+StringConverter::toString(i), GpuProgramParameters::ACT_LIGHT_ATTENUATION, i); - } - fragment->getDefaultParameters()->setNamedAutoConstant("emissive", GpuProgramParameters::ACT_SURFACE_EMISSIVE_COLOUR); - fragment->getDefaultParameters()->setNamedAutoConstant("diffuse", GpuProgramParameters::ACT_SURFACE_DIFFUSE_COLOUR); - fragment->getDefaultParameters()->setNamedAutoConstant("ambient", GpuProgramParameters::ACT_SURFACE_AMBIENT_COLOUR); - fragment->getDefaultParameters()->setNamedAutoConstant("lightAmbient", GpuProgramParameters::ACT_AMBIENT_LIGHT_COLOUR); - fragment->getDefaultParameters()->setNamedAutoConstant("fogColour", GpuProgramParameters::ACT_FOG_COLOUR); + // Create a fallback technique without shadows and without mrt + Technique* tech2 = material->createTechnique(); + tech2->setSchemeName("Fallback"); + Pass* pass2 = tech2->createPass(); + pass2->createTextureUnitState(texName); + pass2->setVertexColourTracking(TVC_DIFFUSE); + if (Settings::Manager::getBool("shaders", "Objects")) + { + pass2->setVertexProgram("main_fallback_vp"); + pass2->setFragmentProgram("main_fallback_fp"); } - else - fragment = mgr.getByName("main_fp"); - material->getTechnique(0)->getPass(0)->setFragmentProgram(fragment->getName()); + + // Add material bells and whistles + material->setAmbient(ambient.array[0], ambient.array[1], ambient.array[2]); + material->setDiffuse(diffuse.array[0], diffuse.array[1], diffuse.array[2], alpha); + material->setSpecular(specular.array[0], specular.array[1], specular.array[2], alpha); + material->setSelfIllumination(emissive.array[0], emissive.array[1], emissive.array[2]); + material->setShininess(glossiness); } // Takes a name and adds a unique part to it. This is just used to @@ -509,7 +424,8 @@ void NIFLoader::createOgreSubMesh(NiTriShape *shape, const String &material, std datamod[index+1] = original.y; datamod[index+2] = original.z; } - vbuf->writeData(0, vbuf->getSizeInBytes(), datamod, false); + vbuf->writeData(0, vbuf->getSizeInBytes(), datamod, false); + delete [] datamod; } else { @@ -550,6 +466,7 @@ void NIFLoader::createOgreSubMesh(NiTriShape *shape, const String &material, std datamod[index+2] = original.z; } vbuf->writeData(0, vbuf->getSizeInBytes(), datamod, false); + delete [] datamod; } else { @@ -601,6 +518,7 @@ void NIFLoader::createOgreSubMesh(NiTriShape *shape, const String &material, std datamod[i + 1] =y; } vbuf->writeData(0, vbuf->getSizeInBytes(), datamod, false); + delete [] datamod; } else vbuf->writeData(0, vbuf->getSizeInBytes(), data->uvlist.ptr, false); @@ -644,15 +562,13 @@ void NIFLoader::createOgreSubMesh(NiTriShape *shape, const String &material, std index += 3; } - ibuf->writeData(0, ibuf->getSizeInBytes(), datamod, false); + ibuf->writeData(0, ibuf->getSizeInBytes(), datamod, false); + delete [] datamod; } else - ibuf->writeData(0, ibuf->getSizeInBytes(), data->triangles.ptr, false); + ibuf->writeData(0, ibuf->getSizeInBytes(), data->triangles.ptr, false); sub->indexData->indexBuffer = ibuf; - - - } // Set material if one was given @@ -1367,7 +1283,7 @@ void NIFLoader::loadResource(Resource *resource) if (!vfs->isFile(resourceName)) { - warn("File not found."); + warn("File "+resourceName+" not found."); return; } diff --git a/components/nifoverrides/nifoverrides.cpp b/components/nifoverrides/nifoverrides.cpp new file mode 100644 index 000000000..1c8fefd24 --- /dev/null +++ b/components/nifoverrides/nifoverrides.cpp @@ -0,0 +1,37 @@ +#include "nifoverrides.hpp" + +#include + +#include + +using namespace NifOverrides; + +Ogre::ConfigFile Overrides::mTransparencyOverrides = Ogre::ConfigFile(); + +void Overrides::loadTransparencyOverrides (const std::string& file) +{ + mTransparencyOverrides.load(file); +} + +TransparencyResult Overrides::getTransparencyOverride(const std::string& texture) +{ + TransparencyResult result; + result.first = false; + + std::string tex = texture; + boost::to_lower(tex); + + Ogre::ConfigFile::SectionIterator seci = mTransparencyOverrides.getSectionIterator(); + while (seci.hasMoreElements()) + { + Ogre::String sectionName = seci.peekNextKey(); + if (sectionName == tex) + { + result.first = true; + result.second = Ogre::StringConverter::parseInt(mTransparencyOverrides.getSetting("alphaRejectValue", sectionName)); + break; + } + seci.getNext(); + } + return result; +} diff --git a/components/nifoverrides/nifoverrides.hpp b/components/nifoverrides/nifoverrides.hpp new file mode 100644 index 000000000..c9b711df6 --- /dev/null +++ b/components/nifoverrides/nifoverrides.hpp @@ -0,0 +1,23 @@ +#ifndef COMPONENTS_NIFOVERRIDES_H +#define COMPONENTS_NIFOVERRIDES_H + +#include + +namespace NifOverrides +{ + + typedef std::pair TransparencyResult; + + /// \brief provide overrides for some model / texture properties that bethesda has chosen poorly + class Overrides + { + public: + static Ogre::ConfigFile mTransparencyOverrides; + void loadTransparencyOverrides (const std::string& file); + + static TransparencyResult getTransparencyOverride(const std::string& texture); + }; + +} + +#endif diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp new file mode 100644 index 000000000..28201eda2 --- /dev/null +++ b/components/settings/settings.cpp @@ -0,0 +1,158 @@ +#include "settings.hpp" + +#include + +#include +#include + +using namespace Settings; + +Ogre::ConfigFile Manager::mFile = Ogre::ConfigFile(); +Ogre::ConfigFile Manager::mDefaultFile = Ogre::ConfigFile(); +CategorySettingVector Manager::mChangedSettings = CategorySettingVector(); +CategorySettingValueMap Manager::mNewSettings = CategorySettingValueMap(); + +void Manager::loadUser (const std::string& file) +{ + mFile.load(file); +} + +void Manager::loadDefault (const std::string& file) +{ + mDefaultFile.load(file); +} + +void Manager::saveUser(const std::string& file) +{ + std::fstream fout(file.c_str(), std::ios::out); + + Ogre::ConfigFile::SectionIterator seci = mFile.getSectionIterator(); + + while (seci.hasMoreElements()) + { + Ogre::String sectionName = seci.peekNextKey(); + + if (sectionName.length() > 0) + fout << '\n' << '[' << seci.peekNextKey() << ']' << '\n'; + + Ogre::ConfigFile::SettingsMultiMap *settings = seci.getNext(); + Ogre::ConfigFile::SettingsMultiMap::iterator i; + for (i = settings->begin(); i != settings->end(); ++i) + { + fout << i->first.c_str() << " = " << i->second.c_str() << '\n'; + } + + CategorySettingValueMap::iterator it = mNewSettings.begin(); + while (it != mNewSettings.end()) + { + if (it->first.first == sectionName) + { + fout << it->first.second << " = " << it->second << '\n'; + mNewSettings.erase(it++); + } + else + ++it; + } + } + + std::string category = ""; + for (CategorySettingValueMap::iterator it = mNewSettings.begin(); + it != mNewSettings.end(); ++it) + { + if (category != it->first.first) + { + category = it->first.first; + fout << '\n' << '[' << category << ']' << '\n'; + } + fout << it->first.second << " = " << it->second << '\n'; + } +} + +const std::string Manager::getString (const std::string& setting, const std::string& category) +{ + if (mNewSettings.find(std::make_pair(category, setting)) != mNewSettings.end()) + return mNewSettings[std::make_pair(category, setting)]; + + std::string defaultval = mDefaultFile.getSetting(setting, category); + return mFile.getSetting(setting, category, defaultval); +} + +const float Manager::getFloat (const std::string& setting, const std::string& category) +{ + return Ogre::StringConverter::parseReal( getString(setting, category) ); +} + +const int Manager::getInt (const std::string& setting, const std::string& category) +{ + return Ogre::StringConverter::parseInt( getString(setting, category) ); +} + +const bool Manager::getBool (const std::string& setting, const std::string& category) +{ + return Ogre::StringConverter::parseBool( getString(setting, category) ); +} + +void Manager::setString (const std::string& setting, const std::string& category, const std::string& value) +{ + CategorySetting s = std::make_pair(category, setting); + + bool found=false; + try + { + Ogre::ConfigFile::SettingsIterator it = mFile.getSettingsIterator(category); + while (it.hasMoreElements()) + { + Ogre::ConfigFile::SettingsMultiMap::iterator i = it.current(); + + if ((*i).first == setting) + { + if ((*i).second != value) + { + mChangedSettings.push_back(std::make_pair(category, setting)); + (*i).second = value; + } + found = true; + } + + it.getNext(); + } + } + catch (Ogre::Exception&) + {} + + if (!found) + { + if (mNewSettings.find(s) != mNewSettings.end()) + { + if (mNewSettings[s] != value) + { + mChangedSettings.push_back(std::make_pair(category, setting)); + mNewSettings[s] = value; + } + } + else + mNewSettings[s] = value; + } +} + +void Manager::setInt (const std::string& setting, const std::string& category, const int value) +{ + setString(setting, category, Ogre::StringConverter::toString(value)); +} + +void Manager::setFloat (const std::string& setting, const std::string& category, const float value) +{ + setString(setting, category, Ogre::StringConverter::toString(value)); +} + +void Manager::setBool (const std::string& setting, const std::string& category, const bool value) +{ + setString(setting, category, Ogre::StringConverter::toString(value)); +} + +const CategorySettingVector Manager::apply() +{ + CategorySettingVector vec = mChangedSettings; + mChangedSettings.clear(); + return vec; +} diff --git a/components/settings/settings.hpp b/components/settings/settings.hpp new file mode 100644 index 000000000..e9858eb94 --- /dev/null +++ b/components/settings/settings.hpp @@ -0,0 +1,52 @@ +#ifndef _COMPONENTS_SETTINGS_H +#define _COMPONENTS_SETTINGS_H + +#include + +namespace Settings +{ + typedef std::pair < std::string, std::string > CategorySetting; + typedef std::vector< std::pair > CategorySettingVector; + typedef std::map < CategorySetting, std::string > CategorySettingValueMap; + + /// + /// \brief Settings management (can change during runtime) + /// + class Manager + { + public: + static Ogre::ConfigFile mFile; + static Ogre::ConfigFile mDefaultFile; + + static CategorySettingVector mChangedSettings; + ///< tracks all the settings that were changed since the last apply() call + + static CategorySettingValueMap mNewSettings; + ///< tracks all the settings that are in the default file, but not in user file yet + + void loadDefault (const std::string& file); + ///< load file as the default settings (can be overridden by user settings) + + void loadUser (const std::string& file); + ///< load file as user settings + + void saveUser (const std::string& file); + ///< save user settings to file + + static const CategorySettingVector apply(); + ///< returns the list of changed settings and then clears it + + static const int getInt (const std::string& setting, const std::string& category); + static const float getFloat (const std::string& setting, const std::string& category); + static const std::string getString (const std::string& setting, const std::string& category); + static const bool getBool (const std::string& setting, const std::string& category); + + static void setInt (const std::string& setting, const std::string& category, const int value); + static void setFloat (const std::string& setting, const std::string& category, const float value); + static void setString (const std::string& setting, const std::string& category, const std::string& value); + static void setBool (const std::string& setting, const std::string& category, const bool value); + }; + +} + +#endif // _COMPONENTS_SETTINGS_H diff --git a/components/to_utf8/gen_iconv.cpp b/components/to_utf8/gen_iconv.cpp index 620205245..b7298e304 100644 --- a/components/to_utf8/gen_iconv.cpp +++ b/components/to_utf8/gen_iconv.cpp @@ -12,7 +12,7 @@ void tab() { cout << " "; } // write one number with a space in front of it and a comma after it void num(unsigned char i, bool last) { - cout << " 0x" << (unsigned)i; + cout << " (char)0x" << (unsigned)i; if(!last) cout << ","; } diff --git a/components/to_utf8/tables_gen.hpp b/components/to_utf8/tables_gen.hpp index 79945bddc..1084ca28f 100644 --- a/components/to_utf8/tables_gen.hpp +++ b/components/to_utf8/tables_gen.hpp @@ -10,785 +10,785 @@ namespace ToUTF8 /// Serbian (Latin script), Romanian and Albanian. static char windows_1250[] = { - 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x2, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x3, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x4, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x5, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x6, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x7, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x8, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x9, 0x0, 0x0, 0x0, 0x0, - 0x1, 0xa, 0x0, 0x0, 0x0, 0x0, - 0x1, 0xb, 0x0, 0x0, 0x0, 0x0, - 0x1, 0xc, 0x0, 0x0, 0x0, 0x0, - 0x1, 0xd, 0x0, 0x0, 0x0, 0x0, - 0x1, 0xe, 0x0, 0x0, 0x0, 0x0, - 0x1, 0xf, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x10, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x11, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x12, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x13, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x14, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x15, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x16, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x17, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x18, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x19, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x1a, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x1b, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x1c, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x1d, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x1e, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x1f, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x20, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x21, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x22, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x23, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x24, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x25, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x26, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x27, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x28, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x29, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x2a, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x2b, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x2c, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x2d, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x2e, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x2f, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x30, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x31, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x32, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x33, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x34, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x35, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x36, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x37, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x38, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x39, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x3a, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x3b, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x3c, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x3d, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x3e, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x3f, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x40, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x41, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x42, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x43, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x44, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x45, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x46, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x47, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x48, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x49, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x4a, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x4b, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x4c, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x4d, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x4e, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x4f, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x50, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x51, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x52, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x53, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x54, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x55, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x56, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x57, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x58, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x59, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x5a, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x5b, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x5c, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x5d, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x5e, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x5f, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x60, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x61, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x62, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x63, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x64, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x65, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x66, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x67, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x68, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x69, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x6a, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x6b, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x6c, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x6d, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x6e, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x6f, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x70, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x71, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x72, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x73, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x74, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x75, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x76, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x77, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x78, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x79, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x7a, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x7b, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x7c, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x7d, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x7e, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x7f, 0x0, 0x0, 0x0, 0x0, - 0x3, 0xe2, 0x82, 0xac, 0x0, 0x0, - 0x1, 0x20, 0x0, 0x0, 0x0, 0x0, // not part of this charset - 0x3, 0xe2, 0x80, 0x9a, 0x0, 0x0, - 0x1, 0x20, 0x0, 0x0, 0x0, 0x0, // not part of this charset - 0x3, 0xe2, 0x80, 0x9e, 0x0, 0x0, - 0x3, 0xe2, 0x80, 0xa6, 0x0, 0x0, - 0x3, 0xe2, 0x80, 0xa0, 0x0, 0x0, - 0x3, 0xe2, 0x80, 0xa1, 0x0, 0x0, - 0x1, 0x20, 0x0, 0x0, 0x0, 0x0, // not part of this charset - 0x3, 0xe2, 0x80, 0xb0, 0x0, 0x0, - 0x2, 0xc5, 0xa0, 0x0, 0x0, 0x0, - 0x3, 0xe2, 0x80, 0xb9, 0x0, 0x0, - 0x2, 0xc5, 0x9a, 0x0, 0x0, 0x0, - 0x2, 0xc5, 0xa4, 0x0, 0x0, 0x0, - 0x2, 0xc5, 0xbd, 0x0, 0x0, 0x0, - 0x2, 0xc5, 0xb9, 0x0, 0x0, 0x0, - 0x1, 0x20, 0x0, 0x0, 0x0, 0x0, // not part of this charset - 0x3, 0xe2, 0x80, 0x98, 0x0, 0x0, - 0x3, 0xe2, 0x80, 0x99, 0x0, 0x0, - 0x3, 0xe2, 0x80, 0x9c, 0x0, 0x0, - 0x3, 0xe2, 0x80, 0x9d, 0x0, 0x0, - 0x3, 0xe2, 0x80, 0xa2, 0x0, 0x0, - 0x3, 0xe2, 0x80, 0x93, 0x0, 0x0, - 0x3, 0xe2, 0x80, 0x94, 0x0, 0x0, - 0x1, 0x20, 0x0, 0x0, 0x0, 0x0, // not part of this charset - 0x3, 0xe2, 0x84, 0xa2, 0x0, 0x0, - 0x2, 0xc5, 0xa1, 0x0, 0x0, 0x0, - 0x3, 0xe2, 0x80, 0xba, 0x0, 0x0, - 0x2, 0xc5, 0x9b, 0x0, 0x0, 0x0, - 0x2, 0xc5, 0xa5, 0x0, 0x0, 0x0, - 0x2, 0xc5, 0xbe, 0x0, 0x0, 0x0, - 0x2, 0xc5, 0xba, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xa0, 0x0, 0x0, 0x0, - 0x2, 0xcb, 0x87, 0x0, 0x0, 0x0, - 0x2, 0xcb, 0x98, 0x0, 0x0, 0x0, - 0x2, 0xc5, 0x81, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xa4, 0x0, 0x0, 0x0, - 0x2, 0xc4, 0x84, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xa6, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xa7, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xa8, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xa9, 0x0, 0x0, 0x0, - 0x2, 0xc5, 0x9e, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xab, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xac, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xad, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xae, 0x0, 0x0, 0x0, - 0x2, 0xc5, 0xbb, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xb0, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xb1, 0x0, 0x0, 0x0, - 0x2, 0xcb, 0x9b, 0x0, 0x0, 0x0, - 0x2, 0xc5, 0x82, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xb4, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xb5, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xb6, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xb7, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xb8, 0x0, 0x0, 0x0, - 0x2, 0xc4, 0x85, 0x0, 0x0, 0x0, - 0x2, 0xc5, 0x9f, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xbb, 0x0, 0x0, 0x0, - 0x2, 0xc4, 0xbd, 0x0, 0x0, 0x0, - 0x2, 0xcb, 0x9d, 0x0, 0x0, 0x0, - 0x2, 0xc4, 0xbe, 0x0, 0x0, 0x0, - 0x2, 0xc5, 0xbc, 0x0, 0x0, 0x0, - 0x2, 0xc5, 0x94, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x81, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x82, 0x0, 0x0, 0x0, - 0x2, 0xc4, 0x82, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x84, 0x0, 0x0, 0x0, - 0x2, 0xc4, 0xb9, 0x0, 0x0, 0x0, - 0x2, 0xc4, 0x86, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x87, 0x0, 0x0, 0x0, - 0x2, 0xc4, 0x8c, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x89, 0x0, 0x0, 0x0, - 0x2, 0xc4, 0x98, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x8b, 0x0, 0x0, 0x0, - 0x2, 0xc4, 0x9a, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x8d, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x8e, 0x0, 0x0, 0x0, - 0x2, 0xc4, 0x8e, 0x0, 0x0, 0x0, - 0x2, 0xc4, 0x90, 0x0, 0x0, 0x0, - 0x2, 0xc5, 0x83, 0x0, 0x0, 0x0, - 0x2, 0xc5, 0x87, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x93, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x94, 0x0, 0x0, 0x0, - 0x2, 0xc5, 0x90, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x96, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x97, 0x0, 0x0, 0x0, - 0x2, 0xc5, 0x98, 0x0, 0x0, 0x0, - 0x2, 0xc5, 0xae, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x9a, 0x0, 0x0, 0x0, - 0x2, 0xc5, 0xb0, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x9c, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x9d, 0x0, 0x0, 0x0, - 0x2, 0xc5, 0xa2, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x9f, 0x0, 0x0, 0x0, - 0x2, 0xc5, 0x95, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xa1, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xa2, 0x0, 0x0, 0x0, - 0x2, 0xc4, 0x83, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xa4, 0x0, 0x0, 0x0, - 0x2, 0xc4, 0xba, 0x0, 0x0, 0x0, - 0x2, 0xc4, 0x87, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xa7, 0x0, 0x0, 0x0, - 0x2, 0xc4, 0x8d, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xa9, 0x0, 0x0, 0x0, - 0x2, 0xc4, 0x99, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xab, 0x0, 0x0, 0x0, - 0x2, 0xc4, 0x9b, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xad, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xae, 0x0, 0x0, 0x0, - 0x2, 0xc4, 0x8f, 0x0, 0x0, 0x0, - 0x2, 0xc4, 0x91, 0x0, 0x0, 0x0, - 0x2, 0xc5, 0x84, 0x0, 0x0, 0x0, - 0x2, 0xc5, 0x88, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xb3, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xb4, 0x0, 0x0, 0x0, - 0x2, 0xc5, 0x91, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xb6, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xb7, 0x0, 0x0, 0x0, - 0x2, 0xc5, 0x99, 0x0, 0x0, 0x0, - 0x2, 0xc5, 0xaf, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xba, 0x0, 0x0, 0x0, - 0x2, 0xc5, 0xb1, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xbc, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xbd, 0x0, 0x0, 0x0, - 0x2, 0xc5, 0xa3, 0x0, 0x0, 0x0, - 0x2, 0xcb, 0x99, 0x0, 0x0, 0x0 + (char)0x1, (char)0x0, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x1, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x2, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x3, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x4, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x5, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x6, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x7, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x8, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x9, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0xa, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0xb, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0xc, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0xd, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0xe, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0xf, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x10, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x11, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x12, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x13, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x14, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x15, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x16, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x17, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x18, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x19, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x1a, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x1b, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x1c, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x1d, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x1e, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x1f, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x20, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x21, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x22, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x23, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x24, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x25, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x26, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x27, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x28, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x29, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x2a, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x2b, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x2c, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x2d, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x2e, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x2f, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x30, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x31, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x32, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x33, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x34, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x35, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x36, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x37, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x38, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x39, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x3a, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x3b, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x3c, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x3d, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x3e, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x3f, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x40, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x41, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x42, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x43, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x44, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x45, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x46, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x47, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x48, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x49, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x4a, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x4b, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x4c, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x4d, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x4e, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x4f, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x50, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x51, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x52, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x53, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x54, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x55, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x56, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x57, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x58, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x59, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x5a, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x5b, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x5c, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x5d, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x5e, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x5f, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x60, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x61, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x62, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x63, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x64, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x65, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x66, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x67, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x68, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x69, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x6a, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x6b, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x6c, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x6d, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x6e, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x6f, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x70, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x71, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x72, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x73, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x74, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x75, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x76, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x77, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x78, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x79, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x7a, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x7b, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x7c, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x7d, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x7e, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x7f, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x82, (char)0xac, (char)0x0, (char)0x0, + (char)0x1, (char)0x20, (char)0x0, (char)0x0, (char)0x0, (char)0x0, // not part of this charset + (char)0x3, (char)0xe2, (char)0x80, (char)0x9a, (char)0x0, (char)0x0, + (char)0x1, (char)0x20, (char)0x0, (char)0x0, (char)0x0, (char)0x0, // not part of this charset + (char)0x3, (char)0xe2, (char)0x80, (char)0x9e, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x80, (char)0xa6, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x80, (char)0xa0, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x80, (char)0xa1, (char)0x0, (char)0x0, + (char)0x1, (char)0x20, (char)0x0, (char)0x0, (char)0x0, (char)0x0, // not part of this charset + (char)0x3, (char)0xe2, (char)0x80, (char)0xb0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc5, (char)0xa0, (char)0x0, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x80, (char)0xb9, (char)0x0, (char)0x0, + (char)0x2, (char)0xc5, (char)0x9a, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc5, (char)0xa4, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc5, (char)0xbd, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc5, (char)0xb9, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x20, (char)0x0, (char)0x0, (char)0x0, (char)0x0, // not part of this charset + (char)0x3, (char)0xe2, (char)0x80, (char)0x98, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x80, (char)0x99, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x80, (char)0x9c, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x80, (char)0x9d, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x80, (char)0xa2, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x80, (char)0x93, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x80, (char)0x94, (char)0x0, (char)0x0, + (char)0x1, (char)0x20, (char)0x0, (char)0x0, (char)0x0, (char)0x0, // not part of this charset + (char)0x3, (char)0xe2, (char)0x84, (char)0xa2, (char)0x0, (char)0x0, + (char)0x2, (char)0xc5, (char)0xa1, (char)0x0, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x80, (char)0xba, (char)0x0, (char)0x0, + (char)0x2, (char)0xc5, (char)0x9b, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc5, (char)0xa5, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc5, (char)0xbe, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc5, (char)0xba, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xa0, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xcb, (char)0x87, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xcb, (char)0x98, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc5, (char)0x81, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xa4, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc4, (char)0x84, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xa6, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xa7, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xa8, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xa9, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc5, (char)0x9e, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xab, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xac, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xad, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xae, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc5, (char)0xbb, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xb0, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xb1, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xcb, (char)0x9b, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc5, (char)0x82, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xb4, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xb5, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xb6, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xb7, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xb8, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc4, (char)0x85, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc5, (char)0x9f, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xbb, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc4, (char)0xbd, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xcb, (char)0x9d, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc4, (char)0xbe, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc5, (char)0xbc, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc5, (char)0x94, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x81, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x82, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc4, (char)0x82, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x84, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc4, (char)0xb9, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc4, (char)0x86, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x87, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc4, (char)0x8c, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x89, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc4, (char)0x98, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x8b, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc4, (char)0x9a, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x8d, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x8e, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc4, (char)0x8e, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc4, (char)0x90, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc5, (char)0x83, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc5, (char)0x87, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x93, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x94, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc5, (char)0x90, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x96, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x97, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc5, (char)0x98, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc5, (char)0xae, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x9a, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc5, (char)0xb0, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x9c, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x9d, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc5, (char)0xa2, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x9f, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc5, (char)0x95, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xa1, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xa2, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc4, (char)0x83, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xa4, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc4, (char)0xba, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc4, (char)0x87, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xa7, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc4, (char)0x8d, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xa9, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc4, (char)0x99, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xab, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc4, (char)0x9b, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xad, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xae, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc4, (char)0x8f, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc4, (char)0x91, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc5, (char)0x84, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc5, (char)0x88, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xb3, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xb4, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc5, (char)0x91, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xb6, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xb7, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc5, (char)0x99, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc5, (char)0xaf, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xba, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc5, (char)0xb1, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xbc, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xbd, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc5, (char)0xa3, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xcb, (char)0x99, (char)0x0, (char)0x0, (char)0x0 }; /// Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic /// and other languages static char windows_1251[] = { - 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x2, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x3, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x4, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x5, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x6, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x7, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x8, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x9, 0x0, 0x0, 0x0, 0x0, - 0x1, 0xa, 0x0, 0x0, 0x0, 0x0, - 0x1, 0xb, 0x0, 0x0, 0x0, 0x0, - 0x1, 0xc, 0x0, 0x0, 0x0, 0x0, - 0x1, 0xd, 0x0, 0x0, 0x0, 0x0, - 0x1, 0xe, 0x0, 0x0, 0x0, 0x0, - 0x1, 0xf, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x10, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x11, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x12, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x13, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x14, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x15, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x16, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x17, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x18, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x19, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x1a, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x1b, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x1c, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x1d, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x1e, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x1f, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x20, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x21, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x22, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x23, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x24, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x25, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x26, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x27, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x28, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x29, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x2a, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x2b, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x2c, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x2d, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x2e, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x2f, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x30, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x31, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x32, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x33, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x34, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x35, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x36, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x37, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x38, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x39, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x3a, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x3b, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x3c, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x3d, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x3e, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x3f, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x40, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x41, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x42, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x43, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x44, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x45, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x46, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x47, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x48, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x49, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x4a, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x4b, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x4c, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x4d, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x4e, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x4f, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x50, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x51, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x52, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x53, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x54, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x55, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x56, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x57, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x58, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x59, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x5a, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x5b, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x5c, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x5d, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x5e, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x5f, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x60, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x61, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x62, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x63, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x64, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x65, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x66, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x67, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x68, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x69, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x6a, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x6b, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x6c, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x6d, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x6e, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x6f, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x70, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x71, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x72, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x73, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x74, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x75, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x76, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x77, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x78, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x79, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x7a, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x7b, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x7c, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x7d, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x7e, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x7f, 0x0, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0x82, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0x83, 0x0, 0x0, 0x0, - 0x3, 0xe2, 0x80, 0x9a, 0x0, 0x0, - 0x2, 0xd1, 0x93, 0x0, 0x0, 0x0, - 0x3, 0xe2, 0x80, 0x9e, 0x0, 0x0, - 0x3, 0xe2, 0x80, 0xa6, 0x0, 0x0, - 0x3, 0xe2, 0x80, 0xa0, 0x0, 0x0, - 0x3, 0xe2, 0x80, 0xa1, 0x0, 0x0, - 0x3, 0xe2, 0x82, 0xac, 0x0, 0x0, - 0x3, 0xe2, 0x80, 0xb0, 0x0, 0x0, - 0x2, 0xd0, 0x89, 0x0, 0x0, 0x0, - 0x3, 0xe2, 0x80, 0xb9, 0x0, 0x0, - 0x2, 0xd0, 0x8a, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0x8c, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0x8b, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0x8f, 0x0, 0x0, 0x0, - 0x2, 0xd1, 0x92, 0x0, 0x0, 0x0, - 0x3, 0xe2, 0x80, 0x98, 0x0, 0x0, - 0x3, 0xe2, 0x80, 0x99, 0x0, 0x0, - 0x3, 0xe2, 0x80, 0x9c, 0x0, 0x0, - 0x3, 0xe2, 0x80, 0x9d, 0x0, 0x0, - 0x3, 0xe2, 0x80, 0xa2, 0x0, 0x0, - 0x3, 0xe2, 0x80, 0x93, 0x0, 0x0, - 0x3, 0xe2, 0x80, 0x94, 0x0, 0x0, - 0x1, 0x20, 0x0, 0x0, 0x0, 0x0, // not part of this charset - 0x3, 0xe2, 0x84, 0xa2, 0x0, 0x0, - 0x2, 0xd1, 0x99, 0x0, 0x0, 0x0, - 0x3, 0xe2, 0x80, 0xba, 0x0, 0x0, - 0x2, 0xd1, 0x9a, 0x0, 0x0, 0x0, - 0x2, 0xd1, 0x9c, 0x0, 0x0, 0x0, - 0x2, 0xd1, 0x9b, 0x0, 0x0, 0x0, - 0x2, 0xd1, 0x9f, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xa0, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0x8e, 0x0, 0x0, 0x0, - 0x2, 0xd1, 0x9e, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0x88, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xa4, 0x0, 0x0, 0x0, - 0x2, 0xd2, 0x90, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xa6, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xa7, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0x81, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xa9, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0x84, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xab, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xac, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xad, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xae, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0x87, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xb0, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xb1, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0x86, 0x0, 0x0, 0x0, - 0x2, 0xd1, 0x96, 0x0, 0x0, 0x0, - 0x2, 0xd2, 0x91, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xb5, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xb6, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xb7, 0x0, 0x0, 0x0, - 0x2, 0xd1, 0x91, 0x0, 0x0, 0x0, - 0x3, 0xe2, 0x84, 0x96, 0x0, 0x0, - 0x2, 0xd1, 0x94, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xbb, 0x0, 0x0, 0x0, - 0x2, 0xd1, 0x98, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0x85, 0x0, 0x0, 0x0, - 0x2, 0xd1, 0x95, 0x0, 0x0, 0x0, - 0x2, 0xd1, 0x97, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0x90, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0x91, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0x92, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0x93, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0x94, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0x95, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0x96, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0x97, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0x98, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0x99, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0x9a, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0x9b, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0x9c, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0x9d, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0x9e, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0x9f, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0xa0, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0xa1, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0xa2, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0xa3, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0xa4, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0xa5, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0xa6, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0xa7, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0xa8, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0xa9, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0xaa, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0xab, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0xac, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0xad, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0xae, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0xaf, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0xb0, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0xb1, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0xb2, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0xb3, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0xb4, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0xb5, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0xb6, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0xb7, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0xb8, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0xb9, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0xba, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0xbb, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0xbc, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0xbd, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0xbe, 0x0, 0x0, 0x0, - 0x2, 0xd0, 0xbf, 0x0, 0x0, 0x0, - 0x2, 0xd1, 0x80, 0x0, 0x0, 0x0, - 0x2, 0xd1, 0x81, 0x0, 0x0, 0x0, - 0x2, 0xd1, 0x82, 0x0, 0x0, 0x0, - 0x2, 0xd1, 0x83, 0x0, 0x0, 0x0, - 0x2, 0xd1, 0x84, 0x0, 0x0, 0x0, - 0x2, 0xd1, 0x85, 0x0, 0x0, 0x0, - 0x2, 0xd1, 0x86, 0x0, 0x0, 0x0, - 0x2, 0xd1, 0x87, 0x0, 0x0, 0x0, - 0x2, 0xd1, 0x88, 0x0, 0x0, 0x0, - 0x2, 0xd1, 0x89, 0x0, 0x0, 0x0, - 0x2, 0xd1, 0x8a, 0x0, 0x0, 0x0, - 0x2, 0xd1, 0x8b, 0x0, 0x0, 0x0, - 0x2, 0xd1, 0x8c, 0x0, 0x0, 0x0, - 0x2, 0xd1, 0x8d, 0x0, 0x0, 0x0, - 0x2, 0xd1, 0x8e, 0x0, 0x0, 0x0, - 0x2, 0xd1, 0x8f, 0x0, 0x0, 0x0 + (char)0x1, (char)0x0, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x1, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x2, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x3, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x4, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x5, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x6, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x7, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x8, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x9, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0xa, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0xb, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0xc, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0xd, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0xe, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0xf, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x10, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x11, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x12, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x13, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x14, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x15, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x16, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x17, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x18, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x19, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x1a, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x1b, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x1c, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x1d, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x1e, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x1f, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x20, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x21, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x22, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x23, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x24, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x25, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x26, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x27, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x28, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x29, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x2a, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x2b, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x2c, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x2d, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x2e, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x2f, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x30, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x31, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x32, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x33, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x34, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x35, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x36, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x37, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x38, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x39, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x3a, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x3b, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x3c, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x3d, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x3e, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x3f, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x40, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x41, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x42, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x43, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x44, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x45, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x46, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x47, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x48, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x49, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x4a, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x4b, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x4c, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x4d, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x4e, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x4f, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x50, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x51, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x52, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x53, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x54, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x55, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x56, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x57, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x58, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x59, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x5a, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x5b, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x5c, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x5d, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x5e, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x5f, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x60, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x61, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x62, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x63, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x64, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x65, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x66, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x67, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x68, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x69, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x6a, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x6b, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x6c, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x6d, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x6e, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x6f, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x70, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x71, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x72, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x73, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x74, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x75, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x76, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x77, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x78, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x79, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x7a, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x7b, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x7c, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x7d, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x7e, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x7f, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0x82, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0x83, (char)0x0, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x80, (char)0x9a, (char)0x0, (char)0x0, + (char)0x2, (char)0xd1, (char)0x93, (char)0x0, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x80, (char)0x9e, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x80, (char)0xa6, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x80, (char)0xa0, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x80, (char)0xa1, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x82, (char)0xac, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x80, (char)0xb0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0x89, (char)0x0, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x80, (char)0xb9, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0x8a, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0x8c, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0x8b, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0x8f, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd1, (char)0x92, (char)0x0, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x80, (char)0x98, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x80, (char)0x99, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x80, (char)0x9c, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x80, (char)0x9d, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x80, (char)0xa2, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x80, (char)0x93, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x80, (char)0x94, (char)0x0, (char)0x0, + (char)0x1, (char)0x20, (char)0x0, (char)0x0, (char)0x0, (char)0x0, // not part of this charset + (char)0x3, (char)0xe2, (char)0x84, (char)0xa2, (char)0x0, (char)0x0, + (char)0x2, (char)0xd1, (char)0x99, (char)0x0, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x80, (char)0xba, (char)0x0, (char)0x0, + (char)0x2, (char)0xd1, (char)0x9a, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd1, (char)0x9c, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd1, (char)0x9b, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd1, (char)0x9f, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xa0, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0x8e, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd1, (char)0x9e, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0x88, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xa4, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd2, (char)0x90, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xa6, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xa7, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0x81, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xa9, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0x84, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xab, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xac, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xad, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xae, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0x87, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xb0, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xb1, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0x86, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd1, (char)0x96, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd2, (char)0x91, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xb5, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xb6, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xb7, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd1, (char)0x91, (char)0x0, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x84, (char)0x96, (char)0x0, (char)0x0, + (char)0x2, (char)0xd1, (char)0x94, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xbb, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd1, (char)0x98, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0x85, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd1, (char)0x95, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd1, (char)0x97, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0x90, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0x91, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0x92, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0x93, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0x94, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0x95, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0x96, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0x97, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0x98, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0x99, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0x9a, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0x9b, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0x9c, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0x9d, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0x9e, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0x9f, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0xa0, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0xa1, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0xa2, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0xa3, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0xa4, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0xa5, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0xa6, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0xa7, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0xa8, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0xa9, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0xaa, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0xab, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0xac, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0xad, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0xae, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0xaf, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0xb0, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0xb1, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0xb2, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0xb3, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0xb4, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0xb5, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0xb6, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0xb7, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0xb8, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0xb9, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0xba, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0xbb, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0xbc, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0xbd, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0xbe, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd0, (char)0xbf, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd1, (char)0x80, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd1, (char)0x81, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd1, (char)0x82, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd1, (char)0x83, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd1, (char)0x84, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd1, (char)0x85, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd1, (char)0x86, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd1, (char)0x87, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd1, (char)0x88, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd1, (char)0x89, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd1, (char)0x8a, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd1, (char)0x8b, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd1, (char)0x8c, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd1, (char)0x8d, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd1, (char)0x8e, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xd1, (char)0x8f, (char)0x0, (char)0x0, (char)0x0 }; /// Latin alphabet used by English and some other Western languages static char windows_1252[] = { - 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x2, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x3, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x4, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x5, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x6, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x7, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x8, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x9, 0x0, 0x0, 0x0, 0x0, - 0x1, 0xa, 0x0, 0x0, 0x0, 0x0, - 0x1, 0xb, 0x0, 0x0, 0x0, 0x0, - 0x1, 0xc, 0x0, 0x0, 0x0, 0x0, - 0x1, 0xd, 0x0, 0x0, 0x0, 0x0, - 0x1, 0xe, 0x0, 0x0, 0x0, 0x0, - 0x1, 0xf, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x10, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x11, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x12, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x13, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x14, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x15, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x16, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x17, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x18, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x19, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x1a, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x1b, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x1c, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x1d, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x1e, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x1f, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x20, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x21, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x22, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x23, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x24, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x25, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x26, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x27, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x28, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x29, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x2a, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x2b, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x2c, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x2d, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x2e, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x2f, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x30, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x31, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x32, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x33, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x34, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x35, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x36, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x37, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x38, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x39, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x3a, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x3b, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x3c, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x3d, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x3e, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x3f, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x40, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x41, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x42, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x43, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x44, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x45, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x46, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x47, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x48, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x49, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x4a, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x4b, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x4c, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x4d, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x4e, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x4f, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x50, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x51, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x52, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x53, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x54, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x55, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x56, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x57, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x58, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x59, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x5a, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x5b, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x5c, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x5d, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x5e, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x5f, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x60, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x61, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x62, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x63, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x64, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x65, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x66, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x67, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x68, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x69, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x6a, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x6b, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x6c, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x6d, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x6e, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x6f, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x70, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x71, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x72, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x73, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x74, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x75, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x76, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x77, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x78, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x79, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x7a, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x7b, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x7c, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x7d, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x7e, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x7f, 0x0, 0x0, 0x0, 0x0, - 0x3, 0xe2, 0x82, 0xac, 0x0, 0x0, - 0x1, 0x20, 0x0, 0x0, 0x0, 0x0, // not part of this charset - 0x3, 0xe2, 0x80, 0x9a, 0x0, 0x0, - 0x2, 0xc6, 0x92, 0x0, 0x0, 0x0, - 0x3, 0xe2, 0x80, 0x9e, 0x0, 0x0, - 0x3, 0xe2, 0x80, 0xa6, 0x0, 0x0, - 0x3, 0xe2, 0x80, 0xa0, 0x0, 0x0, - 0x3, 0xe2, 0x80, 0xa1, 0x0, 0x0, - 0x2, 0xcb, 0x86, 0x0, 0x0, 0x0, - 0x3, 0xe2, 0x80, 0xb0, 0x0, 0x0, - 0x2, 0xc5, 0xa0, 0x0, 0x0, 0x0, - 0x3, 0xe2, 0x80, 0xb9, 0x0, 0x0, - 0x2, 0xc5, 0x92, 0x0, 0x0, 0x0, - 0x1, 0x20, 0x0, 0x0, 0x0, 0x0, // not part of this charset - 0x2, 0xc5, 0xbd, 0x0, 0x0, 0x0, - 0x1, 0x20, 0x0, 0x0, 0x0, 0x0, // not part of this charset - 0x1, 0x20, 0x0, 0x0, 0x0, 0x0, // not part of this charset - 0x3, 0xe2, 0x80, 0x98, 0x0, 0x0, - 0x3, 0xe2, 0x80, 0x99, 0x0, 0x0, - 0x3, 0xe2, 0x80, 0x9c, 0x0, 0x0, - 0x3, 0xe2, 0x80, 0x9d, 0x0, 0x0, - 0x3, 0xe2, 0x80, 0xa2, 0x0, 0x0, - 0x3, 0xe2, 0x80, 0x93, 0x0, 0x0, - 0x3, 0xe2, 0x80, 0x94, 0x0, 0x0, - 0x2, 0xcb, 0x9c, 0x0, 0x0, 0x0, - 0x3, 0xe2, 0x84, 0xa2, 0x0, 0x0, - 0x2, 0xc5, 0xa1, 0x0, 0x0, 0x0, - 0x3, 0xe2, 0x80, 0xba, 0x0, 0x0, - 0x2, 0xc5, 0x93, 0x0, 0x0, 0x0, - 0x1, 0x20, 0x0, 0x0, 0x0, 0x0, // not part of this charset - 0x2, 0xc5, 0xbe, 0x0, 0x0, 0x0, - 0x2, 0xc5, 0xb8, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xa0, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xa1, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xa2, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xa3, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xa4, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xa5, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xa6, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xa7, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xa8, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xa9, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xaa, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xab, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xac, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xad, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xae, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xaf, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xb0, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xb1, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xb2, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xb3, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xb4, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xb5, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xb6, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xb7, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xb8, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xb9, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xba, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xbb, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xbc, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xbd, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xbe, 0x0, 0x0, 0x0, - 0x2, 0xc2, 0xbf, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x80, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x81, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x82, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x83, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x84, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x85, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x86, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x87, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x88, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x89, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x8a, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x8b, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x8c, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x8d, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x8e, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x8f, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x90, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x91, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x92, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x93, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x94, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x95, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x96, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x97, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x98, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x99, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x9a, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x9b, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x9c, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x9d, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x9e, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0x9f, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xa0, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xa1, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xa2, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xa3, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xa4, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xa5, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xa6, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xa7, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xa8, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xa9, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xaa, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xab, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xac, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xad, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xae, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xaf, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xb0, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xb1, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xb2, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xb3, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xb4, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xb5, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xb6, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xb7, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xb8, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xb9, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xba, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xbb, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xbc, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xbd, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xbe, 0x0, 0x0, 0x0, - 0x2, 0xc3, 0xbf, 0x0, 0x0, 0x0 + (char)0x1, (char)0x0, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x1, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x2, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x3, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x4, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x5, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x6, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x7, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x8, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x9, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0xa, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0xb, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0xc, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0xd, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0xe, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0xf, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x10, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x11, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x12, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x13, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x14, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x15, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x16, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x17, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x18, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x19, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x1a, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x1b, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x1c, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x1d, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x1e, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x1f, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x20, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x21, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x22, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x23, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x24, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x25, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x26, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x27, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x28, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x29, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x2a, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x2b, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x2c, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x2d, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x2e, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x2f, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x30, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x31, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x32, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x33, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x34, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x35, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x36, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x37, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x38, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x39, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x3a, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x3b, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x3c, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x3d, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x3e, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x3f, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x40, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x41, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x42, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x43, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x44, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x45, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x46, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x47, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x48, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x49, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x4a, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x4b, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x4c, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x4d, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x4e, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x4f, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x50, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x51, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x52, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x53, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x54, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x55, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x56, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x57, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x58, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x59, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x5a, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x5b, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x5c, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x5d, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x5e, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x5f, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x60, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x61, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x62, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x63, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x64, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x65, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x66, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x67, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x68, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x69, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x6a, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x6b, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x6c, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x6d, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x6e, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x6f, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x70, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x71, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x72, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x73, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x74, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x75, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x76, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x77, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x78, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x79, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x7a, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x7b, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x7c, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x7d, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x7e, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x7f, (char)0x0, (char)0x0, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x82, (char)0xac, (char)0x0, (char)0x0, + (char)0x1, (char)0x20, (char)0x0, (char)0x0, (char)0x0, (char)0x0, // not part of this charset + (char)0x3, (char)0xe2, (char)0x80, (char)0x9a, (char)0x0, (char)0x0, + (char)0x2, (char)0xc6, (char)0x92, (char)0x0, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x80, (char)0x9e, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x80, (char)0xa6, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x80, (char)0xa0, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x80, (char)0xa1, (char)0x0, (char)0x0, + (char)0x2, (char)0xcb, (char)0x86, (char)0x0, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x80, (char)0xb0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc5, (char)0xa0, (char)0x0, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x80, (char)0xb9, (char)0x0, (char)0x0, + (char)0x2, (char)0xc5, (char)0x92, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x20, (char)0x0, (char)0x0, (char)0x0, (char)0x0, // not part of this charset + (char)0x2, (char)0xc5, (char)0xbd, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x20, (char)0x0, (char)0x0, (char)0x0, (char)0x0, // not part of this charset + (char)0x1, (char)0x20, (char)0x0, (char)0x0, (char)0x0, (char)0x0, // not part of this charset + (char)0x3, (char)0xe2, (char)0x80, (char)0x98, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x80, (char)0x99, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x80, (char)0x9c, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x80, (char)0x9d, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x80, (char)0xa2, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x80, (char)0x93, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x80, (char)0x94, (char)0x0, (char)0x0, + (char)0x2, (char)0xcb, (char)0x9c, (char)0x0, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x84, (char)0xa2, (char)0x0, (char)0x0, + (char)0x2, (char)0xc5, (char)0xa1, (char)0x0, (char)0x0, (char)0x0, + (char)0x3, (char)0xe2, (char)0x80, (char)0xba, (char)0x0, (char)0x0, + (char)0x2, (char)0xc5, (char)0x93, (char)0x0, (char)0x0, (char)0x0, + (char)0x1, (char)0x20, (char)0x0, (char)0x0, (char)0x0, (char)0x0, // not part of this charset + (char)0x2, (char)0xc5, (char)0xbe, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc5, (char)0xb8, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xa0, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xa1, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xa2, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xa3, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xa4, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xa5, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xa6, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xa7, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xa8, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xa9, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xaa, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xab, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xac, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xad, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xae, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xaf, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xb0, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xb1, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xb2, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xb3, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xb4, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xb5, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xb6, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xb7, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xb8, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xb9, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xba, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xbb, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xbc, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xbd, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xbe, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc2, (char)0xbf, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x80, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x81, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x82, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x83, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x84, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x85, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x86, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x87, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x88, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x89, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x8a, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x8b, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x8c, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x8d, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x8e, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x8f, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x90, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x91, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x92, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x93, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x94, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x95, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x96, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x97, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x98, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x99, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x9a, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x9b, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x9c, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x9d, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x9e, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0x9f, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xa0, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xa1, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xa2, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xa3, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xa4, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xa5, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xa6, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xa7, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xa8, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xa9, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xaa, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xab, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xac, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xad, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xae, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xaf, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xb0, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xb1, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xb2, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xb3, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xb4, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xb5, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xb6, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xb7, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xb8, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xb9, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xba, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xbb, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xbc, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xbd, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xbe, (char)0x0, (char)0x0, (char)0x0, + (char)0x2, (char)0xc3, (char)0xbf, (char)0x0, (char)0x0, (char)0x0 }; } diff --git a/files/CMakeLists.txt b/files/CMakeLists.txt new file mode 100644 index 000000000..8ab3d5b51 --- /dev/null +++ b/files/CMakeLists.txt @@ -0,0 +1,16 @@ +project(resources) + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/caustic_0.png "${OpenMW_BINARY_DIR}/resources/water/caustic_0.png" COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/underwater.cg "${OpenMW_BINARY_DIR}/resources/water/underwater.cg" COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/perlinvolume.dds "${OpenMW_BINARY_DIR}/resources/water/perlinvolume.dds" COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/water.compositor "${OpenMW_BINARY_DIR}/resources/water/water.compositor" COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/water.material "${OpenMW_BINARY_DIR}/resources/water/water.material" COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/WaterNormal2.tga "${OpenMW_BINARY_DIR}/resources/water/WaterNormal2.tga" COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/water.cg "${OpenMW_BINARY_DIR}/resources/water/water.cg" COPYONLY) + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/gbuffer/gbuffer.cg "${OpenMW_BINARY_DIR}/resources/gbuffer/gbuffer.cg" COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/gbuffer/gbuffer.material "${OpenMW_BINARY_DIR}/resources/gbuffer/gbuffer.material" COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/gbuffer/gbuffer.compositor "${OpenMW_BINARY_DIR}/resources/gbuffer/gbuffer.compositor" COPYONLY) + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/shadows/depthshadowcaster.material "${OpenMW_BINARY_DIR}/resources/shadows/depthshadowcaster.material" COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/shadows/depthshadowcaster.cg "${OpenMW_BINARY_DIR}/resources/shadows/depthshadowcaster.cg" COPYONLY) diff --git a/files/gbuffer/gbuffer.cg b/files/gbuffer/gbuffer.cg new file mode 100644 index 000000000..c7f2fe678 --- /dev/null +++ b/files/gbuffer/gbuffer.cg @@ -0,0 +1,18 @@ +void RenderScene_vs(in float4 position : POSITION + ,in float2 uv :TEXCOORD0 + ,uniform float4x4 wvp + ,out float4 oPosition : POSITION + ,out float2 oUV :TEXCOORD0) +{ + oPosition = mul(wvp, position); + oUV = uv; +} + +void RenderScene_ps(in float4 position : POSITION + ,in float2 uv :TEXCOORD0 + ,uniform sampler2D tex1 : TEXUNIT0 + ,out float4 oColor : COLOR) +{ + float4 scene =tex2D(tex1, uv); + oColor= scene; +} diff --git a/files/gbuffer/gbuffer.compositor b/files/gbuffer/gbuffer.compositor new file mode 100644 index 000000000..316003af6 --- /dev/null +++ b/files/gbuffer/gbuffer.compositor @@ -0,0 +1,95 @@ +// Compositor that just controls output to the MRT textures +compositor gbuffer +{ + technique + { + // MRT output. Currently this is a color texture plus a depth texture + texture mrt_output target_width target_height PF_FLOAT16_RGBA PF_FLOAT16_RGBA chain_scope depth_pool 2 + + target mrt_output + { + input none + pass clear + { + clear + { + // make sure to set this to the viewport background color from outside + colour_value 0 0 0 1 + } + } + pass render_scene + { + // Renders everything except water + first_render_queue 0 + last_render_queue 70 + } + + } + + target_output + { + input none + + pass render_quad + { + material RenderScene + input 0 mrt_output 0 + } + } + } +} + +// Finalizer compositor to render objects that we don't want in the MRT textures (ex. water) +// NB the water has to be rendered in a seperate compositor anyway, because it +// accesses the MRT textures which can't be done while they are still being rendered to. +compositor gbufferFinalizer +{ + technique + { + texture no_mrt_output target_width target_height PF_R8G8B8A8 depth_pool 2 no_fsaa + texture previousscene target_width target_height PF_R8G8B8A8 + + target previousscene + { + input previous + } + target no_mrt_output + { + input none + shadows off + pass clear + { + clear + { + buffers colour + colour_value 0 0 0 0 + } + } + pass render_quad + { + material RenderSceneNoDepth + input 0 previousscene + } + pass render_scene + { + first_render_queue 71 + last_render_queue 100 + } + } + target_output + { + input none + pass clear + { + clear + { + } + } + pass render_quad + { + material RenderSceneNoDepth + input 0 no_mrt_output + } + } + } +} diff --git a/files/gbuffer/gbuffer.material b/files/gbuffer/gbuffer.material new file mode 100644 index 000000000..faa8dd498 --- /dev/null +++ b/files/gbuffer/gbuffer.material @@ -0,0 +1,63 @@ +vertex_program RenderGBuffer_vs cg +{ + source gbuffer.cg + profiles vs_4_0 vs_1_1 arbvp1 + entry_point RenderScene_vs + default_params + { + param_named_auto wvp worldviewproj_matrix + } +} +fragment_program RenderGBuffer_ps cg +{ + source gbuffer.cg + entry_point RenderScene_ps + profiles ps_4_0 ps_2_x arbfp1 + default_params + { + } +} +material RenderScene +{ + technique + { + pass + { + vertex_program_ref RenderGBuffer_vs + { + } + + fragment_program_ref RenderGBuffer_ps + { + } + + texture_unit tex1 + { + //scenebuffer + } + } + } +} + +material RenderSceneNoDepth +{ + technique + { + pass + { + depth_write off + vertex_program_ref RenderGBuffer_vs + { + } + + fragment_program_ref RenderGBuffer_ps + { + } + + texture_unit tex1 + { + //scenebuffer + } + } + } +} diff --git a/files/launcher.qss b/files/launcher.qss index dad87022c..8be235f71 100644 --- a/files/launcher.qss +++ b/files/launcher.qss @@ -22,7 +22,7 @@ stop:0.9 rgba(0, 0, 0, 55), stop:1 rgba(0, 0, 0, 100)); - font: 24pt "Trebuchet MS"; + font: 26pt "EB Garamond"; color: black; border-right: 1px solid rgba(0, 0, 0, 155); @@ -54,7 +54,7 @@ } #ProfileLabel { - font: 14pt "Trebuchet MS"; + font: 18pt "EB Garamond"; } #ProfilesComboBox { @@ -82,7 +82,7 @@ padding-top: 3px; padding-left: 4px; - font: 11pt "Trebuchet MS"; + font: 12pt "EB Garamond"; } #ProfilesComboBox::drop-down { diff --git a/files/mac/openmw.icns b/files/mac/openmw.icns index dfea24660..3ff899a79 100644 Binary files a/files/mac/openmw.icns and b/files/mac/openmw.icns differ diff --git a/files/mygui/CMakeLists.txt b/files/mygui/CMakeLists.txt index c24bd59f4..69f6cbb74 100644 --- a/files/mygui/CMakeLists.txt +++ b/files/mygui/CMakeLists.txt @@ -6,10 +6,8 @@ set(DDIR ${OpenMW_BINARY_DIR}/resources/mygui) configure_file("${SDIR}/bigbars.png" "${DDIR}/bigbars.png" COPYONLY) configure_file("${SDIR}/black.png" "${DDIR}/black.png" COPYONLY) -configure_file("${SDIR}/Comic.TTF" "${DDIR}/Comic.TTF" COPYONLY) configure_file("${SDIR}/core.skin" "${DDIR}/core.skin" COPYONLY) configure_file("${SDIR}/core.xml" "${DDIR}/core.xml" COPYONLY) -configure_file("${SDIR}/mwpointer.png" "${DDIR}/mwpointer.png" COPYONLY) configure_file("${SDIR}/mwgui.png" "${DDIR}/mwgui.png" COPYONLY) configure_file("${SDIR}/openmw_images.xml" "${DDIR}/openmw_images.xml" COPYONLY) configure_file("${SDIR}/openmw_settings.xml" "${DDIR}/openmw_settings.xml" COPYONLY) @@ -55,4 +53,5 @@ configure_file("${SDIR}/openmw_journal_layout.xml" "${DDIR}/openmw_journal_layou configure_file("${SDIR}/openmw_journal_skin.xml" "${DDIR}/openmw_journal_skin.xml" COPYONLY) configure_file("${SDIR}/smallbars.png" "${DDIR}/smallbars.png" COPYONLY) configure_file("${SDIR}/transparent.png" "${DDIR}/transparent.png" COPYONLY) +configure_file("${SDIR}/EBGaramond-Regular.ttf" "${DDIR}/EBGaramond-Regular.ttf" COPYONLY) configure_file("${SDIR}/VeraMono.ttf" "${DDIR}/VeraMono.ttf" COPYONLY) diff --git a/files/mygui/Comic.TTF b/files/mygui/Comic.TTF deleted file mode 100644 index 309894baf..000000000 Binary files a/files/mygui/Comic.TTF and /dev/null differ diff --git a/files/mygui/EBGaramond-Regular.ttf b/files/mygui/EBGaramond-Regular.ttf new file mode 100644 index 000000000..dde486903 Binary files /dev/null and b/files/mygui/EBGaramond-Regular.ttf differ diff --git a/files/mygui/core.skin b/files/mygui/core.skin index 28838c234..e52080fe0 100644 --- a/files/mygui/core.skin +++ b/files/mygui/core.skin @@ -2,11 +2,11 @@ - - + + - + diff --git a/files/mygui/core.xml b/files/mygui/core.xml index 5bec13aef..7417328cf 100644 --- a/files/mygui/core.xml +++ b/files/mygui/core.xml @@ -1,28 +1,26 @@ - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/mygui/mwpointer.png b/files/mygui/mwpointer.png deleted file mode 100644 index 90bc19b5e..000000000 Binary files a/files/mygui/mwpointer.png and /dev/null differ diff --git a/files/mygui/openmw.font.xml b/files/mygui/openmw.font.xml index 454c3caec..e7d0f50c8 100644 --- a/files/mygui/openmw.font.xml +++ b/files/mygui/openmw.font.xml @@ -1,95 +1,40 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/files/mygui/openmw_button.skin.xml b/files/mygui/openmw_button.skin.xml index 0533a360f..1c6893026 100644 --- a/files/mygui/openmw_button.skin.xml +++ b/files/mygui/openmw_button.skin.xml @@ -45,10 +45,7 @@ - - - - + diff --git a/files/mygui/openmw_console.skin.xml b/files/mygui/openmw_console.skin.xml index 1c8740ede..598252734 100644 --- a/files/mygui/openmw_console.skin.xml +++ b/files/mygui/openmw_console.skin.xml @@ -2,8 +2,6 @@ - - diff --git a/files/mygui/openmw_dialogue_window_layout.xml b/files/mygui/openmw_dialogue_window_layout.xml index 6e833004b..29a3b511e 100644 --- a/files/mygui/openmw_dialogue_window_layout.xml +++ b/files/mygui/openmw_dialogue_window_layout.xml @@ -1,35 +1,29 @@ - - - - - - + - + - - + - - + + - + - + diff --git a/files/mygui/openmw_edit.skin.xml b/files/mygui/openmw_edit.skin.xml index 609dfe2c8..a86317d62 100644 --- a/files/mygui/openmw_edit.skin.xml +++ b/files/mygui/openmw_edit.skin.xml @@ -11,8 +11,7 @@ - - + @@ -33,8 +32,7 @@ - - + @@ -51,7 +49,7 @@ - + diff --git a/files/mygui/openmw_hud_layout.xml b/files/mygui/openmw_hud_layout.xml index 20370770e..2dafa7298 100644 --- a/files/mygui/openmw_hud_layout.xml +++ b/files/mygui/openmw_hud_layout.xml @@ -11,22 +11,24 @@ align="Left Bottom" name="Stamina"/> - - + + + + + - - - + + + + + - - diff --git a/files/mygui/openmw_images.xml b/files/mygui/openmw_images.xml index 6487742c3..e149273e2 100644 --- a/files/mygui/openmw_images.xml +++ b/files/mygui/openmw_images.xml @@ -2,37 +2,37 @@ - + - + - + - + - + - + - + - + - + diff --git a/files/mygui/openmw_interactive_messagebox_layout.xml b/files/mygui/openmw_interactive_messagebox_layout.xml index 4ef2243d4..744f21227 100644 --- a/files/mygui/openmw_interactive_messagebox_layout.xml +++ b/files/mygui/openmw_interactive_messagebox_layout.xml @@ -3,14 +3,13 @@ + + + - - - - diff --git a/files/mygui/openmw_journal_skin.xml b/files/mygui/openmw_journal_skin.xml index a0d6ee2e8..0ef87852f 100644 --- a/files/mygui/openmw_journal_skin.xml +++ b/files/mygui/openmw_journal_skin.xml @@ -14,13 +14,13 @@ - + - + diff --git a/files/mygui/openmw_list.skin.xml b/files/mygui/openmw_list.skin.xml index 98390367c..0ac8e03ba 100644 --- a/files/mygui/openmw_list.skin.xml +++ b/files/mygui/openmw_list.skin.xml @@ -167,8 +167,7 @@ - - + @@ -177,10 +176,9 @@ - - + - + diff --git a/files/mygui/openmw_map_window_layout.xml b/files/mygui/openmw_map_window_layout.xml index f5c2c9991..fbba8ddf4 100644 --- a/files/mygui/openmw_map_window_layout.xml +++ b/files/mygui/openmw_map_window_layout.xml @@ -1,7 +1,7 @@ - + diff --git a/files/mygui/openmw_messagebox_layout.xml b/files/mygui/openmw_messagebox_layout.xml index 244f58c99..81d1c0a57 100644 --- a/files/mygui/openmw_messagebox_layout.xml +++ b/files/mygui/openmw_messagebox_layout.xml @@ -6,14 +6,13 @@ + + + - - - - diff --git a/files/mygui/openmw_progress.skin.xml b/files/mygui/openmw_progress.skin.xml index a5fbfb0a3..c4b94e28e 100644 --- a/files/mygui/openmw_progress.skin.xml +++ b/files/mygui/openmw_progress.skin.xml @@ -19,11 +19,10 @@ - - + - + diff --git a/files/mygui/openmw_settings.xml b/files/mygui/openmw_settings.xml index ca62294de..c63f962fb 100644 --- a/files/mygui/openmw_settings.xml +++ b/files/mygui/openmw_settings.xml @@ -1,9 +1,9 @@ - - + + diff --git a/files/mygui/openmw_stats_window_layout.xml b/files/mygui/openmw_stats_window_layout.xml index 1380d474c..fd99f863e 100644 --- a/files/mygui/openmw_stats_window_layout.xml +++ b/files/mygui/openmw_stats_window_layout.xml @@ -1,7 +1,7 @@ - + diff --git a/files/mygui/openmw_text.skin.xml b/files/mygui/openmw_text.skin.xml index d62a5b8c0..6ae14c558 100644 --- a/files/mygui/openmw_text.skin.xml +++ b/files/mygui/openmw_text.skin.xml @@ -2,40 +2,35 @@ - - + - - + - - + - - + - - + @@ -45,8 +40,7 @@ - - + diff --git a/files/mygui/openmw_windows.skin.xml b/files/mygui/openmw_windows.skin.xml index ea8eb5330..7c194ea5d 100644 --- a/files/mygui/openmw_windows.skin.xml +++ b/files/mygui/openmw_windows.skin.xml @@ -189,14 +189,7 @@ - - - + @@ -221,8 +214,7 @@ ------------------------------------------------------ --> - - + @@ -297,13 +289,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + - + diff --git a/files/mygui/test.skin b/files/mygui/test.skin deleted file mode 100644 index 5f198ab4a..000000000 --- a/files/mygui/test.skin +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/files/openmw.desktop b/files/openmw.desktop index 8643d4b13..234f660c6 100644 --- a/files/openmw.desktop +++ b/files/openmw.desktop @@ -1,5 +1,5 @@ [Desktop Entry] -Version=0.11 +Version=${OPENMW_VERSION} Type=Application Name=OpenMW Launcher GenericName=Role Playing Game diff --git a/files/settings-default.cfg b/files/settings-default.cfg new file mode 100644 index 000000000..553a82e49 --- /dev/null +++ b/files/settings-default.cfg @@ -0,0 +1,109 @@ +# WARNING: Editing this file might have no effect, as these +# settings are overwritten by your user settings file. + +[General] +# Camera field of view +field of view = 55 + +# Texture filtering mode. valid values: +# none +# anisotropic +# bilinear +# trilinear +texture filtering = anisotropic + +# Has no effect when texture filtering is not anisotropic +anisotropy = 4 + +# Number of texture mipmaps to generate +num mipmaps = 5 + +[Shadows] +# Shadows are only supported when object shaders are on! +enabled = false + +# Split the shadow maps, allows for a larger shadow distance +# Warning: enabling this will cause some terrain textures to disappear due to +# hitting the texture unit limit of the terrain material +split = false + +# Increasing shadow distance will lower the shadow quality. +# Uses "shadow distance" or "split shadow distance" depending on "split" setting. +shadow distance = 1300 +# This one shouldn't be too low, otherwise you'll see artifacts. Use at least 2x max viewing distance. +split shadow distance = 14000 + +# Size of the shadow textures, higher means higher quality +texture size = 1024 + +# Turn on/off various shadow casters +actor shadows = true +misc shadows = true +statics shadows = true + +# Fraction of the total shadow distance after which the shadow starts to fade out +fade start = 0.8 + +[HUD] +# FPS counter +# 0: not visible +# 1: basic FPS display +# 2: advanced FPS display (batches, triangles) +fps = 0 + +[Objects] +shaders = true + +# Max. number of lights that affect objects. Setting to 1 will only reflect sunlight +# Note: has no effect when shaders are turned off +num lights = 8 + +# Use static geometry for static objects. Improves rendering speed. +use static geometry = true + +[Viewing distance] +# Limit the rendering distance of small objects +limit small object distance = false + +# Size below which an object is considered as small +small object size = 250 + +# Rendering distance for small objects +small object distance = 3500 + +# Max viewing distance at clear weather conditions +max viewing distance = 5600 + +# Distance at which fog starts (proportional to viewing distance) +fog start factor = 0.5 + +# Distance at which fog ends (proportional to viewing distance) +fog end factor = 1.0 + +[Terrain] +# Max. number of lights that affect the terrain. Setting to 1 will only reflect sunlight +num lights = 8 + +[Water] +# Enable this to get fancy-looking water with reflections and refractions +# All the settings below have no effect if this is false +shader = true + +rtt size = 512 +reflect terrain = true +reflect statics = false +reflect small statics = false +reflect actors = false +reflect misc = false + +# Enable underwater effect. It is not resource intensive, so only disable it if you have problems. +underwater effect = true + +[Sound] +# Device name. Blank means default +device = + +# Volumes. Sfx and music volumes are both affected by the master volume +master volume = 1.0 +sfx volume = 1.0 +music volume = 0.4 diff --git a/files/shadows/depthshadowcaster.cg b/files/shadows/depthshadowcaster.cg new file mode 100644 index 000000000..3457a4f8d --- /dev/null +++ b/files/shadows/depthshadowcaster.cg @@ -0,0 +1,51 @@ +void main_vp( + float4 position : POSITION, + float2 uv : TEXCOORD0, + + out float4 oPosition : POSITION, + out float2 oDepth : TEXCOORD0, + out float2 oUv : TEXCOORD1, + + uniform float4x4 wvpMat) +{ + // this is the view space position + oPosition = mul(wvpMat, position); + + // depth info for the fragment. + oDepth.x = oPosition.z; + oDepth.y = oPosition.w; + + // clamp z to zero. seem to do the trick. :-/ + oPosition.z = max(oPosition.z, 0); + + oUv = uv; +} + +void main_fp( + float2 depth : TEXCOORD0, + float2 uv : TEXCOORD1, + uniform sampler2D texture1 : register(s0), + + out float4 oColour : COLOR) +{ + float finalDepth = depth.x / depth.y; + + // use alpha channel of the first texture + float alpha = tex2D(texture1, uv).a; + + // discard if alpha is less than 0.5 + clip((alpha >= 0.5) ? 1 : -1); + + oColour = float4(finalDepth, finalDepth, finalDepth, 1); +} + +void main_fp_noalpha( + float2 depth : TEXCOORD0, + float2 uv : TEXCOORD1, + + out float4 oColour : COLOR) +{ + float finalDepth = depth.x / depth.y; + + oColour = float4(finalDepth, finalDepth, finalDepth, 1); +} diff --git a/files/shadows/depthshadowcaster.material b/files/shadows/depthshadowcaster.material new file mode 100644 index 000000000..9ff51c5b1 --- /dev/null +++ b/files/shadows/depthshadowcaster.material @@ -0,0 +1,67 @@ +vertex_program depth_shadow_caster_vs cg +{ + source depthshadowcaster.cg + profiles vs_1_1 arbvp1 + entry_point main_vp + + default_params + { + param_named_auto wvpMat worldviewproj_matrix + } +} + +fragment_program depth_shadow_caster_ps cg +{ + source depthshadowcaster.cg + profiles ps_2_0 arbfp1 + entry_point main_fp + + default_params + { + } +} + +fragment_program depth_shadow_caster_ps_noalpha cg +{ + source depthshadowcaster.cg + profiles ps_2_0 arbfp1 + entry_point main_fp_noalpha + + default_params + { + } +} + +material depth_shadow_caster +{ + technique + { + pass + { + vertex_program_ref depth_shadow_caster_vs + { + } + + fragment_program_ref depth_shadow_caster_ps + { + } + } + } +} + +material depth_shadow_caster_noalpha +{ + technique + { + pass + { + vertex_program_ref depth_shadow_caster_vs + { + } + + fragment_program_ref depth_shadow_caster_ps_noalpha + { + } + } + } +} diff --git a/files/transparency-overrides.cfg b/files/transparency-overrides.cfg new file mode 100644 index 000000000..299792be1 --- /dev/null +++ b/files/transparency-overrides.cfg @@ -0,0 +1,574 @@ +# Bethesda has used wrong transparency settings for many textures +# (who would have guessed) +# This is very unfortunate because objects with real transparency: +# - cannot cast shadows +# - cannot receive advanced framebuffer effects like depth of field or ambient occlusion +# - cannot cover lens flare effects (the lens flare will just shine through) + +# This file lists textures that should be using alpha rejection instead of transparency +# basically these are textures that are not translucent (i.e. at one spot on the texture, either transparent or opaque) + +# Note: all the texture names here have to be lowercase + +# fauna +[textures\tx_wickwheat_01.dds] + alphaRejectValue = 128 + +[textures\tx_wickwheat_03.dds] + alphaRejectValue = 128 + +[textures\tx_red_lichen_01.dds] + alphaRejectValue = 128 + +[textures\tx_stone_flower_01.dds] + alphaRejectValue = 128 + +[textures\tx_ivy_02.dds] + alphaRejectValue = 128 + +[textures\tx_ivy_01.dds] + alphaRejectValue = 128 + +[textures\tx_saltrice_04.dds] + alphaRejectValue = 128 + +[textures\tx_black_lichen_01.dds] + alphaRejectValue = 128 + +[textures\tx_leaves_01.dds] + alphaRejectValue = 128 + +[textures\tx_leaves_02.dds] + alphaRejectValue = 128 + +[textures\tx_leaves_03.dds] + alphaRejectValue = 128 + +[textures\tx_leaves_04.dds] + alphaRejectValue = 128 + +[textures\tx_leaves_06.dds] + alphaRejectValue = 128 + +[textures\tx_leaves_07.dds] + alphaRejectValue = 128 + +[textures\tx_ai_heather_01.dds] + alphaRejectValue = 96 + +[textures\tx_goldkanet_01.dds] + alphaRejectValue = 128 + +[textures\tx_goldkanet_02.dds] + alphaRejectValue = 128 + +[textures\tx_plant_tails00.dds] + alphaRejectValue = 128 + +[textures\tx_vine_01.dds] + alphaRejectValue = 128 + +[textures\tx_comberry_01.dds] + alphaRejectValue = 128 + +[textures\tx_willow_flower_02.dds] + alphaRejectValue = 128 + +[textures\tx_cork_bulb_02.dds] + alphaRejectValue = 128 + +[textures\tx_green_lichen_01.dds] + alphaRejectValue = 128 + +[textures\tx_roobrush_01.dds] + alphaRejectValue = 128 + +[textures\tx_bittergreen_02.dds] + alphaRejectValue = 128 + +[textures\tx_chokeweed_01.dds] + alphaRejectValue = 128 + +[textures\tx_branches_01.dds] + alphaRejectValue = 128 + +[textures\tx_branches_02.dds] + alphaRejectValue = 128 + +[textures\tx_guarskin_hut_03.dds] + alphaRejectValue = 128 + +[textures\tx_hackle-lo_02.dds] + alphaRejectValue = 128 + +[textures\tx_bc_fern_01.dds] + alphaRejectValue = 128 + +[textures\tx_bc_fern_02.dds] + alphaRejectValue = 128 + +[textures\tx_bc_leaves_02.dds] + alphaRejectValue = 128 + +[textures\tx_marshmerrow_03.dds] + alphaRejectValue = 128 + +[textures\tx_bc_moss_01.dds] + alphaRejectValue = 128 + +[textures\tx_bc_moss_02.dds] + alphaRejectValue = 128 + +[textures\tx_bc_lilypad_01.dds] + alphaRejectValue = 128 + +[textures\tx_bc_lilypad_02.dds] + alphaRejectValue = 128 + +[textures\tx_bc_lilypad_03.dds] + alphaRejectValue = 128 + +[textures\tx_fire_fern_01.dds] + alphaRejectValue = 128 + +# banners and flags +[textures\tx_flag_imp_01.dds] + alphaRejectValue = 128 + +[textures\tx_v_b_arena_01.dds] + alphaRejectValue = 128 + +[textures\tx_v_b_comfort_01.dds] + alphaRejectValue = 128 + +[textures\tx_v_b_child_01.dds] + alphaRejectValue = 128 + +[textures\tx_v_b_count_01.dds] + alphaRejectValue = 128 + +[textures\tx_v_b_faith_01.dds] + alphaRejectValue = 128 + +[textures\tx_v_b_walk_01.dds] + alphaRejectValue = 128 + +[textures\tx_v_b_imp_01.dds] + alphaRejectValue = 128 + +[textures\tx_v_b_redoran_01.dds] + alphaRejectValue = 128 + +[textures\tx_v_b_avs_01.dds] + alphaRejectValue = 128 + +[textures\tx_v_b_serving_01.dds] + alphaRejectValue = 128 + +[textures\tx_v_b_speak_01.dds] + alphaRejectValue = 128 + +[textures\tx_v_b_stdeyln_01.dds] + alphaRejectValue = 128 + +[textures\tx_v_b_stolms_01.dds] + alphaRejectValue = 128 + +[textures\tx_v_b_thin_01.dds] + alphaRejectValue = 128 + +[textures\tx_v_b_vivec_01.dds] + alphaRejectValue = 128 + +[textures\tx_v_b_vivec_02.dds] + alphaRejectValue = 128 + +[textures\tx_ashl_banner_01.dds] + alphaRejectValue = 128 + +[textures\tx_ashl_banner_02.dds] + alphaRejectValue = 128 + +[textures\tx_ashl_banner_04.dds] + alphaRejectValue = 128 + +[textures\tx_ashl_banner_05.dds] + alphaRejectValue = 128 + +[textures\tx_ashl_banner_06.dds] + alphaRejectValue = 128 + +[textures\tx_ashl_banner_07.dds] + alphaRejectValue = 128 + +[textures\tx_ashl_a_banner.dds] + alphaRejectValue = 128 + +[textures\tx_ashl_e_banner.dds] + alphaRejectValue = 128 + +[textures\tx_ashl_u_banner.dds] + alphaRejectValue = 128 + +[textures\tx_ashl_z_banner.dds] + alphaRejectValue = 128 + +[textures\tx_banner_6th.dds] + alphaRejectValue = 128 + +[textures\tx_banner_6th_tall.dds] + alphaRejectValue = 128 + +[textures\tx_banner_gnisis_01.dds] + alphaRejectValue = 128 + +[textures\tx_banner_gnisis_02.dds] + alphaRejectValue = 128 + +[textures\tx_de_tapestry_bhm_01.dds] + alphaRejectValue = 128 + +[textures\tx_de_tapestry_02.dds] + alphaRejectValue = 128 + +[textures\tx_de_tapestry_03.dds] + alphaRejectValue = 128 + +[textures\tx_de_tapestry_04.dds] + alphaRejectValue = 128 + +[textures\tx_de_tapestry_05.dds] + alphaRejectValue = 128 + +[textures\tx_de_tapestry_06.dds] + alphaRejectValue = 128 + +[textures\tx_de_tapestry_07.dds] + alphaRejectValue = 128 + +[textures\tx_de_tapestry_08.dds] + alphaRejectValue = 128 + +[textures\tx_de_tapestry_08.dds] + alphaRejectValue = 128 + +[textures\tx_de_tapestry_09.dds] + alphaRejectValue = 128 + +[textures\tx_de_tapestry_10.dds] + alphaRejectValue = 128 + +[textures\tx_de_tapestry_11.dds] + alphaRejectValue = 128 + +[textures\tx_de_tapestry_12.dds] + alphaRejectValue = 128 + +[textures\tx_de_tapestry_13.dds] + alphaRejectValue = 128 + +[textures\tx_de_lutestrings_01.dds] + alphaRejectValue = 128 + +[textures\tx_fabric_imp_altar_01.dds] + alphaRejectValue = 128 + +[textures\tx_c_t_akatosh_01.dds] + alphaRejectValue = 128 + +[textures\tx_c_t_apprentice_01.dds] + alphaRejectValue = 128 + +[textures\tx_c_t_arkay_01.dds] + alphaRejectValue = 128 + +[textures\tx_c_t_dibella_01.dds] + alphaRejectValue = 128 + +[textures\tx_c_t_golem_01.dds] + alphaRejectValue = 128 + +[textures\tx_c_t_julianos_01.dds] + alphaRejectValue = 128 + +[textures\tx_c_t_kynareth_01.dds] + alphaRejectValue = 128 + +[textures\tx_c_t_lady_01.dds] + alphaRejectValue = 128 + +[textures\tx_c_t_lord_01.dds] + alphaRejectValue = 128 + +[textures\tx_c_t_lover_01.dds] + alphaRejectValue = 128 + +[textures\tx_c_t_mara_01.dds] + alphaRejectValue = 128 + +[textures\tx_c_t_ritual_01.dds] + alphaRejectValue = 128 + +[textures\tx_c_t_shadow_01.dds] + alphaRejectValue = 128 + +[textures\tx_c_t_steed_01.dds] + alphaRejectValue = 128 + +[textures\tx_c_t_stendarr_01.dds] + alphaRejectValue = 128 + +[textures\tx_c_t_thief_01.dds] + alphaRejectValue = 128 + +[textures\tx_c_t_tower_01.dds] + alphaRejectValue = 128 + +[textures\tx_c_t_warrior_01.dds] + alphaRejectValue = 128 + +[textures\tx_c_t_wizard_01.dds] + alphaRejectValue = 128 + +[textures\tx_c_t_zenithar_01.dds] + alphaRejectValue = 128 + +[textures\tx_banner_dagoth_01.dds] + alphaRejectValue = 128 + +[textures\tx_bannerd_tavern_01.dds] + alphaRejectValue = 128 + +[textures\tx_bannerd_goods_01.dds] + alphaRejectValue = 128 + +[textures\tx_bannerd_danger_01.dds] + alphaRejectValue = 128 + +[textures\tx_bannerd_welcome_01.dds] + alphaRejectValue = 128 + +[textures\tx_bannerd_clothing_01.dds] + alphaRejectValue = 128 + +[textures\tx_bannerd_alchemy_01.dds] + alphaRejectValue = 128 + +[textures\tx_banner_hlaalu_01.dds] + alphaRejectValue = 128 + +[textures\tx_banner_redoran_01.dds] + alphaRejectValue = 128 + +[textures\tx_banner_temple_01.dds] + alphaRejectValue = 128 + +[textures\tx_banner_temple_03.dds] + alphaRejectValue = 128 + +[textures\tx_de_banner_book_01.dds] + alphaRejectValue = 128 + +[textures\tx_de_banner_ald_velothi.dds] + alphaRejectValue = 128 + +[textures\tx_de_banner_gnaar_mok.dds] + alphaRejectValue = 128 + +[textures\tx_de_banner_hla_oad.dds] + alphaRejectValue = 128 + +[textures\tx_de_banner_khull.dds] + alphaRejectValue = 128 + +[textures\tx_de_banner_pawn_01.dds] + alphaRejectValue = 128 + +[textures\tx_de_banner_sadrith_mora.dds] + alphaRejectValue = 128 + +[textures\tx_de_banner_tel_aruhn.dds] + alphaRejectValue = 128 + +[textures\tx_de_banner_tel_branora.dds] + alphaRejectValue = 128 + +[textures\tx_de_banner_tel_fyr.dds] + alphaRejectValue = 128 + +[textures\tx_de_banner_tel_mora.dds] + alphaRejectValue = 128 + +[textures\tx_de_banner_telvani_01.dds] + alphaRejectValue = 128 + +[textures\tx_de_banner_tel_vos.dds] + alphaRejectValue = 128 + +[textures\tx_de_banner_vos.dds] + alphaRejectValue = 128 + +[textures\tx_bannerd_w_a_shop_01.dds] + alphaRejectValue = 128 + +[textures\tx_banner_temple_02.dds] + alphaRejectValue = 128 + +[textures\tx_mural1_00.dds] + alphaRejectValue = 128 + +[textures\tx_mural1_01.dds] + alphaRejectValue = 128 + +[textures\tx_mural4_00.dds] + alphaRejectValue = 128 + +[textures\tx_mural4_01.dds] + alphaRejectValue = 128 + +[textures\tx_mural5_00.dds] + alphaRejectValue = 128 + +[textures\tx_v_b_telvanni_01.dds] + alphaRejectValue = 128 + +[textures\tx_v_b_hlaalu_01.dds] + alphaRejectValue = 128 + +[textures\tx_fabric_tapestry.dds] + alphaRejectValue = 128 + +[textures\tx_fabric_tapestry_01.dds] + alphaRejectValue = 128 + +[textures\tx_fabric_tapestry_02.dds] + alphaRejectValue = 128 + +[textures\tx_fabric_tapestry_03.dds] + alphaRejectValue = 128 + +[textures\tx_fabric_tapestry_04.dds] + alphaRejectValue = 128 + +# characters +[textures\tx_netchgod00.dds] + alphaRejectValue = 128 + +[textures\tx_b_n_argonian_f_hair02.dds] + alphaRejectValue = 128 + +[textures\tx_b_n_argonian_f_hair03.dds] + alphaRejectValue = 128 + +[textures\tx_b_n_argonian_m_hair01.dds] + alphaRejectValue = 128 + +[textures\tx_b_n_argonian_m_hair04.dds] + alphaRejectValue = 128 + +[textures\tx_b_n_argonian_m_hair05.dds] + alphaRejectValue = 128 + +[textures\tx_b_n_khajiit_f_hair01.dds] + alphaRejectValue = 128 + +[textures\tx_b_n_khajiit_f_hair02.dds] + alphaRejectValue = 128 + +[textures\tx_b_n_khajiit_m_hair01.dds] + alphaRejectValue = 128 + +[textures\tx_corprus_stalker12.dds] + alphaRejectValue = 128 + +[textures\tx_a_clavicus02.dds] + alphaRejectValue = 128 + +[textures\tx_b_n_dark elf_m_hair11.dds] + alphaRejectValue = 128 + +[textures\tx_b_n_dark elf_f_hair10.dds] + alphaRejectValue = 128 + +# misc items +[textures\tx_sail.dds] + alphaRejectValue = 128 + +[textures\tx_longboatsail01.dds] + alphaRejectValue = 128 + +[textures\tx_longboatsail01a.dds] + alphaRejectValue = 128 + +[textures\tx_longboatsail01b.dds] + alphaRejectValue = 128 + +[textures\tx_longboatsail02.dds] + alphaRejectValue = 128 + +[textures\tx_quill.dds] + alphaRejectValue = 128 + +[textures\tx_note_01.dds] + alphaRejectValue = 128 + +[textures\tx_note_02.dds] + alphaRejectValue = 128 + +[textures\tx_parchment_02.dds] + alphaRejectValue = 128 + +[textures\tx_parchment_03.dds] + alphaRejectValue = 128 + +[textures\tx_scroll_01.dds] + alphaRejectValue = 128 + +[textures\tx_scroll_02.dds] + alphaRejectValue = 128 + +[textures\tx_scroll_03.dds] + alphaRejectValue = 128 + +[textures\tx_alpha_small_edge.dds] + alphaRejectValue = 128 + +[textures\tx_alpha_shadow_circular.dds] + alphaRejectValue = 128 + +# building materials +[textures\tx_shack_thatch_strip.dds] + alphaRejectValue = 128 + +[textures\tx_rug00.dds] + alphaRejectValue = 128 + +[textures\tx_rug_02.dds] + alphaRejectValue = 128 + +[textures\tx_rug_edge_01.dds] + alphaRejectValue = 128 + +[textures\tx_awning_thatch_02.dds] + alphaRejectValue = 128 + +[textures\tx_awning_woven_01.dds] + alphaRejectValue = 128 + +[textures\tx_bridgeropes.dds] + alphaRejectValue = 128 + +[textures\tx_rope_woven_01.dds] + alphaRejectValue = 128 + +[textures\tx_rope_woven_02.dds] + alphaRejectValue = 128 + +[textures\tx_ashl_tent_06.dds] + alphaRejectValue = 128 + +[textures\tx_guar_tarp.dds] + alphaRejectValue = 128 + +[textures\tx_velothi_glyph00.dds] + alphaRejectValue = 128 diff --git a/files/water/WaterNormal2.tga b/files/water/WaterNormal2.tga new file mode 100644 index 000000000..771d15041 Binary files /dev/null and b/files/water/WaterNormal2.tga differ diff --git a/files/water/caustic_0.png b/files/water/caustic_0.png new file mode 100644 index 000000000..fee464860 Binary files /dev/null and b/files/water/caustic_0.png differ diff --git a/files/water/perlinvolume.dds b/files/water/perlinvolume.dds new file mode 100644 index 000000000..bd8147d49 Binary files /dev/null and b/files/water/perlinvolume.dds differ diff --git a/files/water/underwater.cg b/files/water/underwater.cg new file mode 100644 index 000000000..b853dd535 --- /dev/null +++ b/files/water/underwater.cg @@ -0,0 +1,61 @@ +void main_vp +( + in float4 inPos : POSITION, + + out float4 pos : POSITION, + out float2 uv0 : TEXCOORD0, + out float4 noiseCoord : TEXCOORD1, + + uniform float4x4 worldViewProj, + uniform float timeVal, + uniform float scale +) +{ + // Use standardise transform, so work accord with render system specific (RS depth, requires texture flipping, etc) + pos = mul(worldViewProj, inPos); + + // The input positions adjusted by texel offsets, so clean up inaccuracies + inPos.xy = sign(inPos.xy); + + // Convert to image-space + uv0 = (float2(inPos.x, -inPos.y) + 1.0f) * 0.5f; + noiseCoord = (pos + timeVal) * scale; +} + + + +float4 main_fp_nomrt (float2 iTexCoord : TEXCOORD0, + float3 noiseCoord : TEXCOORD1, + uniform sampler2D RT : register(s0), + uniform sampler2D NormalMap : register(s1), + uniform sampler2D CausticMap : register(s2), + uniform float4 tintColour) : COLOR +{ + float4 normal = tex2D(NormalMap, noiseCoord) * 2 - 1; + + return tex2D(RT, iTexCoord + normal.xy * 0.015) + + (tex2D(CausticMap, noiseCoord) / 5) + + tintColour ; +} + + +float4 main_fp (float2 iTexCoord : TEXCOORD0, + float3 noiseCoord : TEXCOORD1, + uniform float far, + uniform sampler2D RT : register(s0), + uniform sampler2D NormalMap : register(s1), + uniform sampler2D CausticMap : register(s2), + uniform sampler2D DepthMap : register(s3), + uniform float4 tintColour) : COLOR +{ + float4 normal = tex2D(NormalMap, noiseCoord) * 2 - 1; + + float depth = tex2D(DepthMap, iTexCoord + normal.xy * 0.015).r * far; + depth = saturate(depth / 2000.f); + + float4 color = tex2D(RT, iTexCoord + normal.xy * 0.015) + + (tex2D(CausticMap, noiseCoord) / 5) + + tintColour; + + return lerp(color, float4(0, 0.65, 0.65, 1), depth); +} diff --git a/files/water/water.cg b/files/water/water.cg new file mode 100644 index 000000000..bf6d04c5c --- /dev/null +++ b/files/water/water.cg @@ -0,0 +1,121 @@ +void main_vp +( + in float4 iPos : POSITION + , in float2 iUv : TEXCOORD0 + + , out float4 oPos : POSITION + , out float3 oScreenCoords : TEXCOORD0 + , out float2 oUv : TEXCOORD1 + , out float oDepth : TEXCOORD2 + , out float4 oEyeVector : TEXCOORD3 + + , uniform float4x4 wvpMat + , uniform float4 camPosObjSpace +) +{ + oPos = mul(wvpMat, iPos); + + oUv = iUv * 10; // uv scale + oDepth = oPos.z; + + float4x4 scalemat = float4x4( 0.5, 0, 0, 0.5, + 0, -0.5, 0, 0.5, + 0, 0, 0.5, 0.5, + 0, 0, 0, 1 ); + float4 texcoordProj = mul(scalemat, oPos); + oScreenCoords = float3(texcoordProj.x, texcoordProj.y, texcoordProj.w); + + oEyeVector = camPosObjSpace - iPos; +} + +void main_fp +( + out float4 oColor : COLOR + + , in float3 iScreenCoords : TEXCOORD0 + , in float2 iUv : TEXCOORD1 + , in float iDepth : TEXCOORD2 + , in float4 iEyeVector : TEXCOORD3 + , uniform float renderTargetFlipping + , uniform float4 lightPosObjSpace0 + , uniform float4 lightSpecularColour0 + + , uniform sampler2D reflectionMap : register(s0) + , uniform sampler2D refractionMap : register(s1) + , uniform sampler2D depthMap : register(s2) + , uniform sampler2D normalMap : register(s3) + , uniform float time + , uniform float far + , uniform float4 fogParams + , uniform float4 fogColour + , uniform float isUnderwater +) +{ + + float2 screenCoords = iScreenCoords.xy / iScreenCoords.z; + screenCoords.y = (1-saturate(renderTargetFlipping))+renderTargetFlipping*screenCoords.y; + + // No need for transparency since we are using a refraction map + oColor.a = 1; + + // Sample screen-space depth map and subtract pixel depth to get the real water depth + float depthTex = tex2D(depthMap, screenCoords).r; + float depth1 = depthTex * far - iDepth; + depth1 = saturate(depth1 / 500.f); + + // Simple wave effect. to be replaced by something better + float2 uv1 = iUv + time * float2(0.5, 0); + float2 uv2 = iUv + time * float2(0, 0.5); + float2 uv3 = iUv + time * float2(-0.5, 0); + float2 uv4 = iUv + time * float2(0, -0.5); + float4 normal = tex2D(normalMap, uv1) + tex2D(normalMap, uv2) + tex2D(normalMap, uv3) + tex2D(normalMap, uv4); + normal = normal / 4.f; + normal = 2*normal - 1; + + float2 screenCoords_reflect = screenCoords + normal.yx * 0.05; + float2 screenCoords_refract = screenCoords + normal.yx * 0.05 * depth1; + + // Sample depth again with the refracted coordinates + depthTex = tex2D(depthMap, screenCoords_refract).r; + float depth2 = (depthTex * far - iDepth) / 500.f; + depth2 = (depthTex == 0 ? 1 : depth2); + // if depth2 is less than 0, this means we would refract something which is above water, + // which we don't want to - so in that case, don't refract + if (depth2 < 0.25) // delta due to inaccuracies + { + screenCoords_refract = screenCoords; + depth2 = depth1; + } + depth2 = saturate(depth2); + + float4 reflection = tex2D(reflectionMap, screenCoords_reflect); + float4 refraction = tex2D(refractionMap, screenCoords_refract); + + // tangent to object space + normal.xyz = normal.xzy; + + iEyeVector.xyz = normalize(iEyeVector.xyz); + + // fresnel + float facing = 1.0 - max(abs(dot(iEyeVector.xyz, normal.xyz)), 0); + float reflectionFactor = saturate(0.35 + 0.65 * pow(facing, 2)); + + // specular + float3 lightDir = normalize(lightPosObjSpace0.xyz); // assumes that light 0 is a directional light + float3 halfVector = normalize(iEyeVector + lightDir); + float specular = pow(max(dot(normal.xyz, halfVector.xyz), 0), 64); + + float opacity = depth2 * saturate(reflectionFactor + specular); + opacity *= (1-isUnderwater); + + reflection.xyz += lightSpecularColour0.xyz * specular; + + oColor.xyz = lerp(refraction.xyz, reflection.xyz, opacity); + + oColor.xyz += isUnderwater * float3(0, 0.35, 0.35); // underwater tint color + oColor.xyz = lerp(oColor.xyz, float3(0, 0.65, 0.65), saturate(isUnderwater * (iDepth / 2000.f))); // underwater fog + + // add fog + //float fogValue = saturate((iDepth - fogParams.y) * fogParams.w); + //oColor.xyz = lerp(oColor.xyz, fogColour, fogValue); +} diff --git a/files/water/water.compositor b/files/water/water.compositor new file mode 100644 index 000000000..8d9c3cb39 --- /dev/null +++ b/files/water/water.compositor @@ -0,0 +1,46 @@ +compositor UnderwaterNoMRT +{ + technique + { + texture rt0 target_width target_height PF_R8G8B8 + + target rt0 { input previous } + + target_output + { + // Start with clear output + input none + + pass render_quad + { + material Water/CompositorNoMRT + input 0 rt0 + } + } + } +} + + +compositor Underwater +{ + technique + { + texture_ref scene gbuffer mrt_output + texture rt0 target_width target_height PF_R8G8B8 + + target rt0 { input previous } + + target_output + { + // Start with clear output + input none + + pass render_quad + { + material Water/Compositor + input 0 rt0 + input 3 scene 1 + } + } + } +} diff --git a/files/water/water.material b/files/water/water.material new file mode 100644 index 000000000..8b4ff96f5 --- /dev/null +++ b/files/water/water.material @@ -0,0 +1,198 @@ +vertex_program UnderwaterEffectVP cg +{ + source underwater.cg + entry_point main_vp + profiles vs_1_1 arbvp1 + + default_params + { + param_named_auto worldViewProj worldviewproj_matrix + } +} + + +fragment_program UnderwaterEffectFP_NoMRT cg +{ + source underwater.cg + entry_point main_fp_nomrt + profiles ps_2_0 arbfp1 +} + +fragment_program UnderwaterEffectFP cg +{ + source underwater.cg + entry_point main_fp + profiles ps_2_0 arbfp1 +} + +vertex_program Water_VP cg +{ + source water.cg + entry_point main_vp + profiles vs_2_x arbvp1 + + default_params + { + param_named_auto wvpMat worldviewproj_matrix + } +} + +fragment_program Water_FP cg +{ + source water.cg + entry_point main_fp + profiles ps_2_x arbfp1 +} + +material Water +{ + technique + { + pass + { + cull_hardware none + + vertex_program_ref Water_VP + { + param_named_auto camPosObjSpace camera_position_object_space + } + fragment_program_ref Water_FP + { + param_named_auto time time 0.1 + //param_named_auto fogColour fog_colour + //param_named_auto fogParams fog_params + param_named_auto renderTargetFlipping render_target_flipping + param_named_auto far far_clip_distance + param_named_auto lightPosObjSpace0 light_position_object_space 0 + param_named_auto lightSpecularColour0 light_specular_colour 0 + param_named isUnderwater float 0 + } + + texture_unit reflectionMap + { + texture WaterReflection + tex_address_mode clamp + } + + texture_unit refractionMap + { + tex_address_mode clamp + } + + texture_unit depthMap + { + tex_address_mode clamp + } + + texture_unit normalMap + { + texture WaterNormal2.tga + } + } + } + technique + { + scheme Fallback + pass + { + cull_hardware none + scene_blend alpha_blend + depth_write off + diffuse 0 0 0 1 + emissive 0.6 0.7 1.0 + ambient 0 0 0 + texture_unit + { + // texture names set via code + scale 0.1 0.1 + alpha_op_ex source1 src_manual src_current 0.7 + } + } + } +} + +material Water/CompositorNoMRT +{ + technique + { + pass + { + depth_check off + vertex_program_ref UnderwaterEffectVP + { + param_named_auto timeVal time 0.25 + param_named scale float 0.1 + } + + fragment_program_ref UnderwaterEffectFP_NoMRT + { + param_named tintColour float4 0 0.35 0.35 1 + } + + texture_unit RT + { + tex_coord_set 0 + tex_address_mode clamp + filtering linear linear linear + } + + texture_unit + { + texture WaterNormal2.tga 2d + tex_coord_set 1 + //tex_address_mode clamp + filtering linear linear linear + } + texture_unit + { + texture caustic_0.png 2d + tex_coord_set 2 + //tex_address_mode clamp + filtering linear linear linear + } + } + } +} + +material Water/Compositor +{ + technique + { + pass + { + depth_check off + vertex_program_ref UnderwaterEffectVP + { + param_named_auto timeVal time 0.25 + param_named scale float 0.1 + } + + fragment_program_ref UnderwaterEffectFP + { + param_named tintColour float4 0 0.35 0.35 1 + param_named_auto far far_clip_distance + } + + texture_unit RT + { + tex_coord_set 0 + tex_address_mode clamp + } + + texture_unit + { + texture WaterNormal2.tga 2d + tex_coord_set 2 + } + texture_unit + { + texture caustic_0.png 2d + tex_coord_set 3 + } + + texture_unit DepthMap + { + } + } + } +} diff --git a/libs/mangle/sound/.gitignore b/libs/mangle/sound/.gitignore deleted file mode 100644 index 8b1378917..000000000 --- a/libs/mangle/sound/.gitignore +++ /dev/null @@ -1 +0,0 @@ - diff --git a/libs/mangle/sound/clients/ogre_listener_mover.hpp b/libs/mangle/sound/clients/ogre_listener_mover.hpp deleted file mode 100644 index 74c21db32..000000000 --- a/libs/mangle/sound/clients/ogre_listener_mover.hpp +++ /dev/null @@ -1,79 +0,0 @@ -#ifndef MANGLE_SOUND_OGRELISTENERMOVER_H -#define MANGLE_SOUND_OGRELISTENERMOVER_H - -#include -#include -#include "../output.hpp" - -namespace Mangle { -namespace Sound { - - /** This class lets a sound listener (ie. the SoundFactory) track a - given camera in Ogre3D. The position and orientation of the - listener will be updated to match the camera whenever the camera - is moved. - */ - struct OgreListenerMover : Ogre::Camera::Listener - { - OgreListenerMover(Mangle::Sound::SoundFactoryPtr snd) - : soundFact(snd), camera(NULL) - {} - - /// Follow a camera. WARNING: This will OVERRIDE any other - /// MovableObject::Listener you may have attached to the camera. - void followCamera(Ogre::Camera *cam) - { - camera = cam; - camera->addListener(this); - } - - void unfollowCamera() - { - // If the camera is null, this object wasn't following a camera. - // It doesn't make sense to call unfollow - assert(camera != NULL); - - camera->removeListener(this); - camera = NULL; - } - - private: - Mangle::Sound::SoundFactoryPtr soundFact; - Ogre::Camera *camera; - Ogre::Vector3 pos, dir, up; - - /// From Camera::Listener. This is called once per - /// frame. Unfortunately, Ogre doesn't allow us to be notified - /// only when the camera itself has moved, so we must poll every - /// frame. - void cameraPreRenderScene(Ogre::Camera *cam) - { - assert(cam == camera); - - Ogre::Vector3 nPos, nDir, nUp; - - nPos = camera->getRealPosition(); - nDir = camera->getRealDirection(); - nUp = camera->getRealUp(); - - // Don't bother the sound system needlessly - if(nDir != dir || nPos != pos || nUp != up) - { - pos = nPos; - dir = nDir; - up = nUp; - - soundFact->setListenerPos(pos.x, pos.y, pos.z, - dir.x, dir.y, dir.z, - up.x, up.y, up.z); - } - } - - void cameraDestroyed(Ogre::Camera *cam) - { - assert(cam == camera); - camera = NULL; - } - }; -}} -#endif diff --git a/libs/mangle/sound/clients/ogre_output_updater.hpp b/libs/mangle/sound/clients/ogre_output_updater.hpp deleted file mode 100644 index b73168c75..000000000 --- a/libs/mangle/sound/clients/ogre_output_updater.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef MANGLE_SOUND_OGREUPDATER_H -#define MANGLE_SOUND_OGREUPDATER_H - -/* - This Ogre FrameListener calls update on a SoundFactory - */ - -#include -#include "../output.hpp" -#include - -namespace Mangle { -namespace Sound { - - struct OgreOutputUpdater : Ogre::FrameListener - { - Mangle::Sound::SoundFactoryPtr driver; - - OgreOutputUpdater(Mangle::Sound::SoundFactoryPtr drv) - : driver(drv) - { assert(drv->needsUpdate); } - - bool frameStarted(const Ogre::FrameEvent &evt) - { - driver->update(); - return true; - } - }; -}} - -#endif diff --git a/libs/mangle/sound/filters/input_filter.hpp b/libs/mangle/sound/filters/input_filter.hpp deleted file mode 100644 index 00ee18766..000000000 --- a/libs/mangle/sound/filters/input_filter.hpp +++ /dev/null @@ -1,68 +0,0 @@ -#ifndef MANGLE_INPUT_FILTER_H -#define MANGLE_INPUT_FILTER_H - -#include "../output.hpp" - -#include - -namespace Mangle { -namespace Sound { - -/** - @brief This filter class adds file loading capabilities to a - Sound::SoundFactory class, by associating a SampleSourceLoader with - it. - - The class takes an existing SoundFactory able to load streams, and - associates a SampleSourceLoader with it. The combined class is able - to load files directly. */ -class InputFilter : public SoundFactory -{ - protected: - SoundFactoryPtr snd; - SampleSourceLoaderPtr inp; - - public: - /// Empty constructor - InputFilter() {} - - /// Assign an input manager and a sound manager to this object - InputFilter(SoundFactoryPtr _snd, SampleSourceLoaderPtr _inp) - { set(_snd, _inp); } - - /// Assign an input manager and a sound manager to this object - void set(SoundFactoryPtr _snd, SampleSourceLoaderPtr _inp) - { - inp = _inp; - snd = _snd; - - // Set capabilities - needsUpdate = snd->needsUpdate; - has3D = snd->has3D; - canLoadStream = inp->canLoadStream; - - // Both these should be true, or the use of this class is pretty - // pointless - canLoadSource = snd->canLoadSource; - canLoadFile = inp->canLoadFile; - assert(canLoadSource && canLoadFile); - } - - virtual SoundPtr load(const std::string &file) - { return loadRaw(inp->load(file)); } - - virtual SoundPtr load(Stream::StreamPtr input) - { return loadRaw(inp->load(input)); } - - virtual SoundPtr loadRaw(SampleSourcePtr input) - { return snd->loadRaw(input); } - - virtual void update() { snd->update(); } - virtual void setListenerPos(float x, float y, float z, - float fx, float fy, float fz, - float ux, float uy, float uz) - { snd->setListenerPos(x,y,z,fx,fy,fz,ux,uy,uz); } -}; - -}} -#endif diff --git a/libs/mangle/sound/filters/openal_audiere.hpp b/libs/mangle/sound/filters/openal_audiere.hpp deleted file mode 100644 index 5b9b51824..000000000 --- a/libs/mangle/sound/filters/openal_audiere.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef MANGLE_AUDIERE_OPENAL_H -#define MANGLE_AUDIERE_OPENAL_H - -#include "input_filter.hpp" -#include "../sources/audiere_source.hpp" -#include "../outputs/openal_out.hpp" - -namespace Mangle { -namespace Sound { - -/// A InputFilter that adds audiere decoding to OpenAL. Audiere has -/// it's own output, but OpenAL sports 3D and other advanced features. -class OpenAL_Audiere_Factory : public InputFilter -{ - public: - OpenAL_Audiere_Factory() - { - set(SoundFactoryPtr(new OpenAL_Factory), - SampleSourceLoaderPtr(new AudiereLoader)); - } -}; - -}} -#endif diff --git a/libs/mangle/sound/filters/openal_ffmpeg.hpp b/libs/mangle/sound/filters/openal_ffmpeg.hpp deleted file mode 100644 index 42c76af0c..000000000 --- a/libs/mangle/sound/filters/openal_ffmpeg.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef MANGLE_FFMPEG_OPENAL_H -#define MANGLE_FFMPEG_OPENAL_H - -#include "input_filter.hpp" -#include "../sources/ffmpeg_source.hpp" -#include "../outputs/openal_out.hpp" - -namespace Mangle { -namespace Sound { - -/// A InputFilter that adds ffmpeg decoding to OpenAL. -class OpenAL_FFMpeg_Factory : public InputFilter -{ - public: - OpenAL_FFMpeg_Factory() - { - set(SoundFactoryPtr(new OpenAL_Factory), - SampleSourceLoaderPtr(new FFMpegLoader)); - } -}; - -}} -#endif diff --git a/libs/mangle/sound/filters/openal_mpg123.hpp b/libs/mangle/sound/filters/openal_mpg123.hpp deleted file mode 100644 index bfd926c0b..000000000 --- a/libs/mangle/sound/filters/openal_mpg123.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef MANGLE_MPG123_OPENAL_H -#define MANGLE_MPG123_OPENAL_H - -#include "input_filter.hpp" -#include "../sources/mpg123_source.hpp" -#include "../outputs/openal_out.hpp" - -namespace Mangle { -namespace Sound { - -/// A InputFilter that adds mpg123 decoding to OpenAL. Only supports -/// MP3 files. -class OpenAL_Mpg123_Factory : public InputFilter -{ - public: - OpenAL_Mpg123_Factory() - { - set(SoundFactoryPtr(new OpenAL_Factory), - SampleSourceLoaderPtr(new Mpg123Loader)); - } -}; - -}} -#endif diff --git a/libs/mangle/sound/filters/openal_sndfile.hpp b/libs/mangle/sound/filters/openal_sndfile.hpp deleted file mode 100644 index fd7e78025..000000000 --- a/libs/mangle/sound/filters/openal_sndfile.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef MANGLE_SNDFILE_OPENAL_H -#define MANGLE_SNDFILE_OPENAL_H - -#include "input_filter.hpp" -#include "../sources/libsndfile.hpp" -#include "../outputs/openal_out.hpp" - -namespace Mangle { -namespace Sound { - -/// A InputFilter that adds libsnd decoding to OpenAL. libsndfile -/// supports most formats except MP3. -class OpenAL_SndFile_Factory : public InputFilter -{ - public: - OpenAL_SndFile_Factory() - { - set(SoundFactoryPtr(new OpenAL_Factory), - SampleSourceLoaderPtr(new SndFileLoader)); - } -}; - -}} -#endif diff --git a/libs/mangle/sound/filters/openal_sndfile_mpg123.hpp b/libs/mangle/sound/filters/openal_sndfile_mpg123.hpp deleted file mode 100644 index 6e5db4d0e..000000000 --- a/libs/mangle/sound/filters/openal_sndfile_mpg123.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef MANGLE_SNDFILE_MPG123_OPENAL_H -#define MANGLE_SNDFILE_MPG123_OPENAL_H - -#include "input_filter.hpp" -#include "source_splicer.hpp" -#include "../sources/mpg123_source.hpp" -#include "../sources/libsndfile.hpp" -#include "../outputs/openal_out.hpp" - -namespace Mangle { -namespace Sound { - -/// A InputFilter that uses OpenAL for output, and mpg123 (for MP3) + -/// libsndfile (for everything else) to decode files. Can only load -/// from the file system, and uses the file name to differentiate -/// between mp3 and non-mp3 types. -class OpenAL_SndFile_Mpg123_Factory : public InputFilter -{ - public: - OpenAL_SndFile_Mpg123_Factory() - { - SourceSplicer *splice = new SourceSplicer; - - splice->add("mp3", SampleSourceLoaderPtr(new Mpg123Loader)); - splice->setDefault(SampleSourceLoaderPtr(new SndFileLoader)); - - set(SoundFactoryPtr(new OpenAL_Factory), - SampleSourceLoaderPtr(splice)); - } -}; - -}} -#endif diff --git a/libs/mangle/sound/filters/openal_various.hpp b/libs/mangle/sound/filters/openal_various.hpp deleted file mode 100644 index 945b3dabd..000000000 --- a/libs/mangle/sound/filters/openal_various.hpp +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef MANGLE_VARIOUS_OPENAL_H -#define MANGLE_VARIOUS_OPENAL_H - -#include "input_filter.hpp" -#include "source_splicer.hpp" -#include "../sources/mpg123_source.hpp" -#include "../sources/wav_source.hpp" -#include "../outputs/openal_out.hpp" - -namespace Mangle { -namespace Sound { - -/** A InputFilter that uses OpenAL for output, and load input from - various individual sources, depending on file extension. Currently - supports: - - MP3: mpg123 - WAV: custom wav loader (PCM only) - - This could be an alternative to using eg. 3rd party decoder - libraries like libsndfile. - */ -class OpenAL_Various_Factory : public InputFilter -{ - public: - OpenAL_Various_Factory() - { - SourceSplicer *splice = new SourceSplicer; - - splice->add("mp3", SampleSourceLoaderPtr(new Mpg123Loader)); - splice->add("wav", SampleSourceLoaderPtr(new WavLoader)); - - set(SoundFactoryPtr(new OpenAL_Factory), - SampleSourceLoaderPtr(splice)); - } -}; - -}} -#endif diff --git a/libs/mangle/sound/filters/pure_filter.hpp b/libs/mangle/sound/filters/pure_filter.hpp deleted file mode 100644 index fc5e62574..000000000 --- a/libs/mangle/sound/filters/pure_filter.hpp +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef MANGLE_SOUND_OUTPUT_PUREFILTER_H -#define MANGLE_SOUND_OUTPUT_PUREFILTER_H - -#include "../output.hpp" - -namespace Mangle -{ - namespace Sound - { - // For use in writing other filters - class SoundFilter : public Sound - { - protected: - SoundPtr client; - - public: - SoundFilter(SoundPtr c) : client(c) {} - void play() { client->play(); } - void stop() { client->stop(); } - void pause() { client->pause(); } - bool isPlaying() const { return client->isPlaying(); } - void setVolume(float f) { client->setVolume(f); } - void setPan(float f) { client->setPan(f); } - void setPos(float x, float y, float z) - { client->setPos(x,y,z); } - void setPitch(float p) { client->setPitch(p); } - void setRepeat(bool b) { client->setRepeat(b); } - void setRange(float a, float b=0, float c=0) - { client->setRange(a,b,c); } - void setStreaming(bool b) { client->setStreaming(b); } - void setRelative(bool b) { client->setRelative(b); } - - // The clone() function is not implemented here, as you will - // almost certainly want to override it yourself - }; - - class FactoryFilter : public SoundFactory - { - protected: - SoundFactoryPtr client; - - public: - FactoryFilter(SoundFactoryPtr c) : client(c) - { - needsUpdate = client->needsUpdate; - has3D = client->has3D; - canLoadFile = client->canLoadFile; - canLoadStream = client->canLoadStream; - canLoadSource = client->canLoadSource; - } - - SoundPtr loadRaw(SampleSourcePtr input) - { return client->loadRaw(input); } - - SoundPtr load(Stream::StreamPtr input) - { return client->load(input); } - - SoundPtr load(const std::string &file) - { return client->load(file); } - - void update() - { client->update(); } - - void setListenerPos(float x, float y, float z, - float fx, float fy, float fz, - float ux, float uy, float uz) - { - client->setListenerPos(x,y,z,fx,fy,fz,ux,uy,uz); - } - }; - } -} -#endif diff --git a/libs/mangle/sound/filters/source_splicer.hpp b/libs/mangle/sound/filters/source_splicer.hpp deleted file mode 100644 index 9c7623086..000000000 --- a/libs/mangle/sound/filters/source_splicer.hpp +++ /dev/null @@ -1,90 +0,0 @@ -#ifndef MANGLE_SOUND_SOURCE_SPLICE_H -#define MANGLE_SOUND_SOURCE_SPLICE_H - -#include "../source.hpp" -#include -#include -#include -#include - -namespace Mangle -{ - namespace Sound - { - class SourceSplicer : public SampleSourceLoader - { - struct SourceType - { - std::string type; - SampleSourceLoaderPtr loader; - }; - - typedef std::list TypeList; - TypeList list; - SampleSourceLoaderPtr catchAll; - - static bool isMatch(char a, char b) - { - if(a >= 'A' && a <= 'Z') - a += 'a' - 'A'; - if(b >= 'A' && b <= 'Z') - b += 'a' - 'A'; - return a == b; - } - - public: - SourceSplicer() - { - canLoadStream = false; - canLoadFile = true; - } - - void add(const std::string &type, SampleSourceLoaderPtr fact) - { - SourceType tp; - tp.type = type; - tp.loader = fact; - list.push_back(tp); - } - - void setDefault(SampleSourceLoaderPtr def) - { - catchAll = def; - } - - SampleSourcePtr load(const std::string &file) - { - // Search the list for this file type. - for(TypeList::iterator it = list.begin(); - it != list.end(); it++) - { - const std::string &t = it->type; - - int diff = file.size() - t.size(); - if(diff < 0) continue; - - bool match = true; - for(unsigned i=0; iloader->load(file); - } - // If not found, use the catch-all - if(catchAll) - return catchAll->load(file); - - throw std::runtime_error("No handler for sound file " + file); - } - - SampleSourcePtr load(Stream::StreamPtr input) { assert(0); } - }; - } -} - -#endif diff --git a/libs/mangle/sound/output.hpp b/libs/mangle/sound/output.hpp deleted file mode 100644 index e30bf21e2..000000000 --- a/libs/mangle/sound/output.hpp +++ /dev/null @@ -1,183 +0,0 @@ -#ifndef MANGLE_SOUND_OUTPUT_H -#define MANGLE_SOUND_OUTPUT_H - -#include -#include - -#include "source.hpp" -#include "../stream/stream.hpp" - -namespace Mangle { -namespace Sound { - -/// Abstract interface for a single playable sound -/** This class represents one sound outlet, which may be played, - stopped, paused and so on. - - Sound instances are created from the SoundFactory class. Sounds - may be connected to a SampleSource or read directly from a file, - and they may support 3d sounds, looping and other features - depending on the capabilities of the backend system. - - To create multiple instances of one sound, it is recommended to - 'clone' an existing instance instead of reloading it from - file. Cloned sounds will often (depending on the back-end) use - less memory due to shared buffers. -*/ -class Sound; -typedef boost::shared_ptr SoundPtr; -typedef boost::weak_ptr WSoundPtr; - -class Sound -{ - public: - /// Play or resume the sound - virtual void play() = 0; - - /// Stop the sound - virtual void stop() = 0; - - /// Pause the sound, may be resumed later - virtual void pause() = 0; - - /// Check if the sound is still playing - virtual bool isPlaying() const = 0; - - /// Set the volume. The parameter must be between 0.0 and 1.0. - virtual void setVolume(float) = 0; - - /// Set left/right pan. -1.0 is left, 0.0 is center and 1.0 is right. - virtual void setPan(float) = 0; - - /// Set pitch (1.0 is normal speed) - virtual void setPitch(float) = 0; - - /// Set range factors for 3D sounds. The meaning of the fields - /// depend on implementation. - virtual void setRange(float a, float b=0.0, float c=0.0) = 0; - - /// Set the position. May not work with all backends. - virtual void setPos(float x, float y, float z) = 0; - - /// Set loop mode - virtual void setRepeat(bool) = 0; - - /// If set to true the sound will not be affected by player movement - virtual void setRelative(bool) = 0; - - /// Set streaming mode. - /** This may be used by implementations to optimize for very large - files. If streaming mode is off (default), most implementations - will load the entire file into memory before starting playback. - */ - virtual void setStreaming(bool) = 0; - - /// Create a new instance of this sound. - /** Playback status is not cloned, only the sound data - itself. Back-ends can use this as a means of sharing data and - saving memory. */ - virtual SoundPtr clone() = 0; - - /// Virtual destructor - virtual ~Sound() {} -}; - -/// Factory interface for creating Sound objects -/** The SoundFactory is the main entry point to a given sound output - system. It is used to create Sound objects, which may be connected - to a sound file or stream, and which may be individually played, - paused, and so on. - - The class also contains a set of public bools which describe the - capabilities the particular system. These should be set by - implementations (base classes) in their respective constructors. - */ -class SoundFactory -{ - public: - /// Virtual destructor - virtual ~SoundFactory() {} - - /** @brief If set to true, you should call update() regularly (every frame - or so) on this sound manager. If false, update() should not be - called. - */ - bool needsUpdate; - - /** @brief true if 3D functions are available. If false, all use of - 3D sounds and calls to setPos / setListenerPos will result in - undefined behavior. - */ - bool has3D; - - /// true if we can load sounds directly from file (containing encoded data) - bool canLoadFile; - - /// If true, we can lound sound files from a Stream (containing encoded data) - bool canLoadStream; - - /// true if we can load sounds from a SampleSource (containing raw data) - bool canLoadSource; - - /** - @brief Load a sound from a sample source. Only valid if - canLoadSource is true. - - This function loads a sound from a given stream as defined by - SampleSource. - - @param input the input source - @param stream true if the file should be streamed. - Implementations may use this for optimizing playback of - large files, but they are not required to. - @return a new Sound object - */ - virtual SoundPtr loadRaw(SampleSourcePtr input) = 0; - - /** - @brief Load a sound file from stream. Only valid if canLoadStream - is true. - - @param input audio file stream - @param stream true if the file should be streamed - @see load(InputSource*,bool) - */ - virtual SoundPtr load(Stream::StreamPtr input) = 0; - - /** - @brief Load a sound directly from file. Only valid if canLoadFile - is true. - - @param file filename - @param stream true if the file should be streamed - @see load(InputSource*,bool) - */ - virtual SoundPtr load(const std::string &file) = 0; - - /// Call this every frame if needsUpdate is true - /** - This should be called regularly (about every frame in a normal - game setting.) Implementions may use this for filling streaming - buffers and similar tasks. Implementations that do not need this - should set needsUpdate to false. - */ - virtual void update() { assert(0); } - - /// Set listener position (coordinates, front and up vectors) - /** - Only valid if has3D is true. - - @param x,y,z listener position - @param fx,fy,fz listener's looking direction - @param ux,uy,uz listener's up direction - */ - virtual void setListenerPos(float x, float y, float z, - float fx, float fy, float fz, - float ux, float uy, float uz) = 0; -}; - -typedef boost::shared_ptr SoundFactoryPtr; - -}} // Namespaces - -#endif diff --git a/libs/mangle/sound/outputs/openal_out.cpp b/libs/mangle/sound/outputs/openal_out.cpp deleted file mode 100644 index 2056b4f60..000000000 --- a/libs/mangle/sound/outputs/openal_out.cpp +++ /dev/null @@ -1,500 +0,0 @@ -#include "openal_out.hpp" -#include -#include - -#include "../../stream/filters/buffer_stream.hpp" - -#ifdef _WIN32 -#include -#include -#elif defined(__APPLE__) -#include -#include -#else -#include -#include -#endif - -using namespace Mangle::Sound; - -// ---- Helper functions and classes ---- - -// Static buffer used to shuffle sound data from the input into -// OpenAL. The data is only stored temporarily and then immediately -// shuffled off to the library. This is not thread safe, but it works -// fine with multiple sounds in one thread. It could be made thread -// safe simply by using thread local storage. -const size_t BSIZE = 32*1024; -static char tmp_buffer[BSIZE]; - -// Number of buffers used (per sound) for streaming sounds. Each -// buffer is of size BSIZE. Increasing this will make streaming sounds -// more fault tolerant against temporary lapses in call to update(), -// but will also increase memory usage. -// This was changed from 4 to 150 for an estimated 30 seconds tolerance. -// At some point we should replace it with a more multithreading-ish -// solution. -const int STREAM_BUF_NUM = 150; - -static void fail(const std::string &msg) -{ throw std::runtime_error("OpenAL exception: " + msg); } - -/* - Check for AL error. Since we're always calling this with string - literals, and it only makes sense to optimize for the non-error - case, the parameter is const char* rather than std::string. - - This way we don't force the compiler to create a string object each - time we're called (since the string is never used unless there's an - error), although a good compiler might have optimized that away in - any case. - */ -static void checkALError(const char *where) -{ - ALenum err = alGetError(); - if(err != AL_NO_ERROR) - { - std::string msg = where; - - const ALchar* errmsg = alGetString(err); - if(errmsg) - fail("\"" + std::string(alGetString(err)) + "\" while " + msg); - else - fail("non-specified error while " + msg + " (did you forget to initialize OpenAL?)"); - } -} - -static void getALFormat(SampleSourcePtr inp, int &fmt, int &rate) -{ - boost::int32_t rate_, ch, bits; - inp->getInfo(&rate_, &ch, &bits); - rate = rate_; - - fmt = 0; - - if(bits == 8) - { - if(ch == 1) fmt = AL_FORMAT_MONO8; - if(ch == 2) fmt = AL_FORMAT_STEREO8; - if(alIsExtensionPresent("AL_EXT_MCFORMATS")) - { - if(ch == 4) fmt = alGetEnumValue("AL_FORMAT_QUAD8"); - if(ch == 6) fmt = alGetEnumValue("AL_FORMAT_51CHN8"); - } - } - if(bits == 16) - { - if(ch == 1) fmt = AL_FORMAT_MONO16; - if(ch == 2) fmt = AL_FORMAT_STEREO16; - if(ch == 4) fmt = alGetEnumValue("AL_FORMAT_QUAD16"); - if(alIsExtensionPresent("AL_EXT_MCFORMATS")) - { - if(ch == 4) fmt = alGetEnumValue("AL_FORMAT_QUAD16"); - if(ch == 6) fmt = alGetEnumValue("AL_FORMAT_51CHN16"); - } - } - - if(fmt == 0) - fail("Unsupported input format"); -} - -/// OpenAL sound output -class Mangle::Sound::OpenAL_Sound : public Sound -{ - ALuint inst; - - // Buffers. Only the first is used for non-streaming sounds. - ALuint bufferID[STREAM_BUF_NUM]; - - // Number of buffers used - int bufNum; - - // Parameters used for filling buffers - int fmt, rate; - - // Poor mans reference counting. Might improve this later. When - // NULL, the buffer has not been set up yet. - int *refCnt; - - bool streaming; - - // Input stream - SampleSourcePtr input; - - OpenAL_Factory *owner; - bool ownerAlive; - - // Used for streamed sound list - OpenAL_Sound *next, *prev; - - void setupBuffer(); - - // Fill data into the given buffer and queue it, if there is any - // data left to queue. Assumes the buffer is already unqueued, if - // necessary. - void queueBuffer(ALuint buf) - { - // If there is no more data, do nothing - if(!input) return; - if(input->eof()) - { - input.reset(); - return; - } - - // Get some new data - size_t bytes = input->read(tmp_buffer, BSIZE); - if(bytes == 0) - { - input.reset(); - return; - } - - // Move data into the OpenAL buffer - alBufferData(buf, fmt, tmp_buffer, bytes, rate); - // Queue it - alSourceQueueBuffers(inst, 1, &buf); - checkALError("Queueing buffer data"); - } - - public: - /// Read samples from the given input buffer - OpenAL_Sound(SampleSourcePtr input, OpenAL_Factory *fact); - - /// Play an existing buffer, with a given ref counter. Used - /// internally for cloning. - OpenAL_Sound(ALuint buf, int *ref, OpenAL_Factory *fact); - - ~OpenAL_Sound(); - - // Must be called regularly on streamed sounds - void update() - { - if(!streaming) return; - if(!input) return; - - // Get the number of processed buffers - ALint count; - alGetSourcei(inst, AL_BUFFERS_PROCESSED, &count); - checkALError("getting number of unprocessed buffers"); - - for(int i=0; iupdate(); -} - -void OpenAL_Factory::notifyStreaming(OpenAL_Sound *snd) -{ - // Add the sound to the streaming list - streaming.push_back(snd); -} - -void OpenAL_Factory::notifyDelete(OpenAL_Sound *snd) -{ - // Remove the sound from the stream list - streaming.remove(snd); -} - -OpenAL_Factory::~OpenAL_Factory() -{ - // Notify remaining streamed sounds that we're dying - StreamList::iterator it = streaming.begin(); - for(;it != streaming.end(); it++) - (*it)->notifyOwnerDeath(); - - // Deinitialize sound system - if(didSetup) - { - alcMakeContextCurrent(NULL); - if(context) alcDestroyContext((ALCcontext*)context); - if(device) alcCloseDevice((ALCdevice*)device); - } -} - -// ---- OpenAL_Sound ---- - -void OpenAL_Sound::play() -{ - setupBuffer(); - alSourcePlay(inst); - checkALError("starting playback"); -} - -void OpenAL_Sound::stop() -{ - alSourceStop(inst); - checkALError("stopping"); -} - -void OpenAL_Sound::pause() -{ - alSourcePause(inst); - checkALError("pausing"); -} - -bool OpenAL_Sound::isPlaying() const -{ - ALint state; - alGetSourcei(inst, AL_SOURCE_STATE, &state); - - return state == AL_PLAYING; -} - -void OpenAL_Sound::setVolume(float volume) -{ - if(volume > 1.0) volume = 1.0; - if(volume < 0.0) volume = 0.0; - alSourcef(inst, AL_GAIN, volume); - checkALError("setting volume"); -} - -void OpenAL_Sound::setRange(float a, float b, float) -{ - alSourcef(inst, AL_REFERENCE_DISTANCE, a); - alSourcef(inst, AL_MAX_DISTANCE, b); - checkALError("setting sound ranges"); -} - -void OpenAL_Sound::setPos(float x, float y, float z) -{ - alSource3f(inst, AL_POSITION, x, y, z); - checkALError("setting position"); -} - -void OpenAL_Sound::setPitch(float pitch) -{ - alSourcef(inst, AL_PITCH, pitch); - checkALError("setting pitch"); -} - -void OpenAL_Sound::setRepeat(bool rep) -{ - alSourcei(inst, AL_LOOPING, rep?AL_TRUE:AL_FALSE); -} - -void OpenAL_Sound::setRelative(bool rel) -{ - alSourcei(inst, AL_SOURCE_RELATIVE, rel?AL_TRUE:AL_FALSE); - checkALError("setting relative"); -} - -SoundPtr OpenAL_Sound::clone() -{ - setupBuffer(); - assert(!streaming && "cloning streamed sounds not supported"); - return SoundPtr(new OpenAL_Sound(bufferID[0], refCnt, owner)); -} - -// Constructor used for cloned sounds -OpenAL_Sound::OpenAL_Sound(ALuint buf, int *ref, OpenAL_Factory *fact) - : refCnt(ref), streaming(false), owner(fact), ownerAlive(false) -{ - // Increase the reference count - assert(ref != NULL); - (*refCnt)++; - - // Set up buffer - bufferID[0] = buf; - bufNum = 1; - - // Create a source - alGenSources(1, &inst); - checkALError("creating instance (clone)"); - alSourcei(inst, AL_BUFFER, bufferID[0]); - checkALError("assigning buffer (clone)"); -} - -// Constructor used for original (non-cloned) sounds -OpenAL_Sound::OpenAL_Sound(SampleSourcePtr _input, OpenAL_Factory *fact) - : refCnt(NULL), streaming(false), input(_input), owner(fact), ownerAlive(false) -{ - // Create a source - alGenSources(1, &inst); - checkALError("creating source"); - - // By default, the sound starts out in a buffer-less mode. We don't - // create a buffer until the sound is played. This gives the user - // the chance to call setStreaming(true) first. -} - -void OpenAL_Sound::setupBuffer() -{ - if(refCnt != NULL) return; - - assert(input); - - // Get the format - getALFormat(input, fmt, rate); - - // Create a cheap reference counter for the buffer - refCnt = new int; - *refCnt = 1; - - if(streaming) bufNum = STREAM_BUF_NUM; - else bufNum = 1; - - // Set up the OpenAL buffer(s) - alGenBuffers(bufNum, bufferID); - checkALError("generating buffer(s)"); - assert(bufferID[0] != 0); - - // STREAMING. - if(streaming) - { - // Just queue all the buffers with data and exit. queueBuffer() - // will work correctly also in the case where there is not - // enough data to fill all the buffers. - for(int i=0; inotifyStreaming(this); - ownerAlive = true; - - return; - } - - // NON-STREAMING. We have to load all the data and shove it into the - // buffer. - - // Does the stream support pointer operations? - if(input->hasPtr) - { - // If so, we can read the data directly from the stream - alBufferData(bufferID[0], fmt, input->getPtr(), input->size(), rate); - } - else - { - // Read the entire stream into a temporary buffer first - Mangle::Stream::BufferStream buf(input, 128*1024); - - // Then copy that into OpenAL - alBufferData(bufferID[0], fmt, buf.getPtr(), buf.size(), rate); - } - checkALError("loading sound data"); - - // We're done with the input stream, release the pointer - input.reset(); - - alSourcei(inst, AL_BUFFER, bufferID[0]); - checkALError("assigning buffer"); -} - -OpenAL_Sound::~OpenAL_Sound() -{ - // Stop - alSourceStop(inst); - - // Return sound - alDeleteSources(1, &inst); - - // Notify the factory that we quit. You will hear from our union - // rep. The bool check is to handle cases where the manager goes out - // of scope before the sounds do. In that case, don't try to contact - // the factory. - if(ownerAlive) - owner->notifyDelete(this); - - // Decrease the reference counter - if((-- (*refCnt)) == 0) - { - // We're the last owner. Delete the buffer(s) and the counter - // itself. - alDeleteBuffers(bufNum, bufferID); - checkALError("deleting buffer"); - delete refCnt; - } -} diff --git a/libs/mangle/sound/outputs/openal_out.hpp b/libs/mangle/sound/outputs/openal_out.hpp deleted file mode 100644 index 44d03ecf8..000000000 --- a/libs/mangle/sound/outputs/openal_out.hpp +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef MANGLE_SOUND_OPENAL_OUT_H -#define MANGLE_SOUND_OPENAL_OUT_H - -#include "../output.hpp" -#include - -namespace Mangle { -namespace Sound { - -class OpenAL_Sound; - -class OpenAL_Factory : public SoundFactory -{ - void *device; - void *context; - bool didSetup; - - // List of streaming sounds that need to be updated every frame. - typedef std::list StreamList; - StreamList streaming; - - friend class OpenAL_Sound; - void notifyStreaming(OpenAL_Sound*); - void notifyDelete(OpenAL_Sound*); - - public: - /// Initialize object. Pass true (default) if you want the - /// constructor to set up the current ALCdevice and ALCcontext for - /// you. - OpenAL_Factory(bool doSetup = true); - ~OpenAL_Factory(); - - SoundPtr load(const std::string &file) { assert(0); return SoundPtr(); } - SoundPtr load(Stream::StreamPtr input) { assert(0); return SoundPtr(); } - SoundPtr loadRaw(SampleSourcePtr input); - - void update(); - void setListenerPos(float x, float y, float z, - float fx, float fy, float fz, - float ux, float uy, float uz); -}; - -}} // namespaces -#endif diff --git a/libs/mangle/sound/source.hpp b/libs/mangle/sound/source.hpp deleted file mode 100644 index fbe7cf958..000000000 --- a/libs/mangle/sound/source.hpp +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef MANGLE_SOUND_SOURCE_H -#define MANGLE_SOUND_SOURCE_H - -#include -#include -#include - -#include "../stream/stream.hpp" - -namespace Mangle { -namespace Sound { - -typedef boost::int32_t int32_t; - -/// A stream containing raw sound data and information about the format -class SampleSource : public Stream::Stream -{ - protected: - bool isEof; - - public: - SampleSource() : isEof(false) {} - - /// Get the sample rate, number of channels, and bits per - /// sample. NULL parameters are ignored. - virtual void getInfo(int32_t *rate, int32_t *channels, int32_t *bits) = 0; - - bool eof() const { return isEof; } - - // Disabled functions by default. You can still override them in - // subclasses. - void seek(size_t pos) { assert(0); } - size_t tell() const { assert(0); return 0; } - size_t size() const { assert(0); return 0; } -}; - -typedef boost::shared_ptr SampleSourcePtr; - -/// A factory interface for loading SampleSources from file or stream -class SampleSourceLoader -{ - public: - /// If true, the stream version of load() works - bool canLoadStream; - - /// If true, the file version of load() works - bool canLoadFile; - - /// Load a sound input source from file (if canLoadFile is true) - virtual SampleSourcePtr load(const std::string &file) = 0; - - /// Load a sound input source from stream (if canLoadStream is true) - virtual SampleSourcePtr load(Stream::StreamPtr input) = 0; - - /// Virtual destructor - virtual ~SampleSourceLoader() {} -}; - -typedef boost::shared_ptr SampleSourceLoaderPtr; - -}} // namespaces -#endif diff --git a/libs/mangle/sound/sources/audiere_source.cpp b/libs/mangle/sound/sources/audiere_source.cpp deleted file mode 100644 index faaa3c8c5..000000000 --- a/libs/mangle/sound/sources/audiere_source.cpp +++ /dev/null @@ -1,77 +0,0 @@ -#include "audiere_source.hpp" - -#include "../../stream/clients/audiere_file.hpp" - -#include - -using namespace Mangle::Stream; - -static void fail(const std::string &msg) -{ throw std::runtime_error("Audiere exception: " + msg); } - -using namespace audiere; -using namespace Mangle::Sound; - -// --- SampleSource --- - -void AudiereSource::getInfo(Mangle::Sound::int32_t *rate, - Mangle::Sound::int32_t *channels, Mangle::Sound::int32_t *bits) -{ - SampleFormat fmt; - int channels_, rate_; - sample->getFormat(channels_, rate_, fmt); - *channels = channels_; - *rate = rate_; - if(bits) - { - if(fmt == SF_U8) - *bits = 8; - else if(fmt == SF_S16) - *bits = 16; - else assert(0); - } -} - -// --- Constructors --- - -AudiereSource::AudiereSource(const std::string &file) -{ - sample = OpenSampleSource(file.c_str()); - - if(!sample) - fail("Couldn't load file " + file); - - doSetup(); -} - -AudiereSource::AudiereSource(StreamPtr input) -{ - // Use our Stream::AudiereFile implementation to convert a Mangle - // 'Stream' to an Audiere 'File' - sample = OpenSampleSource(new AudiereFile(input)); - if(!sample) - fail("Couldn't load stream"); - - doSetup(); -} - -AudiereSource::AudiereSource(audiere::SampleSourcePtr src) - : sample(src) -{ assert(sample); doSetup(); } - -// Common function called from all constructors -void AudiereSource::doSetup() -{ - assert(sample); - - SampleFormat fmt; - int channels, rate; - sample->getFormat(channels, rate, fmt); - - // Calculate the size of one frame, and pass it to SampleReader. - setup(GetSampleSize(fmt) * channels); - - isSeekable = sample->isSeekable(); - hasPosition = true; - hasSize = true; -} diff --git a/libs/mangle/sound/sources/audiere_source.hpp b/libs/mangle/sound/sources/audiere_source.hpp deleted file mode 100644 index d797c55c8..000000000 --- a/libs/mangle/sound/sources/audiere_source.hpp +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef MANGLE_SOUND_AUDIERE_SOURCE_H -#define MANGLE_SOUND_AUDIERE_SOURCE_H - -#include "sample_reader.hpp" - -// audiere.h from 1.9.4 (latest) release uses -// cstring routines like strchr() and strlen() without -// including cstring itself. -#include -#include - -namespace Mangle { -namespace Sound { - -/// A sample source that decodes files using Audiere -class AudiereSource : public SampleReader -{ - audiere::SampleSourcePtr sample; - - size_t readSamples(void *data, size_t length) - { return sample->read(length, data); } - - void doSetup(); - - public: - /// Decode the given sound file - AudiereSource(const std::string &file); - - /// Decode the given sound stream - AudiereSource(Mangle::Stream::StreamPtr src); - - /// Read directly from an existing audiere::SampleSource - AudiereSource(audiere::SampleSourcePtr src); - - void getInfo(int32_t *rate, int32_t *channels, int32_t *bits); - - void seek(size_t pos) { sample->setPosition(pos/frameSize); } - size_t tell() const { return sample->getPosition()*frameSize; } - size_t size() const { return sample->getLength()*frameSize; } -}; - -#include "loadertemplate.hpp" - -/// A factory that loads AudiereSources from file and stream -typedef SSL_Template AudiereLoader; - -}} // Namespace -#endif diff --git a/libs/mangle/sound/sources/ffmpeg_source.cpp b/libs/mangle/sound/sources/ffmpeg_source.cpp deleted file mode 100644 index 6349be691..000000000 --- a/libs/mangle/sound/sources/ffmpeg_source.cpp +++ /dev/null @@ -1,189 +0,0 @@ -#include "ffmpeg_source.hpp" - -#include - -using namespace Mangle::Sound; - -// Static output buffer. Not thread safe, but supports multiple -// streams operated from the same thread. -static uint8_t outBuf[AVCODEC_MAX_AUDIO_FRAME_SIZE]; - -static void fail(const std::string &msg) -{ throw std::runtime_error("FFMpeg exception: " + msg); } - -// --- Loader --- - -static bool init = false; - -FFMpegLoader::FFMpegLoader(bool setup) -{ - if(setup && !init) - { - av_register_all(); - av_log_set_level(AV_LOG_ERROR); - init = true; - } -} - -// --- Source --- - -FFMpegSource::FFMpegSource(const std::string &file) -{ - std::string msg; - AVCodec *codec; - - if(av_open_input_file(&FmtCtx, file.c_str(), NULL, 0, NULL) != 0) - fail("Error loading audio file " + file); - - if(av_find_stream_info(FmtCtx) < 0) - { - msg = "Error in file stream " + file; - goto err; - } - - // Pick the first audio stream, if any - for(StreamNum = 0; StreamNum < FmtCtx->nb_streams; StreamNum++) - { - // Pick the first audio stream - if(FmtCtx->streams[StreamNum]->codec->codec_type == CODEC_TYPE_AUDIO) - break; - } - - if(StreamNum == FmtCtx->nb_streams) - fail("File '" + file + "' didn't contain any audio streams"); - - // Open the decoder - CodecCtx = FmtCtx->streams[StreamNum]->codec; - codec = avcodec_find_decoder(CodecCtx->codec_id); - - if(!codec || avcodec_open(CodecCtx, codec) < 0) - { - msg = "Error loading '" + file + "': "; - if(codec) - msg += "coded error"; - else - msg += "no codec found"; - goto err; - } - - // No errors, we're done - return; - - // Handle errors - err: - av_close_input_file(FmtCtx); - fail(msg); -} - -FFMpegSource::~FFMpegSource() -{ - avcodec_close(CodecCtx); - av_close_input_file(FmtCtx); -} - -void FFMpegSource::getInfo(int32_t *rate, int32_t *channels, int32_t *bits) -{ - if(rate) *rate = CodecCtx->sample_rate; - if(channels) *channels = CodecCtx->channels; - if(bits) *bits = 16; -} - -size_t FFMpegSource::read(void *data, size_t length) -{ - if(isEof) return 0; - - size_t left = length; - uint8_t *outPtr = (uint8_t*)data; - - // First, copy over any stored data we might be sitting on - { - size_t s = storage.size(); - size_t copy = s; - if(s) - { - // Make sure there's room - if(copy > left) - copy = left; - - // Copy - memcpy(outPtr, &storage[0], copy); - outPtr += copy; - left -= copy; - - // Is there anything left in the storage? - assert(s>= copy); - s -= copy; - if(s) - { - assert(left == 0); - - // Move it to the start and resize - memmove(&storage[0], &storage[copy], s); - storage.resize(s); - } - } - } - - // Next, get more input data from stream, and decode it - while(left) - { - AVPacket packet; - - // Get the next packet, if any - if(av_read_frame(FmtCtx, &packet) < 0) - break; - - // We only allow one stream per file at the moment - assert((int)StreamNum == packet.stream_index); - - // Decode the packet - int len = AVCODEC_MAX_AUDIO_FRAME_SIZE; - int tmp = avcodec_decode_audio2(CodecCtx, (int16_t*)outBuf, - &len, packet.data, packet.size); - assert(tmp < 0 || tmp == packet.size); - - // We don't need the input packet any longer - av_free_packet(&packet); - - if(tmp < 0) - fail("Error decoding audio stream"); - - // Copy whatever data we got, and advance the pointer - if(len > 0) - { - // copy = how many bytes do we copy now - size_t copy = len; - if(copy > left) - copy = left; - - // len = how many bytes are left uncopied - len -= copy; - - // copy data - memcpy(outPtr, outBuf, copy); - - // left = how much space is left in the caller output - // buffer. This loop repeats as long left is > 0 - left -= copy; - outPtr += copy; - assert(left >= 0); - - if(len > 0) - { - // There were uncopied bytes. Store them for later. - assert(left == 0); - storage.resize(len); - memcpy(&storage[0], outBuf, len); - } - } - } - - // End of loop. Return the number of bytes copied. - assert(left <= length); - - // If we're returning less than asked for, then we're done - if(left > 0) - isEof = true; - - return length - left; -} diff --git a/libs/mangle/sound/sources/ffmpeg_source.hpp b/libs/mangle/sound/sources/ffmpeg_source.hpp deleted file mode 100644 index d422b9809..000000000 --- a/libs/mangle/sound/sources/ffmpeg_source.hpp +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef MANGLE_SOUND_FFMPEG_H -#define MANGLE_SOUND_FFMPEG_H - -#include "../source.hpp" -#include -#include - -extern "C" -{ -#include -#include -} - -namespace Mangle { -namespace Sound { - -class FFMpegSource : public SampleSource -{ - AVFormatContext *FmtCtx; - AVCodecContext *CodecCtx; - unsigned int StreamNum; - - std::vector storage; - - public: - /// Decode the given sound file - FFMpegSource(const std::string &file); - - /// Decode the given sound stream (not supported by FFmpeg) - FFMpegSource(Mangle::Stream::StreamPtr src) { assert(0); } - - ~FFMpegSource(); - - // Overrides - void getInfo(int32_t *rate, int32_t *channels, int32_t *bits); - size_t read(void *data, size_t length); -}; - -#include "loadertemplate.hpp" - -/// A factory that loads FFMpegSources from file -class FFMpegLoader : public SSL_Template -{ - public: - - /// Sets up the libavcodec library. If you want to do your own - /// setup, send a setup=false parameter. - FFMpegLoader(bool setup=true); -}; - -}} // namespaces -#endif diff --git a/libs/mangle/sound/sources/libsndfile.cpp b/libs/mangle/sound/sources/libsndfile.cpp deleted file mode 100644 index b69a2d436..000000000 --- a/libs/mangle/sound/sources/libsndfile.cpp +++ /dev/null @@ -1,48 +0,0 @@ -#include "libsndfile.hpp" - -#include -#include - -using namespace Mangle::Stream; - -static void fail(const std::string &msg) -{ throw std::runtime_error("Mangle::libsndfile: " + msg); } - -using namespace Mangle::Sound; - -void SndFileSource::getInfo(int32_t *_rate, int32_t *_channels, int32_t *_bits) -{ - *_rate = rate; - *_channels = channels; - *_bits = bits; -} - -size_t SndFileSource::readSamples(void *data, size_t length) -{ - // readf_* reads entire frames, including channels - return sf_readf_short((SNDFILE*)handle, (short*)data, length); -} - -SndFileSource::SndFileSource(const std::string &file) -{ - SF_INFO info; - info.format = 0; - handle = sf_open(file.c_str(), SFM_READ, &info); - if(handle == NULL) - fail("Failed to open " + file); - - // I THINK that using sf_read_short forces the library to convert to - // 16 bits no matter what, but the libsndfile docs aren't exactly - // very clear on this point. - channels = info.channels; - rate = info.samplerate; - bits = 16; - - // 16 bits per sample times number of channels - setup(2*channels); -} - -SndFileSource::~SndFileSource() -{ - sf_close((SNDFILE*)handle); -} diff --git a/libs/mangle/sound/sources/libsndfile.hpp b/libs/mangle/sound/sources/libsndfile.hpp deleted file mode 100644 index 7286cf0fe..000000000 --- a/libs/mangle/sound/sources/libsndfile.hpp +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef MANGLE_SOUND_SNDFILE_SOURCE_H -#define MANGLE_SOUND_SNDFILE_SOURCE_H - -#include "sample_reader.hpp" - -namespace Mangle { -namespace Sound { - -/// A sample source that decodes files using libsndfile. Supports most -/// formats except mp3. -class SndFileSource : public SampleReader -{ - void *handle; - int channels, rate, bits; - - size_t readSamples(void *data, size_t length); - - public: - /// Decode the given sound file - SndFileSource(const std::string &file); - - /// Decode the given sound stream (not supported) - SndFileSource(Mangle::Stream::StreamPtr src) { assert(0); } - - ~SndFileSource(); - - void getInfo(int32_t *rate, int32_t *channels, int32_t *bits); -}; - -#include "loadertemplate.hpp" - -/// A factory that loads SndFileSources from file and stream -typedef SSL_Template SndFileLoader; - -}} // Namespace -#endif diff --git a/libs/mangle/sound/sources/loadertemplate.hpp b/libs/mangle/sound/sources/loadertemplate.hpp deleted file mode 100644 index a27a77d10..000000000 --- a/libs/mangle/sound/sources/loadertemplate.hpp +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef SSL_TEMPL_H -#define SSL_TEMPL_H - -template -class SSL_Template : public SampleSourceLoader -{ - public: - - SSL_Template() - { - canLoadStream = stream; - canLoadFile = file; - } - - SampleSourcePtr load(const std::string &filename) - { - assert(canLoadFile); - return SampleSourcePtr(new SourceT(filename)); - } - - SampleSourcePtr load(Stream::StreamPtr input) - { - assert(canLoadStream); - return SampleSourcePtr(new SourceT(input)); - } -}; - -#endif diff --git a/libs/mangle/sound/sources/mpg123_source.cpp b/libs/mangle/sound/sources/mpg123_source.cpp deleted file mode 100644 index 24d6ecce1..000000000 --- a/libs/mangle/sound/sources/mpg123_source.cpp +++ /dev/null @@ -1,115 +0,0 @@ -#include "mpg123_source.hpp" - -#include - -#include - -using namespace Mangle::Stream; - -/* - TODOs: - - - mpg123 impressively enough supports custom stream reading. Which - means we could (and SHOULD!) support reading from Mangle::Streams - as well. But I'll save it til I need it. - - An alternative way to do this is through feeding (which they also - support), but that's more messy. - - - the library also supports output, via various other sources, - including ALSA, OSS, PortAudio, PulseAudio and SDL. Using this - library as a pure output library (if that is possible) would be a - nice shortcut over using those libraries - OTOH it's another - dependency. - - - we could implement seek(), tell() and size(), but they aren't - really necessary. Furthermore, since the returned size is only a - guess, it is not safe to rely on it. - */ - -static void fail(const std::string &msg) -{ throw std::runtime_error("Mangle::Mpg123 exception: " + msg); } - -static void checkError(int err, void *mh = NULL) -{ - if(err != MPG123_OK) - { - std::string msg; - if(mh) msg = mpg123_strerror((mpg123_handle*)mh); - else msg = mpg123_plain_strerror(err); - fail(msg); - } -} - -using namespace Mangle::Sound; - -void Mpg123Source::getInfo(int32_t *pRate, int32_t *pChannels, int32_t *pBits) -{ - // Use the values we found in the constructor - *pRate = rate; - *pChannels = channels; - *pBits = bits; -} - -size_t Mpg123Source::read(void *data, size_t length) -{ - size_t done; - // This is extraordinarily nice. I like this library. - int err = mpg123_read((mpg123_handle*)mh, (unsigned char*)data, length, &done); - assert(done <= length); - if(err == MPG123_DONE) - isEof = true; - else - checkError(err, mh); - return done; -} - -Mpg123Loader::Mpg123Loader(bool setup) -{ - // Do as we're told - if(setup) - { - int err = mpg123_init(); - checkError(err); - } - didSetup = setup; -} - -Mpg123Loader::~Mpg123Loader() -{ - // Deinitialize the library on exit - if(didSetup) - mpg123_exit(); -} - -Mpg123Source::Mpg123Source(const std::string &file) -{ - int err; - - // Create a new handle - mh = mpg123_new(NULL, &err); - if(mh == NULL) - checkError(err, mh); - - mpg123_handle *mhh = (mpg123_handle*)mh; - - // Open the file (hack around constness) - err = mpg123_open(mhh, (char*)file.c_str()); - checkError(err, mh); - - // Get the format - int encoding; - err = mpg123_getformat(mhh, &rate, &channels, &encoding); - checkError(err, mh); - if(encoding != MPG123_ENC_SIGNED_16) - fail("Unsupported encoding in " + file); - - // This is the only bit size we support. - bits = 16; -} - -Mpg123Source::~Mpg123Source() -{ - mpg123_close((mpg123_handle*)mh); - mpg123_delete((mpg123_handle*)mh); -} diff --git a/libs/mangle/sound/sources/mpg123_source.hpp b/libs/mangle/sound/sources/mpg123_source.hpp deleted file mode 100644 index 1ac16b530..000000000 --- a/libs/mangle/sound/sources/mpg123_source.hpp +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef MANGLE_SOUND_MPG123_SOURCE_H -#define MANGLE_SOUND_MPG123_SOURCE_H - -#include "../source.hpp" -#include - -namespace Mangle { -namespace Sound { - -/// A sample source that decodes files using libmpg123. Only supports -/// MP3 files. -class Mpg123Source : public SampleSource -{ - void *mh; - long int rate; - int channels, bits; - - public: - /// Decode the given sound file - Mpg123Source(const std::string &file); - - /// Needed by SSL_Template but not yet supported - Mpg123Source(Mangle::Stream::StreamPtr data) - { assert(0); } - - ~Mpg123Source(); - - void getInfo(int32_t *rate, int32_t *channels, int32_t *bits); - size_t read(void *data, size_t length); -}; - -#include "loadertemplate.hpp" - -/// A factory that loads Mpg123Sources from file and stream -struct Mpg123Loader : SSL_Template -{ - /** Sets up libmpg123 for you, and closes it on destruction. If you - want to do this yourself, send setup=false. - */ - Mpg123Loader(bool setup=true); - ~Mpg123Loader(); -private: - bool didSetup; -}; - -}} // Namespace -#endif diff --git a/libs/mangle/sound/sources/sample_reader.cpp b/libs/mangle/sound/sources/sample_reader.cpp deleted file mode 100644 index c30de654a..000000000 --- a/libs/mangle/sound/sources/sample_reader.cpp +++ /dev/null @@ -1,99 +0,0 @@ -#include "sample_reader.hpp" - -#include - -using namespace Mangle::Sound; - -void SampleReader::setup(int size) -{ - pullSize = 0; - frameSize = size; - pullOver = new char[size]; -} - -SampleReader::~SampleReader() -{ - if(pullOver) - delete[] pullOver; -} - -size_t SampleReader::read(void *_data, size_t length) -{ - if(isEof) return 0; - char *data = (char*)_data; - - // Pullsize holds the number of bytes that were copied "extra" at - // the end of LAST round. If non-zero, it also means there is data - // left in the pullOver buffer. - if(pullSize) - { - // Amount of data left - size_t doRead = frameSize - pullSize; - assert(doRead > 0); - - // Make sure we don't read more than we're supposed to - if(doRead > length) doRead = length; - - memcpy(data, pullOver+pullSize, doRead); - - // Update the number of bytes now copied - pullSize += doRead; - assert(pullSize <= frameSize); - - if(pullSize < frameSize) - { - // There is STILL data left in the pull buffer, and we've - // done everything we were supposed to. Leave it and return. - assert(doRead == length); - return doRead; - } - - // Set up variables for further reading below. No need to update - // pullSize, it is overwritten anyway. - length -= doRead; - data += doRead; - } - - // Number of whole frames - size_t frames = length / frameSize; - - // Read the data - size_t res = readSamples(data, frames); - assert(res <= frames); - - // Total bytes read - size_t num = res*frameSize; - data += num; - - if(res < frames) - { - // End of stream. - isEof = true; - // Determine how much we read - return data-(char*)_data; - } - - // Determine the overshoot - pullSize = length - num; - assert(pullSize < frameSize && pullSize >= 0); - - // Are we missing data? - if(pullSize) - { - // Fill in one sample - res = readSamples(pullOver,1); - assert(res == 1 || res == 0); - if(res) - { - // Move as much as we can into the output buffer - memcpy(data, pullOver, pullSize); - data += pullSize; - } - else - // Failed reading, we're out of data - isEof = true; - } - - // Return the total number of bytes stored - return data-(char*)_data; -} diff --git a/libs/mangle/sound/sources/sample_reader.hpp b/libs/mangle/sound/sources/sample_reader.hpp deleted file mode 100644 index 89ddf1f65..000000000 --- a/libs/mangle/sound/sources/sample_reader.hpp +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef MANGLE_SOUND_SAMPLE_READER_H -#define MANGLE_SOUND_SAMPLE_READER_H - -#include "../source.hpp" - -namespace Mangle { -namespace Sound { - - /* This is a helper base class for other SampleSource - implementations. Certain sources (like Audiere and libsndfile) - insist on reading whole samples rather than bytes. This class - compensates for that, and allows you to read bytes rather than - samples. - - There are two ways for subclasses to use this class. EITHER call - setup() with the size of frameSize. This will allocate a buffer, - which the destructor frees. OR set frameSize manually and - manipulate the pullOver pointer yourself. In that case you MUST - reset it to NULL if you don't want the destructor to call - delete[] on it. - */ -class SampleReader : public SampleSource -{ - // How much of the above buffer is in use. - int pullSize; - -protected: - // Pullover buffer - char* pullOver; - - // Size of one frame, in bytes. This is also the size of the - // pullOver buffer. - int frameSize; - - // The parameter gives the size of one sample/frame, in bytes. - void setup(int); - - // Read the given number of samples, in multiples of frameSize. Does - // not have to set or respect isEof. - virtual size_t readSamples(void *data, size_t num) = 0; - - public: - SampleReader() : pullSize(0), pullOver(NULL) {} - ~SampleReader(); - size_t read(void *data, size_t length); -}; -}} // Namespace -#endif diff --git a/libs/mangle/sound/sources/stream_source.hpp b/libs/mangle/sound/sources/stream_source.hpp deleted file mode 100644 index 43c605a00..000000000 --- a/libs/mangle/sound/sources/stream_source.hpp +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef MANGLE_SOUND_STREAMSOURCE_H -#define MANGLE_SOUND_STREAMSOURCE_H - -#include "../source.hpp" - -namespace Mangle { -namespace Sound { - -/// A class for reading raw samples directly from a stream. -class Stream2Samples : public SampleSource -{ - Mangle::Stream::StreamPtr inp; - int32_t rate, channels, bits; - - public: - Stream2Samples(Mangle::Stream::StreamPtr _inp, int32_t _rate, int32_t _channels, int32_t _bits) - : inp(_inp), rate(_rate), channels(_channels), bits(_bits) - { - isSeekable = inp->isSeekable; - hasPosition = inp->hasPosition; - hasSize = inp->hasSize; - hasPtr = inp->hasPtr; - } - - /// Get the sample rate, number of channels, and bits per - /// sample. NULL parameters are ignored. - void getInfo(int32_t *_rate, int32_t *_channels, int32_t *_bits) - { - if(_rate) *_rate = rate; - if(_channels) *_channels = channels; - if(_bits) *_bits = bits; - } - - size_t read(void *out, size_t count) - { return inp->read(out, count); } - - void seek(size_t pos) { inp->seek(pos); } - size_t tell() const { return inp->tell(); } - size_t size() const { return inp->size(); } - bool eof() const { return inp->eof(); } - const void *getPtr() { return inp->getPtr(); } - const void *getPtr(size_t size) { return inp->getPtr(size); } - const void *getPtr(size_t pos, size_t size) { return inp->getPtr(pos, size); } -}; - -}} // namespaces -#endif diff --git a/libs/mangle/sound/sources/wav_source.cpp b/libs/mangle/sound/sources/wav_source.cpp deleted file mode 100644 index a46b3d27e..000000000 --- a/libs/mangle/sound/sources/wav_source.cpp +++ /dev/null @@ -1,99 +0,0 @@ -#include "wav_source.hpp" - -#include "../../stream/servers/file_stream.hpp" - -#include - -using namespace Mangle::Stream; -using namespace Mangle::Sound; - -static void fail(const std::string &msg) -{ throw std::runtime_error("Mangle::Wav exception: " + msg); } - -void WavSource::getInfo(int32_t *pRate, int32_t *pChannels, int32_t *pBits) -{ - // Use the values we found in the constructor - *pRate = rate; - *pChannels = channels; - *pBits = bits; -} - -void WavSource::seek(size_t pos) -{ - // Seek the stream and set 'left' - assert(isSeekable); - if(pos > total) pos = total; - input->seek(dataOffset + pos); - left = total-pos; -} - -size_t WavSource::read(void *data, size_t length) -{ - if(length > left) - length = left; - size_t read = input->read(data, length); - if(read < length) - // Something went wrong - fail("WAV read error"); - return length; -} - -void WavSource::open(Mangle::Stream::StreamPtr data) -{ - input = data; - - hasPosition = true; - hasSize = true; - // If we can check position and seek in the input stream, then we - // can seek the wav data too. - isSeekable = input->isSeekable && input->hasPosition; - - // Read header - unsigned int val; - - input->read(&val,4); // header - if(val != 0x46464952) // "RIFF" - fail("Not a WAV file"); - - input->read(&val,4); // size (ignored) - input->read(&val,4); // file format - if(val != 0x45564157) // "WAVE" - fail("Not a valid WAV file"); - - input->read(&val,4); // "fmt " - input->read(&val,4); // chunk size (must be 16) - if(val != 16) - fail("Unsupported WAV format"); - - input->read(&val,2); - if(val != 1) - fail("Non-PCM (compressed) WAV files not supported"); - - // Sound data specification - channels = 0; - input->read(&channels,2); - input->read(&rate, 4); - - // Skip next 6 bytes - input->read(&val, 4); - input->read(&val, 2); - - // Bits per sample - bits = 0; - input->read(&bits,2); - - input->read(&val,4); // Data header - if(val != 0x61746164) // "data" - fail("Expected data block"); - - // Finally, read the data size - input->read(&total,4); - left = total; - - // Store the beginning of the data block for later - if(input->hasPosition) - dataOffset = input->tell(); -} - -WavSource::WavSource(const std::string &file) -{ open(StreamPtr(new FileStream(file))); } diff --git a/libs/mangle/sound/sources/wav_source.hpp b/libs/mangle/sound/sources/wav_source.hpp deleted file mode 100644 index 227f4da73..000000000 --- a/libs/mangle/sound/sources/wav_source.hpp +++ /dev/null @@ -1,49 +0,0 @@ -#ifndef MANGLE_SOUND_WAV_SOURCE_H -#define MANGLE_SOUND_WAV_SOURCE_H - -#include "../source.hpp" -#include - -namespace Mangle { -namespace Sound { - -/// WAV file decoder. Has no external library dependencies. -class WavSource : public SampleSource -{ - // Sound info - uint32_t rate, channels, bits; - - // Total size (of output) and bytes left - uint32_t total, left; - - // Offset in input of the beginning of the data block - size_t dataOffset; - - Mangle::Stream::StreamPtr input; - - void open(Mangle::Stream::StreamPtr); - - public: - /// Decode the given sound file - WavSource(const std::string&); - - /// Decode from stream - WavSource(Mangle::Stream::StreamPtr s) - { open(s); } - - void getInfo(int32_t *rate, int32_t *channels, int32_t *bits); - size_t read(void *data, size_t length); - - void seek(size_t); - size_t tell() const { return total-left; } - size_t size() const { return total; } - bool eof() const { return left > 0; } -}; - -#include "loadertemplate.hpp" - -/// A factory that loads WavSources from file and stream -typedef SSL_Template WavLoader; - -}} // Namespace -#endif diff --git a/libs/mangle/sound/tests/.gitignore b/libs/mangle/sound/tests/.gitignore deleted file mode 100644 index 814490404..000000000 --- a/libs/mangle/sound/tests/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*_test diff --git a/libs/mangle/sound/tests/Makefile b/libs/mangle/sound/tests/Makefile deleted file mode 100644 index 6fcac72da..000000000 --- a/libs/mangle/sound/tests/Makefile +++ /dev/null @@ -1,38 +0,0 @@ -GCC=g++ -I../ -Wall - -all: audiere_source_test ffmpeg_source_test openal_output_test openal_audiere_test openal_ffmpeg_test openal_mpg123_test openal_sndfile_test wav_source_test openal_various_test - -L_FFMPEG=$(shell pkg-config --libs libavcodec libavformat) -I_FFMPEG=-I/usr/include/libavcodec -I/usr/include/libavformat -L_OPENAL=$(shell pkg-config --libs openal) -L_AUDIERE=-laudiere - -wav_source_test: wav_source_test.cpp ../sources/wav_source.cpp - $(GCC) $^ -o $@ - -openal_various_test: openal_various_test.cpp ../sources/mpg123_source.cpp ../sources/wav_source.cpp ../outputs/openal_out.cpp - $(GCC) $^ -o $@ -lmpg123 ${L_OPENAL} - -openal_audiere_test: openal_audiere_test.cpp ../sources/audiere_source.cpp ../sources/sample_reader.cpp ../outputs/openal_out.cpp ../../stream/clients/audiere_file.cpp - $(GCC) $^ -o $@ $(L_AUDIERE) $(L_OPENAL) - -openal_ffmpeg_test: openal_ffmpeg_test.cpp ../sources/ffmpeg_source.cpp ../outputs/openal_out.cpp - $(GCC) $^ -o $@ $(L_FFMPEG) $(L_OPENAL) $(I_FFMPEG) - -openal_mpg123_test: openal_mpg123_test.cpp ../sources/mpg123_source.cpp ../outputs/openal_out.cpp - $(GCC) $^ -o $@ -lmpg123 ${L_OPENAL} - -openal_sndfile_test: openal_sndfile_test.cpp ../sources/libsndfile.cpp ../sources/sample_reader.cpp ../outputs/openal_out.cpp - $(GCC) $^ -o $@ -lsndfile ${L_OPENAL} - -openal_output_test: openal_output_test.cpp ../outputs/openal_out.cpp - $(GCC) $^ -o $@ $(L_OPENAL) - -audiere_source_test: audiere_source_test.cpp ../sources/audiere_source.cpp ../../stream/clients/audiere_file.cpp ../sources/sample_reader.cpp - $(GCC) $^ -o $@ $(L_AUDIERE) - -ffmpeg_source_test: ffmpeg_source_test.cpp ../sources/ffmpeg_source.cpp - $(GCC) $^ -o $@ $(L_FFMPEG) $(I_FFMPEG) - -clean: - rm *_test diff --git a/libs/mangle/sound/tests/audiere_source_test.cpp b/libs/mangle/sound/tests/audiere_source_test.cpp deleted file mode 100644 index 637d743b2..000000000 --- a/libs/mangle/sound/tests/audiere_source_test.cpp +++ /dev/null @@ -1,68 +0,0 @@ -#include - -#include "../../stream/servers/file_stream.hpp" -#include "../sources/audiere_source.hpp" - -#include -#include - -using namespace std; -using namespace Mangle::Stream; -using namespace Mangle::Sound; - -// Contents and size of cow.raw -void *orig; -size_t orig_size; - -void run(SampleSourcePtr &src) -{ - size_t ss = src->size(); - assert(ss == orig_size); - - cout << "Source size: " << ss << endl; - int rate, channels, bits; - src->getInfo(&rate, &channels, &bits); - cout << "rate=" << rate << "\nchannels=" << channels - << "\nbits=" << bits << endl; - - cout << "Reading entire buffer into memory\n"; - void *buf = malloc(ss); - src->read(buf, ss); - - cout << "Comparing...\n"; - if(memcmp(buf, orig, ss) != 0) - { - cout << "Oops!\n"; - assert(0); - } - - cout << "Done\n"; -} - -int main() -{ - { - cout << "Reading cow.raw first\n"; - FileStream tmp("cow.raw"); - orig_size = tmp.size(); - cout << "Size: " << orig_size << endl; - orig = malloc(orig_size); - tmp.read(orig, orig_size); - cout << "Done\n"; - } - - { - cout << "\nLoading cow.wav by filename:\n"; - SampleSourcePtr cow_file( new AudiereSource("cow.wav") ); - run(cow_file); - } - - { - cout << "\nLoading cow.wav by stream:\n"; - StreamPtr inp( new FileStream("cow.wav") ); - SampleSourcePtr cow_stream( new AudiereSource(inp) ); - run(cow_stream); - } - - return 0; -} diff --git a/libs/mangle/sound/tests/cow.raw b/libs/mangle/sound/tests/cow.raw deleted file mode 100644 index c4d155bbf..000000000 Binary files a/libs/mangle/sound/tests/cow.raw and /dev/null differ diff --git a/libs/mangle/sound/tests/cow.wav b/libs/mangle/sound/tests/cow.wav deleted file mode 100644 index 494e6c4ac..000000000 Binary files a/libs/mangle/sound/tests/cow.wav and /dev/null differ diff --git a/libs/mangle/sound/tests/ffmpeg_source_test.cpp b/libs/mangle/sound/tests/ffmpeg_source_test.cpp deleted file mode 100644 index f03b15b99..000000000 --- a/libs/mangle/sound/tests/ffmpeg_source_test.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#include - -#include "../../stream/servers/file_stream.hpp" -#include "../sources/ffmpeg_source.hpp" - -#include -#include - -using namespace std; -using namespace Mangle::Stream; -using namespace Mangle::Sound; - -// Contents and size of cow.raw -void *orig; -size_t orig_size; - -void run(SampleSourcePtr &src) -{ - int rate, channels, bits; - src->getInfo(&rate, &channels, &bits); - cout << "rate=" << rate << "\nchannels=" << channels - << "\nbits=" << bits << endl; - - cout << "Reading entire buffer into memory\n"; - void *buf = malloc(orig_size); - size_t ss = src->read(buf, orig_size); - cout << "Actually read: " << ss << endl; - assert(ss == orig_size); - - cout << "Comparing...\n"; - if(memcmp(buf, orig, ss) != 0) - { - cout << "Oops!\n"; - assert(0); - } - - cout << "Done\n"; -} - -int main() -{ - { - cout << "Reading cow.raw first\n"; - FileStream tmp("cow.raw"); - orig_size = tmp.size(); - cout << "Size: " << orig_size << endl; - orig = malloc(orig_size); - tmp.read(orig, orig_size); - cout << "Done\n"; - } - - // Initializes the library, not used for anything else. - FFMpegLoader fm; - - { - cout << "\nLoading cow.wav by filename:\n"; - SampleSourcePtr cow_file( new FFMpegSource("cow.wav") ); - run(cow_file); - } - - return 0; -} diff --git a/libs/mangle/sound/tests/openal_audiere_test.cpp b/libs/mangle/sound/tests/openal_audiere_test.cpp deleted file mode 100644 index ced7fe5d2..000000000 --- a/libs/mangle/sound/tests/openal_audiere_test.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include -#include - -#include "../../stream/servers/file_stream.hpp" -#include "../filters/openal_audiere.hpp" - -using namespace std; -using namespace Mangle::Stream; -using namespace Mangle::Sound; - -OpenAL_Audiere_Factory mg; - -void play(const char* name, bool stream=false) -{ - // Only load streams if the backend supports it - if(stream && !mg.canLoadStream) - return; - - cout << "Playing " << name; - if(stream) cout << " (from stream)"; - cout << "\n"; - - SoundPtr snd; - - try - { - if(stream) - snd = mg.load(StreamPtr(new FileStream(name))); - else - snd = mg.load(name); - - snd->play(); - - while(snd->isPlaying()) - { - usleep(10000); - if(mg.needsUpdate) mg.update(); - } - } - catch(exception &e) - { - cout << " ERROR: " << e.what() << "\n"; - } -} - -int main() -{ - play("cow.wav"); - play("owl.ogg"); - play("cow.wav", true); - return 0; -} diff --git a/libs/mangle/sound/tests/openal_ffmpeg_test.cpp b/libs/mangle/sound/tests/openal_ffmpeg_test.cpp deleted file mode 100644 index d4b8e9300..000000000 --- a/libs/mangle/sound/tests/openal_ffmpeg_test.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include -#include - -#include "../../stream/servers/file_stream.hpp" -#include "../filters/openal_ffmpeg.hpp" - -using namespace std; -using namespace Mangle::Stream; -using namespace Mangle::Sound; - -OpenAL_FFMpeg_Factory mg; - -void play(const char* name, bool stream=false) -{ - // Only load streams if the backend supports it - if(stream && !mg.canLoadStream) - return; - - cout << "Playing " << name; - if(stream) cout << " (from stream)"; - cout << "\n"; - - SoundPtr snd; - - try - { - if(stream) - snd = mg.load(StreamPtr(new FileStream(name))); - else - snd = mg.load(name); - - snd->play(); - - while(snd->isPlaying()) - { - usleep(10000); - if(mg.needsUpdate) mg.update(); - } - } - catch(exception &e) - { - cout << " ERROR: " << e.what() << "\n"; - } -} - -int main() -{ - play("cow.wav"); - play("owl.ogg"); - play("cow.wav", true); - return 0; -} diff --git a/libs/mangle/sound/tests/openal_mpg123_test.cpp b/libs/mangle/sound/tests/openal_mpg123_test.cpp deleted file mode 100644 index fef1a5605..000000000 --- a/libs/mangle/sound/tests/openal_mpg123_test.cpp +++ /dev/null @@ -1,54 +0,0 @@ -#include -#include - -#include "../../stream/servers/file_stream.hpp" -#include "../filters/openal_mpg123.hpp" - -using namespace std; -using namespace Mangle::Stream; -using namespace Mangle::Sound; - -OpenAL_Mpg123_Factory mg; - -void play(const char* name, bool stream=false) -{ - // Only load streams if the backend supports it - if(stream && !mg.canLoadStream) - return; - - cout << "Playing " << name; - if(stream) cout << " (from stream)"; - cout << "\n"; - - SoundPtr snd; - - try - { - if(stream) - snd = mg.load(StreamPtr(new FileStream(name))); - else - snd = mg.load(name); - - snd->setStreaming(true); - snd->play(); - - while(snd->isPlaying()) - { - usleep(10000); - if(mg.needsUpdate) mg.update(); - } - } - catch(exception &e) - { - cout << " ERROR: " << e.what() << "\n"; - } -} - -int main(int argc, char**argv) -{ - if(argc != 2) - cout << "Please specify an MP3 file\n"; - else - play(argv[1]); - return 0; -} diff --git a/libs/mangle/sound/tests/openal_output_test.cpp b/libs/mangle/sound/tests/openal_output_test.cpp deleted file mode 100644 index a8059ec65..000000000 --- a/libs/mangle/sound/tests/openal_output_test.cpp +++ /dev/null @@ -1,59 +0,0 @@ -#include -#include - -#include "../../stream/servers/file_stream.hpp" -#include "../sources/stream_source.hpp" -#include "../outputs/openal_out.hpp" - -using namespace std; -using namespace Mangle::Stream; -using namespace Mangle::Sound; - -int main() -{ - cout << "Loading cow.raw\n"; - - int rate = 11025; - int chan = 1; - int bits = 16; - - cout << " rate=" << rate << "\n channels=" << chan - << "\n bits=" << bits << endl; - - StreamPtr file( new FileStream("cow.raw") ); - SampleSourcePtr source( new Stream2Samples( file, rate, chan, bits)); - - cout << "Playing\n"; - - OpenAL_Factory mg; - - SoundPtr snd = mg.loadRaw(source); - - try - { - // Try setting all kinds of stuff before playing. OpenAL_Sound - // uses delayed buffer loading, but these should still work - // without a buffer. - snd->stop(); - snd->pause(); - snd->setVolume(0.8); - snd->setPitch(0.9); - - // Also test streaming, since all the other examples test - // non-streaming sounds. - snd->setStreaming(true); - - snd->play(); - - while(snd->isPlaying()) - { - usleep(10000); - mg.update(); - } - } - catch(exception &e) - { - cout << " ERROR: " << e.what() << "\n"; - } - return 0; -} diff --git a/libs/mangle/sound/tests/openal_sndfile_test.cpp b/libs/mangle/sound/tests/openal_sndfile_test.cpp deleted file mode 100644 index bd5f117a5..000000000 --- a/libs/mangle/sound/tests/openal_sndfile_test.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include -#include - -#include "../../stream/servers/file_stream.hpp" -#include "../filters/openal_sndfile.hpp" - -using namespace std; -using namespace Mangle::Stream; -using namespace Mangle::Sound; - -OpenAL_SndFile_Factory mg; - -void play(const char* name, bool stream=false) -{ - // Only load streams if the backend supports it - if(stream && !mg.canLoadStream) - return; - - cout << "Playing " << name; - if(stream) cout << " (from stream)"; - cout << "\n"; - - SoundPtr snd; - - try - { - if(stream) - snd = mg.load(StreamPtr(new FileStream(name))); - else - snd = mg.load(name); - - snd->play(); - - while(snd->isPlaying()) - { - usleep(10000); - if(mg.needsUpdate) mg.update(); - } - } - catch(exception &e) - { - cout << " ERROR: " << e.what() << "\n"; - } -} - -int main() -{ - play("cow.wav"); - play("owl.ogg"); - play("cow.wav", true); - return 0; -} diff --git a/libs/mangle/sound/tests/openal_various_test.cpp b/libs/mangle/sound/tests/openal_various_test.cpp deleted file mode 100644 index 9426a672e..000000000 --- a/libs/mangle/sound/tests/openal_various_test.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include -#include - -#include "../../stream/servers/file_stream.hpp" -#include "../filters/openal_various.hpp" - -using namespace std; -using namespace Mangle::Stream; -using namespace Mangle::Sound; - -OpenAL_Various_Factory mg; - -void play(const char* name, bool stream=false) -{ - // Only load streams if the backend supports it - if(stream && !mg.canLoadStream) - return; - - cout << "Playing " << name; - if(stream) cout << " (from stream)"; - cout << "\n"; - - SoundPtr snd; - - try - { - if(stream) - snd = mg.load(StreamPtr(new FileStream(name))); - else - snd = mg.load(name); - - snd->play(); - - while(snd->isPlaying()) - { - usleep(10000); - if(mg.needsUpdate) mg.update(); - } - } - catch(exception &e) - { - cout << " ERROR: " << e.what() << "\n"; - } -} - -int main() -{ - play("cow.wav"); - play("cow.wav", true); - return 0; -} diff --git a/libs/mangle/sound/tests/output/audiere_source_test.out b/libs/mangle/sound/tests/output/audiere_source_test.out deleted file mode 100644 index 47a5a9e41..000000000 --- a/libs/mangle/sound/tests/output/audiere_source_test.out +++ /dev/null @@ -1,21 +0,0 @@ -Reading cow.raw first -Size: 37502 -Done - -Loading cow.wav by filename: -Source size: 37502 -rate=11025 -channels=1 -bits=16 -Reading entire buffer into memory -Comparing... -Done - -Loading cow.wav by stream: -Source size: 37502 -rate=11025 -channels=1 -bits=16 -Reading entire buffer into memory -Comparing... -Done diff --git a/libs/mangle/sound/tests/output/ffmpeg_source_test.out b/libs/mangle/sound/tests/output/ffmpeg_source_test.out deleted file mode 100644 index 1c7d49113..000000000 --- a/libs/mangle/sound/tests/output/ffmpeg_source_test.out +++ /dev/null @@ -1,12 +0,0 @@ -Reading cow.raw first -Size: 37502 -Done - -Loading cow.wav by filename: -rate=11025 -channels=1 -bits=16 -Reading entire buffer into memory -Actually read: 37502 -Comparing... -Done diff --git a/libs/mangle/sound/tests/output/openal_audiere_test.out b/libs/mangle/sound/tests/output/openal_audiere_test.out deleted file mode 100644 index 4fe01eac2..000000000 --- a/libs/mangle/sound/tests/output/openal_audiere_test.out +++ /dev/null @@ -1,3 +0,0 @@ -Playing cow.wav -Playing owl.ogg -Playing cow.wav (from stream) diff --git a/libs/mangle/sound/tests/output/openal_ffmpeg_test.out b/libs/mangle/sound/tests/output/openal_ffmpeg_test.out deleted file mode 100644 index 96e1db0f9..000000000 --- a/libs/mangle/sound/tests/output/openal_ffmpeg_test.out +++ /dev/null @@ -1,2 +0,0 @@ -Playing cow.wav -Playing owl.ogg diff --git a/libs/mangle/sound/tests/output/openal_mpg123_test.out b/libs/mangle/sound/tests/output/openal_mpg123_test.out deleted file mode 100644 index e55dabbb1..000000000 --- a/libs/mangle/sound/tests/output/openal_mpg123_test.out +++ /dev/null @@ -1 +0,0 @@ -Please specify an MP3 file diff --git a/libs/mangle/sound/tests/output/openal_output_test.out b/libs/mangle/sound/tests/output/openal_output_test.out deleted file mode 100644 index 04392a72e..000000000 --- a/libs/mangle/sound/tests/output/openal_output_test.out +++ /dev/null @@ -1,5 +0,0 @@ -Loading cow.raw - rate=11025 - channels=1 - bits=16 -Playing diff --git a/libs/mangle/sound/tests/output/openal_sndfile_test.out b/libs/mangle/sound/tests/output/openal_sndfile_test.out deleted file mode 100644 index 96e1db0f9..000000000 --- a/libs/mangle/sound/tests/output/openal_sndfile_test.out +++ /dev/null @@ -1,2 +0,0 @@ -Playing cow.wav -Playing owl.ogg diff --git a/libs/mangle/sound/tests/output/openal_various_test.out b/libs/mangle/sound/tests/output/openal_various_test.out deleted file mode 100644 index f25a55513..000000000 --- a/libs/mangle/sound/tests/output/openal_various_test.out +++ /dev/null @@ -1 +0,0 @@ -Playing cow.wav diff --git a/libs/mangle/sound/tests/output/wav_source_test.out b/libs/mangle/sound/tests/output/wav_source_test.out deleted file mode 100644 index b6fc8e6fc..000000000 --- a/libs/mangle/sound/tests/output/wav_source_test.out +++ /dev/null @@ -1,12 +0,0 @@ -Source size: 37502 -rate=11025 -channels=1 -bits=16 -Reading entire buffer into memory - -Reading cow.raw -Size: 37502 - -Comparing... - -Done diff --git a/libs/mangle/sound/tests/owl.ogg b/libs/mangle/sound/tests/owl.ogg deleted file mode 100644 index e992f24d4..000000000 Binary files a/libs/mangle/sound/tests/owl.ogg and /dev/null differ diff --git a/libs/mangle/sound/tests/test.sh b/libs/mangle/sound/tests/test.sh deleted file mode 100755 index 2d07708ad..000000000 --- a/libs/mangle/sound/tests/test.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -make || exit - -mkdir -p output - -PROGS=*_test - -for a in $PROGS; do - if [ -f "output/$a.out" ]; then - echo "Running $a:" - ./$a | diff output/$a.out - - else - echo "Creating $a.out" - ./$a > "output/$a.out" - git add "output/$a.out" - fi -done diff --git a/libs/mangle/sound/tests/wav_source_test.cpp b/libs/mangle/sound/tests/wav_source_test.cpp deleted file mode 100644 index 749af1849..000000000 --- a/libs/mangle/sound/tests/wav_source_test.cpp +++ /dev/null @@ -1,48 +0,0 @@ -#include - -#include "../sources/wav_source.hpp" -#include "../../stream/servers/file_stream.hpp" - -#include -#include - -using namespace std; -using namespace Mangle::Sound; -using namespace Mangle::Stream; - -int main() -{ - WavSource wav("cow.wav"); - - cout << "Source size: " << wav.size() << endl; - int rate, channels, bits; - wav.getInfo(&rate, &channels, &bits); - cout << "rate=" << rate << "\nchannels=" << channels - << "\nbits=" << bits << endl; - - cout << "Reading entire buffer into memory\n"; - void *buf = malloc(wav.size()); - wav.read(buf, wav.size()); - - cout << "\nReading cow.raw\n"; - FileStream tmp("cow.raw"); - cout << "Size: " << tmp.size() << endl; - void *buf2 = malloc(tmp.size()); - tmp.read(buf2, tmp.size()); - - cout << "\nComparing...\n"; - if(tmp.size() != wav.size()) - { - cout << "SIZE MISMATCH!\n"; - assert(0); - } - - if(memcmp(buf, buf2, wav.size()) != 0) - { - cout << "CONTENT MISMATCH!\n"; - assert(0); - } - - cout << "\nDone\n"; - return 0; -} diff --git a/libs/openengine/bullet/BulletShapeLoader.cpp b/libs/openengine/bullet/BulletShapeLoader.cpp index 48b87e1e8..59a414f30 100644 --- a/libs/openengine/bullet/BulletShapeLoader.cpp +++ b/libs/openengine/bullet/BulletShapeLoader.cpp @@ -22,6 +22,7 @@ Ogre::Resource(creator, name, handle, group, isManual, loader) BulletShape::~BulletShape() { + deleteShape(Shape); } // farm out to BulletShapeLoader @@ -62,17 +63,17 @@ size_t BulletShape::calculateSize() const //============================================================================================================= -template<> BulletShapeManager *Ogre::Singleton::ms_Singleton = 0; +template<> BulletShapeManager *Ogre::Singleton::msSingleton = 0; BulletShapeManager *BulletShapeManager::getSingletonPtr() { - return ms_Singleton; + return msSingleton; } BulletShapeManager &BulletShapeManager::getSingleton() { - assert(ms_Singleton); - return(*ms_Singleton); + assert(msSingleton); + return(*msSingleton); } BulletShapeManager::BulletShapeManager() diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index 07bad3053..e7da9f085 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -134,10 +134,15 @@ namespace Physic RigidBody::RigidBody(btRigidBody::btRigidBodyConstructionInfo& CI,std::string name) - :btRigidBody(CI),mName(name) + : btRigidBody(CI) + , mName(name) { + } - }; + RigidBody::~RigidBody() + { + delete getMotionState(); + } @@ -146,7 +151,8 @@ namespace Physic - PhysicEngine::PhysicEngine(BulletShapeLoader* shapeLoader) + PhysicEngine::PhysicEngine(BulletShapeLoader* shapeLoader) : + mDebugActive(0) { // Set up the collision configuration and dispatcher collisionConfiguration = new btDefaultCollisionConfiguration(); @@ -155,8 +161,7 @@ namespace Physic // The actual physics solver solver = new btSequentialImpulseConstraintSolver; - //TODO: memory leak? - btOverlappingPairCache* pairCache = new btSortedOverlappingPairCache(); + pairCache = new btSortedOverlappingPairCache(); //pairCache->setInternalGhostPairCallback( new btGhostPairCallback() ); broadphase = new btDbvtBroadphase(pairCache); @@ -173,6 +178,7 @@ namespace Physic mShapeLoader = shapeLoader; isDebugCreated = false; + mDebugDrawer = NULL; } void PhysicEngine::createDebugRendering() @@ -198,15 +204,52 @@ namespace Physic createDebugRendering(); } mDebugDrawer->setDebugMode(mode); + mDebugActive = mode; + } + + bool PhysicEngine::toggleDebugRendering() + { + setDebugRenderingMode(!mDebugActive); + return mDebugActive; } PhysicEngine::~PhysicEngine() { + + RigidBodyContainer::iterator rb_it = RigidBodyMap.begin(); + for (; rb_it != RigidBodyMap.end(); ++rb_it) + { + if (rb_it->second != NULL) + { + dynamicsWorld->removeRigidBody(rb_it->second); + + delete rb_it->second; + rb_it->second = NULL; + } + } + + PhysicActorContainer::iterator pa_it = PhysicActorMap.begin(); + for (; pa_it != PhysicActorMap.end(); ++pa_it) + { + if (pa_it->second != NULL) + { + dynamicsWorld->removeCollisionObject(pa_it->second->externalGhostObject); + dynamicsWorld->removeCollisionObject(pa_it->second->internalGhostObject); + dynamicsWorld->removeAction(pa_it->second->mCharacter); + + delete pa_it->second; + pa_it->second = NULL; + } + } + + delete mDebugDrawer; + delete dynamicsWorld; delete solver; delete collisionConfiguration; delete dispatcher; delete broadphase; + delete pairCache; delete mShapeLoader; } @@ -239,32 +282,39 @@ namespace Physic dynamicsWorld->addRigidBody(body,COL_WORLD,COL_NOTHING); } body->setActivationState(DISABLE_DEACTIVATION); + RigidBody* oldBody = RigidBodyMap[body->mName]; + if (oldBody != NULL) + { + dynamicsWorld->removeRigidBody(oldBody); + delete oldBody; + } + RigidBodyMap[body->mName] = body; } void PhysicEngine::removeRigidBody(std::string name) { - std::map::iterator it = RigidBodyMap.find(name); + RigidBodyContainer::iterator it = RigidBodyMap.find(name); if (it != RigidBodyMap.end() ) { RigidBody* body = it->second; if(body != NULL) { // broadphase->getOverlappingPairCache()->removeOverlappingPairsContainingProxy(body->getBroadphaseProxy(),dispatcher); - /*std::map::iterator it2 = PhysicActorMap.begin(); + /*PhysicActorContainer::iterator it2 = PhysicActorMap.begin(); for(;it2!=PhysicActorMap.end();it++) { it2->second->internalGhostObject->getOverlappingPairCache()->removeOverlappingPairsContainingProxy(body->getBroadphaseProxy(),dispatcher); it2->second->externalGhostObject->getOverlappingPairCache()->removeOverlappingPairsContainingProxy(body->getBroadphaseProxy(),dispatcher); }*/ - dynamicsWorld->removeRigidBody(RigidBodyMap[name]); + dynamicsWorld->removeRigidBody(body); } } } void PhysicEngine::deleteRigidBody(std::string name) { - std::map::iterator it = RigidBodyMap.find(name); + RigidBodyContainer::iterator it = RigidBodyMap.find(name); if (it != RigidBodyMap.end() ) { RigidBody* body = it->second; @@ -293,6 +343,10 @@ namespace Physic void PhysicEngine::addCharacter(std::string name) { + // Remove character with given name, so we don't make memory + // leak when character would be added twice + removeCharacter(name); + PhysicActor* newActor = new PhysicActor(name); dynamicsWorld->addCollisionObject( newActor->externalGhostObject, COL_ACTOR_EXTERNAL, COL_WORLD |COL_ACTOR_EXTERNAL ); dynamicsWorld->addCollisionObject( newActor->internalGhostObject, COL_ACTOR_INTERNAL, COL_WORLD |COL_ACTOR_INTERNAL ); @@ -303,7 +357,7 @@ namespace Physic void PhysicEngine::removeCharacter(std::string name) { //std::cout << "remove"; - std::map::iterator it = PhysicActorMap.find(name); + PhysicActorContainer::iterator it = PhysicActorMap.find(name); if (it != PhysicActorMap.end() ) { PhysicActor* act = it->second; @@ -311,7 +365,7 @@ namespace Physic { /*broadphase->getOverlappingPairCache()->removeOverlappingPairsContainingProxy(act->externalGhostObject->getBroadphaseHandle(),dispatcher); broadphase->getOverlappingPairCache()->removeOverlappingPairsContainingProxy(act->internalGhostObject->getBroadphaseHandle(),dispatcher); - std::map::iterator it2 = PhysicActorMap.begin(); + PhysicActorContainer::iterator it2 = PhysicActorMap.begin(); for(;it2!=PhysicActorMap.end();it++) { it->second->internalGhostObject->getOverlappingPairCache()->removeOverlappingPairsContainingProxy(act->externalGhostObject->getBroadphaseHandle(),dispatcher); @@ -372,4 +426,35 @@ namespace Physic return std::pair(name,d); } + + std::vector< std::pair > PhysicEngine::rayTest2(btVector3& from, btVector3& to) + { + MyRayResultCallback resultCallback1; + resultCallback1.m_collisionFilterMask = COL_WORLD; + dynamicsWorld->rayTest(from, to, resultCallback1); + std::vector< std::pair > results = resultCallback1.results; + + MyRayResultCallback resultCallback2; + resultCallback2.m_collisionFilterMask = COL_ACTOR_INTERNAL|COL_ACTOR_EXTERNAL; + dynamicsWorld->rayTest(from, to, resultCallback2); + std::vector< std::pair > actorResults = resultCallback2.results; + + std::vector< std::pair > results2; + + for (std::vector< std::pair >::iterator it=results.begin(); + it != results.end(); ++it) + { + results2.push_back( std::make_pair( (*it).first, static_cast(*(*it).second).mName ) ); + } + + for (std::vector< std::pair >::iterator it=actorResults.begin(); + it != actorResults.end(); ++it) + { + results2.push_back( std::make_pair( (*it).first, static_cast(*(*it).second).mName ) ); + } + + std::sort(results2.begin(), results2.end(), MyRayResultCallback::cmp); + + return results2; + } }}; diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp index 88e3699ae..8d177efda 100644 --- a/libs/openengine/bullet/physic.hpp +++ b/libs/openengine/bullet/physic.hpp @@ -42,6 +42,8 @@ namespace Physic :btPairCachingGhostObject(),mName(name) { } + virtual ~PairCachingGhostObject(){} + std::string mName; }; @@ -106,6 +108,7 @@ namespace Physic { public: RigidBody(btRigidBody::btRigidBodyConstructionInfo& CI,std::string name); + virtual ~RigidBody(); std::string mName; //is this body used for raycasting only? @@ -196,11 +199,18 @@ namespace Physic */ void setDebugRenderingMode(int mode); + bool toggleDebugRendering(); + /** * Return the closest object hit by a ray. If there are no objects, it will return ("",-1). */ std::pair rayTest(btVector3& from,btVector3& to); + /** + * Return all objects hit by a ray. + */ + std::vector< std::pair > rayTest2(btVector3& from, btVector3& to); + //event list of non player object std::list NPEventList; @@ -208,6 +218,7 @@ namespace Physic std::list PEventList; //Bullet Stuff + btOverlappingPairCache* pairCache; btBroadphaseInterface* broadphase; btDefaultCollisionConfiguration* collisionConfiguration; btSequentialImpulseConstraintSolver* solver; @@ -217,12 +228,35 @@ namespace Physic //the NIF file loader. BulletShapeLoader* mShapeLoader; - std::map RigidBodyMap; - std::map PhysicActorMap; + typedef std::map RigidBodyContainer; + RigidBodyContainer RigidBodyMap; + + typedef std::map PhysicActorContainer; + PhysicActorContainer PhysicActorMap; //debug rendering BtOgre::DebugDrawer* mDebugDrawer; bool isDebugCreated; + bool mDebugActive; + }; + + + struct MyRayResultCallback : public btCollisionWorld::RayResultCallback + { + virtual btScalar addSingleResult( btCollisionWorld::LocalRayResult& rayResult, bool bNormalInWorldSpace) + { + results.push_back( std::make_pair(rayResult.m_hitFraction, rayResult.m_collisionObject) ); + return rayResult.m_hitFraction; + } + + static bool cmp( const std::pair& i, const std::pair& j ) + { + if( i.first > j.first ) return false; + if( j.first > i.first ) return true; + return false; + } + + std::vector < std::pair > results; }; }} diff --git a/libs/openengine/gui/layout.hpp b/libs/openengine/gui/layout.hpp index 3c03423c3..05a23e8ae 100644 --- a/libs/openengine/gui/layout.hpp +++ b/libs/openengine/gui/layout.hpp @@ -80,7 +80,30 @@ namespace GUI mMainWidget->setCoord(x,y,w,h); } - void setVisible(bool b) + void adjustWindowCaption() + { + // adjust the size of the window caption so that all text is visible + // NOTE: this assumes that mMainWidget is of type Window. + MyGUI::TextBox* box = static_cast(mMainWidget)->getCaptionWidget(); + box->setSize(box->getTextSize().width + 48, box->getSize().height); + + // in order to trigger alignment updates, we need to update the parent + // mygui doesn't provide a proper way of doing this, so we are just changing size + box->getParent()->setCoord(MyGUI::IntCoord( + box->getParent()->getCoord().left, + box->getParent()->getCoord().top, + box->getParent()->getCoord().width, + box->getParent()->getCoord().height+1 + )); + box->getParent()->setCoord(MyGUI::IntCoord( + box->getParent()->getCoord().left, + box->getParent()->getCoord().top, + box->getParent()->getCoord().width, + box->getParent()->getCoord().height-1 + )); + } + + virtual void setVisible(bool b) { mMainWidget->setVisible(b); } diff --git a/libs/openengine/gui/manager.cpp b/libs/openengine/gui/manager.cpp index 2d84ac804..1bf8ec325 100644 --- a/libs/openengine/gui/manager.cpp +++ b/libs/openengine/gui/manager.cpp @@ -18,7 +18,6 @@ void MyGUIManager::setup(Ogre::RenderWindow *wnd, Ogre::SceneManager *mgr, bool // manager before the main gui system itself, otherwise the main // object will get the chance to spit out a few messages before we // can able to disable it. - /// \todo - can't avoid this with MyGUI 3.2? std::string theLogFile = std::string(MYGUI_PLATFORM_LOG_FILENAME); if(!logDir.empty()) @@ -26,9 +25,9 @@ void MyGUIManager::setup(Ogre::RenderWindow *wnd, Ogre::SceneManager *mgr, bool // Set up OGRE platform. We might make this more generic later. mPlatform = new OgrePlatform(); + LogManager::getInstance().setSTDOutputEnabled(logging); mPlatform->initialise(wnd, mgr, "General", theLogFile); - LogManager::getInstance().setSTDOutputEnabled(logging); // Create GUI mGui = new Gui(); diff --git a/libs/openengine/ogre/imagerotate.cpp b/libs/openengine/ogre/imagerotate.cpp new file mode 100644 index 000000000..1147559d6 --- /dev/null +++ b/libs/openengine/ogre/imagerotate.cpp @@ -0,0 +1,74 @@ +#include "imagerotate.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Ogre; +using namespace OEngine::Render; + +void ImageRotate::rotate(const std::string& sourceImage, const std::string& destImage, const float angle) +{ + Root* root = Ogre::Root::getSingletonPtr(); + + SceneManager* sceneMgr = root->createSceneManager(ST_GENERIC); + Camera* camera = sceneMgr->createCamera("ImageRotateCamera"); + + MaterialPtr material = MaterialManager::getSingleton().create("ImageRotateMaterial", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); + material->getTechnique(0)->getPass(0)->setLightingEnabled(false); + material->getTechnique(0)->getPass(0)->setDepthCheckEnabled(false); + TextureUnitState* tus = material->getTechnique(0)->getPass(0)->createTextureUnitState(sourceImage); + Degree deg(angle); + tus->setTextureRotate(Radian(deg.valueRadians())); + tus->setTextureAddressingMode(TextureUnitState::TAM_BORDER); + tus->setTextureBorderColour(ColourValue(0, 0, 0, 0)); + + Rectangle2D* rect = new Rectangle2D(true); + rect->setCorners(-1.0, 1.0, 1.0, -1.0); + rect->setMaterial("ImageRotateMaterial"); + // Render the background before everything else + rect->setRenderQueueGroup(RENDER_QUEUE_BACKGROUND); + + // Use infinite AAB to always stay visible + AxisAlignedBox aabInf; + aabInf.setInfinite(); + rect->setBoundingBox(aabInf); + + // Attach background to the scene + SceneNode* node = sceneMgr->getRootSceneNode()->createChildSceneNode(); + node->attachObject(rect); + + // retrieve image width and height + TexturePtr sourceTexture = TextureManager::getSingleton().getByName(sourceImage); + unsigned int width = sourceTexture->getWidth(); + unsigned int height = sourceTexture->getHeight(); + + TexturePtr destTexture = TextureManager::getSingleton().createManual( + destImage, + ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + TEX_TYPE_2D, + width, height, + 0, + PF_A8R8G8B8, + TU_RENDERTARGET); + + RenderTarget* rtt = destTexture->getBuffer()->getRenderTarget(); + rtt->setAutoUpdated(false); + Viewport* vp = rtt->addViewport(camera); + vp->setOverlaysEnabled(false); + vp->setShadowsEnabled(false); + vp->setBackgroundColour(ColourValue(0,0,0,0)); + vp->setClearEveryFrame(true, FBT_DEPTH); + + rtt->update(); + + // remove all the junk we've created + MaterialManager::getSingleton().remove("ImageRotateMaterial"); + root->destroySceneManager(sceneMgr); + delete rect; +} diff --git a/libs/openengine/ogre/imagerotate.hpp b/libs/openengine/ogre/imagerotate.hpp new file mode 100644 index 000000000..a3f6d662f --- /dev/null +++ b/libs/openengine/ogre/imagerotate.hpp @@ -0,0 +1,27 @@ +#ifndef OENGINE_OGRE_IMAGEROTATE_HPP +#define OENGINE_OGRE_IMAGEROTATE_HPP + +#include + +namespace OEngine +{ +namespace Render +{ + + /// Rotate an image by certain degrees and save as file, uses the GPU + /// Make sure Ogre Root is initialised before calling + class ImageRotate + { + public: + /** + * @param source image (file name - has to exist in an resource group) + * @param name of the destination texture to save to (in memory) + * @param angle in degrees to turn + */ + static void rotate(const std::string& sourceImage, const std::string& destImage, const float angle); + }; + +} +} + +#endif diff --git a/libs/openengine/ogre/renderer.hpp b/libs/openengine/ogre/renderer.hpp index 6516b0a80..179515aa9 100644 --- a/libs/openengine/ogre/renderer.hpp +++ b/libs/openengine/ogre/renderer.hpp @@ -44,27 +44,51 @@ namespace Render Ogre::SceneManager *mScene; Ogre::Camera *mCamera; Ogre::Viewport *mView; - #ifdef ENABLE_PLUGIN_CgProgramManager +#ifdef ENABLE_PLUGIN_CgProgramManager Ogre::CgPlugin* mCgPlugin; - #endif - #ifdef ENABLE_PLUGIN_OctreeSceneManager +#endif +#ifdef ENABLE_PLUGIN_OctreeSceneManager Ogre::OctreePlugin* mOctreePlugin; - #endif - #ifdef ENABLE_PLUGIN_ParticleFX +#endif +#ifdef ENABLE_PLUGIN_ParticleFX Ogre::ParticleFXPlugin* mParticleFXPlugin; - #endif - #ifdef ENABLE_PLUGIN_GL +#endif +#ifdef ENABLE_PLUGIN_GL Ogre::GLPlugin* mGLPlugin; - #endif - #ifdef ENABLE_PLUGIN_Direct3D9 +#endif +#ifdef ENABLE_PLUGIN_Direct3D9 Ogre::D3D9Plugin* mD3D9Plugin; - #endif +#endif Fader* mFader; bool logging; public: OgreRenderer() - : mRoot(NULL), mWindow(NULL), mScene(NULL), mFader(NULL) {} + : mRoot(NULL) + , mWindow(NULL) + , mScene(NULL) + , mCamera(NULL) + , mView(NULL) +#ifdef ENABLE_PLUGIN_CgProgramManager + , mCgPlugin(NULL) +#endif +#ifdef ENABLE_PLUGIN_OctreeSceneManager + , mOctreePlugin(NULL) +#endif +#ifdef ENABLE_PLUGIN_ParticleFX + , mParticleFXPlugin(NULL) +#endif +#ifdef ENABLE_PLUGIN_GL + , mGLPlugin(NULL) +#endif +#ifdef ENABLE_PLUGIN_Direct3D9 + , mD3D9Plugin(NULL) +#endif + , mFader(NULL) + , logging(false) + { + } + ~OgreRenderer() { cleanup(); } /** Configure the renderer. This will load configuration files and diff --git a/libs/openengine/sound/sndmanager.cpp b/libs/openengine/sound/sndmanager.cpp deleted file mode 100644 index 02c6ba1e7..000000000 --- a/libs/openengine/sound/sndmanager.cpp +++ /dev/null @@ -1,219 +0,0 @@ -#include "sndmanager.hpp" - -#include "../misc/list.hpp" -#include - -using namespace OEngine::Sound; -using namespace Mangle::Sound; - -/** This is our own internal implementation of the - Mangle::Sound::Sound interface. This class links a SoundPtr to - itself and prevents itself from being deleted as long as the sound - is playing. - */ -struct OEngine::Sound::ManagedSound : SoundFilter -{ -private: - /** Who's your daddy? This is set if and only if we are listed - internally in the given SoundManager. - - It may be NULL if the manager has been deleted but the user - keeps their own SoundPtrs to the object. - */ - SoundManager *mgr; - - /** Keep a weak pointer to ourselves, which we convert into a - 'strong' pointer when we are playing. When 'self' is pointing to - ourselves, the object will never be deleted. - - This is used to make sure the sound is not deleted while - playing, unless it is explicitly ordered to do so by the - manager. - - TODO: This kind of construct is useful. If we need it elsewhere - later, template it. It would be generally useful in any system - where we poll to check if a resource is still needed, but where - manual references are allowed. - */ - WSoundPtr weak; - SoundPtr self; - - // Keep this object from being deleted - void lock() - { - self = SoundPtr(weak); - } - - // Release the lock. This may or may not delete the object. Never do - // anything after calling unlock()! - void unlock() - { - self.reset(); - } - -public: - // Used for putting ourselves in linked lists - ManagedSound *next, *prev; - - /** Detach this sound from its manager. This means that the manager - will no longer know we exist. Typically only called when either - the sound or the manager is about to get deleted. - - Since this means update() will no longer be called, we also have - to unlock the sound manually since it will no longer be able to - do that itself. This means that the sound may be deleted, even - if it is still playing, when the manager is deleted. - - However, you are still allowed to keep and manage your own - SoundPtr references, but the lock/unlock system is disabled - after the manager is gone. - */ - void detach() - { - if(mgr) - { - mgr->detach(this); - mgr = NULL; - } - - // Unlock must be last command. Object may get deleted at this - // point. - unlock(); - } - - ManagedSound(SoundPtr snd, SoundManager *mg) - : SoundFilter(snd), mgr(mg) - {} - ~ManagedSound() { detach(); } - - // Needed to set up the weak pointer - void setup(SoundPtr self) - { - weak = WSoundPtr(self); - } - - // Override play() to mark the object as locked - void play() - { - SoundFilter::play(); - - // Lock the object so that it is not deleted while playing. Only - // do this if we have a manager, otherwise the object will never - // get unlocked. - if(mgr) lock(); - } - - // Called regularly by the manager - void update() - { - // If we're no longer playing, don't force object retention. - if(!isPlaying()) - unlock(); - - // unlock() may delete the object, so don't do anything below this - // point. - } - - SoundPtr clone() - { - // Cloning only works when we have a manager. - assert(mgr); - return mgr->wrap(client->clone()); - } -}; - -struct SoundManager::SoundManagerList -{ -private: - // A linked list of ManagedSound objects. - typedef Misc::List SoundList; - SoundList list; - -public: - // Add a new sound to the list - void addNew(ManagedSound* snd) - { - list.insert(snd); - } - - // Remove a sound from the list - void remove(ManagedSound *snd) - { - list.remove(snd); - } - - // Number of sounds in the list - int numSounds() { return list.getNum(); } - - // Update all sounds - void updateAll() - { - ManagedSound *s = list.getHead(); - while(s) - { - ManagedSound *cur = s; - // Propagate first, since update() may delete object - s = s->next; - cur->update(); - } - } - - // Detach and unlock all sounds - void detachAll() - { - ManagedSound *s = list.getHead(); - while(s) - { - ManagedSound *cur = s; - s = s->next; - cur->detach(); - } - } -}; - -SoundManager::SoundManager(SoundFactoryPtr fact) - : FactoryFilter(fact) -{ - needsUpdate = true; - list = new SoundManagerList; -} - -SoundManager::~SoundManager() -{ - // Detach all sounds - list->detachAll(); -} - -SoundPtr SoundManager::wrap(SoundPtr client) -{ - // Create and set up the sound wrapper - ManagedSound *snd = new ManagedSound(client,this); - SoundPtr ptr(snd); - snd->setup(ptr); - - // Add ourselves to the list of all sounds - list->addNew(snd); - - return ptr; -} - -// Remove the sound from this manager. -void SoundManager::detach(ManagedSound *sound) -{ - list->remove(sound); -} - -int SoundManager::numSounds() -{ - return list->numSounds(); -} - -void SoundManager::update() -{ - // Update all the sounds we own - list->updateAll(); - - // Update the source if it needs it - if(client->needsUpdate) - client->update(); -} diff --git a/libs/openengine/sound/sndmanager.hpp b/libs/openengine/sound/sndmanager.hpp deleted file mode 100644 index 5ea0c4fc3..000000000 --- a/libs/openengine/sound/sndmanager.hpp +++ /dev/null @@ -1,93 +0,0 @@ -#ifndef OENGINE_SOUND_MANAGER_H -#define OENGINE_SOUND_MANAGER_H - -#include - -namespace OEngine -{ - namespace Sound - { - using namespace Mangle::Sound; - - class ManagedSound; - - /** A manager of Mangle::Sounds. - - The sound manager is a wrapper around the more low-level - SoundFactory - although it is also itself an implementation of - SoundFactory. It will: - - keep a list of all created sounds - - let you iterate the list - - keep references to playing sounds so you don't have to - - auto-release references to sounds that are finished playing - (ie. deleting them if you're not referencing them) - */ - class SoundManager : public FactoryFilter - { - // Shove the implementation details into the cpp file. - struct SoundManagerList; - SoundManagerList *list; - - // Create a new sound wrapper based on the given source sound. - SoundPtr wrap(SoundPtr snd); - - /** Internal function. Will completely disconnect the given - sound from this manager. Called from ManagedSound. - */ - friend class ManagedSound; - void detach(ManagedSound *sound); - public: - SoundManager(SoundFactoryPtr fact); - ~SoundManager(); - void update(); - - /// Get number of sounds currently managed by this manager. - int numSounds(); - - SoundPtr loadRaw(SampleSourcePtr input) - { return wrap(client->loadRaw(input)); } - - SoundPtr load(Mangle::Stream::StreamPtr input) - { return wrap(client->load(input)); } - - SoundPtr load(const std::string &file) - { return wrap(client->load(file)); } - - // Play a sound immediately, and release when done unless you - // keep the returned SoundPtr. - SoundPtr play(Mangle::Stream::StreamPtr sound) - { - SoundPtr snd = load(sound); - snd->play(); - return snd; - } - - SoundPtr play(const std::string &sound) - { - SoundPtr snd = load(sound); - snd->play(); - return snd; - } - - // Ditto for 3D sounds - SoundPtr play3D(Mangle::Stream::StreamPtr sound, float x, float y, float z) - { - SoundPtr snd = load(sound); - snd->setPos(x,y,z); - snd->play(); - return snd; - } - - SoundPtr play3D(const std::string &sound, float x, float y, float z) - { - SoundPtr snd = load(sound); - snd->setPos(x,y,z); - snd->play(); - return snd; - } - }; - - typedef boost::shared_ptr SoundManagerPtr; - } -} -#endif diff --git a/libs/openengine/sound/tests/Makefile b/libs/openengine/sound/tests/Makefile deleted file mode 100644 index 04952167f..000000000 --- a/libs/openengine/sound/tests/Makefile +++ /dev/null @@ -1,16 +0,0 @@ -GCC=g++ -I../ - -all: sound_manager_test sound_3d_test - -L_FFMPEG=$(shell pkg-config --libs libavcodec libavformat) -L_OPENAL=$(shell pkg-config --libs openal) -L_AUDIERE=-laudiere - -sound_manager_test: sound_manager_test.cpp ../../mangle/sound/sources/audiere_source.cpp ../../mangle/sound/outputs/openal_out.cpp ../../mangle/stream/clients/audiere_file.cpp ../sndmanager.cpp - $(GCC) $^ -o $@ $(L_AUDIERE) $(L_OPENAL) -I../.. - -sound_3d_test: sound_3d_test.cpp ../../mangle/sound/sources/audiere_source.cpp ../../mangle/sound/outputs/openal_out.cpp ../../mangle/stream/clients/audiere_file.cpp ../sndmanager.cpp - $(GCC) $^ -o $@ $(L_AUDIERE) $(L_OPENAL) -I../.. - -clean: - rm *_test diff --git a/libs/openengine/sound/tests/output/sound_3d_test.out b/libs/openengine/sound/tests/output/sound_3d_test.out deleted file mode 100644 index a443c84f0..000000000 --- a/libs/openengine/sound/tests/output/sound_3d_test.out +++ /dev/null @@ -1,3 +0,0 @@ -Playing at 0,0,0 -Playing at 1,1,0 -Playing at -1,0,0 diff --git a/libs/openengine/sound/tests/output/sound_manager_test.out b/libs/openengine/sound/tests/output/sound_manager_test.out deleted file mode 100644 index 2b458493d..000000000 --- a/libs/openengine/sound/tests/output/sound_manager_test.out +++ /dev/null @@ -1,5 +0,0 @@ -Playing ../../mangle/sound/tests/cow.wav -Replaying -pause -restart -Done playing. diff --git a/libs/openengine/sound/tests/sound_3d_test.cpp b/libs/openengine/sound/tests/sound_3d_test.cpp deleted file mode 100644 index f5b197fd0..000000000 --- a/libs/openengine/sound/tests/sound_3d_test.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include -#include -#include - -#include -#include - -#include - -using namespace std; -using namespace Mangle::Stream; -using namespace Mangle::Sound; -using namespace OEngine::Sound; - -const std::string sound = "../../mangle/sound/tests/cow.wav"; - -SoundManagerPtr m; - -// Play and wait for finish -void play(float x, float y, float z) -{ - cout << "Playing at " << x << "," << y << "," << z << endl; - - SoundPtr snd = m->play3D(sound,x,y,z); - - while(snd->isPlaying()) - { - usleep(10000); - m->update(); - } -} - -int main() -{ - SoundFactoryPtr oaf(new OpenAL_Audiere_Factory); - SoundManagerPtr mg(new SoundManager(oaf)); - m = mg; - - mg->setListenerPos(0,0,0,0,1,0,0,0,1); - - play(0,0,0); - play(1,1,0); - play(-1,0,0); - - return 0; -} diff --git a/libs/openengine/sound/tests/sound_manager_test.cpp b/libs/openengine/sound/tests/sound_manager_test.cpp deleted file mode 100644 index 3794c4a3c..000000000 --- a/libs/openengine/sound/tests/sound_manager_test.cpp +++ /dev/null @@ -1,73 +0,0 @@ -#include -#include -#include - -#include -#include - -#include - -using namespace std; -using namespace Mangle::Stream; -using namespace Mangle::Sound; -using namespace OEngine::Sound; - -const std::string sound = "../../mangle/sound/tests/cow.wav"; - -int main() -{ - SoundFactoryPtr oaf(new OpenAL_Audiere_Factory); - SoundManagerPtr mg(new SoundManager(oaf)); - - cout << "Playing " << sound << "\n"; - - assert(mg->numSounds() == 0); - - /** Start the sound playing, and then let the pointer go out of - scope. Lower-level players (like 'oaf' above) will immediately - delete the sound. SoundManager OTOH will keep it until it's - finished. - */ - mg->play(sound); - - assert(mg->numSounds() == 1); - - // Loop while there are still sounds to manage - while(mg->numSounds() != 0) - { - assert(mg->numSounds() == 1); - usleep(10000); - if(mg->needsUpdate) - mg->update(); - } - - SoundPtr snd = mg->play(sound); - cout << "Replaying\n"; - int i = 0; - while(mg->numSounds() != 0) - { - assert(mg->numSounds() == 1); - usleep(10000); - if(mg->needsUpdate) - mg->update(); - - if(i++ == 70) - { - cout << "pause\n"; - snd->pause(); - } - if(i == 130) - { - cout << "restart\n"; - snd->play(); - // Let the sound go out of scope - snd.reset(); - } - } - - cout << "Done playing.\n"; - - assert(mg->numSounds() == 0); - - return 0; -} diff --git a/libs/openengine/sound/tests/test.sh b/libs/openengine/sound/tests/test.sh deleted file mode 100755 index 2d07708ad..000000000 --- a/libs/openengine/sound/tests/test.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/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/readme.txt b/readme.txt index 8f360235f..e1c24ab52 100644 --- a/readme.txt +++ b/readme.txt @@ -7,6 +7,10 @@ Version: 0.13.0 License: GPL (see GPL3.txt for more information) Website: http://www.openmw.org +Font Licenses: +EBGaramond-Regular.ttf: OFL (see OFL.txt for more information) +VeraMono.ttf: custom (see Bitstream Vera License.txt for more information) + THIS IS A WORK IN PROGRESS @@ -91,6 +95,7 @@ Allowed options: CREDITS Current Developers: +Aleksandar Jovanov Alexander “Ace” Olofsson athile BrotherBrick @@ -144,6 +149,7 @@ Bug #207: Ogre.log not written Bug #209: Sounds do not play Bug #210: Ogre crash at Dren plantation Bug #214: Unsupported file format version +Bug #222: Launcher is writing openmw.cfg file to wrong location Feature #9: NPC Dialogue Window Feature #16/42: New sky/weather implementation Feature #40: Fading