diff --git a/.gitignore b/.gitignore index 8e8f6449c..b3bb8d82d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,9 @@ Docs/mainpage.hpp CMakeFiles */CMakeFiles CMakeCache.txt +moc_*.cxx +cmake_install.cmake +*.[ao] Makefile makefile data diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 1ce6250b3..000000000 --- a/.gitmodules +++ /dev/null @@ -1,6 +0,0 @@ -[submodule "libs/mangle"] - path = libs/mangle - url = git://github.com/zinnschlag/mangle.git -[submodule "libs/openengine"] - path = libs/openengine - url = git://github.com/zinnschlag/OpenEngine diff --git a/CMakeLists.txt b/CMakeLists.txt index eba77c394..cc1dea25c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,8 +18,8 @@ include (OpenMWMacros) # Version set (OPENMW_VERSION_MAJOR 0) -set (OPENMW_VERSION_MINOR 11) -set (OPENMW_VERSION_RELEASE 1) +set (OPENMW_VERSION_MINOR 12) +set (OPENMW_VERSION_RELEASE 0) set (OPENMW_VERSION "${OPENMW_VERSION_MAJOR}.${OPENMW_VERSION_MINOR}.${OPENMW_VERSION_RELEASE}") @@ -192,7 +192,7 @@ find_package(Bullet REQUIRED) include_directories("." ${OGRE_INCLUDE_DIR} ${OGRE_INCLUDE_DIR}/Ogre ${OGRE_INCLUDE_DIR}/OGRE ${OGRE_Terrain_INCLUDE_DIR} - ${OIS_INCLUDE_DIR} ${Boost_INCLUDE_DIR} + ${OIS_INCLUDE_DIRS} ${Boost_INCLUDE_DIR} ${PLATFORM_INCLUDE_DIR} ${CMAKE_HOME_DIRECTORY}/extern/mygui_3.0.1/MyGUIEngine/include ${CMAKE_HOME_DIRECTORY}/extern/mygui_3.0.1/OgrePlatform/include @@ -297,7 +297,7 @@ if(DPKG_PROGRAM) SET(CPACK_GENERATOR "DEB") SET(CPACK_PACKAGE_NAME "openmw") - SET(CPACK_DEBIAN_PACKAGE_HOMEPAGE "http://openmw.com") + SET(CPACK_DEBIAN_PACKAGE_HOMEPAGE "http://openmw.org") SET(CPACK_DEBIAN_PACKAGE_PRIORITY "optional") SET(CPACK_DEBIAN_PACKAGE_MAINTAINER "${PACKAGE_MAINTAINER}") SET(CPACK_DEBIAN_PACKAGE_DESCRIPTION "A reimplementation of The Elder Scrolls III: Morrowind @@ -326,7 +326,6 @@ 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_BINARY_DIR}/launcher.cfg" "${OpenMW_BINARY_DIR}/launcher.qss" DESTINATION ".") INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION ".") SET(CPACK_GENERATOR "NSIS") @@ -344,12 +343,21 @@ if(WIN32) SET(CPACK_NSIS_HELP_LINK "http:\\\\\\\\www.openmw.org") SET(CPACK_NSIS_URL_INFO_ABOUT "http:\\\\\\\\www.openmw.org") SET(CPACK_NSIS_INSTALLED_ICON_NAME "omwlauncher.exe") + SET(CPACK_NSIS_MUI_ICON "${OpenMW_SOURCE_DIR}/apps/launcher/resources/images/openmw.ico") + SET(CPACK_NSIS_MUI_UNIICON "${OpenMW_SOURCE_DIR}/apps/launcher/resources/images/openmw.ico") + # SET(CPACK_PACKAGE_ICON "${OpenMW_SOURCE_DIR}\\\\files\\\\openmw.bmp") - SET(VCREDIST "${OpenMW_BINARY_DIR}/vcredist_x86.exe") - if(EXISTS ${VCREDIST}) - INSTALL(FILES ${VCREDIST} DESTINATION "redist") + SET(VCREDIST32 "${OpenMW_BINARY_DIR}/vcredist_x86.exe") + if(EXISTS ${VCREDIST32}) + INSTALL(FILES ${VCREDIST32} DESTINATION "redist") SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "ExecWait '\\\"$INSTDIR\\\\redist\\\\vcredist_x86.exe\\\" /q'" ) - endif(EXISTS ${VCREDIST}) + endif(EXISTS ${VCREDIST32}) + + SET(VCREDIST64 "${OpenMW_BINARY_DIR}/vcredist_x64.exe") + if(EXISTS ${VCREDIST64}) + INSTALL(FILES ${VCREDIST64} DESTINATION "redist") + SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "ExecWait '\\\"$INSTDIR\\\\redist\\\\vcredist_x64.exe\\\" /q'" ) + endif(EXISTS ${VCREDIST64}) SET(OALREDIST "${OpenMW_BINARY_DIR}/oalinst.exe") if(EXISTS ${OALREDIST}) @@ -358,6 +366,10 @@ if(WIN32) ExecWait '\\\"$INSTDIR\\\\redist\\\\oalinst.exe\\\" /s'" ) endif(EXISTS ${OALREDIST}) + if(CMAKE_CL_64) + SET(CPACK_NSIS_INSTALL_ROOT "$PROGRAMFILES64") + endif() + include(CPack) endif(WIN32) @@ -420,19 +432,13 @@ endif() if (APPLE) set(INSTALL_SUBDIR OpenMW) - #install(FILES ${MISC_FILES} DESTINATION ../MacOS) - #install(DIRECTORY "${APP_BUNDLE_DIR}/Contents/Plugins" DESTINATION ..) - #install(DIRECTORY "${APP_BUNDLE_DIR}/Contents/Resources/resources" DESTINATION ../Resources) install(DIRECTORY "${APP_BUNDLE_DIR}" USE_SOURCE_PERMISSIONS DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) install(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) 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_BUNDLE_PLIST "${CMAKE_SOURCE_DIR}/files/mac/Info.plist") - # set(CPACK_BUNDLE_ICON "${CMAKE_SOURCE_DIR}/files/mac/openmw.icns") - # set(CPACK_BUNDLE_NAME "OpenMW") set(CPACK_PACKAGE_VERSION ${OPENMW_VERSION}) set(CPACK_PACKAGE_VERSION_MAJOR ${OPENMW_VERSION_MAJOR}) set(CPACK_PACKAGE_VERSION_MINOR ${OPENMW_VERSION_MINO}) @@ -442,11 +448,13 @@ if (APPLE) set(PLUGINS "") # Scan Plugins dir for *.dylibs - file(GLOB ALL_PLUGINS "${APP_BUNDLE_DIR}/Contents/Plugins/*.dylib") + set(PLUGIN_SEARCH_ROOT "${APP_BUNDLE_DIR}/Contents/Plugins") + file(GLOB_RECURSE ALL_PLUGINS "${PLUGIN_SEARCH_ROOT}/*.dylib") + set(PLUGIN_INSTALL_BASE "\${CMAKE_INSTALL_PREFIX}/${INSTALL_SUBDIR}/${APP_BUNDLE_NAME}/Contents/Plugins") foreach(PLUGIN ${ALL_PLUGINS}) - get_filename_component(PLUGIN_FILENAME ${PLUGIN} NAME) - set(PLUGINS ${PLUGINS} "\${CMAKE_INSTALL_PREFIX}/${INSTALL_SUBDIR}/${APP_BUNDLE_NAME}/Contents/Plugins/${PLUGIN_FILENAME}") + string(REPLACE "${PLUGIN_SEARCH_ROOT}/" "" PLUGIN_RELATIVE "${PLUGIN}") + set(PLUGINS ${PLUGINS} "${PLUGIN_INSTALL_BASE}/${PLUGIN_RELATIVE}") endforeach() #For now, search unresolved dependencies only in default system paths, so if you put unresolveable (i.e. with @executable_path in id name) lib or framework somewhere else, it would fail @@ -456,7 +464,7 @@ if (APPLE) # some library already has be "fixed up", i.e. its id name contains @executable_path, # but library is not embedded in bundle. For example, it's Ogre.framework from Ogre SDK. # Current implementation of GetPrerequsities/BundleUtilities doesn't handle that case. - # + # # Current limitations: # 1. Handles only frameworks, not simple libs INSTALL(CODE " diff --git a/apps/esmtool/CMakeLists.txt b/apps/esmtool/CMakeLists.txt index f2ab7bce7..af3dc090e 100644 --- a/apps/esmtool/CMakeLists.txt +++ b/apps/esmtool/CMakeLists.txt @@ -1,6 +1,4 @@ set(ESMTOOL - esmtool_cmd.c - esmtool_cmd.h esmtool.cpp ) source_group(apps\\esmtool FILES ${ESMTOOL}) diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index fe067d85d..f417d5c60 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -1,35 +1,138 @@ +#include + +#include + #include #include -#include "esmtool_cmd.h" - -#include +#define ESMTOOL_VERSION 1.1 using namespace std; using namespace ESM; +// Create a local alias for brevity +namespace bpo = boost::program_options; + void printRaw(ESMReader &esm); void loadCell(Cell &cell, ESMReader &esm, bool quiet); -int main(int argc, char**argv) +// Based on the legacy struct +struct Arguments { - gengetopt_args_info info; + unsigned int raw_given; + unsigned int quiet_given; + unsigned int loadcells_given; + std::string encoding; + std::string filename; +}; - if(cmdline_parser(argc, argv, &info) != 0) - return 1; +bool parseOptions (int argc, char** argv, Arguments &info) +{ + bpo::options_description desc("Inspect and extract from Morrowind ES files (ESM, ESP, ESS)\nSyntax: esmtool [options] file \nAllowed options"); + + desc.add_options() + ("help,h", "print help message.") + ("version,v", "print version information and quit.") + ("raw,r", "Show an unformattet list of all records and subrecords.") + ("quiet,q", "Supress all record information. Useful for speed tests.") + ("loadcells,C", "Browse through contents of all cells.") + + ( "encoding,e", bpo::value(&(info.encoding))-> + default_value("win1252"), + "Character encoding used in ESMTool:\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") + ; + + std::string finalText = "\nIf no option is given, the default action is to parse all records in the archive\nand display diagnostic information."; - if(info.inputs_num != 1) + // input-file is hidden and used as a positional argument + bpo::options_description hidden("Hidden Options"); + + hidden.add_options() + ( "input-file,i", bpo::value< vector >(), "input file") + ; + + bpo::positional_options_description p; + p.add("input-file", -1); + + // there might be a better way to do this + bpo::options_description all; + all.add(desc).add(hidden); + bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv) + .options(all).positional(p).run(); + + bpo::variables_map variables; + bpo::store(valid_opts, variables); + bpo::notify(variables); + + if (variables.count ("help")) { - if(info.inputs_num == 0) - cout << "ERROR: missing ES file\n\n"; - else - cout << "ERROR: more than one ES file specified\n\n"; - cmdline_parser_print_help(); - return 1; + std::cout << desc << finalText << std::endl; + return false; + } + if (variables.count ("version")) + { + std::cout << "ESMTool version " << ESMTOOL_VERSION << std::endl; + return false; + } + + if ( !variables.count("input-file") ) + { + std::cout << "\nERROR: missing ES file\n\n"; + std::cout << desc << finalText << std::endl; + return false; } + // handling gracefully the user adding multiple files + if (variables["input-file"].as< vector >().size() > 1) + { + std::cout << "\nERROR: more than one ES file specified\n\n"; + std::cout << desc << finalText << std::endl; + return false; + } + + info.filename = variables["input-file"].as< vector >()[0]; + + info.raw_given = variables.count ("raw"); + info.quiet_given = variables.count ("quiet"); + info.loadcells_given = variables.count ("loadcells"); + + // Font encoding settings + info.encoding = variables["encoding"].as(); + if (info.encoding == "win1250") + { + std::cout << "Using Central and Eastern European font encoding." << std::endl; + } + else if (info.encoding == "win1251") + { + std::cout << "Using Cyrillic font encoding." << std::endl; + } + else + { + if(info.encoding != "win1252") + { + std::cout << info.encoding << " is not a valid encoding option." << std::endl; + info.encoding = "win1252"; + } + std::cout << "Using default (English) font encoding." << std::endl; + } + + return true; +} + + +int main(int argc, char**argv) +{ + Arguments info; + if(!parseOptions (argc, argv, info)) + return 1; + ESMReader esm; - const char* filename = info.inputs[0]; + esm.setEncoding(info.encoding); + + string filename = info.filename; cout << "\nFile: " << filename << endl; try { diff --git a/apps/esmtool/esmtool.ggo b/apps/esmtool/esmtool.ggo deleted file mode 100644 index 9d0f3c189..000000000 --- a/apps/esmtool/esmtool.ggo +++ /dev/null @@ -1,10 +0,0 @@ -package "esmtool" -version "1.0" -purpose "Inspect and extract from Morrowind ES files (ESM, ESP, ESS)" -args "--unamed-opts=ES-FILE -F esmtool_cmd -G" - -option "raw" r "Show an unformattet list of all records and subrecords" optional -option "quiet" q "Supress all record information. Useful for speed tests." optional -option "loadcells" C "Browse through contents of all cells." optional - -text "\nIf no option is given, the default action is to parse all records in the archive and display diagnostic information." diff --git a/apps/esmtool/esmtool_cmd.c b/apps/esmtool/esmtool_cmd.c deleted file mode 100644 index 3fce77de2..000000000 --- a/apps/esmtool/esmtool_cmd.c +++ /dev/null @@ -1,1141 +0,0 @@ -/* - File autogenerated by gengetopt version 2.22.2 - generated with the following command: - gengetopt --unamed-opts=ES-FILE -F esmtool_cmd -G - - The developers of gengetopt consider the fixed text that goes in all - gengetopt output files to be in the public domain: - we make no copyright claims on it. -*/ - -/* If we use autoconf. */ -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#include -#include -#include - -#ifndef FIX_UNUSED -#define FIX_UNUSED(X) (void) (X) /* avoid warnings for unused params */ -#endif - - -#include "esmtool_cmd.h" - -const char *gengetopt_args_info_purpose = "Inspect and extract from Morrowind ES files (ESM, ESP, ESS)"; - -const char *gengetopt_args_info_usage = "Usage: esmtool [OPTIONS]... [ES-FILE]..."; - -const char *gengetopt_args_info_description = ""; - -const char *gengetopt_args_info_help[] = { - " -h, --help Print help and exit", - " -V, --version Print version and exit", - " -r, --raw Show an unformattet list of all records and subrecords", - " -q, --quiet Supress all record information. Useful for speed tests.", - " -C, --loadcells Browse through contents of all cells.", - "\nIf no option is given, the default action is to parse all records in the \narchive and display diagnostic information.", - 0 -}; - -typedef enum {ARG_NO -} cmdline_parser_arg_type; - -static -void clear_given (struct gengetopt_args_info *args_info); -static -void clear_args (struct gengetopt_args_info *args_info); - -static int -cmdline_parser_internal (int argc, char * const *argv, struct gengetopt_args_info *args_info, - struct cmdline_parser_params *params, const char *additional_error); - - -static char * -gengetopt_strdup (const char *s); - -static -void clear_given (struct gengetopt_args_info *args_info) -{ - args_info->help_given = 0 ; - args_info->version_given = 0 ; - args_info->raw_given = 0 ; - args_info->quiet_given = 0 ; - args_info->loadcells_given = 0 ; -} - -static -void clear_args (struct gengetopt_args_info *args_info) -{ - FIX_UNUSED (args_info); - -} - -static -void init_args_info(struct gengetopt_args_info *args_info) -{ - - - args_info->help_help = gengetopt_args_info_help[0] ; - args_info->version_help = gengetopt_args_info_help[1] ; - args_info->raw_help = gengetopt_args_info_help[2] ; - args_info->quiet_help = gengetopt_args_info_help[3] ; - args_info->loadcells_help = gengetopt_args_info_help[4] ; - -} - -void -cmdline_parser_print_version (void) -{ - printf ("%s %s\n", - (strlen(CMDLINE_PARSER_PACKAGE_NAME) ? CMDLINE_PARSER_PACKAGE_NAME : CMDLINE_PARSER_PACKAGE), - CMDLINE_PARSER_VERSION); -} - -static void print_help_common(void) { - cmdline_parser_print_version (); - - if (strlen(gengetopt_args_info_purpose) > 0) - printf("\n%s\n", gengetopt_args_info_purpose); - - if (strlen(gengetopt_args_info_usage) > 0) - printf("\n%s\n", gengetopt_args_info_usage); - - printf("\n"); - - if (strlen(gengetopt_args_info_description) > 0) - printf("%s\n\n", gengetopt_args_info_description); -} - -void -cmdline_parser_print_help (void) -{ - int i = 0; - print_help_common(); - while (gengetopt_args_info_help[i]) - printf("%s\n", gengetopt_args_info_help[i++]); -} - -void -cmdline_parser_init (struct gengetopt_args_info *args_info) -{ - clear_given (args_info); - clear_args (args_info); - init_args_info (args_info); - - args_info->inputs = 0; - args_info->inputs_num = 0; -} - -void -cmdline_parser_params_init(struct cmdline_parser_params *params) -{ - if (params) - { - params->override = 0; - params->initialize = 1; - params->check_required = 1; - params->check_ambiguity = 0; - params->print_errors = 1; - } -} - -struct cmdline_parser_params * -cmdline_parser_params_create(void) -{ - struct cmdline_parser_params *params = - (struct cmdline_parser_params *)malloc(sizeof(struct cmdline_parser_params)); - cmdline_parser_params_init(params); - return params; -} - - - -static void -cmdline_parser_release (struct gengetopt_args_info *args_info) -{ - unsigned int i; - - - for (i = 0; i < args_info->inputs_num; ++i) - free (args_info->inputs [i]); - - if (args_info->inputs_num) - free (args_info->inputs); - - clear_given (args_info); -} - - -static void -write_into_file(FILE *outfile, const char *opt, const char *arg, const char *values[]) -{ - FIX_UNUSED (values); - if (arg) { - fprintf(outfile, "%s=\"%s\"\n", opt, arg); - } else { - fprintf(outfile, "%s\n", opt); - } -} - - -int -cmdline_parser_dump(FILE *outfile, struct gengetopt_args_info *args_info) -{ - int i = 0; - - if (!outfile) - { - fprintf (stderr, "%s: cannot dump options to stream\n", CMDLINE_PARSER_PACKAGE); - return EXIT_FAILURE; - } - - if (args_info->help_given) - write_into_file(outfile, "help", 0, 0 ); - if (args_info->version_given) - write_into_file(outfile, "version", 0, 0 ); - if (args_info->raw_given) - write_into_file(outfile, "raw", 0, 0 ); - if (args_info->quiet_given) - write_into_file(outfile, "quiet", 0, 0 ); - if (args_info->loadcells_given) - write_into_file(outfile, "loadcells", 0, 0 ); - - - i = EXIT_SUCCESS; - return i; -} - -int -cmdline_parser_file_save(const char *filename, struct gengetopt_args_info *args_info) -{ - FILE *outfile; - int i = 0; - - outfile = fopen(filename, "w"); - - if (!outfile) - { - fprintf (stderr, "%s: cannot open file for writing: %s\n", CMDLINE_PARSER_PACKAGE, filename); - return EXIT_FAILURE; - } - - i = cmdline_parser_dump(outfile, args_info); - fclose (outfile); - - return i; -} - -void -cmdline_parser_free (struct gengetopt_args_info *args_info) -{ - cmdline_parser_release (args_info); -} - -/** @brief replacement of strdup, which is not standard */ -char * -gengetopt_strdup (const char *s) -{ - char *result = 0; - if (!s) - return result; - - result = (char*)malloc(strlen(s) + 1); - if (result == (char*)0) - return (char*)0; - strcpy(result, s); - return result; -} - -int -cmdline_parser (int argc, char * const *argv, struct gengetopt_args_info *args_info) -{ - return cmdline_parser2 (argc, argv, args_info, 0, 1, 1); -} - -int -cmdline_parser_ext (int argc, char * const *argv, struct gengetopt_args_info *args_info, - struct cmdline_parser_params *params) -{ - int result; - result = cmdline_parser_internal (argc, argv, args_info, params, 0); - - if (result == EXIT_FAILURE) - { - cmdline_parser_free (args_info); - exit (EXIT_FAILURE); - } - - return result; -} - -int -cmdline_parser2 (int argc, char * const *argv, struct gengetopt_args_info *args_info, int override, int initialize, int check_required) -{ - int result; - struct cmdline_parser_params params; - - params.override = override; - params.initialize = initialize; - params.check_required = check_required; - params.check_ambiguity = 0; - params.print_errors = 1; - - result = cmdline_parser_internal (argc, argv, args_info, ¶ms, 0); - - if (result == EXIT_FAILURE) - { - cmdline_parser_free (args_info); - exit (EXIT_FAILURE); - } - - return result; -} - -int -cmdline_parser_required (struct gengetopt_args_info *args_info, const char *prog_name) -{ - FIX_UNUSED (args_info); - FIX_UNUSED (prog_name); - return EXIT_SUCCESS; -} - -/* - * Extracted from the glibc source tree, version 2.3.6 - * - * Licensed under the GPL as per the whole glibc source tree. - * - * This file was modified so that getopt_long can be called - * many times without risking previous memory to be spoiled. - * - * Modified by Andre Noll and Lorenzo Bettini for use in - * GNU gengetopt generated files. - * - */ - -/* - * we must include anything we need since this file is not thought to be - * inserted in a file already using getopt.h - * - * Lorenzo - */ - -struct option -{ - const char *name; - /* has_arg can't be an enum because some compilers complain about - type mismatches in all the code that assumes it is an int. */ - int has_arg; - int *flag; - int val; -}; - -/* This version of `getopt' appears to the caller like standard Unix `getopt' - but it behaves differently for the user, since it allows the user - to intersperse the options with the other arguments. - - As `getopt' works, it permutes the elements of ARGV so that, - when it is done, all the options precede everything else. Thus - all application programs are extended to handle flexible argument order. -*/ -/* - If the field `flag' is not NULL, it points to a variable that is set - to the value given in the field `val' when the option is found, but - left unchanged if the option is not found. - - To have a long-named option do something other than set an `int' to - a compiled-in constant, such as set a value from `custom_optarg', set the - option's `flag' field to zero and its `val' field to a nonzero - value (the equivalent single-letter option character, if there is - one). For long options that have a zero `flag' field, `getopt' - returns the contents of the `val' field. */ - -/* Names for the values of the `has_arg' field of `struct option'. */ -#ifndef no_argument -#define no_argument 0 -#endif - -#ifndef required_argument -#define required_argument 1 -#endif - -#ifndef optional_argument -#define optional_argument 2 -#endif - -struct custom_getopt_data { - /* - * These have exactly the same meaning as the corresponding global variables, - * except that they are used for the reentrant versions of getopt. - */ - int custom_optind; - int custom_opterr; - int custom_optopt; - char *custom_optarg; - - /* True if the internal members have been initialized. */ - int initialized; - - /* - * The next char to be scanned in the option-element in which the last option - * character we returned was found. This allows us to pick up the scan where - * we left off. If this is zero, or a null string, it means resume the scan by - * advancing to the next ARGV-element. - */ - char *nextchar; - - /* - * Describe the part of ARGV that contains non-options that have been skipped. - * `first_nonopt' is the index in ARGV of the first of them; `last_nonopt' is - * the index after the last of them. - */ - int first_nonopt; - int last_nonopt; -}; - -/* - * the variables optarg, optind, opterr and optopt are renamed with - * the custom_ prefix so that they don't interfere with getopt ones. - * - * Moreover they're static so they are visible only from within the - * file where this very file will be included. - */ - -/* - * For communication from `custom_getopt' to the caller. When `custom_getopt' finds an - * option that takes an argument, the argument value is returned here. - */ -static char *custom_optarg; - -/* - * Index in ARGV of the next element to be scanned. This is used for - * communication to and from the caller and for communication between - * successive calls to `custom_getopt'. - * - * On entry to `custom_getopt', 1 means this is the first call; initialize. - * - * When `custom_getopt' returns -1, this is the index of the first of the non-option - * elements that the caller should itself scan. - * - * Otherwise, `custom_optind' communicates from one call to the next how much of ARGV - * has been scanned so far. - * - * 1003.2 says this must be 1 before any call. - */ -static int custom_optind = 1; - -/* - * Callers store zero here to inhibit the error message for unrecognized - * options. - */ -static int custom_opterr = 1; - -/* - * Set to an option character which was unrecognized. This must be initialized - * on some systems to avoid linking in the system's own getopt implementation. - */ -static int custom_optopt = '?'; - -/* - * Exchange two adjacent subsequences of ARGV. One subsequence is elements - * [first_nonopt,last_nonopt) which contains all the non-options that have been - * skipped so far. The other is elements [last_nonopt,custom_optind), which contains - * all the options processed since those non-options were skipped. - * `first_nonopt' and `last_nonopt' are relocated so that they describe the new - * indices of the non-options in ARGV after they are moved. - */ -static void exchange(char **argv, struct custom_getopt_data *d) -{ - int bottom = d->first_nonopt; - int middle = d->last_nonopt; - int top = d->custom_optind; - char *tem; - - /* - * Exchange the shorter segment with the far end of the longer segment. - * That puts the shorter segment into the right place. It leaves the - * longer segment in the right place overall, but it consists of two - * parts that need to be swapped next. - */ - while (top > middle && middle > bottom) { - if (top - middle > middle - bottom) { - /* Bottom segment is the short one. */ - int len = middle - bottom; - int i; - - /* Swap it with the top part of the top segment. */ - for (i = 0; i < len; i++) { - tem = argv[bottom + i]; - argv[bottom + i] = - argv[top - (middle - bottom) + i]; - argv[top - (middle - bottom) + i] = tem; - } - /* Exclude the moved bottom segment from further swapping. */ - top -= len; - } else { - /* Top segment is the short one. */ - int len = top - middle; - int i; - - /* Swap it with the bottom part of the bottom segment. */ - for (i = 0; i < len; i++) { - tem = argv[bottom + i]; - argv[bottom + i] = argv[middle + i]; - argv[middle + i] = tem; - } - /* Exclude the moved top segment from further swapping. */ - bottom += len; - } - } - /* Update records for the slots the non-options now occupy. */ - d->first_nonopt += (d->custom_optind - d->last_nonopt); - d->last_nonopt = d->custom_optind; -} - -/* Initialize the internal data when the first call is made. */ -static void custom_getopt_initialize(struct custom_getopt_data *d) -{ - /* - * Start processing options with ARGV-element 1 (since ARGV-element 0 - * is the program name); the sequence of previously skipped non-option - * ARGV-elements is empty. - */ - d->first_nonopt = d->last_nonopt = d->custom_optind; - d->nextchar = NULL; - d->initialized = 1; -} - -#define NONOPTION_P (argv[d->custom_optind][0] != '-' || argv[d->custom_optind][1] == '\0') - -/* return: zero: continue, nonzero: return given value to user */ -static int shuffle_argv(int argc, char *const *argv,const struct option *longopts, - struct custom_getopt_data *d) -{ - /* - * Give FIRST_NONOPT & LAST_NONOPT rational values if CUSTOM_OPTIND has been - * moved back by the user (who may also have changed the arguments). - */ - if (d->last_nonopt > d->custom_optind) - d->last_nonopt = d->custom_optind; - if (d->first_nonopt > d->custom_optind) - d->first_nonopt = d->custom_optind; - /* - * If we have just processed some options following some - * non-options, exchange them so that the options come first. - */ - if (d->first_nonopt != d->last_nonopt && - d->last_nonopt != d->custom_optind) - exchange((char **) argv, d); - else if (d->last_nonopt != d->custom_optind) - d->first_nonopt = d->custom_optind; - /* - * Skip any additional non-options and extend the range of - * non-options previously skipped. - */ - while (d->custom_optind < argc && NONOPTION_P) - d->custom_optind++; - d->last_nonopt = d->custom_optind; - /* - * The special ARGV-element `--' means premature end of options. Skip - * it like a null option, then exchange with previous non-options as if - * it were an option, then skip everything else like a non-option. - */ - if (d->custom_optind != argc && !strcmp(argv[d->custom_optind], "--")) { - d->custom_optind++; - if (d->first_nonopt != d->last_nonopt - && d->last_nonopt != d->custom_optind) - exchange((char **) argv, d); - else if (d->first_nonopt == d->last_nonopt) - d->first_nonopt = d->custom_optind; - d->last_nonopt = argc; - d->custom_optind = argc; - } - /* - * If we have done all the ARGV-elements, stop the scan and back over - * any non-options that we skipped and permuted. - */ - if (d->custom_optind == argc) { - /* - * Set the next-arg-index to point at the non-options that we - * previously skipped, so the caller will digest them. - */ - if (d->first_nonopt != d->last_nonopt) - d->custom_optind = d->first_nonopt; - return -1; - } - /* - * If we have come to a non-option and did not permute it, either stop - * the scan or describe it to the caller and pass it by. - */ - if (NONOPTION_P) { - d->custom_optarg = argv[d->custom_optind++]; - return 1; - } - /* - * We have found another option-ARGV-element. Skip the initial - * punctuation. - */ - d->nextchar = (argv[d->custom_optind] + 1 + (longopts != NULL && argv[d->custom_optind][1] == '-')); - return 0; -} - -/* - * Check whether the ARGV-element is a long option. - * - * If there's a long option "fubar" and the ARGV-element is "-fu", consider - * that an abbreviation of the long option, just like "--fu", and not "-f" with - * arg "u". - * - * This distinction seems to be the most useful approach. - * - */ -static int check_long_opt(int argc, char *const *argv, const char *optstring, - const struct option *longopts, int *longind, - int print_errors, struct custom_getopt_data *d) -{ - char *nameend; - const struct option *p; - const struct option *pfound = NULL; - int exact = 0; - int ambig = 0; - int indfound = -1; - int option_index; - - for (nameend = d->nextchar; *nameend && *nameend != '='; nameend++) - /* Do nothing. */ ; - - /* Test all long options for either exact match or abbreviated matches */ - for (p = longopts, option_index = 0; p->name; p++, option_index++) - if (!strncmp(p->name, d->nextchar, nameend - d->nextchar)) { - if ((unsigned int) (nameend - d->nextchar) - == (unsigned int) strlen(p->name)) { - /* Exact match found. */ - pfound = p; - indfound = option_index; - exact = 1; - break; - } else if (pfound == NULL) { - /* First nonexact match found. */ - pfound = p; - indfound = option_index; - } else if (pfound->has_arg != p->has_arg - || pfound->flag != p->flag - || pfound->val != p->val) - /* Second or later nonexact match found. */ - ambig = 1; - } - if (ambig && !exact) { - if (print_errors) { - fprintf(stderr, - "%s: option `%s' is ambiguous\n", - argv[0], argv[d->custom_optind]); - } - d->nextchar += strlen(d->nextchar); - d->custom_optind++; - d->custom_optopt = 0; - return '?'; - } - if (pfound) { - option_index = indfound; - d->custom_optind++; - if (*nameend) { - if (pfound->has_arg != no_argument) - d->custom_optarg = nameend + 1; - else { - if (print_errors) { - if (argv[d->custom_optind - 1][1] == '-') { - /* --option */ - fprintf(stderr, "%s: option `--%s' doesn't allow an argument\n", - argv[0], pfound->name); - } else { - /* +option or -option */ - fprintf(stderr, "%s: option `%c%s' doesn't allow an argument\n", - argv[0], argv[d->custom_optind - 1][0], pfound->name); - } - - } - d->nextchar += strlen(d->nextchar); - d->custom_optopt = pfound->val; - return '?'; - } - } else if (pfound->has_arg == required_argument) { - if (d->custom_optind < argc) - d->custom_optarg = argv[d->custom_optind++]; - else { - if (print_errors) { - fprintf(stderr, - "%s: option `%s' requires an argument\n", - argv[0], - argv[d->custom_optind - 1]); - } - d->nextchar += strlen(d->nextchar); - d->custom_optopt = pfound->val; - return optstring[0] == ':' ? ':' : '?'; - } - } - d->nextchar += strlen(d->nextchar); - if (longind != NULL) - *longind = option_index; - if (pfound->flag) { - *(pfound->flag) = pfound->val; - return 0; - } - return pfound->val; - } - /* - * Can't find it as a long option. If this is not getopt_long_only, or - * the option starts with '--' or is not a valid short option, then - * it's an error. Otherwise interpret it as a short option. - */ - if (print_errors) { - if (argv[d->custom_optind][1] == '-') { - /* --option */ - fprintf(stderr, - "%s: unrecognized option `--%s'\n", - argv[0], d->nextchar); - } else { - /* +option or -option */ - fprintf(stderr, - "%s: unrecognized option `%c%s'\n", - argv[0], argv[d->custom_optind][0], - d->nextchar); - } - } - d->nextchar = (char *) ""; - d->custom_optind++; - d->custom_optopt = 0; - return '?'; -} - -static int check_short_opt(int argc, char *const *argv, const char *optstring, - int print_errors, struct custom_getopt_data *d) -{ - char c = *d->nextchar++; - const char *temp = strchr(optstring, c); - - /* Increment `custom_optind' when we start to process its last character. */ - if (*d->nextchar == '\0') - ++d->custom_optind; - if (!temp || c == ':') { - if (print_errors) - fprintf(stderr, "%s: invalid option -- %c\n", argv[0], c); - - d->custom_optopt = c; - return '?'; - } - if (temp[1] == ':') { - if (temp[2] == ':') { - /* This is an option that accepts an argument optionally. */ - if (*d->nextchar != '\0') { - d->custom_optarg = d->nextchar; - d->custom_optind++; - } else - d->custom_optarg = NULL; - d->nextchar = NULL; - } else { - /* This is an option that requires an argument. */ - if (*d->nextchar != '\0') { - d->custom_optarg = d->nextchar; - /* - * If we end this ARGV-element by taking the - * rest as an arg, we must advance to the next - * element now. - */ - d->custom_optind++; - } else if (d->custom_optind == argc) { - if (print_errors) { - fprintf(stderr, - "%s: option requires an argument -- %c\n", - argv[0], c); - } - d->custom_optopt = c; - if (optstring[0] == ':') - c = ':'; - else - c = '?'; - } else - /* - * We already incremented `custom_optind' once; - * increment it again when taking next ARGV-elt - * as argument. - */ - d->custom_optarg = argv[d->custom_optind++]; - d->nextchar = NULL; - } - } - return c; -} - -/* - * Scan elements of ARGV for option characters given in OPTSTRING. - * - * If an element of ARGV starts with '-', and is not exactly "-" or "--", - * then it is an option element. The characters of this element - * (aside from the initial '-') are option characters. If `getopt' - * is called repeatedly, it returns successively each of the option characters - * from each of the option elements. - * - * If `getopt' finds another option character, it returns that character, - * updating `custom_optind' and `nextchar' so that the next call to `getopt' can - * resume the scan with the following option character or ARGV-element. - * - * If there are no more option characters, `getopt' returns -1. - * Then `custom_optind' is the index in ARGV of the first ARGV-element - * that is not an option. (The ARGV-elements have been permuted - * so that those that are not options now come last.) - * - * OPTSTRING is a string containing the legitimate option characters. - * If an option character is seen that is not listed in OPTSTRING, - * return '?' after printing an error message. If you set `custom_opterr' to - * zero, the error message is suppressed but we still return '?'. - * - * If a char in OPTSTRING is followed by a colon, that means it wants an arg, - * so the following text in the same ARGV-element, or the text of the following - * ARGV-element, is returned in `custom_optarg'. Two colons mean an option that - * wants an optional arg; if there is text in the current ARGV-element, - * it is returned in `custom_optarg', otherwise `custom_optarg' is set to zero. - * - * If OPTSTRING starts with `-' or `+', it requests different methods of - * handling the non-option ARGV-elements. - * See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above. - * - * Long-named options begin with `--' instead of `-'. - * Their names may be abbreviated as long as the abbreviation is unique - * or is an exact match for some defined option. If they have an - * argument, it follows the option name in the same ARGV-element, separated - * from the option name by a `=', or else the in next ARGV-element. - * When `getopt' finds a long-named option, it returns 0 if that option's - * `flag' field is nonzero, the value of the option's `val' field - * if the `flag' field is zero. - * - * The elements of ARGV aren't really const, because we permute them. - * But we pretend they're const in the prototype to be compatible - * with other systems. - * - * LONGOPTS is a vector of `struct option' terminated by an - * element containing a name which is zero. - * - * LONGIND returns the index in LONGOPT of the long-named option found. - * It is only valid when a long-named option has been found by the most - * recent call. - * - * Return the option character from OPTS just read. Return -1 when there are - * no more options. For unrecognized options, or options missing arguments, - * `custom_optopt' is set to the option letter, and '?' is returned. - * - * The OPTS string is a list of characters which are recognized option letters, - * optionally followed by colons, specifying that that letter takes an - * argument, to be placed in `custom_optarg'. - * - * If a letter in OPTS is followed by two colons, its argument is optional. - * This behavior is specific to the GNU `getopt'. - * - * The argument `--' causes premature termination of argument scanning, - * explicitly telling `getopt' that there are no more options. If OPTS begins - * with `--', then non-option arguments are treated as arguments to the option - * '\0'. This behavior is specific to the GNU `getopt'. - */ - -static int getopt_internal_r(int argc, char *const *argv, const char *optstring, - const struct option *longopts, int *longind, - struct custom_getopt_data *d) -{ - int ret, print_errors = d->custom_opterr; - - if (optstring[0] == ':') - print_errors = 0; - if (argc < 1) - return -1; - d->custom_optarg = NULL; - - /* - * This is a big difference with GNU getopt, since optind == 0 - * means initialization while here 1 means first call. - */ - if (d->custom_optind == 0 || !d->initialized) { - if (d->custom_optind == 0) - d->custom_optind = 1; /* Don't scan ARGV[0], the program name. */ - custom_getopt_initialize(d); - } - if (d->nextchar == NULL || *d->nextchar == '\0') { - ret = shuffle_argv(argc, argv, longopts, d); - if (ret) - return ret; - } - if (longopts && (argv[d->custom_optind][1] == '-' )) - return check_long_opt(argc, argv, optstring, longopts, - longind, print_errors, d); - return check_short_opt(argc, argv, optstring, print_errors, d); -} - -static int custom_getopt_internal(int argc, char *const *argv, const char *optstring, - const struct option *longopts, int *longind) -{ - int result; - /* Keep a global copy of all internal members of d */ - static struct custom_getopt_data d; - - d.custom_optind = custom_optind; - d.custom_opterr = custom_opterr; - result = getopt_internal_r(argc, argv, optstring, longopts, - longind, &d); - custom_optind = d.custom_optind; - custom_optarg = d.custom_optarg; - custom_optopt = d.custom_optopt; - return result; -} - -static int custom_getopt_long (int argc, char *const *argv, const char *options, - const struct option *long_options, int *opt_index) -{ - return custom_getopt_internal(argc, argv, options, long_options, - opt_index); -} - - -static char *package_name = 0; - -/** - * @brief updates an option - * @param field the generic pointer to the field to update - * @param orig_field the pointer to the orig field - * @param field_given the pointer to the number of occurrence of this option - * @param prev_given the pointer to the number of occurrence already seen - * @param value the argument for this option (if null no arg was specified) - * @param possible_values the possible values for this option (if specified) - * @param default_value the default value (in case the option only accepts fixed values) - * @param arg_type the type of this option - * @param check_ambiguity @see cmdline_parser_params.check_ambiguity - * @param override @see cmdline_parser_params.override - * @param no_free whether to free a possible previous value - * @param multiple_option whether this is a multiple option - * @param long_opt the corresponding long option - * @param short_opt the corresponding short option (or '-' if none) - * @param additional_error possible further error specification - */ -static -int update_arg(void *field, char **orig_field, - unsigned int *field_given, unsigned int *prev_given, - char *value, const char *possible_values[], - const char *default_value, - cmdline_parser_arg_type arg_type, - int check_ambiguity, int override, - int no_free, int multiple_option, - const char *long_opt, char short_opt, - const char *additional_error) -{ - //char *stop_char = 0; - //const char *val = value; - //int found; - FIX_UNUSED (field); - - //stop_char = 0; - //found = 0; - - if (!multiple_option && prev_given && (*prev_given || (check_ambiguity && *field_given))) - { - if (short_opt != '-') - fprintf (stderr, "%s: `--%s' (`-%c') option given more than once%s\n", - package_name, long_opt, short_opt, - (additional_error ? additional_error : "")); - else - fprintf (stderr, "%s: `--%s' option given more than once%s\n", - package_name, long_opt, - (additional_error ? additional_error : "")); - return 1; /* failure */ - } - - FIX_UNUSED (default_value); - - if (field_given && *field_given && ! override) - return 0; - if (prev_given) - (*prev_given)++; - if (field_given) - (*field_given)++; - //if (possible_values) - //val = possible_values[found]; - - switch(arg_type) { - default: - break; - }; - - - /* store the original value */ - switch(arg_type) { - case ARG_NO: - break; - default: - if (value && orig_field) { - if (no_free) { - *orig_field = value; - } else { - if (*orig_field) - free (*orig_field); /* free previous string */ - *orig_field = gengetopt_strdup (value); - } - } - }; - - return 0; /* OK */ -} - - -int -cmdline_parser_internal ( - int argc, char * const *argv, struct gengetopt_args_info *args_info, - struct cmdline_parser_params *params, const char *additional_error) -{ - int c; /* Character of the parsed option. */ - - int error = 0; - struct gengetopt_args_info local_args_info; - - int override; - int initialize; - //int check_required; - int check_ambiguity; - - char *optarg; - int optind; - int opterr; - int optopt; - - package_name = argv[0]; - - override = params->override; - initialize = params->initialize; - //check_required = params->check_required; - check_ambiguity = params->check_ambiguity; - - if (initialize) - cmdline_parser_init (args_info); - - cmdline_parser_init (&local_args_info); - - optarg = 0; - optind = 0; - opterr = params->print_errors; - optopt = '?'; - - while (1) - { - int option_index = 0; - - static struct option long_options[] = { - { "help", 0, NULL, 'h' }, - { "version", 0, NULL, 'V' }, - { "raw", 0, NULL, 'r' }, - { "quiet", 0, NULL, 'q' }, - { "loadcells", 0, NULL, 'C' }, - { 0, 0, 0, 0 } - }; - - custom_optarg = optarg; - custom_optind = optind; - custom_opterr = opterr; - custom_optopt = optopt; - - c = custom_getopt_long (argc, argv, "hVrqC", long_options, &option_index); - - optarg = custom_optarg; - optind = custom_optind; - opterr = custom_opterr; - optopt = custom_optopt; - - if (c == -1) break; /* Exit from `while (1)' loop. */ - - switch (c) - { - case 'h': /* Print help and exit. */ - cmdline_parser_print_help (); - cmdline_parser_free (&local_args_info); - exit (EXIT_SUCCESS); - - case 'V': /* Print version and exit. */ - cmdline_parser_print_version (); - cmdline_parser_free (&local_args_info); - exit (EXIT_SUCCESS); - - case 'r': /* Show an unformattet list of all records and subrecords. */ - - - if (update_arg( 0 , - 0 , &(args_info->raw_given), - &(local_args_info.raw_given), optarg, 0, 0, ARG_NO, - check_ambiguity, override, 0, 0, - "raw", 'r', - additional_error)) - goto failure; - - break; - case 'q': /* Supress all record information. Useful for speed tests.. */ - - - if (update_arg( 0 , - 0 , &(args_info->quiet_given), - &(local_args_info.quiet_given), optarg, 0, 0, ARG_NO, - check_ambiguity, override, 0, 0, - "quiet", 'q', - additional_error)) - goto failure; - - break; - case 'C': /* Browse through contents of all cells.. */ - - - if (update_arg( 0 , - 0 , &(args_info->loadcells_given), - &(local_args_info.loadcells_given), optarg, 0, 0, ARG_NO, - check_ambiguity, override, 0, 0, - "loadcells", 'C', - additional_error)) - goto failure; - - break; - - case 0: /* Long option with no short option */ - case '?': /* Invalid option. */ - /* `getopt_long' already printed an error message. */ - goto failure; - - default: /* bug: option not considered. */ - fprintf (stderr, "%s: option unknown: %c%s\n", CMDLINE_PARSER_PACKAGE, c, (additional_error ? additional_error : "")); - abort (); - } /* switch */ - } /* while */ - - - - - cmdline_parser_release (&local_args_info); - - if ( error ) - return (EXIT_FAILURE); - - if (optind < argc) - { - int i = 0 ; - int found_prog_name = 0; - /* whether program name, i.e., argv[0], is in the remaining args - (this may happen with some implementations of getopt, - but surely not with the one included by gengetopt) */ - - - args_info->inputs_num = argc - optind - found_prog_name; - args_info->inputs = - (char **)(malloc ((args_info->inputs_num)*sizeof(char *))) ; - while (optind < argc) - args_info->inputs[ i++ ] = gengetopt_strdup (argv[optind++]) ; - } - - return 0; - -failure: - - cmdline_parser_release (&local_args_info); - return (EXIT_FAILURE); -} diff --git a/apps/esmtool/esmtool_cmd.h b/apps/esmtool/esmtool_cmd.h deleted file mode 100644 index 8c420c189..000000000 --- a/apps/esmtool/esmtool_cmd.h +++ /dev/null @@ -1,179 +0,0 @@ -/** @file esmtool_cmd.h - * @brief The header file for the command line option parser - * generated by GNU Gengetopt version 2.22.2 - * http://www.gnu.org/software/gengetopt. - * DO NOT modify this file, since it can be overwritten - * @author GNU Gengetopt by Lorenzo Bettini */ - -#ifndef ESMTOOL_CMD_H -#define ESMTOOL_CMD_H - -/* If we use autoconf. */ -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#include /* for FILE */ - -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ - -#ifndef CMDLINE_PARSER_PACKAGE -/** @brief the program name (used for printing errors) */ -#define CMDLINE_PARSER_PACKAGE "esmtool" -#endif - -#ifndef CMDLINE_PARSER_PACKAGE_NAME -/** @brief the complete program name (used for help and version) */ -#define CMDLINE_PARSER_PACKAGE_NAME "esmtool" -#endif - -#ifndef CMDLINE_PARSER_VERSION -/** @brief the program version */ -#define CMDLINE_PARSER_VERSION "1.0" -#endif - -/** @brief Where the command line options are stored */ -struct gengetopt_args_info -{ - const char *help_help; /**< @brief Print help and exit help description. */ - const char *version_help; /**< @brief Print version and exit help description. */ - const char *raw_help; /**< @brief Show an unformattet list of all records and subrecords help description. */ - const char *quiet_help; /**< @brief Supress all record information. Useful for speed tests. help description. */ - const char *loadcells_help; /**< @brief Browse through contents of all cells. help description. */ - - unsigned int help_given ; /**< @brief Whether help was given. */ - unsigned int version_given ; /**< @brief Whether version was given. */ - unsigned int raw_given ; /**< @brief Whether raw was given. */ - unsigned int quiet_given ; /**< @brief Whether quiet was given. */ - unsigned int loadcells_given ; /**< @brief Whether loadcells was given. */ - - char **inputs ; /**< @brief unamed options (options without names) */ - unsigned inputs_num ; /**< @brief unamed options number */ -} ; - -/** @brief The additional parameters to pass to parser functions */ -struct cmdline_parser_params -{ - int override; /**< @brief whether to override possibly already present options (default 0) */ - int initialize; /**< @brief whether to initialize the option structure gengetopt_args_info (default 1) */ - int check_required; /**< @brief whether to check that all required options were provided (default 1) */ - int check_ambiguity; /**< @brief whether to check for options already specified in the option structure gengetopt_args_info (default 0) */ - int print_errors; /**< @brief whether getopt_long should print an error message for a bad option (default 1) */ -} ; - -/** @brief the purpose string of the program */ -extern const char *gengetopt_args_info_purpose; -/** @brief the usage string of the program */ -extern const char *gengetopt_args_info_usage; -/** @brief all the lines making the help output */ -extern const char *gengetopt_args_info_help[]; - -/** - * The command line parser - * @param argc the number of command line options - * @param argv the command line options - * @param args_info the structure where option information will be stored - * @return 0 if everything went fine, NON 0 if an error took place - */ -int cmdline_parser (int argc, char * const *argv, - struct gengetopt_args_info *args_info); - -/** - * The command line parser (version with additional parameters - deprecated) - * @param argc the number of command line options - * @param argv the command line options - * @param args_info the structure where option information will be stored - * @param override whether to override possibly already present options - * @param initialize whether to initialize the option structure my_args_info - * @param check_required whether to check that all required options were provided - * @return 0 if everything went fine, NON 0 if an error took place - * @deprecated use cmdline_parser_ext() instead - */ -int cmdline_parser2 (int argc, char * const *argv, - struct gengetopt_args_info *args_info, - int override, int initialize, int check_required); - -/** - * The command line parser (version with additional parameters) - * @param argc the number of command line options - * @param argv the command line options - * @param args_info the structure where option information will be stored - * @param params additional parameters for the parser - * @return 0 if everything went fine, NON 0 if an error took place - */ -int cmdline_parser_ext (int argc, char * const *argv, - struct gengetopt_args_info *args_info, - struct cmdline_parser_params *params); - -/** - * Save the contents of the option struct into an already open FILE stream. - * @param outfile the stream where to dump options - * @param args_info the option struct to dump - * @return 0 if everything went fine, NON 0 if an error took place - */ -int cmdline_parser_dump(FILE *outfile, - struct gengetopt_args_info *args_info); - -/** - * Save the contents of the option struct into a (text) file. - * This file can be read by the config file parser (if generated by gengetopt) - * @param filename the file where to save - * @param args_info the option struct to save - * @return 0 if everything went fine, NON 0 if an error took place - */ -int cmdline_parser_file_save(const char *filename, - struct gengetopt_args_info *args_info); - -/** - * Print the help - */ -void cmdline_parser_print_help(void); -/** - * Print the version - */ -void cmdline_parser_print_version(void); - -/** - * Initializes all the fields a cmdline_parser_params structure - * to their default values - * @param params the structure to initialize - */ -void cmdline_parser_params_init(struct cmdline_parser_params *params); - -/** - * Allocates dynamically a cmdline_parser_params structure and initializes - * all its fields to their default values - * @return the created and initialized cmdline_parser_params structure - */ -struct cmdline_parser_params *cmdline_parser_params_create(void); - -/** - * Initializes the passed gengetopt_args_info structure's fields - * (also set default values for options that have a default) - * @param args_info the structure to initialize - */ -void cmdline_parser_init (struct gengetopt_args_info *args_info); -/** - * Deallocates the string fields of the gengetopt_args_info structure - * (but does not deallocate the structure itself) - * @param args_info the structure to deallocate - */ -void cmdline_parser_free (struct gengetopt_args_info *args_info); - -/** - * Checks that all the required options were specified - * @param args_info the structure to check - * @param prog_name the name of the program that will be used to print - * possible errors - * @return - */ -int cmdline_parser_required (struct gengetopt_args_info *args_info, - const char *prog_name); - - -#ifdef __cplusplus -} -#endif /* __cplusplus */ -#endif /* ESMTOOL_CMD_H */ diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index e46a06205..a34ad7429 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -41,27 +41,20 @@ source_group(launcher FILES ${LAUNCHER} ${LAUNCHER_HEADER} ${LAUNCHER_HEADER_MOC find_package(Qt4 REQUIRED) set(QT_USE_QTGUI 1) -#find_package(PNG REQUIRED) -#include_directories(${PNG_INCLUDE_DIR}) +# Set some platform specific settings +if(WIN32) + set(GUI_TYPE WIN32) + set(QT_USE_QTMAIN TRUE) +endif(WIN32) QT4_ADD_RESOURCES(RCC_SRCS resources.qrc) QT4_WRAP_CPP(MOC_SRCS ${LAUNCHER_HEADER_MOC}) include(${QT_USE_FILE}) -# list here plugins that can't be detected statically, but loaded in runtime -# it needed for packaging automatisation -#set(USED_QT_PLUGINS imageformats/libqgif -# imageformats/libqico -# imageformats/libqjpeg -# imageformats/libqmng -# imageformats/libqsvg -# imageformats/libqtga -# imageformats/libqtiff) -# It seems that launcher works without this plugins, but it loads them into memory if they exists - # Main executable add_executable(omwlauncher + ${GUI_TYPE} ${LAUNCHER} ${RCC_SRCS} ${MOC_SRCS} @@ -71,7 +64,6 @@ target_link_libraries(omwlauncher ${Boost_LIBRARIES} ${OGRE_LIBRARIES} ${QT_LIBRARIES} -# ${PNG_LIBRARY} components ) @@ -82,19 +74,16 @@ endif() if (APPLE) configure_file(${CMAKE_SOURCE_DIR}/files/launcher.qss "${APP_BUNDLE_DIR}/../launcher.qss") - configure_file(${CMAKE_SOURCE_DIR}/files/launcher.qss + configure_file(${CMAKE_SOURCE_DIR}/files/launcher.cfg "${APP_BUNDLE_DIR}/../launcher.cfg") - - # copy used QT plugins into ${APP_BUNDLE_DIR}/Contents/Plugins - #foreach(PLUGIN ${USED_QT_PLUGINS}) - # get_filename_component(PLUGIN_FILENAME ${PLUGIN} NAME) - # configure_file("${QT_PLUGINS_DIR}/${PLUGIN}.dylib" "${APP_BUNDLE_DIR}/Contents/Plugins/${PLUGIN_FILENAME}.dylib" COPYONLY) - #endforeach() - else() + configure_file(${CMAKE_SOURCE_DIR}/files/launcher.qss + "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/resources/launcher.qss") + + # Fallback in case getGlobalDataPath does not point to resources configure_file(${CMAKE_SOURCE_DIR}/files/launcher.qss "${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 c8311846f..054cbf141 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -1,9 +1,7 @@ #include #include -#include -#include -#include +#include #include "datafilespage.hpp" #include "lineedit.hpp" @@ -11,6 +9,23 @@ #include "pluginsmodel.hpp" #include "pluginsview.hpp" +#include +/** + * Workaround for problems with whitespaces in paths in older versions of Boost library + */ +#if (BOOST_VERSION <= 104600) +namespace boost +{ + + template<> + inline boost::filesystem::path lexical_cast(const std::string& arg) + { + return boost::filesystem::path(arg); + } + +} /* namespace boost */ +#endif /* (BOOST_VERSION <= 104600) */ + using namespace ESM; using namespace std; @@ -26,7 +41,9 @@ bool rowSmallerThan(const QModelIndex &index1, const QModelIndex &index2) return index1.row() <= index2.row(); } -DataFilesPage::DataFilesPage(QWidget *parent) : QWidget(parent) +DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, QWidget *parent) + : QWidget(parent) + , mCfgMgr(cfg) { mDataFilesModel = new QStandardItemModel(); // Contains all plugins with masters mPluginsModel = new PluginsModel(); // Contains selectable plugins @@ -118,37 +135,139 @@ DataFilesPage::DataFilesPage(QWidget *parent) : QWidget(parent) connect(mPluginsTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex))); connect(mPluginsTable, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(showContextMenu(const QPoint&))); + connect(mProfilesComboBox, SIGNAL(textChanged(const QString&, const QString&)), this, SLOT(profileChanged(const QString&, const QString&))); - - setupConfig(); createActions(); + setupConfig(); + setupDataFiles(); + } -void DataFilesPage::setupDataFiles(const QStringList &paths, bool strict) +void DataFilesPage::setupConfig() { + QString config = QString::fromStdString((mCfgMgr.getLocalPath() / "launcher.cfg").string()); + QFile file(config); + + if (!file.exists()) { + config = QString::fromStdString((mCfgMgr.getUserPath() / "launcher.cfg").string()); + } + + // Open our config file + mLauncherConfig = new QSettings(config, QSettings::IniFormat); + file.close(); + + // Make sure we have no groups open + while (!mLauncherConfig->group().isEmpty()) { + mLauncherConfig->endGroup(); + } + + mLauncherConfig->beginGroup("Profiles"); + QStringList profiles = mLauncherConfig->childGroups(); + + if (profiles.isEmpty()) { + // Add a default profile + profiles.append("Default"); + } + + mProfilesComboBox->addItems(profiles); + + QString currentProfile = mLauncherConfig->value("CurrentProfile").toString(); + + if (currentProfile.isEmpty()) { + // No current profile selected + currentProfile = "Default"; + } + + const int currentIndex = mProfilesComboBox->findText(currentProfile); + if (currentIndex != -1) { + // Profile is found + mProfilesComboBox->setCurrentIndex(currentIndex); + } + + mLauncherConfig->endGroup(); +} + + +void DataFilesPage::setupDataFiles() +{ + // We use the Configuration Manager to retrieve the configuration values + boost::program_options::variables_map variables; + boost::program_options::options_description desc; + + desc.add_options() + ("data", boost::program_options::value()->default_value(Files::PathContainer(), "data")->multitoken()) +// ("data-local", boost::program_options::value()->default_value("")) + ("fs-strict", boost::program_options::value()->implicit_value(true)->default_value(false)) + ("encoding", boost::program_options::value()->default_value("win1252")); + + mCfgMgr.readConfiguration(variables, desc); + // Put the paths in a boost::filesystem vector to use with Files::Collections - Files::PathContainer dataDirs; + Files::PathContainer dataDirs(variables["data"].as()); + mDataDirs = dataDirs; + +// std::string local = variables["data-local"].as(); +// if (!local.empty()) { +// mDataLocal.push_back(Files::PathContainer::value_type(local)); +// dataDirs.push_back(Files::PathContainer::value_type(local)); +// } + + if (dataDirs.size()>1) + dataDirs.resize (1); + + mCfgMgr.processPaths(dataDirs); + + while (dataDirs.empty()) { + // No valid data files directory found + + QMessageBox msgBox; + msgBox.setWindowTitle("Error detecting Morrowind installation"); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Cancel); + msgBox.setText(tr("
Could not find the Data Files location

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

\ + Press \"Browse...\" to specify the location manually.
")); + + QAbstractButton *dirSelectButton = + msgBox.addButton(tr("B&rowse..."), QMessageBox::ActionRole); + + msgBox.exec(); + + if (msgBox.clickedButton() == dirSelectButton) { + + QString dataDir = QFileDialog::getExistingDirectory( + this, tr("Select Data Files Directory"), + "/home", + QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); - foreach (const QString ¤tPath, paths) { - dataDirs.push_back(boost::filesystem::path(currentPath.toStdString())); + dataDirs.push_back(Files::PathContainer::value_type(dataDir.toStdString())); + mDataDirs.push_back(Files::PathContainer::value_type(dataDir.toStdString())); + } else { + // Cancel + break; + } + } + + // Check if cancel was clicked because we can't exit from while loop + if (dataDirs.empty()) { + QApplication::exit(1); + return; } // Create a file collection for the dataDirs - Files::Collections mFileCollections(dataDirs, strict); + Files::Collections fileCollections(dataDirs, !variables["fs-strict"].as()); // First we add all the master files - const Files::MultiDirCollection &esm = mFileCollections.getCollection(".esm"); + const Files::MultiDirCollection &esm = fileCollections.getCollection(".esm"); unsigned int i = 0; // Row number - for (Files::MultiDirCollection::TIter iter(esm.begin()); iter!=esm.end(); ++iter) - { + for (Files::MultiDirCollection::TIter iter(esm.begin()); iter!=esm.end(); ++iter) { QString currentMaster = QString::fromStdString( boost::filesystem::path (iter->second.filename()).string()); const QList itemList = mMastersWidget->findItems(currentMaster, Qt::MatchExactly); - if (itemList.isEmpty()) // Master is not yet in the widget - { + if (itemList.isEmpty()) { // Master is not yet in the widget mMastersWidget->insertRow(i); QTableWidgetItem *item = new QTableWidgetItem(currentMaster); mMastersWidget->setItem(i, 0, item); @@ -157,14 +276,13 @@ void DataFilesPage::setupDataFiles(const QStringList &paths, bool strict) } // Now on to the plugins - const Files::MultiDirCollection &esp = mFileCollections.getCollection(".esp"); + const Files::MultiDirCollection &esp = fileCollections.getCollection(".esp"); - for (Files::MultiDirCollection::TIter iter(esp.begin()); iter!=esp.end(); ++iter) - { + for (Files::MultiDirCollection::TIter iter(esp.begin()); iter!=esp.end(); ++iter) { ESMReader fileReader; QStringList availableMasters; // Will contain all found masters - fileReader.setEncoding("win1252"); // FIXME: This should be configurable! + fileReader.setEncoding(variables["encoding"].as()); fileReader.open(iter->second.string()); // First we fill the availableMasters and the mMastersWidget @@ -176,8 +294,7 @@ void DataFilesPage::setupDataFiles(const QStringList &paths, bool strict) const QList itemList = mMastersWidget->findItems(currentMaster, Qt::MatchExactly); - if (itemList.isEmpty()) // Master is not yet in the widget - { + if (itemList.isEmpty()) { // Master is not yet in the widget mMastersWidget->insertRow(i); QTableWidgetItem *item = new QTableWidgetItem(currentMaster); @@ -234,54 +351,6 @@ void DataFilesPage::setupDataFiles(const QStringList &paths, bool strict) readConfig(); } -void DataFilesPage::setupConfig() -{ - Cfg::ConfigurationManager cfg; - - QString config = (cfg.getRuntimeConfigPath() / "launcher.cfg").string().c_str(); - QFile file(config); - - if (!file.exists()) { - config = QString::fromStdString((cfg.getLocalConfigPath() / "launcher.cfg").string()); - } - - file.setFileName(config); // Just for displaying information - - // Open our config file - mLauncherConfig = new QSettings(config, QSettings::IniFormat); - mLauncherConfig->sync(); - - - // Make sure we have no groups open - while (!mLauncherConfig->group().isEmpty()) { - mLauncherConfig->endGroup(); - } - - mLauncherConfig->beginGroup("Profiles"); - QStringList profiles = mLauncherConfig->childGroups(); - - if (profiles.isEmpty()) { - // Add a default profile - profiles.append("Default"); - } - - mProfilesComboBox->addItems(profiles); - - QString currentProfile = mLauncherConfig->value("CurrentProfile").toString(); - - if (currentProfile.isEmpty()) { - // No current profile selected - currentProfile = "Default"; - } - mProfilesComboBox->setCurrentIndex(mProfilesComboBox->findText(currentProfile)); - - mLauncherConfig->endGroup(); - - // Now we connect the combobox to do something if the profile changes - // This prevents strange behaviour while reading and appending the profiles - connect(mProfilesComboBox, SIGNAL(textChanged(const QString&, const QString&)), this, SLOT(profileChanged(const QString&, const QString&))); -} - void DataFilesPage::createActions() { // Refresh the plugins @@ -414,18 +483,18 @@ void DataFilesPage::deleteProfile() return; } - QMessageBox deleteMessageBox(this); - deleteMessageBox.setWindowTitle(tr("Delete Profile")); - deleteMessageBox.setText(tr("Are you sure you want to delete %0?").arg(profile)); - deleteMessageBox.setIcon(QMessageBox::Warning); - QAbstractButton *deleteButton = - deleteMessageBox.addButton(tr("Delete"), QMessageBox::ActionRole); + QMessageBox msgBox(this); + msgBox.setWindowTitle(tr("Delete Profile")); + msgBox.setIcon(QMessageBox::Warning); + msgBox.setStandardButtons(QMessageBox::Cancel); + msgBox.setText(tr("Are you sure you want to delete %0?").arg(profile)); - deleteMessageBox.addButton(QMessageBox::Cancel); + QAbstractButton *deleteButton = + msgBox.addButton(tr("Delete"), QMessageBox::ActionRole); - deleteMessageBox.exec(); + msgBox.exec(); - if (deleteMessageBox.clickedButton() == deleteButton) { + if (msgBox.clickedButton() == deleteButton) { // Make sure we have no groups open while (!mLauncherConfig->group().isEmpty()) { mLauncherConfig->endGroup(); @@ -482,7 +551,6 @@ void DataFilesPage::moveUp() void DataFilesPage::moveDown() { // Shift the selected plugins down one row - if (!mPluginsTable->selectionModel()->hasSelection()) { return; } @@ -898,9 +966,18 @@ void DataFilesPage::filterChanged(const QString filter) void DataFilesPage::profileChanged(const QString &previous, const QString ¤t) { + // Prevent the deletion of the default profile + if (current == "Default") { + mDeleteProfileAction->setEnabled(false); + } else { + mDeleteProfileAction->setEnabled(true); + } + if (!previous.isEmpty()) { writeConfig(previous); mLauncherConfig->sync(); + } else { + return; } uncheckPlugins(); @@ -968,11 +1045,105 @@ void DataFilesPage::readConfig() void DataFilesPage::writeConfig(QString profile) { - // Don't overwrite the config if no plugins are found - if (mPluginsModel->rowCount() < 1) { + // Don't overwrite the config if no masters are found + if (mMastersWidget->rowCount() < 1) { + 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); + + if (!file.open(QIODevice::ReadWrite | QIODevice::Text)) { + // File cannot be opened or created + QMessageBox msgBox; + msgBox.setWindowTitle("Error writing OpenMW configuration file"); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("
Could not open or create %0

\ + Please make sure you have the right permissions and try again.
").arg(file.fileName())); + msgBox.exec(); + + qApp->exit(1); + return; + } + + QTextStream in(&file); + QByteArray buffer; + + // Remove all previous entries from config + while (!in.atEnd()) { + QString line = in.readLine(); + if (!line.startsWith("master") && + !line.startsWith("plugin") && + !line.startsWith("data") && + !line.startsWith("data-local")) + { + buffer += line += "\n"; + } + } + + file.close(); + + // Now we write back the other config entries + if (!file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) { + QMessageBox msgBox; + msgBox.setWindowTitle("Error writing OpenMW configuration file"); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("
Could not write to %0

\ + Please make sure you have the right permissions and try again.
").arg(file.fileName())); + msgBox.exec(); + + qApp->exit(1); return; } + if (!buffer.isEmpty()) { + file.write(buffer); + } + + QTextStream gameConfig(&file); + + // First write the list of data dirs + mCfgMgr.processPaths(mDataDirs); + mCfgMgr.processPaths(mDataLocal); + + QString path; + + // data= directories + for (Files::PathContainer::iterator it = mDataDirs.begin(); it != mDataDirs.end(); ++it) { + path = QString::fromStdString(it->string()); + path.remove(QChar('\"')); + + // Make sure the string is quoted when it contains spaces + if (path.contains(" ")) { + gameConfig << "data=\"" << path << "\"" << endl; + } else { + gameConfig << "data=" << path << endl; + } + } + + // data-local directory + if (!mDataLocal.empty()) { + path = QString::fromStdString(mDataLocal.front().string()); + path.remove(QChar('\"')); + + if (path.contains(" ")) { + gameConfig << "data-local=\"" << path << "\"" << endl; + } else { + gameConfig << "data-local=" << path << endl; + } + } + + if (profile.isEmpty()) { profile = mProfilesComboBox->currentText(); } @@ -993,24 +1164,29 @@ void DataFilesPage::writeConfig(QString profile) mLauncherConfig->beginGroup(profile); mLauncherConfig->remove(""); // Clear the subgroup - // First write the masters to the config - const QStringList masterList = selectedMasters(); + // Now write the masters to the configs + const QStringList masters = selectedMasters(); // We don't use foreach because we need i - for (int i = 0; i < masterList.size(); ++i) { - const QString master = masterList.at(i); - mLauncherConfig->setValue(QString("Master%0").arg(i), master); + for (int i = 0; i < masters.size(); ++i) { + const QString currentMaster = masters.at(i); + + mLauncherConfig->setValue(QString("Master%0").arg(i), currentMaster); + gameConfig << "master=" << currentMaster << endl; + } - // Now write all checked plugins + // And finally write all checked plugins const QStringList plugins = checkedPlugins(); - for (int i = 0; i < plugins.size(); ++i) - { - mLauncherConfig->setValue(QString("Plugin%1").arg(i), plugins.at(i)); + for (int i = 0; i < plugins.size(); ++i) { + const QString currentPlugin = plugins.at(i); + mLauncherConfig->setValue(QString("Plugin%1").arg(i), currentPlugin); + gameConfig << "plugin=" << currentPlugin << endl; } + file.close(); mLauncherConfig->endGroup(); mLauncherConfig->endGroup(); - + mLauncherConfig->sync(); } diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index 2d0a385a7..ad5e90511 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -3,6 +3,9 @@ #include #include + +#include + #include "combobox.hpp" class QTableWidget; @@ -19,24 +22,19 @@ class PluginsModel; class PluginsView; class ComboBox; +namespace Files { struct ConfigurationManager; } + class DataFilesPage : public QWidget { Q_OBJECT public: - DataFilesPage(QWidget *parent = 0); + DataFilesPage(Files::ConfigurationManager& cfg, QWidget *parent = 0); ComboBox *mProfilesComboBox; - QSettings *mLauncherConfig; - const QStringList checkedPlugins(); - const QStringList selectedMasters(); - void setupConfig(); - void readConfig(); void writeConfig(QString profile = QString()); - void setupDataFiles(const QStringList &paths, bool strict); - public slots: void masterSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected); void setCheckState(QModelIndex index); @@ -81,11 +79,22 @@ private: QAction *mCheckAction; QAction *mUncheckAction; + Files::ConfigurationManager &mCfgMgr; + Files::PathContainer mDataDirs; + Files::PathContainer mDataLocal; + + QSettings *mLauncherConfig; + + const QStringList checkedPlugins(); + const QStringList selectedMasters(); + void addPlugins(const QModelIndex &index); void removePlugins(const QModelIndex &index); void uncheckPlugins(); void createActions(); - + void setupDataFiles(); + void setupConfig(); + void readConfig(); void scrollToSelection(); }; diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 92fbf3350..b7d397f06 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -1,8 +1,11 @@ #include #include "graphicspage.hpp" +#include -GraphicsPage::GraphicsPage(QWidget *parent) : QWidget(parent) +GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, QWidget *parent) + : QWidget(parent) + , mCfgMgr(cfg) { QGroupBox *rendererGroup = new QGroupBox(tr("Renderer"), this); @@ -147,21 +150,21 @@ void GraphicsPage::createPages() void GraphicsPage::setupConfig() { - QString ogreCfg = mCfg.getOgreConfigPath().string().c_str(); + QString ogreCfg = mCfgMgr.getOgreConfigPath().string().c_str(); QFile file(ogreCfg); mOgreConfig = new QSettings(ogreCfg, QSettings::IniFormat); } void GraphicsPage::setupOgre() { - QString pluginCfg = mCfg.getPluginsConfigPath().string().c_str(); + QString pluginCfg = mCfgMgr.getPluginsConfigPath().string().c_str(); QFile file(pluginCfg); // Create a log manager so we can surpress debug text to stdout/stderr Ogre::LogManager* logMgr = OGRE_NEW Ogre::LogManager; - logMgr->createLog((mCfg.getLogPath().string() + "/launcherOgre.log"), true, false, false); + logMgr->createLog((mCfgMgr.getLogPath().string() + "/launcherOgre.log"), true, false, false); - QString ogreCfg = QString::fromStdString(mCfg.getOgreConfigPath().string()); + QString ogreCfg = QString::fromStdString(mCfgMgr.getOgreConfigPath().string()); file.setFileName(ogreCfg); //we need to check that the path to the configuration file exists before we @@ -177,7 +180,7 @@ void GraphicsPage::setupOgre() Make sure you have write access to
%1

")).arg(configDir.path())); msgBox.exec(); - QApplication::exit(1); + qApp->exit(1); return; } @@ -200,7 +203,7 @@ void GraphicsPage::setupOgre() qCritical("Error creating Ogre::Root, the error reported was:\n %s", qPrintable(ogreError)); - QApplication::exit(1); + qApp->exit(1); return; } @@ -234,7 +237,7 @@ void GraphicsPage::setupOgre() Please make sure the plugins.cfg file exists and contains a valid rendering plugin.
")); msgBox.exec(); - QApplication::exit(1); + qApp->exit(1); return; } @@ -243,13 +246,7 @@ void GraphicsPage::setupOgre() if (mOpenGLRenderSystem) { mOGLRTTComboBox->addItems(getAvailableOptions(QString("RTT Preferred Mode"), mOpenGLRenderSystem)); mOGLAntiAliasingComboBox->addItems(getAvailableOptions(QString("FSAA"), mOpenGLRenderSystem)); - - QStringList videoModes = getAvailableOptions(QString("Video Mode"), mOpenGLRenderSystem); - // Remove extraneous spaces - videoModes.replaceInStrings(QRegExp("\\s{2,}"), QString(" ")); - videoModes.replaceInStrings(QRegExp("^\\s"), QString()); - - mOGLResolutionComboBox->addItems(videoModes); + mOGLResolutionComboBox->addItems(getAvailableOptions(QString("Video Mode"), mOpenGLRenderSystem)); mOGLFrequencyComboBox->addItems(getAvailableOptions(QString("Display Frequency"), mOpenGLRenderSystem)); } @@ -258,12 +255,7 @@ void GraphicsPage::setupOgre() mD3DRenderDeviceComboBox->addItems(getAvailableOptions(QString("Rendering Device"), mDirect3DRenderSystem)); mD3DAntiAliasingComboBox->addItems(getAvailableOptions(QString("FSAA"), mDirect3DRenderSystem)); mD3DFloatingPointComboBox->addItems(getAvailableOptions(QString("Floating-point mode"), mDirect3DRenderSystem)); - - QStringList videoModes = getAvailableOptions(QString("Video Mode"), mDirect3DRenderSystem); - // Remove extraneous spaces - videoModes.replaceInStrings(QRegExp("\\s{2,}"), QString(" ")); - videoModes.replaceInStrings(QRegExp("^\\s"), QString()); - mD3DResolutionComboBox->addItems(videoModes); + mD3DResolutionComboBox->addItems(getAvailableOptions(QString("Video Mode"), mDirect3DRenderSystem)); } } @@ -420,7 +412,7 @@ void GraphicsPage::writeConfig() qCritical("Error validating configuration"); - QApplication::exit(1); + qApp->exit(1); return; } @@ -446,7 +438,8 @@ void GraphicsPage::writeConfig() qCritical("Error saving Ogre configuration, the error reported was:\n %s", qPrintable(ogreError)); - QApplication::exit(1); + qApp->exit(1); + return; } } @@ -478,7 +471,7 @@ QStringList GraphicsPage::getAvailableOptions(const QString &key, Ogre::RenderSy { if (strcmp (key.toStdString().c_str(), i->first.c_str()) == 0) - result << (*opt_it).c_str(); + result << QString::fromStdString((*opt_it).c_str()).simplified(); } } diff --git a/apps/launcher/graphicspage.hpp b/apps/launcher/graphicspage.hpp index 5d50cfc61..bdfd4f038 100644 --- a/apps/launcher/graphicspage.hpp +++ b/apps/launcher/graphicspage.hpp @@ -7,21 +7,20 @@ #include #include #include -#include class QComboBox; class QCheckBox; class QStackedWidget; class QSettings; +namespace Files { struct ConfigurationManager; } + class GraphicsPage : public QWidget { Q_OBJECT public: - GraphicsPage(QWidget *parent = 0); - - QSettings *mOgreConfig; + GraphicsPage(Files::ConfigurationManager &cfg, QWidget *parent = 0); void writeConfig(); @@ -29,7 +28,6 @@ public slots: void rendererChanged(const QString &renderer); private: - Cfg::ConfigurationManager mCfg; Ogre::Root *mOgre; Ogre::RenderSystem *mSelectedRenderSystem; Ogre::RenderSystem *mOpenGLRenderSystem; @@ -59,6 +57,10 @@ private: QCheckBox *mD3DVSyncCheckBox; QCheckBox *mD3DFullScreenCheckBox; + QSettings *mOgreConfig; + + Files::ConfigurationManager &mCfgMgr; + QString getConfigValue(const QString &key, Ogre::RenderSystem *renderer); QStringList getAvailableOptions(const QString &key, Ogre::RenderSystem *renderer); diff --git a/apps/launcher/main.cpp b/apps/launcher/main.cpp index 4e438a4db..bd29e2bca 100644 --- a/apps/launcher/main.cpp +++ b/apps/launcher/main.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "maindialog.hpp" @@ -17,16 +18,18 @@ int main(int argc, char *argv[]) dir.cdUp(); dir.cdUp(); } - #endif - QDir::setCurrent(dir.absolutePath()); + // force Qt to load only LOCAL plugins, don't touch system Qt installation + QDir pluginsPath(QCoreApplication::applicationDirPath()); + pluginsPath.cdUp(); + pluginsPath.cd("Plugins"); - // Load the stylesheet - QFile file("./launcher.qss"); + QStringList libraryPaths; + libraryPaths << pluginsPath.path() << QCoreApplication::applicationDirPath(); + app.setLibraryPaths(libraryPaths); + #endif - file.open(QFile::ReadOnly); - QString styleSheet = QLatin1String(file.readAll()); - app.setStyleSheet(styleSheet); + QDir::setCurrent(dir.absolutePath()); MainDialog dialog; return dialog.exec(); diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 4ec8b309c..49c0bd960 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -45,6 +45,20 @@ MainDialog::MainDialog() setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint); setMinimumSize(QSize(575, 575)); + // Load the stylesheet + QString config = QString::fromStdString((mCfgMgr.getGlobalDataPath() / "resources/launcher.qss").string()); + QFile file(config); + + if (!file.exists()) { + file.setFileName(QString::fromStdString((mCfgMgr.getLocalPath() / "launcher.qss").string())); + } + + file.open(QFile::ReadOnly); + QString styleSheet = QLatin1String(file.readAll()); + qApp->setStyleSheet(styleSheet); + file.close(); + + connect(buttonBox, SIGNAL(rejected()), this, SLOT(close())); connect(buttonBox, SIGNAL(accepted()), this, SLOT(play())); @@ -85,116 +99,13 @@ void MainDialog::createIcons() } -QStringList MainDialog::readConfig(const QString &fileName) -{ - // We can't use QSettings directly because it - // does not support multiple keys with the same name - QFile file(fileName); - - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - QMessageBox msgBox; - msgBox.setWindowTitle("Error opening OpenMW configuration file"); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not open %0

\ - Please make sure you have the right permissions and try again.
").arg(file.fileName())); - msgBox.exec(); - - QApplication::exit(); // File cannot be opened or created - } - - QTextStream in(&file); - QStringList dataDirs; - QString dataLocal; - - // Read the config line by line - while (!in.atEnd()) { - QString line = in.readLine(); - - if (line.startsWith("data=")) { - dataDirs.append(line.remove("data=")); - } - - // Read the data-local key, if more than one are found only the last is used - if (line.startsWith("data-local=")) { - dataLocal = line.remove("data-local="); - } - - // Read fs-strict key - if (line.startsWith("fs-strict=")) { - QString value = line.remove("fs-strict="); - - (value.toLower() == QLatin1String("true")) - ? mStrict = true - : mStrict = false; - - } - - } - - // Add the data-local= key to the end of the dataDirs for priority reasons - if (!dataLocal.isEmpty()) { - dataDirs.append(dataLocal); - } - - if (!dataDirs.isEmpty()) - { - // Reset the global datadirs to the newly read entries - // Else return the previous dataDirs because nothing was found in this file; - mDataDirs = dataDirs; - } - - file.close(); - - return mDataDirs; -} - void MainDialog::createPages() { mPlayPage = new PlayPage(this); - mGraphicsPage = new GraphicsPage(this); - mDataFilesPage = new DataFilesPage(this); - - // Retrieve all data entries from the configs - QStringList dataDirs; + mGraphicsPage = new GraphicsPage(mCfgMgr, this); + mDataFilesPage = new DataFilesPage(mCfgMgr, this); - // Global location - QFile file(QString::fromStdString((mCfg.getGlobalConfigPath()/"openmw.cfg").string())); - if (file.exists()) { - dataDirs = readConfig(file.fileName()); - } - - // User location - file.setFileName(QString::fromStdString((mCfg.getLocalConfigPath()/"openmw.cfg").string())); - if (file.exists()) { - dataDirs = readConfig(file.fileName()); - } - - // Local location - file.setFileName("./openmw.cfg"); - - if (file.exists()) { - dataDirs = readConfig(file.fileName()); - } - - file.close(); - - if (!dataDirs.isEmpty()) { - // Now pass the datadirs on to the DataFilesPage - mDataFilesPage->setupDataFiles(dataDirs, mStrict); - } else { - QMessageBox msgBox; - msgBox.setWindowTitle("Error reading OpenMW configuration file"); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not read the location of the data files

\ - Please make sure OpenMW is correctly configured and try again.
")); - msgBox.exec(); - - QApplication::exit(); // No data or data-local entries in openmw.cfg - } - - // Set the combobox of the play page to imitate the comobox on the datafilespage + // Set the combobox of the play page to imitate the combobox on the datafilespage mPlayPage->mProfilesComboBox->setModel(mDataFilesPage->mProfilesComboBox->model()); mPlayPage->mProfilesComboBox->setCurrentIndex(mDataFilesPage->mProfilesComboBox->currentIndex()); @@ -246,14 +157,16 @@ void MainDialog::changePage(QListWidgetItem *current, QListWidgetItem *previous) void MainDialog::closeEvent(QCloseEvent *event) { // Now write all config files - writeConfig(); + mDataFilesPage->writeConfig(); + mGraphicsPage->writeConfig(); event->accept(); } void MainDialog::play() { // First do a write of all the configs, just to be sure - writeConfig(); + mDataFilesPage->writeConfig(); + mGraphicsPage->writeConfig(); #ifdef Q_WS_WIN QString game = "./openmw.exe"; @@ -313,75 +226,3 @@ void MainDialog::play() close(); } } - -void MainDialog::writeConfig() -{ - // Write the profiles - mDataFilesPage->writeConfig(); - mDataFilesPage->mLauncherConfig->sync(); - - // Write the graphics settings - mGraphicsPage->writeConfig(); - mGraphicsPage->mOgreConfig->sync(); - - QStringList dataFiles = mDataFilesPage->selectedMasters(); - dataFiles.append(mDataFilesPage->checkedPlugins()); - - // Open the config as a QFile - QFile file(QString::fromStdString((mCfg.getLocalConfigPath()/"openmw.cfg").string())); - - if (!file.open(QIODevice::ReadWrite | QIODevice::Text)) { - // File cannot be opened or created - QMessageBox msgBox; - msgBox.setWindowTitle("Error writing OpenMW configuration file"); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not open or create %0

\ - Please make sure you have the right permissions and try again.
").arg(file.fileName())); - msgBox.exec(); - - QApplication::exit(1); - } - - QTextStream in(&file); - QByteArray buffer; - - // Remove all previous master/plugin entries from config - while (!in.atEnd()) { - QString line = in.readLine(); - if (!line.contains("master") && !line.contains("plugin")) { - buffer += line += "\n"; - } - } - - file.close(); - - // Now we write back the other config entries - if (!file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) { - QMessageBox msgBox; - msgBox.setWindowTitle("Error writing OpenMW configuration file"); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not write to %0

\ - Please make sure you have the right permissions and try again.
").arg(file.fileName())); - msgBox.exec(); - - QApplication::exit(1); - } - - file.write(buffer); - - QTextStream out(&file); - - // Write the list of game files to the config - foreach (const QString ¤tFile, dataFiles) { - - if (currentFile.endsWith(QString(".esm"), Qt::CaseInsensitive)) { - out << "master=" << currentFile << endl; - } else if (currentFile.endsWith(QString(".esp"), Qt::CaseInsensitive)) { - out << "plugin=" << currentFile << endl; - } - } - - file.close(); -} diff --git a/apps/launcher/maindialog.hpp b/apps/launcher/maindialog.hpp index 047050902..d6d0e9974 100644 --- a/apps/launcher/maindialog.hpp +++ b/apps/launcher/maindialog.hpp @@ -3,7 +3,7 @@ #include -#include +#include class QListWidget; class QListWidgetItem; @@ -28,15 +28,11 @@ public slots: void play(); void profileChanged(int index); - private: void createIcons(); void createPages(); - void writeConfig(); void closeEvent(QCloseEvent *event); - QStringList readConfig(const QString &fileName); - QListWidget *mIconWidget; QStackedWidget *mPagesWidget; @@ -44,10 +40,7 @@ private: GraphicsPage *mGraphicsPage; DataFilesPage *mDataFilesPage; - QStringList mDataDirs; - bool mStrict; - - Cfg::ConfigurationManager mCfg; + Files::ConfigurationManager mCfgMgr; }; #endif diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 895a09867..ac6886a5a 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -44,7 +44,7 @@ add_openmw_dir (mwsound add_openmw_dir (mwworld refdata world physicssystem scene environment globals class action nullaction actionteleport containerstore actiontalk actiontake manualref player cellfunctors - cells localscripts customdata weather + cells localscripts customdata weather inventorystore ) add_openmw_dir (mwclass diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 7d65b95c5..0e2189307 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -18,7 +18,9 @@ #include #include #include -#include +#include +#include + #include #include @@ -116,8 +118,7 @@ bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt) // sound if (mUseSound) { - if (!mEnvironment.mSoundManager->isMusicPlaying()) - mEnvironment.mSoundManager->startRandomTitle(); + mEnvironment.mSoundManager->playPlaylist(); mEnvironment.mSoundManager->update (evt.timeSinceLastFrame); } @@ -171,7 +172,7 @@ bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt) return true; } -OMW::Engine::Engine(Cfg::ConfigurationManager& configurationManager) +OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) : mOgre (0) , mFpsLevel(0) , mDebug (false) @@ -208,15 +209,16 @@ OMW::Engine::~Engine() void OMW::Engine::loadBSA() { const Files::MultiDirCollection& bsa = mFileCollections.getCollection (".bsa"); - - for (Files::MultiDirCollection::TIter iter (bsa.begin()); iter!=bsa.end(); ++iter) + 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()); - } + std::cout << "Adding " << iter->second.string() << std::endl; + Bsa::addBSA(iter->second.string()); - std::cout << "Data dir " << mDataDir.string() << std::endl; - Bsa::addDir(mDataDir.string(), mFSStrict); + dataDirectory = iter->second.parent_path().string(); + std::cout << "Data dir " << dataDirectory << std::endl; + Bsa::addDir(dataDirectory, mFSStrict); + } } // add resources directory @@ -237,9 +239,7 @@ void OMW::Engine::enableFSStrict(bool fsStrict) void OMW::Engine::setDataDirs (const Files::PathContainer& dataDirs) { - /// \todo remove mDataDir, once resources system can handle multiple directories - assert (!dataDirs.empty()); - mDataDir = dataDirs.back(); + mDataDirs = dataDirs; mFileCollections = Files::Collections (dataDirs, !mFSStrict); } @@ -315,7 +315,7 @@ void OMW::Engine::go() } mOgre->configure(!boost::filesystem::is_regular_file(mCfgMgr.getOgreConfigPath()), mCfgMgr.getOgreConfigPath().string(), - mCfgMgr.getLogPath().string() + std::string("/"), + mCfgMgr.getLogPath().string(), mCfgMgr.getPluginsConfigPath().string(), false); // This has to be added BEFORE MyGUI is initialized, as it needs @@ -340,8 +340,7 @@ void OMW::Engine::go() // Create sound system mEnvironment.mSoundManager = new MWSound::SoundManager(mOgre->getRoot(), mOgre->getCamera(), - mEnvironment.mWorld->getStore(), - (mDataDir), + mDataDirs, mUseSound, mFSStrict, mEnvironment); // Create script system @@ -389,7 +388,7 @@ void OMW::Engine::go() mOgre->getRoot()->addFrameListener (this); // Play some good 'ol tunes - mEnvironment.mSoundManager->startRandomTitle(); + mEnvironment.mSoundManager->playPlaylist(std::string("Explore")); // scripts if (mCompileAll) @@ -450,7 +449,7 @@ void OMW::Engine::screenshot() // Count screenshots. int shotCount = 0; - const std::string screenshotPath = mCfgMgr.getLocalConfigPath().string(); + const std::string screenshotPath = mCfgMgr.getUserPath().string(); // Find the first unused filename with a do-while std::ostringstream stream; diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 00d709be4..5c5cdc018 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -9,7 +9,6 @@ #include #include -#include #include "mwworld/environment.hpp" #include "mwworld/ptr.hpp" @@ -52,13 +51,18 @@ namespace OEngine } } +namespace Files +{ + struct ConfigurationManager; +} + namespace OMW { /// \brief Main engine class, that brings together all the components of OpenMW class Engine : private Ogre::FrameListener { std::string mEncoding; - boost::filesystem::path mDataDir; + Files::PathContainer mDataDirs; boost::filesystem::path mResDir; OEngine::Render::OgreRenderer *mOgre; std::string mCellName; @@ -101,7 +105,7 @@ namespace OMW virtual bool frameRenderingQueued (const Ogre::FrameEvent& evt); public: - Engine(Cfg::ConfigurationManager& configurationManager); + Engine(Files::ConfigurationManager& configurationManager); virtual ~Engine(); /// Enable strict filesystem mode (do not fold case) @@ -161,7 +165,7 @@ namespace OMW void setAnimationVerbose(bool animverbose); private: - Cfg::ConfigurationManager& mCfgMgr; + Files::ConfigurationManager& mCfgMgr; }; } diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 2d0c8e44b..cd1e0e26e 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -6,9 +6,9 @@ #include #include -#include +#include #include -#include +#include #include "engine.hpp" @@ -35,6 +35,23 @@ #include "config.hpp" +#include +/** + * Workaround for problems with whitespaces in paths in older versions of Boost library + */ +#if (BOOST_VERSION <= 104600) +namespace boost +{ + +template<> +inline boost::filesystem::path lexical_cast(const std::string& arg) +{ + return boost::filesystem::path(arg); +} + +} /* namespace boost */ +#endif /* (BOOST_VERSION <= 104600) */ + using namespace std; /** @@ -46,7 +63,7 @@ using namespace std; * \retval true - Everything goes OK * \retval false - Error */ -bool parseOptions (int argc, char** argv, OMW::Engine& engine, Cfg::ConfigurationManager& cfgMgr) +bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::ConfigurationManager& cfgMgr) { // Create a local alias for brevity namespace bpo = boost::program_options; @@ -164,14 +181,19 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Cfg::Configuratio std::string local(variables["data-local"].as()); if (!local.empty()) { - dataDirs.push_back(Files::PathContainer::value_type(local)); + std::cout << "Ignoring data-local (currently not supported)" << std::endl; +// dataDirs.push_back(Files::PathContainer::value_type(local)); } - if (dataDirs.empty()) + if (dataDirs.size()>1) { - dataDirs.push_back(cfgMgr.getLocalDataPath()); + dataDirs.resize (1); + std::cout << "Ignoring all but the first data path (multiple data paths currently not supported)" + << std::endl; } + cfgMgr.processPaths(dataDirs); + engine.setDataDirs(dataDirs); engine.setResourceDir(variables["resources"].as()); @@ -224,7 +246,7 @@ int main(int argc, char**argv) try { - Cfg::ConfigurationManager cfgMgr; + Files::ConfigurationManager cfgMgr; OMW::Engine engine(cfgMgr); if (parseOptions(argc, argv, engine, cfgMgr)) diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index 3cdf63119..2dc861430 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -2,11 +2,16 @@ #include "armor.hpp" #include +#include +#include #include #include "../mwworld/ptr.hpp" #include "../mwworld/actiontake.hpp" +#include "../mwworld/inventorystore.hpp" +#include "../mwworld/environment.hpp" +#include "../mwworld/world.hpp" #include "../mwrender/objects.hpp" @@ -77,6 +82,79 @@ namespace MWClass return ref->base->script; } + std::pair, bool> Armor::getEquipmentSlots (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + std::vector slots; + + const int size = 11; + + static const int sMapping[size][2] = + { + { ESM::Armor::Helmet, MWWorld::InventoryStore::Slot_Helmet }, + { ESM::Armor::Cuirass, MWWorld::InventoryStore::Slot_Cuirass }, + { ESM::Armor::LPauldron, MWWorld::InventoryStore::Slot_LeftPauldron }, + { ESM::Armor::RPauldron, MWWorld::InventoryStore::Slot_RightPauldron }, + { ESM::Armor::Greaves, MWWorld::InventoryStore::Slot_Greaves }, + { ESM::Armor::Boots, MWWorld::InventoryStore::Slot_Boots }, + { ESM::Armor::LGauntlet, MWWorld::InventoryStore::Slot_LeftGauntlet }, + { ESM::Armor::RGauntlet, MWWorld::InventoryStore::Slot_RightGauntlet }, + { ESM::Armor::Shield, MWWorld::InventoryStore::Slot_CarriedLeft }, + { ESM::Armor::LBracer, MWWorld::InventoryStore::Slot_LeftGauntlet }, + { ESM::Armor::RBracer, MWWorld::InventoryStore::Slot_RightGauntlet } + }; + + for (int i=0; ibase->data.type) + { + slots.push_back (int (sMapping[i][1])); + break; + } + + return std::make_pair (slots, false); + } + + int Armor::getEuqipmentSkill (const MWWorld::Ptr& ptr, const MWWorld::Environment& environment) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + std::string typeGmst; + + switch (ref->base->data.type) + { + case ESM::Armor::Helmet: typeGmst = "iHelmWeight"; break; + case ESM::Armor::Cuirass: typeGmst = "iCuirassWeight"; break; + case ESM::Armor::LPauldron: + case ESM::Armor::RPauldron: typeGmst = "iPauldronWeight"; break; + case ESM::Armor::Greaves: typeGmst = "iGreavesWeight"; break; + case ESM::Armor::Boots: typeGmst = "iBootsWeight"; break; + case ESM::Armor::LGauntlet: + case ESM::Armor::RGauntlet: typeGmst = "iGauntletWeight"; break; +/// \todo how to determine if shield light, medium or heavy? +// case ESM::Armor::Shield: + case ESM::Armor::LBracer: + case ESM::Armor::RBracer: typeGmst = "iGauntletWeight"; break; + } + + if (typeGmst.empty()) + return -1; + + float iWeight = environment.mWorld->getStore().gameSettings.find (typeGmst)->f; + + if (iWeight * environment.mWorld->getStore().gameSettings.find ("fLightMaxMod")->f<= + ref->base->data.weight) + return ESM::Skill::LightArmor; + + if (iWeight * environment.mWorld->getStore().gameSettings.find ("fMedMaxMod")->f<= + ref->base->data.weight) + return ESM::Skill::MediumArmor; + + return ESM::Skill::HeavyArmor; + } + void Armor::registerSelf() { boost::shared_ptr instance (new Armor); diff --git a/apps/openmw/mwclass/armor.hpp b/apps/openmw/mwclass/armor.hpp index 060bc364e..6c78a535a 100644 --- a/apps/openmw/mwclass/armor.hpp +++ b/apps/openmw/mwclass/armor.hpp @@ -31,6 +31,15 @@ namespace MWClass virtual std::string getScript (const MWWorld::Ptr& ptr) const; ///< Return name of the script attached to ptr + virtual std::pair, bool> getEquipmentSlots (const MWWorld::Ptr& ptr) const; + ///< \return first: Return IDs of the slot this object can be equipped in; second: can object + /// stay stacked when equipped? + + virtual int getEuqipmentSkill (const MWWorld::Ptr& ptr, + const MWWorld::Environment& environment) const; + /// Return the index of the skill this item corresponds to when equiopped or -1, if there is + /// no such skill. + static void registerSelf(); }; } diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 88c43d82c..9af59937a 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -7,6 +7,7 @@ #include "../mwworld/ptr.hpp" #include "../mwworld/actiontake.hpp" +#include "../mwworld/inventorystore.hpp" #include "../mwrender/objects.hpp" @@ -65,6 +66,58 @@ namespace MWClass return ref->base->script; } + std::pair, bool> Clothing::getEquipmentSlots (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + std::vector slots; + + if (ref->base->data.type==ESM::Clothing::Ring) + { + slots.push_back (int (MWWorld::InventoryStore::Slot_LeftRing)); + slots.push_back (int (MWWorld::InventoryStore::Slot_RightRing)); + } + else + { + const int size = 9; + + static const int sMapping[size][2] = + { + { ESM::Clothing::Shirt, MWWorld::InventoryStore::Slot_Cuirass }, + { ESM::Clothing::Belt, MWWorld::InventoryStore::Slot_Belt }, + { ESM::Clothing::Robe, MWWorld::InventoryStore::Slot_Robe }, + { ESM::Clothing::Pants, MWWorld::InventoryStore::Slot_Pants }, + { ESM::Clothing::Shoes, MWWorld::InventoryStore::Slot_Boots }, + { ESM::Clothing::LGlove, MWWorld::InventoryStore::Slot_LeftGauntlet }, + { ESM::Clothing::RGlove, MWWorld::InventoryStore::Slot_RightGauntlet }, + { ESM::Clothing::Skirt, MWWorld::InventoryStore::Slot_Skirt }, + { ESM::Clothing::Amulet, MWWorld::InventoryStore::Slot_Amulet } + }; + + for (int i=0; ibase->data.type) + { + slots.push_back (int (sMapping[i][1])); + break; + } + } + + return std::make_pair (slots, false); + } + + int Clothing::getEuqipmentSkill (const MWWorld::Ptr& ptr, + const MWWorld::Environment& environment) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + if (ref->base->data.type==ESM::Clothing::Shoes) + return ESM::Skill::Unarmored; + + return -1; + } + void Clothing::registerSelf() { boost::shared_ptr instance (new Clothing); diff --git a/apps/openmw/mwclass/clothing.hpp b/apps/openmw/mwclass/clothing.hpp index 606aba9e0..448c73a73 100644 --- a/apps/openmw/mwclass/clothing.hpp +++ b/apps/openmw/mwclass/clothing.hpp @@ -25,6 +25,15 @@ namespace MWClass virtual std::string getScript (const MWWorld::Ptr& ptr) const; ///< Return name of the script attached to ptr + virtual std::pair, bool> getEquipmentSlots (const MWWorld::Ptr& ptr) const; + ///< \return first: Return IDs of the slot this object can be equipped in; second: can object + /// stay stacked when equipped? + + virtual int getEuqipmentSkill (const MWWorld::Ptr& ptr, + const MWWorld::Environment& environment) const; + /// Return the index of the skill this item corresponds to when equiopped or -1, if there is + /// no such skill. + static void registerSelf(); }; } diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index f9ec1c956..fc3c7a6a9 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -9,6 +9,7 @@ #include "../mwworld/actiontake.hpp" #include "../mwworld/nullaction.hpp" #include "../mwworld/environment.hpp" +#include "../mwworld/inventorystore.hpp" #include "../mwsound/soundmanager.hpp" @@ -94,6 +95,19 @@ namespace MWClass return ref->base->script; } + std::pair, bool> Light::getEquipmentSlots (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + std::vector slots; + + if (ref->base->data.flags & ESM::Light::Carry) + slots.push_back (int (MWWorld::InventoryStore::Slot_CarriedLeft)); + + return std::make_pair (slots, false); + } + void Light::registerSelf() { boost::shared_ptr instance (new Light); diff --git a/apps/openmw/mwclass/light.hpp b/apps/openmw/mwclass/light.hpp index c9940d0a5..179531808 100644 --- a/apps/openmw/mwclass/light.hpp +++ b/apps/openmw/mwclass/light.hpp @@ -30,6 +30,10 @@ namespace MWClass virtual std::string getScript (const MWWorld::Ptr& ptr) const; ///< Return name of the script attached to ptr + virtual std::pair, bool> getEquipmentSlots (const MWWorld::Ptr& ptr) const; + ///< \return first: Return IDs of the slot this object can be equipped in; second: can object + /// stay stacked when equipped? + static void registerSelf(); }; } diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 98c05a1b3..5fc6ea9c7 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -7,6 +7,7 @@ #include "../mwworld/ptr.hpp" #include "../mwworld/actiontake.hpp" +#include "../mwworld/inventorystore.hpp" #include "../mwrender/objects.hpp" @@ -66,6 +67,15 @@ namespace MWClass return ref->base->script; } + std::pair, bool> Lockpick::getEquipmentSlots (const MWWorld::Ptr& ptr) const + { + std::vector slots; + + slots.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight)); + + return std::make_pair (slots, false); + } + void Lockpick::registerSelf() { boost::shared_ptr instance (new Lockpick); diff --git a/apps/openmw/mwclass/lockpick.hpp b/apps/openmw/mwclass/lockpick.hpp index 9cbfa0d23..5465e5215 100644 --- a/apps/openmw/mwclass/lockpick.hpp +++ b/apps/openmw/mwclass/lockpick.hpp @@ -25,6 +25,10 @@ namespace MWClass virtual std::string getScript (const MWWorld::Ptr& ptr) const; ///< Return name of the script attached to ptr + virtual std::pair, bool> getEquipmentSlots (const MWWorld::Ptr& ptr) const; + ///< \return first: Return IDs of the slot this object can be equipped in; second: can object + /// stay stacked when equipped? + static void registerSelf(); }; } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index cc7daa83e..83a94d27d 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -16,7 +16,7 @@ #include "../mwworld/actiontalk.hpp" #include "../mwworld/environment.hpp" #include "../mwworld/world.hpp" -#include "../mwworld/containerstore.hpp" +#include "../mwworld/inventorystore.hpp" #include "../mwworld/customdata.hpp" namespace @@ -29,7 +29,7 @@ namespace MWMechanics::NpcStats mNpcStats; MWMechanics::CreatureStats mCreatureStats; MWMechanics::Movement mMovement; - MWWorld::ContainerStore mContainerStore; + MWWorld::InventoryStore mInventoryStore; virtual MWWorld::CustomData *clone() const; }; @@ -161,7 +161,15 @@ namespace MWClass { ensureCustomData (ptr); - return dynamic_cast (*ptr.getRefData().getCustomData()).mContainerStore; + return dynamic_cast (*ptr.getRefData().getCustomData()).mInventoryStore; + } + + MWWorld::InventoryStore& Npc::getInventoryStore (const MWWorld::Ptr& ptr) + const + { + ensureCustomData (ptr); + + return dynamic_cast (*ptr.getRefData().getCustomData()).mInventoryStore; } std::string Npc::getScript (const MWWorld::Ptr& ptr) const diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index bef417332..f210eda5f 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -38,6 +38,9 @@ namespace MWClass virtual MWWorld::ContainerStore& getContainerStore (const MWWorld::Ptr& ptr) const; ///< Return container store + virtual MWWorld::InventoryStore& getInventoryStore (const MWWorld::Ptr& ptr) const; + ///< Return inventory store + virtual boost::shared_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const; ///< Generate action for activation diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index de024e430..d70cda83d 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -7,6 +7,7 @@ #include "../mwworld/ptr.hpp" #include "../mwworld/actiontake.hpp" +#include "../mwworld/inventorystore.hpp" #include "../mwrender/objects.hpp" @@ -65,6 +66,15 @@ namespace MWClass return ref->base->script; } + std::pair, bool> Probe::getEquipmentSlots (const MWWorld::Ptr& ptr) const + { + std::vector slots; + + slots.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight)); + + return std::make_pair (slots, false); + } + void Probe::registerSelf() { boost::shared_ptr instance (new Probe); diff --git a/apps/openmw/mwclass/probe.hpp b/apps/openmw/mwclass/probe.hpp index 3f2bfed5b..2aadaa22e 100644 --- a/apps/openmw/mwclass/probe.hpp +++ b/apps/openmw/mwclass/probe.hpp @@ -25,6 +25,10 @@ namespace MWClass virtual std::string getScript (const MWWorld::Ptr& ptr) const; ///< Return name of the script attached to ptr + virtual std::pair, bool> getEquipmentSlots (const MWWorld::Ptr& ptr) const; + ///< \return first: Return IDs of the slot this object can be equipped in; second: can object + /// stay stacked when equipped? + static void registerSelf(); }; } diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 90fd3e33b..e4cb4582b 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -7,6 +7,7 @@ #include "../mwworld/ptr.hpp" #include "../mwworld/actiontake.hpp" +#include "../mwworld/inventorystore.hpp" #include "../mwrender/objects.hpp" @@ -78,6 +79,61 @@ namespace MWClass return ref->base->script; } + std::pair, bool> Weapon::getEquipmentSlots (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + std::vector slots; + bool stack = false; + + if (ref->base->data.type==ESM::Weapon::Arrow || ref->base->data.type==ESM::Weapon::Bolt) + { + slots.push_back (int (MWWorld::InventoryStore::Slot_Ammunition)); + stack = true; + } + else if (ref->base->data.type==ESM::Weapon::MarksmanThrown) + { + slots.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight)); + stack = true; + } + else + slots.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight)); + + return std::make_pair (slots, stack); + } + + int Weapon::getEuqipmentSkill (const MWWorld::Ptr& ptr, + const MWWorld::Environment& environment) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + const int size = 12; + + static const int sMapping[size][2] = + { + { ESM::Weapon::ShortBladeOneHand, ESM::Skill::ShortBlade }, + { ESM::Weapon::LongBladeOneHand, ESM::Skill::LongBlade }, + { ESM::Weapon::LongBladeTwoHand, ESM::Skill::LongBlade }, + { ESM::Weapon::BluntOneHand, ESM::Skill::BluntWeapon }, + { ESM::Weapon::BluntTwoClose, ESM::Skill::BluntWeapon }, + { ESM::Weapon::BluntTwoWide, ESM::Skill::BluntWeapon }, + { ESM::Weapon::SpearTwoWide, ESM::Skill::Spear }, + { ESM::Weapon::AxeOneHand, ESM::Skill::Axe }, + { ESM::Weapon::AxeTwoHand, ESM::Skill::Axe }, + { ESM::Weapon::MarksmanBow, ESM::Skill::Marksman }, + { ESM::Weapon::MarksmanCrossbow, ESM::Skill::Marksman }, + { ESM::Weapon::MarksmanThrown, ESM::Skill::Marksman } + }; + + for (int i=0; ibase->data.type) + return sMapping[i][1]; + + return -1; + } + void Weapon::registerSelf() { boost::shared_ptr instance (new Weapon); diff --git a/apps/openmw/mwclass/weapon.hpp b/apps/openmw/mwclass/weapon.hpp index b056249b9..2b6dfb5a8 100644 --- a/apps/openmw/mwclass/weapon.hpp +++ b/apps/openmw/mwclass/weapon.hpp @@ -31,6 +31,15 @@ namespace MWClass virtual std::string getScript (const MWWorld::Ptr& ptr) const; ///< Return name of the script attached to ptr + virtual std::pair, bool> getEquipmentSlots (const MWWorld::Ptr& ptr) const; + ///< \return first: Return IDs of the slot this object can be equipped in; second: can object + /// stay stacked when equipped? + + virtual int getEuqipmentSkill (const MWWorld::Ptr& ptr, + const MWWorld::Environment& environment) const; + /// Return the index of the skill this item corresponds to when equiopped or -1, if there is + /// no such skill. + static void registerSelf(); }; } diff --git a/apps/openmw/mwrender/actors.cpp b/apps/openmw/mwrender/actors.cpp index acc655404..d8ca78e3a 100644 --- a/apps/openmw/mwrender/actors.cpp +++ b/apps/openmw/mwrender/actors.cpp @@ -99,14 +99,15 @@ void Actors::removeCell(MWWorld::Ptr::CellStore* store){ mRend.getScene()->destroySceneNode(base); base = 0; } - for(std::map::iterator iter = mAllActors.begin(); iter != mAllActors.end(); iter++) - { - if(iter->first.getCell() == store){ - delete iter->second; - mAllActors.erase(iter); - } - } - + for(std::map::iterator iter = mAllActors.begin(); iter != mAllActors.end(); ) + { + if(iter->first.getCell() == store){ + delete iter->second; + mAllActors.erase(iter++); + } + else + ++iter; + } } void Actors::playAnimationGroup (const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number){ diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 63855f3b8..9df987dc1 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -276,6 +276,7 @@ namespace MWRender{ rotmult = bonePtr->getOrientation(); scale = bonePtr->getScale().x; boneSequenceIter++; + for(; boneSequenceIter != boneSequence.end(); boneSequenceIter++) { if(creaturemodel->getSkeleton()->hasBone(*boneSequenceIter)){ @@ -330,7 +331,7 @@ namespace MWRender{ } } - bool Animation::timeIndex( float time, std::vector times, int & i, int & j, float & x ){ + bool Animation::timeIndex( float time, const std::vector & times, int & i, int & j, float & x ){ int count; if ( (count = times.size()) > 0 ) { @@ -388,6 +389,8 @@ namespace MWRender{ } void Animation::handleAnimationTransforms(){ + + Ogre::SkeletonInstance* skel = base->getSkeleton(); @@ -404,10 +407,10 @@ namespace MWRender{ for(unsigned int i = 0; i < entityparts.size(); i++){ //Ogre::SkeletonInstance* skel = entityparts[i]->getSkeleton(); - Ogre::Bone* b = skel->getRootBone(); - b->setOrientation(Ogre::Real(.3),Ogre::Real(.3),Ogre::Real(.3), Ogre::Real(.3));//This is a trick + //Ogre::Bone* b = skel->getRootBone(); + //b->setOrientation(Ogre::Real(.3),Ogre::Real(.3),Ogre::Real(.3), Ogre::Real(.3));//This is a trick - entityparts[i]->getAllAnimationStates()->_notifyDirty(); + //entityparts[i]->getAllAnimationStates()->_notifyDirty(); } @@ -424,18 +427,19 @@ namespace MWRender{ float x; float x2; - std::vector quats = iter->getQuat(); + const std::vector & quats = iter->getQuat(); - std::vector ttime = iter->gettTime(); - std::vector::iterator ttimeiter = ttime.begin(); + const std::vector & ttime = iter->gettTime(); + - std::vector rtime = iter->getrTime(); - int rindexJ = 0; + const std::vector & rtime = iter->getrTime(); + int rindexJ = rindexI[slot]; + timeIndex(time, rtime, rindexI[slot], rindexJ, x2); - int tindexJ = 0; + int tindexJ = tindexI[slot]; - std::vector translist1 = iter->getTranslist1(); + const std::vector & translist1 = iter->getTranslist1(); timeIndex(time, ttime, tindexI[slot], tindexJ, x); @@ -443,34 +447,35 @@ namespace MWRender{ Ogre::Quaternion r; bool bTrans = translist1.size() > 0; - if(bTrans){ - Ogre::Vector3 v1 = translist1[tindexI[slot]]; - Ogre::Vector3 v2 = translist1[tindexJ]; - t = (v1 + (v2 - v1) * x); - - } + bool bQuats = quats.size() > 0; - if(bQuats){ - r = Ogre::Quaternion::Slerp(x2, quats[rindexI[slot]], quats[rindexJ], true); - } - skel = base->getSkeleton(); + if(skel->hasBone(iter->getBonename())){ Ogre::Bone* bone = skel->getBone(iter->getBonename()); - if(bTrans) + if(bTrans){ + Ogre::Vector3 v1 = translist1[tindexI[slot]]; + Ogre::Vector3 v2 = translist1[tindexJ]; + t = (v1 + (v2 - v1) * x); bone->setPosition(t); - if(bQuats) + + } + if(bQuats){ + r = Ogre::Quaternion::Slerp(x2, quats[rindexI[slot]], quats[rindexJ], true); bone->setOrientation(r); + } - skel->_updateTransforms(); - base->getAllAnimationStates()->_notifyDirty(); + } + slot++; } + skel->_updateTransforms(); + base->getAllAnimationStates()->_notifyDirty(); } } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index bad7eca15..e08b86e8d 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -28,6 +28,7 @@ class Animation{ MWWorld::Environment& mEnvironment; std::map vecRotPos; static std::map mUniqueIDs; + std::vector* > shapeparts; //All the NiTriShape data that we need for animating an npc @@ -55,7 +56,7 @@ class Animation{ Ogre::Entity* base; void handleShapes(std::vector* allshapes, Ogre::Entity* creaturemodel, Ogre::SkeletonInstance *skel); void handleAnimationTransforms(); - bool timeIndex( float time, std::vector times, int & i, int & j, float & x ); + bool timeIndex( float time, const std::vector & times, int & i, int & j, float & x ); std::string getUniqueID(std::string mesh); public: diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 4c9c0e466..c6fe023d6 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -42,6 +42,7 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, MWWorld::Environment& _env,O std::string bodyRaceID = headID.substr(0, headID.find_last_of("head_") - 4); char secondtolast = bodyRaceID.at(bodyRaceID.length() - 2); bool female = tolower(secondtolast) == 'f'; + std::transform(bodyRaceID.begin(), bodyRaceID.end(), bodyRaceID.begin(), ::tolower); bool beast = bodyRaceID == "b_n_khajiit_m_" || bodyRaceID == "b_n_khajiit_f_" || bodyRaceID == "b_n_argonian_m_" || bodyRaceID == "b_n_argonian_f_"; /*std::cout << "Race: " << ref->base->race ; @@ -276,6 +277,7 @@ void NpcAnimation::runAnimation(float timepassed){ shapepartsiter++; entitypartsiter++; } + } } diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index 4e2a3caab..717064ada 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -101,6 +101,14 @@ void Objects::insertMesh (const MWWorld::Ptr& ptr, const std::string& mesh) 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)); } else { @@ -108,7 +116,6 @@ void Objects::insertMesh (const MWWorld::Ptr& ptr, const std::string& mesh) } sg->addEntity(ent,insert->_getDerivedPosition(),insert->_getDerivedOrientation(),insert->_getDerivedScale()); - sg->setRegionDimensions(Ogre::Vector3(100000,10000,100000)); mRenderer.getScene()->destroyEntity(ent); } diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index b8bd588c4..1b324459a 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -658,9 +658,15 @@ void SkyManager::setWeather(const MWWorld::WeatherResult& weather) if (weather.mNight && mStarsOpacity != weather.mNightFade) { - for (int i=0; i<7; ++i) - mStarsMaterials[i]->getTechnique(0)->getPass(0)->setDiffuse(0.0, 0.0, 0.0, weather.mNightFade); - mStarsOpacity = weather.mNightFade; + if (weather.mNightFade == 0) + mAtmosphereNight->setVisible(false); + else + { + mAtmosphereNight->setVisible(true); + for (int i=0; i<7; ++i) + mStarsMaterials[i]->getTechnique(0)->getPass(0)->setDiffuse(0.0, 0.0, 0.0, weather.mNightFade); + mStarsOpacity = weather.mNightFade; + } } float strength; diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 7390e4c5c..2440eda23 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -4,15 +4,12 @@ #include #include -using namespace std; - #include #include #include #include -#include #include #include "../mwworld/environment.hpp" @@ -46,7 +43,6 @@ using namespace std; using namespace Mangle::Sound; typedef OEngine::Sound::SoundManager OEManager; -typedef OEngine::Sound::SoundManagerPtr OEManagerPtr; // Set the position on a sound based on a Ptr. static void setPos(SoundPtr &snd, const MWWorld::Ptr ref) @@ -61,158 +57,65 @@ static void setPos(SoundPtr &snd, const MWWorld::Ptr ref) namespace MWSound { - struct SoundManager::SoundImpl - { - /* This is the sound manager. It loades, stores and deletes - sounds based on the sound factory it is given. - */ - OEManagerPtr mgr; - SoundPtr music; - - /* This class calls update() on the sound manager each frame - using and Ogre::FrameListener - */ - Mangle::Sound::OgreOutputUpdater updater; - - /* This class tracks the movement of an Ogre::Camera and moves - a sound listener automatically to follow it. - */ - Mangle::Sound::OgreListenerMover cameraTracker; - - const ESMS::ESMStore &store; - - typedef std::map IDMap; - typedef std::map PtrMap; - PtrMap sounds; - - // This is used for case insensitive and slash-type agnostic file - // finding. It takes DOS paths (any case, \\ slashes or / slashes) - // relative to the sound dir, and translates them into full paths - // of existing files in the filesystem, if they exist. - bool FSstrict; - FileFinder::FileFinder files; - FileFinder::FileFinderStrict strict; - FileFinder::FileFinder musicpath; - FileFinder::FileFinderStrict musicpathStrict; - - SoundImpl(Ogre::Root *root, Ogre::Camera *camera, - const ESMS::ESMStore &str, - const std::string &soundDir, const std::string &musicDir, bool fsstrict) - : mgr(new OEManager(SoundFactoryPtr(new SOUND_FACTORY))) - , updater(mgr) - , cameraTracker(mgr) - , store(str) - , files(soundDir), strict(soundDir) - ,musicpath(musicDir), musicpathStrict(musicDir) - { - FSstrict = fsstrict; - cout << "Sound output: " << SOUND_OUT << endl; - cout << "Sound decoder: " << SOUND_IN << endl; - // Attach the camera to the camera tracker - cameraTracker.followCamera(camera); - - // Tell Ogre to update the sound system each frame - root->addFrameListener(&updater); - } - - ~SoundImpl() - { - Ogre::Root::getSingleton().removeFrameListener(&updater); - cameraTracker.unfollowCamera(); - } - - - - static std::string toMp3(std::string str) - { - std::string::size_type i = str.rfind('.'); - if(str.find('/', i) == std::string::npos && - str.find('\\', i) == std::string::npos) - str = str.substr(0, i) + ".mp3"; - else - str += ".mp3"; - return str; - } - bool hasFile(const std::string &str, bool music = false) + SoundManager::SoundManager(Ogre::Root *root, Ogre::Camera *camera, + const Files::PathContainer& dataDirs, + bool useSound, bool fsstrict, MWWorld::Environment& environment) + : mFSStrict(fsstrict) + , mEnvironment(environment) + , mgr(new OEManager(SoundFactoryPtr(new SOUND_FACTORY))) + , updater(mgr) + , cameraTracker(mgr) + , mCurrentPlaylist(NULL) { - if(FSstrict == false) + if(useSound) { - if(music) - { - if(musicpath.has(str)) return true; - - // Not found? Try with .mp3 - return musicpath.has(toMp3(str)); - } - else + // 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) { - if(files.has(str)) return true; - return files.has(toMp3(str)); + Files::FileLister(*it / std::string("Sound"), mSoundFiles, true); } - } - else - { - if(music) - { - if(musicpathStrict.has(str)) return true; - // Not found? Try with .mp3 - return musicpathStrict.has(toMp3(str)); - } - else + // 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) { - if(strict.has(str)) return true; - return strict.has(toMp3(str)); + mMusicLibrary.add(*it / std::string("Music"), true, mFSStrict, acceptableExtensions); } - } - } - // Convert a Morrowind sound path (eg. Fx\funny.wav) to full path - // with proper slash conversion (eg. datadir/Sound/Fx/funny.wav) - std::string convertPath(const std::string &str, bool music = false) - { - if(FSstrict == false) - { - // Search and return - if(music && musicpath.has(str)) - return musicpath.lookup(str); - else if(files.has(str)) - return files.lookup(str); - - // Try mp3 if the wav wasn't found - std::string mp3 = toMp3(str); - if(music && musicpath.has(mp3)) - return musicpath.lookup(mp3); - else if(files.has(mp3)) - return files.lookup(mp3); - } - else - { - if(music && musicpathStrict.has(str)) - return musicpathStrict.lookup(str); - else if(strict.has(str)) - return strict.lookup(str); - - // Try mp3 if the wav wasn't found - std::string mp3 = toMp3(str); - if(music && musicpathStrict.has(mp3)) - return musicpathStrict.lookup(mp3); - else if(strict.has(str)) - return strict.lookup(mp3); + 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); } + } - // Give up - return ""; + SoundManager::~SoundManager() + { + Ogre::Root::getSingleton().removeFrameListener(&updater); + cameraTracker.unfollowCamera(); } // Convert a soundId to file name, and modify the volume // according to the sounds local volume setting, minRange and // maxRange. - std::string lookup(const std::string &soundId, + std::string SoundManager::lookup(const std::string &soundId, float &volume, float &min, float &max) { - const ESM::Sound *snd = store.sounds.search(soundId); + const ESM::Sound *snd = mEnvironment.mWorld->getStore().sounds.search(soundId); if(snd == NULL) return ""; if(snd->data.volume == 0) @@ -233,11 +136,11 @@ namespace MWSound max = std::max(min, max); } - return convertPath(snd->sound); + return Files::FileListLocator(mSoundFiles, snd->sound, mFSStrict); } // Add a sound to the list and play it - void add(const std::string &file, + void SoundManager::add(const std::string &file, MWWorld::Ptr ptr, const std::string &id, float volume, float pitch, @@ -258,13 +161,13 @@ namespace MWSound } catch(...) { - cout << "Error loading " << file << ", skipping.\n"; + std::cout << "Error loading " << file << ", skipping.\n"; } } // Clears all the sub-elements of a given iterator, and then // removes it from 'sounds'. - void clearAll(PtrMap::iterator it) + void SoundManager::clearAll(PtrMap::iterator& it) { IDMap::iterator sit = it->second.begin(); @@ -285,7 +188,7 @@ namespace MWSound // Stop a sound and remove it from the list. If id="" then // remove the entire object and stop all its sounds. - void remove(MWWorld::Ptr ptr, const std::string &id = "") + void SoundManager::remove(MWWorld::Ptr ptr, const std::string &id) { PtrMap::iterator it = sounds.find(ptr); if(it != sounds.end()) @@ -308,7 +211,7 @@ namespace MWSound } } - bool isPlaying(MWWorld::Ptr ptr, const std::string &id) const + bool SoundManager::isPlaying(MWWorld::Ptr ptr, const std::string &id) const { PtrMap::const_iterator it = sounds.find(ptr); if(it != sounds.end()) @@ -332,7 +235,7 @@ namespace MWSound } // Remove all references to objects belonging to a given cell - void removeCell(const MWWorld::Ptr::CellStore *cell) + void SoundManager::removeCell(const MWWorld::Ptr::CellStore *cell) { PtrMap::iterator it2, it = sounds.begin(); while(it != sounds.end()) @@ -344,7 +247,7 @@ namespace MWSound } } - void updatePositions(MWWorld::Ptr ptr) + void SoundManager::updatePositions(MWWorld::Ptr ptr) { // Find the reference (if any) PtrMap::iterator it = sounds.find(ptr); @@ -362,213 +265,227 @@ namespace MWSound } } } - }; - void SoundManager::streamMusicFull (const std::string& filename) - { - if(!mData) return; + void SoundManager::stopMusic() + { + if (music) + music->stop(); + setPlaylist(); + } + + 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 (mData->music) - mData->music->stop(); - mData->music = mData->mgr->load(filename); - mData->music->setStreaming(true); - mData->music->setVolume(0.4); - mData->music->play(); - - } - - SoundManager::SoundManager(Ogre::Root *root, Ogre::Camera *camera, - const ESMS::ESMStore &store, - boost::filesystem::path dataDir, - bool useSound, bool fsstrict, MWWorld::Environment& environment) - : mData(NULL), fsStrict (fsstrict), mEnvironment (environment) - { - MP3Lookup(dataDir / "Music/Explore/"); - if(useSound) - mData = new SoundImpl(root, camera, store, (dataDir / "Sound").string(), (dataDir / "Music").string(), fsstrict); - - - test.name = ""; - total = 0; - - - } + if (music) + music->stop(); + music = mgr->load(filename); + music->setStreaming(true); + music->setVolume(0.4); + music->play(); - SoundManager::~SoundManager() - { - if(mData) - delete mData; } void SoundManager::streamMusic(const std::string& filename) { - if(mData->hasFile(filename, true)) + std::string filePath = mMusicLibrary.locate(filename, mFSStrict).string(); + if(!filePath.empty()) { - std::string fullpath = mData->convertPath(filename, true); - streamMusicFull(fullpath); + streamMusicFull(filePath); } } - - void SoundManager::MP3Lookup(boost::filesystem::path dir) -{ - boost::filesystem::directory_iterator dir_iter(dir), dir_end; - - std::string mp3extension = ".mp3"; - for(;dir_iter != dir_end; dir_iter++) - { - if(boost::filesystem::extension(*dir_iter) == mp3extension) - { - files.push_back(*dir_iter); - } - } -} - void SoundManager::startRandomTitle() -{ - std::vector::iterator fileIter; - - if(files.size() > 0) + { + if(mCurrentPlaylist && !mCurrentPlaylist->empty()) { - fileIter = files.begin(); - srand ( time(NULL) ); - int r = rand() % files.size() + 1; //old random code + Files::PathContainer::const_iterator fileIter = mCurrentPlaylist->begin(); + srand( time(NULL) ); + int r = rand() % mCurrentPlaylist->size() + 1; //old random code - for(int i = 1; i < r; i++) - { - fileIter++; - } + std::advance(fileIter, r - 1); std::string music = fileIter->string(); + std::cout << "Playing " << music << "\n"; + try { - std::cout << "Playing " << music << "\n"; streamMusicFull(music); } - catch(std::exception &e) + catch (std::exception &e) { std::cout << " Music Error: " << e.what() << "\n"; } } -} - + } bool SoundManager::isMusicPlaying() { bool test = false; - if(mData && mData->music) + if(music) { - test = mData->music->isPlaying(); + test = music->isPlaying(); } return test; } - SoundManager::SoundImpl SoundManager::getMData() - { - // bool test = mData->music->isPlaying(); - return *mData; - } + bool SoundManager::setPlaylist(std::string playlist) + { + const Files::PathContainer* previousPlaylist; + previousPlaylist = mCurrentPlaylist; + if (playlist == "") + { + mCurrentPlaylist = mMusicLibrary.section(playlist, mFSStrict); + } + else if(mMusicLibrary.containsSection(playlist, mFSStrict)) + { + mCurrentPlaylist = mMusicLibrary.section(playlist, mFSStrict); + } + else + { + std::cout << "Warning: playlist named " << playlist << " does not exist.\n"; + } + return previousPlaylist == mCurrentPlaylist; + } + void SoundManager::playPlaylist(std::string playlist) + { + if (playlist == "") + { + if(!isMusicPlaying()) + { + startRandomTitle(); + } + return; + } + if(!setPlaylist(playlist)) + { + startRandomTitle(); + } + else if (!isMusicPlaying()) + { + startRandomTitle(); + } + } void SoundManager::say (MWWorld::Ptr ptr, const std::string& filename) { // The range values are not tested - if(!mData) return; - if(mData->hasFile(filename)) - mData->add(mData->convertPath(filename), ptr, "_say_sound", 1, 1, 100, 20000, false); + std::string filePath = Files::FileListLocator(mSoundFiles, filename, mFSStrict); + if(!filePath.empty()) + add(filePath, ptr, "_say_sound", 1, 1, 100, 20000, false); else - cout << "Sound file " << filename << " not found, skipping.\n"; + std::cout << "Sound file " << filename << " not found, skipping.\n"; } bool SoundManager::sayDone (MWWorld::Ptr ptr) const { - if(!mData) return false; - return !mData->isPlaying(ptr, "_say_sound"); + return !isPlaying(ptr, "_say_sound"); } - void SoundManager::playSound (const std::string& soundId, float volume, float pitch) + void SoundManager::playSound(const std::string& soundId, float volume, float pitch, bool loop) { - if(!mData) return; - // Play and forget float min, max; - const std::string &file = mData->lookup(soundId, volume, min, max); - if(file != "") - { - SoundPtr snd = mData->mgr->load(file); + const std::string &file = lookup(soundId, volume, min, max); + if (file != "") + { + SoundPtr snd = mgr->load(file); + snd->setRepeat(loop); snd->setVolume(volume); snd->setRange(min,max); snd->setPitch(pitch); snd->play(); - } + + if (loop) + { + // Only add the looping sound once + IDMap::iterator it = mLoopedSounds.find(soundId); + if(it == mLoopedSounds.end()) + { + mLoopedSounds[soundId] = WSoundPtr(snd); + } + } + } } void SoundManager::playSound3D (MWWorld::Ptr ptr, const std::string& soundId, float volume, float pitch, bool loop) { - if(!mData) return; - // Look up the sound in the ESM data float min, max; - const std::string &file = mData->lookup(soundId, volume, min, max); - if(file != "") - mData->add(file, ptr, soundId, volume, pitch, min, max, loop); + const std::string &file = lookup(soundId, volume, min, max); + if (file != "") + add(file, ptr, soundId, volume, pitch, min, max, loop); } void SoundManager::stopSound3D (MWWorld::Ptr ptr, const std::string& soundId) { - if(!mData) return; - mData->remove(ptr, soundId); + remove(ptr, soundId); } void SoundManager::stopSound (MWWorld::Ptr::CellStore *cell) { - if(!mData) return; - mData->removeCell(cell); + removeCell(cell); } + void SoundManager::stopSound(const std::string& soundId) + { + IDMap::iterator it = mLoopedSounds.find(soundId); + if(it != mLoopedSounds.end()) + { + SoundPtr snd = it->second.lock(); + if(snd) snd->stop(); + mLoopedSounds.erase(it); + } + } + bool SoundManager::getSoundPlaying (MWWorld::Ptr ptr, const std::string& soundId) const { // Mark all sounds as playing, otherwise the scripts will just // keep trying to play them every frame. - if(!mData) return true; - return mData->isPlaying(ptr, soundId); + return isPlaying(ptr, soundId); } void SoundManager::updateObject(MWWorld::Ptr ptr) { - if(!mData) return; - mData->updatePositions(ptr); + updatePositions(ptr); } void SoundManager::update (float duration) { - std::string effect; - MWWorld::Ptr::CellStore *current = mEnvironment.mWorld->getPlayer().getPlayer().getCell(); + static int total = 0; + static std::string regionName = ""; + static float timePassed = 0.0; + timePassed += duration; //If the region has changed - if(!(current->cell->data.flags & current->cell->Interior) && timer.elapsed() >= 10){ - timer.restart(); - if (test.name != current->cell->region) + if(!(current->cell->data.flags & current->cell->Interior) && timePassed >= 10) + { + + ESM::Region test = (ESM::Region) *(mEnvironment.mWorld->getStore().regions.find(current->cell->region)); + + timePassed = 0; + if (regionName != current->cell->region) { + regionName = current->cell->region; total = 0; - test = (ESM::Region) *(mEnvironment.mWorld->getStore().regions.find(current->cell->region)); } if(test.soundList.size() > 0) { std::vector::iterator soundIter = test.soundList.begin(); //mEnvironment.mSoundManager - if(total == 0){ - while (!(soundIter == test.soundList.end())) + if(total == 0) + { + while (soundIter != test.soundList.end()) { - ESM::NAME32 go = soundIter->sound; int chance = (int) soundIter->chance; + //ESM::NAME32 go = soundIter->sound; //std::cout << "Sound: " << go.name <<" Chance:" << chance << "\n"; soundIter++; total += chance; @@ -578,21 +495,19 @@ namespace MWSound int r = rand() % total; //old random code int pos = 0; soundIter = test.soundList.begin(); - while (!(soundIter == test.soundList.end())) + while (soundIter != test.soundList.end()) { - const ESM::NAME32 go = soundIter->sound; + const std::string go = soundIter->sound.toString(); int chance = (int) soundIter->chance; //std::cout << "Sound: " << go.name <<" Chance:" << chance << "\n"; soundIter++; if( r - pos < chance) { - effect = go.name; //play sound - std::cout << "Sound: " << go.name <<" Chance:" << chance << "\n"; - mEnvironment.mSoundManager->playSound(effect, 20.0, 1.0); + std::cout << "Sound: " << go <<" Chance:" << chance << "\n"; + mEnvironment.mSoundManager->playSound(go, 20.0, 1.0); break; - } pos += chance; } @@ -600,7 +515,7 @@ namespace MWSound } else if(current->cell->data.flags & current->cell->Interior) { - test.name = ""; + regionName = ""; } } diff --git a/apps/openmw/mwsound/soundmanager.hpp b/apps/openmw/mwsound/soundmanager.hpp index 7dff16c76..03c19ce77 100644 --- a/apps/openmw/mwsound/soundmanager.hpp +++ b/apps/openmw/mwsound/soundmanager.hpp @@ -2,14 +2,16 @@ #define GAME_SOUND_SOUNDMANAGER_H #include -#include -#include -#include "../mwworld/ptr.hpp" +#include +#include + #include +#include + +#include "../mwworld/ptr.hpp" -#include namespace Ogre { @@ -17,11 +19,16 @@ namespace Ogre class Camera; } -namespace ESMS +namespace Mangle { - struct ESMStore; + namespace Sound + { + typedef boost::shared_ptr SoundPtr; + } } +typedef OEngine::Sound::SoundManagerPtr OEManagerPtr; + namespace MWWorld { struct Environment; @@ -29,43 +36,94 @@ namespace MWWorld namespace MWSound { - //SoundPtr *music; class SoundManager { - // Hide implementation details - engine.cpp is compiling - // enough as it is. - struct SoundImpl; - SoundImpl *mData; - std::vector files; - bool fsStrict; - MWWorld::Environment& mEnvironment; + // 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; - int total; - ESM::Region test; - boost::timer timer; + 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; + + // A list of all sound files used to lookup paths + Files::PathContainer mSoundFiles; + + // A library of all Music file paths stored by the folder they are contained in + Files::FileLibrary mMusicLibrary; + + // Points to the current playlist of music files stored in the music library + const Files::PathContainer* mCurrentPlaylist; + + IDMap mLoopedSounds; + + std::string lookup(const std::string &soundId, + float &volume, float &min, float &max); + void add(const std::string &file, + MWWorld::Ptr ptr, const std::string &id, + float volume, float pitch, float min, float max, + bool loop); + void clearAll(PtrMap::iterator& it); + void remove(MWWorld::Ptr ptr, const std::string &id = ""); + bool isPlaying(MWWorld::Ptr ptr, const std::string &id) const; + void removeCell(const MWWorld::Ptr::CellStore *cell); + void updatePositions(MWWorld::Ptr ptr); + public: - SoundManager(Ogre::Root*, Ogre::Camera*, const ESMS::ESMStore &store, - boost::filesystem::path dataDir, bool useSound, bool fsstrict, + SoundManager(Ogre::Root*, Ogre::Camera*, + const Files::PathContainer& dataDir, bool useSound, bool fsstrict, MWWorld::Environment& environment); ~SoundManager(); + void stopMusic(); + ///< Stops music if it's playing + void streamMusic(const std::string& filename); ///< Play a soundifle /// \param filename name of a sound file in "Music/" in the data directory. void startRandomTitle(); - void MP3Lookup(boost::filesystem::path dir); + ///< Starts a random track from the current playlist 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 - SoundImpl getMData(); + 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. @@ -74,7 +132,7 @@ namespace MWSound bool sayDone (MWWorld::Ptr reference) const; ///< Is actor not speaking? - void playSound (const std::string& soundId, float volume, float pitch); + void playSound (const std::string& soundId, float volume, float pitch, bool loop=false); ///< Play a sound, independently of 3D-position void playSound3D (MWWorld::Ptr reference, const std::string& soundId, @@ -88,6 +146,9 @@ namespace MWSound void stopSound (MWWorld::Ptr::CellStore *cell); ///< Stop all sounds for the given cell. + void stopSound(const std::string& soundId); + ///< Stop a non-3d looping sound + bool getSoundPlaying (MWWorld::Ptr reference, const std::string& soundId) const; ///< Is the given sound currently playing on the given object? diff --git a/apps/openmw/mwworld/cells.cpp b/apps/openmw/mwworld/cells.cpp index 079c888aa..8c657a5c8 100644 --- a/apps/openmw/mwworld/cells.cpp +++ b/apps/openmw/mwworld/cells.cpp @@ -5,6 +5,8 @@ #include #include "world.hpp" +#include "class.hpp" +#include "containerstore.hpp" MWWorld::Ptr::CellStore *MWWorld::Cells::getCellStore (const ESM::Cell *cell) { @@ -35,6 +37,39 @@ MWWorld::Ptr::CellStore *MWWorld::Cells::getCellStore (const ESM::Cell *cell) } } +void MWWorld::Cells::fillContainers (Ptr::CellStore& cellStore) +{ + for (ESMS::CellRefList::List::iterator iter ( + cellStore.containers.list.begin()); + iter!=cellStore.containers.list.end(); ++iter) + { + Ptr container (&*iter, &cellStore); + + Class::get (container).getContainerStore (container).fill ( + iter->base->inventory, mStore); + } + + for (ESMS::CellRefList::List::iterator iter ( + cellStore.creatures.list.begin()); + iter!=cellStore.creatures.list.end(); ++iter) + { + Ptr container (&*iter, &cellStore); + + Class::get (container).getContainerStore (container).fill ( + iter->base->inventory, mStore); + } + + for (ESMS::CellRefList::List::iterator iter ( + cellStore.npcs.list.begin()); + iter!=cellStore.npcs.list.end(); ++iter) + { + Ptr container (&*iter, &cellStore); + + Class::get (container).getContainerStore (container).fill ( + iter->base->inventory, mStore); + } +} + MWWorld::Cells::Cells (const ESMS::ESMStore& store, ESM::ESMReader& reader, MWWorld::World& world) : mStore (store), mReader (reader), mWorld (world) {} @@ -43,6 +78,8 @@ MWWorld::Ptr::CellStore *MWWorld::Cells::getExterior (int x, int y) std::map, Ptr::CellStore>::iterator result = mExteriors.find (std::make_pair (x, y)); + bool fill = false; + if (result==mExteriors.end()) { const ESM::Cell *cell = mStore.cells.searchExt (x, y); @@ -63,11 +100,16 @@ MWWorld::Ptr::CellStore *MWWorld::Cells::getExterior (int x, int y) result = mExteriors.insert (std::make_pair ( std::make_pair (x, y), Ptr::CellStore (cell))).first; + + fill = true; } if (result->second.mState!=Ptr::CellStore::State_Loaded) result->second.load (mStore, mReader); + if (fill) + fillContainers (result->second); + return &result->second; } @@ -75,16 +117,23 @@ MWWorld::Ptr::CellStore *MWWorld::Cells::getInterior (const std::string& name) { std::map::iterator result = mInteriors.find (name); + bool fill = false; + if (result==mInteriors.end()) { const ESM::Cell *cell = mStore.cells.findInt (name); result = mInteriors.insert (std::make_pair (name, Ptr::CellStore (cell))).first; + + fill = true; } if (result->second.mState!=Ptr::CellStore::State_Loaded) result->second.load (mStore, mReader); + if (fill) + fillContainers (result->second); + return &result->second; } diff --git a/apps/openmw/mwworld/cells.hpp b/apps/openmw/mwworld/cells.hpp index 0ecbd02d3..42aa1050c 100644 --- a/apps/openmw/mwworld/cells.hpp +++ b/apps/openmw/mwworld/cells.hpp @@ -34,6 +34,8 @@ namespace MWWorld Ptr::CellStore *getCellStore (const ESM::Cell *cell); + void fillContainers (Ptr::CellStore& cellStore); + public: Cells (const ESMS::ESMStore& store, ESM::ESMReader& reader, MWWorld::World& world); diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 641da73e1..96afc89a1 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -77,6 +77,11 @@ namespace MWWorld throw std::runtime_error ("class does not have a container store"); } + InventoryStore& Class::getInventoryStore (const Ptr& ptr) const + { + throw std::runtime_error ("class does not have an inventory store"); + } + void Class::lock (const Ptr& ptr, int lockLevel) const { throw std::runtime_error ("class does not support locking"); @@ -122,6 +127,16 @@ namespace MWWorld return Ogre::Vector3 (0, 0, 0); } + std::pair, bool> Class::getEquipmentSlots (const Ptr& ptr) const + { + return std::make_pair (std::vector(), false); + } + + int Class::getEuqipmentSkill (const Ptr& ptr, const Environment& environment) const + { + return -1; + } + const Class& Class::get (const std::string& key) { std::map >::const_iterator iter = sClasses.find (key); diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 9b6acb3ce..49c7bed0e 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -3,6 +3,7 @@ #include #include +#include #include @@ -34,6 +35,7 @@ namespace MWWorld class Ptr; class Environment; class ContainerStore; + class InventoryStore; /// \brief Base class for referenceable esm records class Class @@ -108,6 +110,10 @@ namespace MWWorld ///< Return container store or throw an exception, if class does not have a /// container store (default implementation: throw an exceoption) + virtual InventoryStore& getInventoryStore (const Ptr& ptr) const; + ///< Return inventory store or throw an exception, if class does not have a + /// inventory store (default implementation: throw an exceoption) + virtual void lock (const Ptr& ptr, int lockLevel) const; ///< Lock object (default implementation: throw an exception) @@ -137,6 +143,18 @@ namespace MWWorld ///< Return desired movement vector (determined based on movement settings, /// stance and stats). + virtual std::pair, bool> getEquipmentSlots (const Ptr& ptr) const; + ///< \return first: Return IDs of the slot this object can be equipped in; second: can object + /// stay stacked when equipped? + /// + /// Default implementation: return (empty vector, false). + + virtual int getEuqipmentSkill (const Ptr& ptr, const Environment& environment) + const; + /// Return the index of the skill this item corresponds to when equiopped or -1, if there is + /// no such skill. + /// (default implementation: return -1) + static const Class& get (const std::string& key); ///< If there is no class for this \a key, an exception is thrown. diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 54908deec..c498cabce 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -5,6 +5,12 @@ #include #include +#include + +#include "manualref.hpp" + +MWWorld::ContainerStore::~ContainerStore() {} + MWWorld::ContainerStoreIterator MWWorld::ContainerStore::begin (int mask) { return ContainerStoreIterator (mask, this); @@ -17,7 +23,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::end() void MWWorld::ContainerStore::add (const Ptr& ptr) { - /// \todo implement item stocking + /// \todo implement item stacking switch (getType (ptr)) { @@ -36,6 +42,40 @@ void MWWorld::ContainerStore::add (const Ptr& ptr) } } +void MWWorld::ContainerStore::fill (const ESM::InventoryList& items, const ESMS::ESMStore& store) +{ + for (std::vector::const_iterator iter (items.list.begin()); iter!=items.list.end(); + ++iter) + { + ManualRef ref (store, iter->item.toString()); + + if (ref.getPtr().getTypeName()==typeid (ESM::ItemLevList).name()) + { + /// \todo implement leveled item lists + continue; + } + + ref.getPtr().getRefData().setCount (iter->count); + add (ref.getPtr()); + } +} + +void MWWorld::ContainerStore::clear() +{ + potions.list.clear(); + appas.list.clear(); + armors.list.clear(); + books.list.clear(); + clothes.list.clear(); + ingreds.list.clear(); + lights.list.clear(); + lockpicks.list.clear(); + miscItems.list.clear(); + probes.list.clear(); + repairs.list.clear(); + weapons.list.clear(); +} + int MWWorld::ContainerStore::getType (const Ptr& ptr) { if (ptr.isEmpty()) @@ -331,6 +371,11 @@ int MWWorld::ContainerStoreIterator::getType() const return mType; } +const MWWorld::ContainerStore *MWWorld::ContainerStoreIterator::getContainerStore() const +{ + return mContainer; +} + bool MWWorld::operator== (const ContainerStoreIterator& left, const ContainerStoreIterator& right) { return left.isEqual (right); diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index 7263245f3..69e5ba446 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -1,11 +1,18 @@ #ifndef GAME_MWWORLD_CONTAINERSTORE_H #define GAME_MWWORLD_CONTAINERSTORE_H +#include + #include #include "refdata.hpp" #include "ptr.hpp" +namespace ESM +{ + struct InventoryList; +} + namespace MWWorld { class ContainerStoreIterator; @@ -48,6 +55,8 @@ namespace MWWorld public: + virtual ~ContainerStore(); + ContainerStoreIterator begin (int mask = Type_All); ContainerStoreIterator end(); @@ -60,6 +69,12 @@ namespace MWWorld /// \attention Do not add items to an existing stack by increasing the count instead of /// calling this function! + void fill (const ESM::InventoryList& items, const ESMS::ESMStore& store); + ///< Insert items into *this. + + void clear(); + ///< Empty container. + static int getType (const Ptr& ptr); ///< This function throws an exception, if ptr does not point to an object, that can be /// put into a container. @@ -71,6 +86,7 @@ namespace MWWorld /// /// \note The iterator will automatically skip over deleted objects. class ContainerStoreIterator + : public std::iterator { int mType; int mMask; @@ -126,6 +142,8 @@ namespace MWWorld int getType() const; + const ContainerStore *getContainerStore() const; + friend class ContainerStore; }; diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp new file mode 100644 index 000000000..264be7bb3 --- /dev/null +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -0,0 +1,86 @@ + +#include "inventorystore.hpp" + +#include +#include + +#include "class.hpp" + +void MWWorld::InventoryStore::copySlots (const InventoryStore& store) +{ + // some const-trickery, required because of a flaw in the handling of MW-references and the + // resulting workarounds + for (std::vector::const_iterator iter ( + const_cast (store).mSlots.begin()); + iter!=const_cast (store).mSlots.end(); ++iter) + { + std::size_t distance = std::distance (const_cast (store).begin(), *iter); + + ContainerStoreIterator slot = begin(); + + std::advance (slot, distance); + + mSlots.push_back (slot); + } +} + +MWWorld::InventoryStore::InventoryStore() +{ + for (int i=0; i=static_cast (mSlots.size())) + throw std::runtime_error ("slot number out of range"); + + if (iterator.getContainerStore()!=this) + throw std::runtime_error ("attempt to equip an item that is not in the inventory"); + + if (iterator!=end()) + { + std::pair, bool> slots = Class::get (*iterator).getEquipmentSlots (*iterator); + + if (std::find (slots.first.begin(), slots.first.end(), slot)==slots.first.end()) + throw std::runtime_error ("invalid slot"); + } + + /// \todo restack item previously in this slot (if required) + + /// \todo unstack item pointed to by iterator if required) + + mSlots[slot] = iterator; +} + +MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSlot (int slot) +{ + if (slot<0 || slot>=static_cast (mSlots.size())) + throw std::runtime_error ("slot number out of range"); + + if (mSlots[slot]==end()) + return end(); + + if (mSlots[slot]->getRefData().getCount()<1) + { + // object has been deleted + mSlots[slot] = end(); + return end(); + } + + return mSlots[slot]; +} diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp new file mode 100644 index 000000000..c41f9e8e3 --- /dev/null +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -0,0 +1,58 @@ +#ifndef GAME_MWWORLD_INVENTORYSTORE_H +#define GAME_MWWORLD_INVENTORYSTORE_H + +#include "containerstore.hpp" + +namespace MWWorld +{ + ///< \brief Variant of the ContainerStore for NPCs + class InventoryStore : public ContainerStore + { + public: + + static const int Slot_Helmet = 0; + static const int Slot_Cuirass = 1; + static const int Slot_Greaves = 2; + static const int Slot_LeftPauldron = 3; + static const int Slot_RightPauldron = 4; + static const int Slot_LeftGauntlet = 5; + static const int Slot_RightGauntlet = 6; + static const int Slot_Boots = 7; + static const int Slot_Shirt = 8; + static const int Slot_Pants = 9; + static const int Slot_Skirt = 10; + static const int Slot_Robe = 11; + static const int Slot_LeftRing = 12; + static const int Slot_RightRing = 13; + static const int Slot_Amulet = 14; + static const int Slot_Belt = 15; + static const int Slot_CarriedRight = 16; + static const int Slot_CarriedLeft = 17; + static const int Slot_Ammunition = 18; + + static const int Slots = 19; + + static const int Slot_NoSlot = -1; + + private: + + mutable std::vector mSlots; + + void copySlots (const InventoryStore& store); + + public: + + InventoryStore(); + + InventoryStore (const InventoryStore& store); + + InventoryStore& operator= (const InventoryStore& store); + + void equip (int slot, const ContainerStoreIterator& iterator); + ///< \note \a iteartor can be an end-iterator + + ContainerStoreIterator getSlot (int slot); + }; +} + +#endif diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index b0da4524e..08861e6d9 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -117,10 +117,22 @@ namespace MWWorld return response; } + void PhysicsSystem::addHeightField (float* heights, + int x, int y, float yoffset, + float triSize, float sqrtVerts) + { + mEngine->addHeightField(heights, x, y, yoffset, triSize, sqrtVerts); + } + + void PhysicsSystem::removeHeightField (int x, int y) + { + mEngine->removeHeightField(x, y); + } + void PhysicsSystem::addObject (const std::string& handle, const std::string& mesh, const Ogre::Quaternion& rotation, float scale, const Ogre::Vector3& position) { - OEngine::Physic::RigidBody* body = mEngine->createRigidBody(mesh,handle); + OEngine::Physic::RigidBody* body = mEngine->createRigidBody(mesh,handle,scale); mEngine->addRigidBody(body); btTransform tr; tr.setOrigin(btVector3(position.x,position.y,position.z)); diff --git a/apps/openmw/mwworld/physicssystem.hpp b/apps/openmw/mwworld/physicssystem.hpp index 78cbde083..586c783df 100644 --- a/apps/openmw/mwworld/physicssystem.hpp +++ b/apps/openmw/mwworld/physicssystem.hpp @@ -24,6 +24,12 @@ namespace MWWorld void addActor (const std::string& handle, const std::string& mesh, const Ogre::Vector3& position); + void addHeightField (float* heights, + int x, int y, float yoffset, + float triSize, float sqrtVerts); + + void removeHeightField (int x, int y); + void removeObject (const std::string& handle); void moveObject (const std::string& handle, const Ogre::Vector3& position); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 47d5f1a2d..52350e7ec 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -71,11 +71,14 @@ namespace MWWorld // silence annoying g++ warning - for (std::vector::const_iterator iter (functor.mHandles.begin()); - iter!=functor.mHandles.end(); ++iter){ - Ogre::SceneNode* node = *iter; + for (std::vector::const_iterator iter2 (functor.mHandles.begin()); + iter2!=functor.mHandles.end(); ++iter2){ + Ogre::SceneNode* node = *iter2; mPhysics->removeObject (node->getName()); } + + if (!((*iter)->cell->data.flags & ESM::Cell::Interior)) + mPhysics->removeHeightField( (*iter)->cell->data.gridX, (*iter)->cell->data.gridY ); } mRendering.removeCell(*iter); //mPhysics->removeObject("Unnamed_43"); @@ -97,14 +100,22 @@ namespace MWWorld std::pair result = mActiveCells.insert(cell); - if(result.second){ - insertCell(*cell, mEnvironment); - mRendering.cellAdded (cell); - mRendering.configureAmbient(*cell); - + if(result.second) + { + insertCell(*cell, mEnvironment); + mRendering.cellAdded (cell); + + float verts = ESM::Land::LAND_SIZE; + float worldsize = ESM::Land::REAL_SIZE; + + if (!(cell->cell->data.flags & ESM::Cell::Interior)) + mPhysics->addHeightField (cell->land[1][1]->landData->heights, + cell->cell->data.gridX, cell->cell->data.gridY, + 0, ( worldsize/(verts-1) ), verts); + else + mRendering.configureAmbient(*cell); } - } void Scene::playerCellChange (Ptr::CellStore *cell, const ESM::Position& position, diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 7cb9f3dfc..eadd4b353 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -722,6 +722,40 @@ void WeatherManager::update(float duration) mRendering->skyDisable(); mRendering->getSkyManager()->setThunder(0.f); } + + // play sounds + std::string ambientSnd = (mNextWeather == "" ? mWeatherSettings[mCurrentWeather].mAmbientLoopSoundID : ""); + if (ambientSnd != "") + { + if (std::find(mSoundsPlaying.begin(), mSoundsPlaying.end(), ambientSnd) == mSoundsPlaying.end()) + { + mSoundsPlaying.push_back(ambientSnd); + mEnvironment->mSoundManager->playSound(ambientSnd, 1.0, 1.0, true); + } + } + + std::string rainSnd = (mNextWeather == "" ? mWeatherSettings[mCurrentWeather].mRainLoopSoundID : ""); + if (rainSnd != "") + { + if (std::find(mSoundsPlaying.begin(), mSoundsPlaying.end(), rainSnd) == mSoundsPlaying.end()) + { + mSoundsPlaying.push_back(rainSnd); + mEnvironment->mSoundManager->playSound(rainSnd, 1.0, 1.0, true); + } + } + + // stop sounds + std::vector::iterator it=mSoundsPlaying.begin(); + while (it!=mSoundsPlaying.end()) + { + if ( *it != ambientSnd && *it != rainSnd) + { + mEnvironment->mSoundManager->stopSound(*it); + it = mSoundsPlaying.erase(it); + } + else + ++it; + } } void WeatherManager::setHour(const float hour) @@ -758,7 +792,7 @@ unsigned int WeatherManager::getWeatherID() const return 3; else if (mCurrentWeather == "rain") return 4; - else if (mCurrentWeather == "thunder") + else if (mCurrentWeather == "thunderstorm") return 5; else if (mCurrentWeather == "ashstorm") return 6; @@ -787,7 +821,7 @@ void WeatherManager::changeWeather(const std::string& region, const unsigned int else if (id==4) weather = "rain"; else if (id==5) - weather = "thunder"; + weather = "thunderstorm"; else if (id==6) weather = "ashstorm"; else if (id==7) diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index 7a719252b..1828a90c9 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -243,8 +243,10 @@ namespace MWWorld MWWorld::Environment* mEnvironment; std::map mWeatherSettings; - + std::map mRegionOverrides; + + std::vector mSoundsPlaying; Ogre::String mCurrentWeather; Ogre::String mNextWeather; diff --git a/cmake/FindOGRE.cmake b/cmake/FindOGRE.cmake index dd20dc667..917a8653e 100644 --- a/cmake/FindOGRE.cmake +++ b/cmake/FindOGRE.cmake @@ -18,7 +18,7 @@ # Once done, this will define # # OGRE_FOUND - system has OGRE -# OGRE_INCLUDE_DIRS - the OGRE include directories +# OGRE_INCLUDE_DIRS - the OGRE include directories # OGRE_LIBRARIES - link these to use the OGRE core # OGRE_BINARY_REL - location of the main Ogre binary (win32 non-static only, release) # OGRE_BINARY_DBG - location of the main Ogre binaries (win32 non-static only, debug) @@ -33,9 +33,10 @@ # # For each of these components, the following variables are defined: # + # OGRE_${COMPONENT}_FOUND - ${COMPONENT} is available # OGRE_${COMPONENT}_INCLUDE_DIRS - additional include directories for ${COMPONENT} -# OGRE_${COMPONENT}_LIBRARIES - link these to use ${COMPONENT} +# OGRE_${COMPONENT}_LIBRARIES - link these to use ${COMPONENT} # OGRE_${COMPONENT}_BINARY_REL - location of the component binary (win32 non-static only, release) # OGRE_${COMPONENT}_BINARY_DBG - location of the component binary (win32 non-static only, debug) # @@ -112,7 +113,7 @@ if (OGRE_PREFIX_SOURCE AND OGRE_PREFIX_BUILD) set(OGRE_BIN_SEARCH_PATH ${dir}/bin ${OGRE_BIN_SEARCH_PATH}) set(OGRE_BIN_SEARCH_PATH ${dir}/Samples/Common/bin ${OGRE_BIN_SEARCH_PATH}) endforeach(dir) - + if (OGRE_PREFIX_DEPENDENCIES_DIR) set(OGRE_INC_SEARCH_PATH ${OGRE_PREFIX_DEPENDENCIES_DIR}/include ${OGRE_INC_SEARCH_PATH}) set(OGRE_LIB_SEARCH_PATH ${OGRE_PREFIX_DEPENDENCIES_DIR}/lib ${OGRE_LIB_SEARCH_PATH}) @@ -124,12 +125,12 @@ else() endif () # redo search if any of the environmental hints changed -set(OGRE_COMPONENTS Paging Terrain +set(OGRE_COMPONENTS Paging Terrain Plugin_BSPSceneManager Plugin_CgProgramManager Plugin_OctreeSceneManager Plugin_OctreeZone Plugin_PCZSceneManager Plugin_ParticleFX RenderSystem_Direct3D11 RenderSystem_Direct3D9 RenderSystem_GL RenderSystem_GLES RenderSystem_GLES2) -set(OGRE_RESET_VARS - OGRE_CONFIG_INCLUDE_DIR OGRE_INCLUDE_DIR +set(OGRE_RESET_VARS + OGRE_CONFIG_INCLUDE_DIR OGRE_INCLUDE_DIR OGRE_LIBRARY_FWK OGRE_LIBRARY_REL OGRE_LIBRARY_DBG OGRE_PLUGIN_DIR_DBG OGRE_PLUGIN_DIR_REL OGRE_MEDIA_DIR) foreach (comp ${OGRE_COMPONENTS}) @@ -265,7 +266,7 @@ if (OGRE_STATIC) if (APPLE AND NOT OGRE_BUILD_PLATFORM_APPLE_IOS) set(OGRE_LIBRARIES ${OGRE_LIBRARIES} ${X11_LIBRARIES} ${X11_Xt_LIBRARIES} ${XAW_LIBRARY} ${X11_Xrandr_LIB} ${Carbon_LIBRARIES} ${Cocoa_LIBRARIES}) endif() - + if (NOT ZLIB_FOUND OR NOT ZZip_FOUND) set(OGRE_DEPS_FOUND FALSE) endif () @@ -308,7 +309,7 @@ if (OGRE_STATIC) endif () endif () endif () - + if (NOT OGRE_DEPS_FOUND) pkg_message(OGRE "Could not find all required dependencies for the Ogre package.") set(OGRE_FOUND FALSE) @@ -340,7 +341,7 @@ endif() # Find Ogre components ######################################################### -set(OGRE_COMPONENT_SEARCH_PATH_REL +set(OGRE_COMPONENT_SEARCH_PATH_REL ${OGRE_LIBRARY_DIR_REL}/.. ${OGRE_LIBRARY_DIR_REL}/../.. ${OGRE_BIN_SEARCH_PATH} @@ -392,17 +393,17 @@ macro(ogre_find_plugin PLUGIN HEADER) set(TMP_CMAKE_LIB_PREFIX ${CMAKE_FIND_LIBRARY_PREFIXES}) set(CMAKE_FIND_LIBRARY_PREFIXES ${CMAKE_FIND_LIBRARY_PREFIXES} "") endif() - + # strip RenderSystem_ or Plugin_ prefix from plugin name string(REPLACE "RenderSystem_" "" PLUGIN_TEMP ${PLUGIN}) string(REPLACE "Plugin_" "" PLUGIN_NAME ${PLUGIN_TEMP}) - + # header files for plugins are not usually needed, but find them anyway if they are present set(OGRE_PLUGIN_PATH_SUFFIXES - PlugIns PlugIns/${PLUGIN_NAME} Plugins Plugins/${PLUGIN_NAME} ${PLUGIN} + PlugIns PlugIns/${PLUGIN_NAME} Plugins Plugins/${PLUGIN_NAME} ${PLUGIN} RenderSystems RenderSystems/${PLUGIN_NAME} ${ARGN}) - find_path(OGRE_${PLUGIN}_INCLUDE_DIR NAMES ${HEADER} - HINTS ${OGRE_INCLUDE_DIRS} ${OGRE_PREFIX_SOURCE} + find_path(OGRE_${PLUGIN}_INCLUDE_DIR NAMES ${HEADER} + HINTS ${OGRE_INCLUDE_DIRS} ${OGRE_PREFIX_SOURCE} PATH_SUFFIXES ${OGRE_PLUGIN_PATH_SUFFIXES}) # find link libraries for plugins set(OGRE_${PLUGIN}_LIBRARY_NAMES "${PLUGIN}${OGRE_LIB_SUFFIX}") @@ -428,7 +429,7 @@ macro(ogre_find_plugin PLUGIN HEADER) if (OGRE_${PLUGIN}_FOUND) if (NOT OGRE_PLUGIN_DIR_REL OR NOT OGRE_PLUGIN_DIR_DBG) if (WIN32) - set(OGRE_PLUGIN_SEARCH_PATH_REL + set(OGRE_PLUGIN_SEARCH_PATH_REL ${OGRE_LIBRARY_DIR_REL}/.. ${OGRE_LIBRARY_DIR_REL}/../.. ${OGRE_BIN_SEARCH_PATH} @@ -449,7 +450,7 @@ macro(ogre_find_plugin PLUGIN HEADER) set(OGRE_PLUGIN_DIR_DBG ${OGRE_PLUGIN_DIR_TMP} CACHE STRING "Ogre plugin dir (debug)" FORCE) endif () endif () - + # find binaries if (NOT OGRE_STATIC) if (WIN32) @@ -458,7 +459,7 @@ macro(ogre_find_plugin PLUGIN HEADER) endif() mark_as_advanced(OGRE_${PLUGIN}_REL OGRE_${PLUGIN}_DBG) endif() - + endif () if (TMP_CMAKE_LIB_PREFIX) @@ -498,7 +499,7 @@ if (OGRE_STATIC) if (NOT Cg_FOUND) set(OGRE_Plugin_CgProgramManager_FOUND FALSE) endif () - + set(OGRE_RenderSystem_Direct3D9_LIBRARIES ${OGRE_RenderSystem_Direct3D9_LIBRARIES} ${DirectX_LIBRARIES} ) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 7fcac6eb8..c95efb37d 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -6,10 +6,6 @@ add_component_dir (bsa bsa_archive bsa_file ) -add_component_dir (cfg - configurationmanager - ) - add_component_dir (nif controlled effect nif_types record controller extra node record_ptr data nif_file property ) @@ -47,7 +43,8 @@ add_component_dir (misc ) add_component_dir (files - linuxpath windowspath macospath path multidircollection collections fileops + linuxpath windowspath macospath fixedpath multidircollection collections fileops configurationmanager + filelibrary ) add_component_dir (compiler diff --git a/components/cfg/configurationmanager.cpp b/components/cfg/configurationmanager.cpp deleted file mode 100644 index 0998debee..000000000 --- a/components/cfg/configurationmanager.cpp +++ /dev/null @@ -1,157 +0,0 @@ -#include "configurationmanager.hpp" - -#include -#include -#include - -namespace Cfg -{ - -static const char* const openmwCfgFile = "openmw.cfg"; -static const char* const ogreCfgFile = "ogre.cfg"; -static const char* const pluginsCfgFile = "plugins.cfg"; - - -ConfigurationManager::ConfigurationManager() - : mPath("openmw") -{ - /** - * According to task #168 plugins.cfg file shall be located in global - * configuration path or in runtime configuration path. - */ - mPluginsCfgPath = mPath.getGlobalConfigPath() / pluginsCfgFile; - if (!boost::filesystem::is_regular_file(mPluginsCfgPath)) - { - mPluginsCfgPath = mPath.getRuntimeConfigPath() / pluginsCfgFile; - if (!boost::filesystem::is_regular_file(mPluginsCfgPath)) - { - std::cerr << "Failed to find " << pluginsCfgFile << " file!" << std::endl; - mPluginsCfgPath.clear(); - } - } - - /** - * According to task #168 ogre.cfg file shall be located only - * in user configuration path. - */ - mOgreCfgPath = mPath.getLocalConfigPath() / ogreCfgFile; - - mLogPath = mPath.getLocalConfigPath(); -} - -ConfigurationManager::~ConfigurationManager() -{ -} - -void ConfigurationManager::readConfiguration(boost::program_options::variables_map& variables, - boost::program_options::options_description& description) -{ - loadConfig(mPath.getLocalConfigPath(), variables, description); - boost::program_options::notify(variables); - loadConfig(mPath.getRuntimeConfigPath(), variables, description); - boost::program_options::notify(variables); - loadConfig(mPath.getGlobalConfigPath(), variables, description); - boost::program_options::notify(variables); -} - -void ConfigurationManager::loadConfig(const boost::filesystem::path& path, - boost::program_options::variables_map& variables, - boost::program_options::options_description& description) -{ - boost::filesystem::path cfgFile(path); - cfgFile /= std::string(openmwCfgFile); - if (boost::filesystem::is_regular_file(cfgFile)) - { - std::cout << "Loading config file: " << cfgFile.string() << "... "; - - std::ifstream configFileStream(cfgFile.string().c_str()); - if (configFileStream.is_open()) - { - boost::program_options::store(boost::program_options::parse_config_file( - configFileStream, description), variables); - - std::cout << "done." << std::endl; - } - else - { - std::cout << "failed." << std::endl; - } - } -} - -const boost::filesystem::path& ConfigurationManager::getGlobalConfigPath() const -{ - return mPath.getGlobalConfigPath(); -} - -void ConfigurationManager::setGlobalConfigPath(const boost::filesystem::path& newPath) -{ - mPath.setGlobalConfigPath(newPath); -} - -const boost::filesystem::path& ConfigurationManager::getLocalConfigPath() const -{ - return mPath.getLocalConfigPath(); -} - -void ConfigurationManager::setLocalConfigPath(const boost::filesystem::path& newPath) -{ - mPath.setLocalConfigPath(newPath); -} - -const boost::filesystem::path& ConfigurationManager::getRuntimeConfigPath() const -{ - return mPath.getRuntimeConfigPath(); -} - -void ConfigurationManager::setRuntimeConfigPath(const boost::filesystem::path& newPath) -{ - mPath.setRuntimeConfigPath(newPath); -} - -const boost::filesystem::path& ConfigurationManager::getGlobalDataPath() const -{ - return mPath.getGlobalDataPath(); -} - -void ConfigurationManager::setGlobalDataPath(const boost::filesystem::path& newPath) -{ - mPath.setGlobalDataPath(newPath); -} - -const boost::filesystem::path& ConfigurationManager::getLocalDataPath() const -{ - return mPath.getLocalDataPath(); -} - -void ConfigurationManager::setLocalDataPath(const boost::filesystem::path& newPath) -{ - mPath.setLocalDataPath(newPath); -} - -const boost::filesystem::path& ConfigurationManager::getRuntimeDataPath() const -{ - return mPath.getRuntimeDataPath(); -} - -void ConfigurationManager::setRuntimeDataPath(const boost::filesystem::path& newPath) -{ - mPath.setRuntimeDataPath(newPath); -} - -const boost::filesystem::path& ConfigurationManager::getOgreConfigPath() const -{ - return mOgreCfgPath; -} - -const boost::filesystem::path& ConfigurationManager::getPluginsConfigPath() const -{ - return mPluginsCfgPath; -} - -const boost::filesystem::path& ConfigurationManager::getLogPath() const -{ - return mLogPath; -} - -} /* namespace Cfg */ diff --git a/components/cfg/configurationmanager.hpp b/components/cfg/configurationmanager.hpp deleted file mode 100644 index 7f13d0914..000000000 --- a/components/cfg/configurationmanager.hpp +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef COMPONENTS_CFG_CONFIGURATIONMANAGER_HPP -#define COMPONENTS_CFG_CONFIGURATIONMANAGER_HPP - -#include -#include - -#include - -/** - * \namespace Cfg - */ -namespace Cfg -{ - -/** - * \struct ConfigurationManager - */ -struct ConfigurationManager -{ - ConfigurationManager(); - virtual ~ConfigurationManager(); - - void readConfiguration(boost::program_options::variables_map& variables, - boost::program_options::options_description& description); - - const boost::filesystem::path& getGlobalConfigPath() const; - void setGlobalConfigPath(const boost::filesystem::path& newPath); - - const boost::filesystem::path& getLocalConfigPath() const; - void setLocalConfigPath(const boost::filesystem::path& newPath); - - const boost::filesystem::path& getRuntimeConfigPath() const; - void setRuntimeConfigPath(const boost::filesystem::path& newPath); - - const boost::filesystem::path& getGlobalDataPath() const; - void setGlobalDataPath(const boost::filesystem::path& newPath); - - const boost::filesystem::path& getLocalDataPath() const; - void setLocalDataPath(const boost::filesystem::path& newPath); - - const boost::filesystem::path& getRuntimeDataPath() const; - void setRuntimeDataPath(const boost::filesystem::path& newPath); - - const boost::filesystem::path& getOgreConfigPath() const; - const boost::filesystem::path& getPluginsConfigPath() const; - const boost::filesystem::path& getLogPath() const; - - private: - void loadConfig(const boost::filesystem::path& path, - boost::program_options::variables_map& variables, - boost::program_options::options_description& description); - - Files::Path<> mPath; - - boost::filesystem::path mOgreCfgPath; - boost::filesystem::path mPluginsCfgPath; - boost::filesystem::path mLogPath; -}; - -} /* namespace Cfg */ - -#endif /* COMPONENTS_CFG_CONFIGURATIONMANAGER_HPP */ diff --git a/components/esm/esm_reader.hpp b/components/esm/esm_reader.hpp index e5b230748..0420f37cd 100644 --- a/components/esm/esm_reader.hpp +++ b/components/esm/esm_reader.hpp @@ -153,7 +153,7 @@ public: *************************************************************************/ int getVer() { return mCtx.header.version; } - float getFVer() { return *((float*)&mCtx.header.version); } + float getFVer() { if(mCtx.header.version == VER_12) return 1.2; else return 1.3; } int getSpecial() { return mSpf; } const std::string getAuthor() { return mCtx.header.author.toString(); } const std::string getDesc() { return mCtx.header.desc.toString(); } diff --git a/components/file_finder/file_finder.hpp b/components/file_finder/file_finder.hpp index 0e1e07226..8a15af73a 100644 --- a/components/file_finder/file_finder.hpp +++ b/components/file_finder/file_finder.hpp @@ -1,9 +1,11 @@ #ifndef FILE_FINDER_MAIN_H #define FILE_FINDER_MAIN_H +#include + #include "search.hpp" #include "filename_less.hpp" -#include +#include namespace FileFinder { @@ -11,7 +13,8 @@ namespace FileFinder template class FileFinderT { - std::map table; + typedef std::map TableContainer; + TableContainer table; struct Inserter : ReturnPath { @@ -35,12 +38,12 @@ public: // Remember the original path length, so we can cut it away from // the relative paths used as keys - std::string pstring = path.string(); + const std::string& pstring = path.string(); inserter.cut = pstring.size(); // If the path does not end in a slash, then boost will add one // later, which means one more character we have to remove. - char last = pstring[pstring.size()-1]; + char last = *pstring.rbegin(); if(last != '\\' && last != '/') inserter.cut++; @@ -56,12 +59,84 @@ public: // Find the full path from a relative path. const std::string &lookup(const std::string& file) const { - return table.find(file)->second; + static std::string empty; + typename TableContainer::const_iterator it = table.find(file); + return (it != table.end()) ? it->second : empty; } }; +template +< + class LESS +> +struct TreeFileFinder +{ + typedef TreeFileFinder finder_t; + + TreeFileFinder(const Files::PathContainer& paths, bool recurse = true) + { + struct : ReturnPath + { + finder_t *owner; + int cut; + + void add(const boost::filesystem::path &pth) + { + std::string file = pth.string(); + std::string key = file.substr(cut); + owner->mTable[key] = file; + } + } inserter; + + inserter.owner = this; + + for (Files::PathContainer::const_iterator it = paths.begin(); it != paths.end(); ++it) + { + + // Remember the original path length, so we can cut it away from + // the relative paths used as keys + const std::string& pstring = it->string(); + inserter.cut = pstring.size(); + + // If the path does not end in a slash, then boost will add one + // later, which means one more character we have to remove. + char last = *pstring.rbegin(); + if (last != '\\' && last != '/') + { + inserter.cut++; + } + + // Fill the map + find(*it, inserter, recurse); + } + } + + bool has(const std::string& file) const + { + return mTable.find(file) != mTable.end(); + } + + const std::string& lookup(const std::string& file) const + { + static std::string empty; + typename TableContainer::const_iterator it = mTable.find(file); + return (it != mTable.end()) ? it->second : empty; + } + + private: + typedef std::map TableContainer; + TableContainer mTable; + +// Inserter inserter; +}; + + // The default is to use path_less for equality checks typedef FileFinderT FileFinder; typedef FileFinderT FileFinderStrict; -} -#endif + +typedef TreeFileFinder LessTreeFileFinder; +typedef TreeFileFinder StrictTreeFileFinder; + +} /* namespace FileFinder */ +#endif /* FILE_FINDER_MAIN_H */ diff --git a/components/file_finder/search.cpp b/components/file_finder/search.cpp index b05b30e83..06deaf83a 100644 --- a/components/file_finder/search.cpp +++ b/components/file_finder/search.cpp @@ -2,27 +2,35 @@ #include -using namespace std; -using namespace boost::filesystem; - -void FileFinder::find(const path & dir_path, ReturnPath &ret, bool recurse) +void FileFinder::find(const boost::filesystem::path & dir_path, ReturnPath &ret, bool recurse) { - if ( !exists( dir_path ) ) - { - cout << "Path " << dir_path << " not found\n"; - return; - } - - directory_iterator end_itr; // default construction yields past-the-end - for ( directory_iterator itr(dir_path); - itr != end_itr; - ++itr ) + if (boost::filesystem::exists(dir_path)) { - if ( is_directory( *itr ) ) + if (!recurse) { - if(recurse) find(*itr, ret); + boost::filesystem::directory_iterator end_itr; // default construction yields past-the-end + for (boost::filesystem::directory_iterator itr(dir_path); itr != end_itr; ++itr) + { + if (!boost::filesystem::is_directory( *itr )) + { + ret.add(*itr); + } + } } - else - ret.add(*itr); + else + { + boost::filesystem::recursive_directory_iterator end_itr; // default construction yields past-the-end + for (boost::filesystem::recursive_directory_iterator itr(dir_path); itr != end_itr; ++itr) + { + if (!boost::filesystem::is_directory(*itr)) + { + ret.add(*itr); + } + } + } + } + else + { + std::cout << "Path " << dir_path << " not found" << std::endl; } } diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp new file mode 100644 index 000000000..ef45b6543 --- /dev/null +++ b/components/files/configurationmanager.cpp @@ -0,0 +1,182 @@ +#include "configurationmanager.hpp" + +#include +#include +#include +#include + +#include +#include + +/** + * \namespace Files + */ +namespace Files +{ + +static const char* const openmwCfgFile = "openmw.cfg"; +static const char* const ogreCfgFile = "ogre.cfg"; +static const char* const pluginsCfgFile = "plugins.cfg"; + +const char* const mwToken = "?mw?"; +const char* const localToken = "?local?"; +const char* const userToken = "?user?"; +const char* const globalToken = "?global?"; + +ConfigurationManager::ConfigurationManager() + : mFixedPath("openmw") +{ + setupTokensMapping(); + + mPluginsCfgPath = mFixedPath.getGlobalPath() / pluginsCfgFile; + if (!boost::filesystem::is_regular_file(mPluginsCfgPath)) + { + mPluginsCfgPath = mFixedPath.getLocalPath() / pluginsCfgFile; + if (!boost::filesystem::is_regular_file(mPluginsCfgPath)) + { + std::cerr << "Failed to find " << pluginsCfgFile << " file!" << std::endl; + mPluginsCfgPath.clear(); + } + } + + mOgreCfgPath = mFixedPath.getUserPath() / ogreCfgFile; + mLogPath = mFixedPath.getUserPath(); +} + +ConfigurationManager::~ConfigurationManager() +{ +} + +void ConfigurationManager::setupTokensMapping() +{ + mTokensMapping.insert(std::make_pair(mwToken, &FixedPath<>::getInstallPath)); + mTokensMapping.insert(std::make_pair(localToken, &FixedPath<>::getLocalPath)); + mTokensMapping.insert(std::make_pair(userToken, &FixedPath<>::getUserPath)); + mTokensMapping.insert(std::make_pair(globalToken, &FixedPath<>::getGlobalDataPath)); +} + +void ConfigurationManager::readConfiguration(boost::program_options::variables_map& variables, + boost::program_options::options_description& description) +{ + loadConfig(mFixedPath.getUserPath(), variables, description); + boost::program_options::notify(variables); + + loadConfig(mFixedPath.getLocalPath(), variables, description); + boost::program_options::notify(variables); + loadConfig(mFixedPath.getGlobalPath(), variables, description); + boost::program_options::notify(variables); + +} + +void ConfigurationManager::processPaths(Files::PathContainer& dataDirs) +{ + std::string path; + for (Files::PathContainer::iterator it = dataDirs.begin(); it != dataDirs.end(); ++it) + { + path = it->string(); + boost::erase_all(path, "\""); + *it = boost::filesystem::path(path); + + // Check if path contains a token + if (!path.empty() && *path.begin() == '?') + { + std::string::size_type pos = path.find('?', 1); + if (pos != std::string::npos && pos != 0) + { + TokensMappingContainer::iterator tokenIt = mTokensMapping.find(path.substr(0, pos + 1)); + if (tokenIt != mTokensMapping.end()) + { + boost::filesystem::path tempPath(((mFixedPath).*(tokenIt->second))()); + if (pos < path.length() - 1) + { + // There is something after the token, so we should + // append it to the path + tempPath /= path.substr(pos + 1, path.length() - pos); + } + + *it = tempPath; + } + else + { + // Clean invalid / unknown token, it will be removed outside the loop + (*it).clear(); + } + } + } + + if (!boost::filesystem::is_directory(*it)) + { + (*it).clear(); + } + } + + dataDirs.erase(std::remove_if(dataDirs.begin(), dataDirs.end(), + boost::bind(&boost::filesystem::path::empty, _1)), dataDirs.end()); +} + +void ConfigurationManager::loadConfig(const boost::filesystem::path& path, + boost::program_options::variables_map& variables, + boost::program_options::options_description& description) +{ + boost::filesystem::path cfgFile(path); + cfgFile /= std::string(openmwCfgFile); + if (boost::filesystem::is_regular_file(cfgFile)) + { + std::cout << "Loading config file: " << cfgFile.string() << "... "; + + std::ifstream configFileStream(cfgFile.string().c_str()); + if (configFileStream.is_open()) + { + boost::program_options::store(boost::program_options::parse_config_file( + configFileStream, description, true), variables); + + std::cout << "done." << std::endl; + } + else + { + std::cout << "failed." << std::endl; + } + } +} + +const boost::filesystem::path& ConfigurationManager::getGlobalPath() const +{ + return mFixedPath.getGlobalPath(); +} + +const boost::filesystem::path& ConfigurationManager::getUserPath() const +{ + return mFixedPath.getUserPath(); +} + +const boost::filesystem::path& ConfigurationManager::getLocalPath() const +{ + return mFixedPath.getLocalPath(); +} + +const boost::filesystem::path& ConfigurationManager::getGlobalDataPath() const +{ + return mFixedPath.getGlobalDataPath(); +} + +const boost::filesystem::path& ConfigurationManager::getInstallPath() const +{ + return mFixedPath.getInstallPath(); +} + +const boost::filesystem::path& ConfigurationManager::getOgreConfigPath() const +{ + return mOgreCfgPath; +} + +const boost::filesystem::path& ConfigurationManager::getPluginsConfigPath() const +{ + return mPluginsCfgPath; +} + +const boost::filesystem::path& ConfigurationManager::getLogPath() const +{ + return mLogPath; +} + +} /* namespace Cfg */ diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp new file mode 100644 index 000000000..7fb3793c6 --- /dev/null +++ b/components/files/configurationmanager.hpp @@ -0,0 +1,71 @@ +#ifndef COMPONENTS_FILES_CONFIGURATIONMANAGER_HPP +#define COMPONENTS_FILES_CONFIGURATIONMANAGER_HPP + +#ifdef _WIN32 +#include +#else +#include +#endif + +#include +#include + +#include +#include + +/** + * \namespace Files + */ +namespace Files +{ + +/** + * \struct ConfigurationManager + */ +struct ConfigurationManager +{ + ConfigurationManager(); + virtual ~ConfigurationManager(); + + void readConfiguration(boost::program_options::variables_map& variables, + boost::program_options::options_description& description); + void processPaths(Files::PathContainer& dataDirs); + + /**< Fixed paths */ + const boost::filesystem::path& getGlobalPath() const; + const boost::filesystem::path& getUserPath() const; + const boost::filesystem::path& getLocalPath() const; + + const boost::filesystem::path& getGlobalDataPath() const; + const boost::filesystem::path& getUserDataPath() const; + const boost::filesystem::path& getLocalDataPath() const; + const boost::filesystem::path& getInstallPath() const; + + const boost::filesystem::path& getOgreConfigPath() const; + const boost::filesystem::path& getPluginsConfigPath() const; + const boost::filesystem::path& getLogPath() const; + + private: + typedef Files::FixedPath<> FixedPathType; + + typedef const boost::filesystem::path& (FixedPathType::*path_type_f)() const; + typedef std::tr1::unordered_map TokensMappingContainer; + + void loadConfig(const boost::filesystem::path& path, + boost::program_options::variables_map& variables, + boost::program_options::options_description& description); + + void setupTokensMapping(); + + FixedPathType mFixedPath; + + boost::filesystem::path mOgreCfgPath; + boost::filesystem::path mPluginsCfgPath; + boost::filesystem::path mLogPath; + + TokensMappingContainer mTokensMapping; +}; + +} /* namespace Cfg */ + +#endif /* COMPONENTS_FILES_CONFIGURATIONMANAGER_HPP */ diff --git a/components/files/filelibrary.cpp b/components/files/filelibrary.cpp new file mode 100644 index 000000000..f1467166e --- /dev/null +++ b/components/files/filelibrary.cpp @@ -0,0 +1,120 @@ +#include "filelibrary.hpp" + +#include + +#include +#include + +namespace Files +{ + // Looks for a string in a vector of strings + bool containsVectorString(const StringVector& list, const std::string& str) + { + for (StringVector::const_iterator iter = list.begin(); + iter != list.end(); iter++) + { + if (*iter == str) + return true; + } + return false; + } + + // Searches a path and adds the results to the library + void FileLibrary::add(const boost::filesystem::path &root, bool recursive, bool strict, + const StringVector &acceptableExtensions) + { + if (!boost::filesystem::exists(root)) + { + std::cout << "Warning " << root.string() << " does not exist.\n"; + return; + } + + std::string fileExtension; + std::string type; + + // remember the last location of the priority list when listing new items + int length = mPriorityList.size(); + + // First makes a list of all candidate files + FileLister(root, mPriorityList, recursive); + + // Then sort these files into sections according to the folder they belong to + for (PathContainer::iterator listIter = mPriorityList.begin() + length; + listIter != mPriorityList.end(); ++listIter) + { + if( !acceptableExtensions.empty() ) + { + fileExtension = boost::filesystem::path (listIter->extension()).string(); + boost::algorithm::to_lower(fileExtension); + if(!containsVectorString(acceptableExtensions, fileExtension)) + continue; + } + + type = boost::filesystem::path (listIter->parent_path().leaf()).string(); + if (!strict) + boost::algorithm::to_lower(type); + + mMap[type].push_back(*listIter); + // std::cout << "Added path: " << listIter->string() << " in section "<< type <second); + } + } + + // Searches the library for an item and returns a boost path to it + boost::filesystem::path FileLibrary::locate(std::string item, bool strict, std::string sectionName) + { + boost::filesystem::path result(""); + if (sectionName == "") + { + return FileListLocator(mPriorityList, boost::filesystem::path(item), strict); + } + else + { + if (!containsSection(sectionName, strict)) + { + std::cout << "Warning: There is no section named " << sectionName << "\n"; + return result; + } + result = FileListLocator(mMap[sectionName], boost::filesystem::path(item), strict); + } + return result; + } + + // Prints all the available sections, used for debugging + void FileLibrary::printSections() + { + for(StringPathContMap::const_iterator mapIter = mMap.begin(); + mapIter != mMap.end(); mapIter++) + { + std::cout << mapIter->first < + +namespace Files +{ + typedef std::map StringPathContMap; + typedef std::vector StringVector; + + /// Looks for a string in a vector of strings + bool containsVectorString(const StringVector& list, const std::string& str); + + /// \brief Searches directories and makes lists of files according to folder name + class FileLibrary + { + private: + StringPathContMap mMap; + PathContainer mEmptyPath; + PathContainer mPriorityList; + + public: + /// Searches a path and adds the results to the library + /// Recursive search and fs strict options are available + /// Takes a vector of acceptable files extensions, if none is given it lists everything. + void add(const boost::filesystem::path &root, bool recursive, bool strict, + const StringVector &acceptableExtensions); + + /// Returns true if the named section exists + /// You can run this check before running section() + bool containsSection(std::string sectionName, bool strict); + + /// Returns a pointer to const for a section of the library + /// which is essentially a PathContainer. + /// If the section does not exists it returns a pointer to an empty path. + const PathContainer* section(std::string sectionName, bool strict); + + /// Searches the library for an item and returns a boost path to it + /// Optionally you can provide a specific section + /// The result is the first that comes up according to alphabetical + /// section naming + boost::filesystem::path locate(std::string item, bool strict, std::string sectionName=""); + + /// Prints all the available sections, used for debugging + void printSections(); + }; +} + +#endif diff --git a/components/files/fileops.cpp b/components/files/fileops.cpp index 329e4eb05..f57eaa546 100644 --- a/components/files/fileops.cpp +++ b/components/files/fileops.cpp @@ -1,5 +1,9 @@ #include "fileops.hpp" + +#include + #include +#include namespace Files { @@ -9,4 +13,90 @@ bool isFile(const char *name) return boost::filesystem::exists(boost::filesystem::path(name)); } + // Makes a list of files from a directory + void FileLister( boost::filesystem::path currentPath, Files::PathContainer& list, bool recursive) + { + if (!boost::filesystem::exists(currentPath)) + { + std::cout << "WARNING: " << currentPath.string() << " does not exist.\n"; + return ; + } + if (recursive) + { + for ( boost::filesystem::recursive_directory_iterator end, itr(currentPath.string()); + itr != end; ++itr ) + { + if ( boost::filesystem::is_regular_file(*itr)) + list.push_back(itr->path()); + } + } + else + { + for ( boost::filesystem::directory_iterator end, itr(currentPath.string()); + itr != end; ++itr ) + { + if ( boost::filesystem::is_regular_file(*itr)) + list.push_back(itr->path()); + } + } + } + + // Locates path in path container + boost::filesystem::path FileListLocator (const Files::PathContainer& list, const boost::filesystem::path& toFind, bool strict) + { + boost::filesystem::path result(""); + if (list.empty()) + return result; + + std::string toFindStr = toFind.string(); + + std::string fullPath; + + // The filesystems slash sets the default slash + std::string slash; + std::string wrongslash; + if(list[0].string().find("\\") != std::string::npos) + { + slash = "\\"; + wrongslash = "/"; + } + else + { + slash = "/"; + wrongslash = "\\"; + } + + // The file being looked for is converted to the new slash + if(toFindStr.find(wrongslash) != std::string::npos ) + { + boost::replace_all(toFindStr, wrongslash, slash); + } + + if (!strict) + { + boost::algorithm::to_lower(toFindStr); + } + + for (Files::PathContainer::const_iterator it = list.begin(); it != list.end(); ++it) + { + fullPath = it->string(); + if (!strict) + { + boost::algorithm::to_lower(fullPath); + } + if(fullPath.find(toFindStr) != std::string::npos) + { + result = *it; + break; + } + } + return result; + } + + // Overloaded form of the locator that takes a string and returns a string + std::string FileListLocator (const Files::PathContainer& list,const std::string& toFind, bool strict) + { + return FileListLocator(list, boost::filesystem::path(toFind), strict).string(); + } + } diff --git a/components/files/fileops.hpp b/components/files/fileops.hpp index a541beffc..49ef7b947 100644 --- a/components/files/fileops.hpp +++ b/components/files/fileops.hpp @@ -1,6 +1,12 @@ #ifndef COMPONENTS_FILES_FILEOPS_HPP #define COMPONENTS_FILES_FILEOPS_HPP +#include +#include +#include + +#include + namespace Files { @@ -8,6 +14,24 @@ namespace Files ///\param [in] name - filename bool isFile(const char *name); + /// A vector of Boost Paths, very handy + typedef std::vector PathContainer; + + /// Makes a list of files from a directory by taking a boost + /// path and a Path Container and adds to the Path container + /// all files in the path. It has a recursive option. + void FileLister( boost::filesystem::path currentPath, Files::PathContainer& list, bool recursive); + + /// Locates boost path in path container + /// returns the path from the container + /// that contains the searched path. + /// If it's not found it returns and empty path + /// Takes care of slashes, backslashes and it has a strict option. + boost::filesystem::path FileListLocator (const Files::PathContainer& list, const boost::filesystem::path& toFind, bool strict); + + /// Overloaded form of the locator that takes a string and returns a string + std::string FileListLocator (const Files::PathContainer& list,const std::string& toFind, bool strict); + } #endif /* COMPONENTS_FILES_FILEOPS_HPP */ diff --git a/components/files/fixedpath.hpp b/components/files/fixedpath.hpp new file mode 100644 index 000000000..9e5c4f8f2 --- /dev/null +++ b/components/files/fixedpath.hpp @@ -0,0 +1,145 @@ +/** + * Open Morrowind - an opensource Elder Scrolls III: Morrowind + * engine implementation. + * + * Copyright (C) 2011 Open Morrowind Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** \file components/files/fixedpath.hpp */ + +#ifndef COMPONENTS_FILES_FIXEDPATH_HPP +#define COMPONENTS_FILES_FIXEDPATH_HPP + +#include +#include + +#if defined(__linux__) || defined(__FreeBSD__) + #include + namespace Files { typedef LinuxPath TargetPathType; } + +#elif defined(__WIN32) || defined(__WINDOWS__) || defined(_WIN32) + #include + namespace Files { typedef WindowsPath TargetPathType; } + +#elif defined(macintosh) || defined(Macintosh) || defined(__APPLE__) || defined(__MACH__) + #include + namespace Files { typedef MacOsPath TargetPathType; } + +#else + #error "Unknown platform!" +#endif + + +/** + * \namespace Files + */ +namespace Files +{ + +/** + * \struct Path + * + * \tparam P - Path strategy class type (depends on target system) + * + */ +template +< + class P = TargetPathType +> +struct FixedPath +{ + typedef P PathType; + + /** + * \brief Path constructor. + * + * \param [in] application_name - Name of the application + */ + FixedPath(const std::string& application_name) + : mPath() + , mUserPath(mPath.getUserPath()) + , mGlobalPath(mPath.getGlobalPath()) + , mLocalPath(mPath.getLocalPath()) + , mGlobalDataPath(mPath.getGlobalDataPath()) + , mInstallPath(mPath.getInstallPath()) + { + if (!application_name.empty()) + { + boost::filesystem::path suffix(application_name + std::string("/")); + + mUserPath /= suffix; + mGlobalPath /= suffix; + mGlobalDataPath /= suffix; + } + } + + /** + * \brief Return path pointing to the user local configuration directory. + * + * \return boost::filesystem::path + */ + const boost::filesystem::path& getUserPath() const + { + return mUserPath; + } + + /** + * \brief Return path pointing to the global (system) configuration directory. + * + * \return boost::filesystem::path + */ + const boost::filesystem::path& getGlobalPath() const + { + return mGlobalPath; + } + + /** + * \brief Return path pointing to the directory where application was started. + * + * \return boost::filesystem::path + */ + const boost::filesystem::path& getLocalPath() const + { + return mLocalPath; + } + + const boost::filesystem::path& getInstallPath() const + { + return mInstallPath; + } + + const boost::filesystem::path& getGlobalDataPath() const + { + return mGlobalDataPath; + } + + private: + PathType mPath; + + boost::filesystem::path mUserPath; /**< User path */ + boost::filesystem::path mGlobalPath; /**< Global path */ + boost::filesystem::path mLocalPath; /**< It is the same directory where application was run */ + + boost::filesystem::path mGlobalDataPath; /**< Global application data path */ + + boost::filesystem::path mInstallPath; + +}; + + +} /* namespace Files */ + +#endif /* COMPONENTS_FILES_FIXEDPATH_HPP */ diff --git a/components/files/linuxpath.cpp b/components/files/linuxpath.cpp index c485002fd..92e4dce33 100644 --- a/components/files/linuxpath.cpp +++ b/components/files/linuxpath.cpp @@ -22,12 +22,13 @@ #include "linuxpath.hpp" -#if defined(__linux__) +#if defined(__linux__) || defined(__FreeBSD__) #include #include #include #include +#include /** * \namespace Files @@ -35,126 +36,128 @@ namespace Files { -boost::filesystem::path LinuxPath::getLocalConfigPath() const +boost::filesystem::path LinuxPath::getUserPath() const { - boost::filesystem::path localConfigPath("."); + boost::filesystem::path userPath("."); boost::filesystem::path suffix("/"); - const char* theDir = getenv("OPENMW_CONFIG"); + const char* theDir = getenv("HOME"); if (theDir == NULL) { - theDir = getenv("XDG_CONFIG_HOME"); - if (theDir == NULL) + struct passwd* pwd = getpwuid(getuid()); + if (pwd != NULL) { - theDir = getenv("HOME"); - if (theDir == NULL) - { - struct passwd* pwd = getpwuid(getuid()); - if (pwd != NULL) - { - theDir = pwd->pw_dir; - } - } - if (theDir != NULL) - { - suffix = boost::filesystem::path("/.config/"); - } + theDir = pwd->pw_dir; } } - if (theDir != NULL) { - localConfigPath = boost::filesystem::path(theDir); + if (theDir != NULL) + { + suffix = boost::filesystem::path("/.config/"); + userPath = boost::filesystem::path(theDir); } - localConfigPath /= suffix; + userPath /= suffix; - return localConfigPath; + return userPath; } -boost::filesystem::path LinuxPath::getGlobalConfigPath() const +boost::filesystem::path LinuxPath::getGlobalPath() const { - boost::filesystem::path globalConfigPath("/etc/xdg/"); - - char* theDir = getenv("XDG_CONFIG_DIRS"); - if (theDir != NULL) - { - // We take only first path from list - char* ptr = strtok(theDir, ":"); - if (ptr != NULL) - { - globalConfigPath = boost::filesystem::path(ptr); - globalConfigPath /= boost::filesystem::path("/"); - } - } - - return globalConfigPath; + boost::filesystem::path globalPath("/etc/"); + return globalPath; } -boost::filesystem::path LinuxPath::getRuntimeConfigPath() const +boost::filesystem::path LinuxPath::getLocalPath() const { return boost::filesystem::path("./"); } -boost::filesystem::path LinuxPath::getLocalDataPath() const +boost::filesystem::path LinuxPath::getGlobalDataPath() const { - boost::filesystem::path localDataPath("."); - boost::filesystem::path suffix("/"); + boost::filesystem::path globalDataPath("/usr/share/games/"); + return globalDataPath; +} - const char* theDir = getenv("OPENMW_DATA"); - if (theDir == NULL) +boost::filesystem::path LinuxPath::getInstallPath() const +{ + boost::filesystem::path installPath; + + char *homePath = getenv("HOME"); + if (homePath == NULL) { - theDir = getenv("XDG_DATA_HOME"); - if (theDir == NULL) + struct passwd* pwd = getpwuid(getuid()); + if (pwd != NULL) { - theDir = getenv("HOME"); - if (theDir == NULL) - { - struct passwd* pwd = getpwuid(getuid()); - if (pwd != NULL) - { - theDir = pwd->pw_dir; - } - } - if (theDir != NULL) - { - suffix = boost::filesystem::path("/.local/share/"); - } + homePath = pwd->pw_dir; } } - if (theDir != NULL) { - localDataPath = boost::filesystem::path(theDir); - } + if (homePath != NULL) + { + boost::filesystem::path wineDefaultRegistry(homePath); + wineDefaultRegistry /= ".wine/system.reg"; - localDataPath /= suffix; - return localDataPath; -} + if (boost::filesystem::is_regular_file(wineDefaultRegistry)) + { + boost::filesystem::ifstream file(wineDefaultRegistry); + bool isRegEntry = false; + std::string line; + std::string mwpath; -boost::filesystem::path LinuxPath::getGlobalDataPath() const -{ - boost::filesystem::path globalDataPath("/usr/local/share/"); + while (std::getline(file, line)) + { + if (line[0] == '[') // we found an entry + { + if (isRegEntry) + { + break; + } - char* theDir = getenv("XDG_DATA_DIRS"); - if (theDir != NULL) - { - // We take only first path from list - char* ptr = strtok(theDir, ":"); - if (ptr != NULL) - { - globalDataPath = boost::filesystem::path(ptr); - globalDataPath /= boost::filesystem::path("/"); + isRegEntry = (line.find("Softworks\\\\Morrowind]") != std::string::npos); + } + else if (isRegEntry) + { + if (line[0] == '"') // empty line means new registry key + { + std::string key = line.substr(1, line.find('"', 1) - 1); + if (strcasecmp(key.c_str(), "Installed Path") == 0) + { + std::string::size_type valuePos = line.find('=') + 2; + mwpath = line.substr(valuePos, line.rfind('"') - valuePos); + + std::string::size_type pos = mwpath.find("\\"); + while (pos != std::string::npos) + { + mwpath.replace(pos, 2, "/"); + pos = mwpath.find("\\", pos + 1); + } + break; + } + } + } + } + + if (!mwpath.empty()) + { + // Change drive letter to lowercase, so we could use + // ~/.wine/dosdevices symlinks + mwpath[0] = tolower(mwpath[0]); + installPath /= homePath; + installPath /= ".wine/dosdevices/"; + installPath /= mwpath; + + if (!boost::filesystem::is_directory(installPath)) + { + installPath.clear(); + } + } } } - return globalDataPath; -} - -boost::filesystem::path LinuxPath::getRuntimeDataPath() const -{ - return boost::filesystem::path("./data/"); + return installPath; } - } /* namespace Files */ -#endif /* defined(__linux__) */ +#endif /* defined(__linux__) || defined(__FreeBSD__) */ diff --git a/components/files/linuxpath.hpp b/components/files/linuxpath.hpp index d6e717fc4..48fdbb2ff 100644 --- a/components/files/linuxpath.hpp +++ b/components/files/linuxpath.hpp @@ -23,7 +23,7 @@ #ifndef COMPONENTS_FILES_LINUXPATH_H #define COMPONENTS_FILES_LINUXPATH_H -#if defined(__linux__) +#if defined(__linux__) || defined(__FreeBSD__) #include @@ -39,18 +39,18 @@ namespace Files struct LinuxPath { /** - * \brief Return path to the local configuration directory. + * \brief Return path to the user directory. * * \return boost::filesystem::path */ - boost::filesystem::path getLocalConfigPath() const; + boost::filesystem::path getUserPath() const; /** - * \brief Return path to the global (system) configuration directory. + * \brief Return path to the global (system) directory where game files could be placed. * * \return boost::filesystem::path */ - boost::filesystem::path getGlobalConfigPath() const; + boost::filesystem::path getGlobalPath() const; /** * \brief Return path to the runtime configuration directory which is the @@ -58,33 +58,25 @@ struct LinuxPath * * \return boost::filesystem::path */ - boost::filesystem::path getRuntimeConfigPath() const; + boost::filesystem::path getLocalPath() const; /** - * \brief Return path to the local data directory. - * - * \return boost::filesystem::path - */ - boost::filesystem::path getLocalDataPath() const; - - /** - * \brief Return path to the global (system) data directory. + * \brief * * \return boost::filesystem::path */ boost::filesystem::path getGlobalDataPath() const; /** - * \brief Return runtime data path which is a location where - * an application was started with 'data' suffix. + * \brief Gets the path of the installed Morrowind version if there is one. * * \return boost::filesystem::path */ - boost::filesystem::path getRuntimeDataPath() const; + boost::filesystem::path getInstallPath() const; }; } /* namespace Files */ -#endif /* defined(__linux__) */ +#endif /* defined(__linux__) || defined(__FreeBSD__) */ #endif /* COMPONENTS_FILES_LINUXPATH_H */ diff --git a/components/files/macospath.cpp b/components/files/macospath.cpp index 46e775030..94dafe794 100644 --- a/components/files/macospath.cpp +++ b/components/files/macospath.cpp @@ -27,6 +27,11 @@ #include #include #include +#include + +/** + * FIXME: Someone with MacOS system should check this and correct if necessary + */ /** * \namespace Files @@ -34,9 +39,9 @@ namespace Files { -boost::filesystem::path MacOsPath::getLocalConfigPath() const +boost::filesystem::path MacOsPath::getUserPath() const { - boost::filesystem::path localConfigPath("."); + boost::filesystem::path userPath("."); boost::filesystem::path suffix("/"); const char* theDir = getenv("HOME"); @@ -50,66 +55,107 @@ boost::filesystem::path MacOsPath::getLocalConfigPath() const } if (theDir != NULL) { - localConfigPath = boost::filesystem::path(theDir) / "Library/Preferences/"; + userPath = boost::filesystem::path(theDir) / "Library/Preferences/"; } - localConfigPath /= suffix; + userPath /= suffix; - return localConfigPath; + return userPath; } -boost::filesystem::path MacOsPath::getGlobalConfigPath() const +boost::filesystem::path MacOsPath::getGlobalPath() const { - boost::filesystem::path globalConfigPath("/Library/Preferences/"); - return globalConfigPath; + boost::filesystem::path globalPath("/Library/Preferences/"); + return globalPath; } -boost::filesystem::path MacOsPath::getRuntimeConfigPath() const +boost::filesystem::path MacOsPath::getLocalPath() const { return boost::filesystem::path("./"); } -boost::filesystem::path MacOsPath::getLocalDataPath() const +boost::filesystem::path MacOsPath::getGlobalDataPath() const { - boost::filesystem::path localDataPath("."); - boost::filesystem::path suffix("/"); + boost::filesystem::path globalDataPath("/Library/Application Support/"); + return globalDataPath; +} - const char* theDir = getenv("OPENMW_DATA"); - if (theDir == NULL) +boost::filesystem::path MacOsPath::getInstallPath() const +{ + boost::filesystem::path installPath; + + char *homePath = getenv("HOME"); + if (homePath == NULL) { - theDir = getenv("HOME"); - if (theDir == NULL) - { - struct passwd* pwd = getpwuid(getuid()); - if (pwd != NULL) - { - theDir = pwd->pw_dir; - } - } - if (theDir != NULL) + struct passwd* pwd = getpwuid(getuid()); + if (pwd != NULL) { - suffix = boost::filesystem::path("/Library/Application Support/"); + homePath = pwd->pw_dir; } } - if (theDir != NULL) + if (homePath != NULL) { - localDataPath = boost::filesystem::path(theDir); - } + boost::filesystem::path wineDefaultRegistry(homePath); + wineDefaultRegistry /= ".wine/system.reg"; - localDataPath /= suffix; - return localDataPath; -} + if (boost::filesystem::is_regular_file(wineDefaultRegistry)) + { + boost::filesystem::ifstream file(wineDefaultRegistry); + bool isRegEntry = false; + std::string line; + std::string mwpath; -boost::filesystem::path MacOsPath::getGlobalDataPath() const -{ - boost::filesystem::path globalDataPath("/Library/Application Support/"); - return globalDataPath; -} + while (std::getline(file, line)) + { + if (line[0] == '[') // we found an entry + { + if (isRegEntry) + { + break; + } + + isRegEntry = (line.find("Softworks\\\\Morrowind]") != std::string::npos); + } + else if (isRegEntry) + { + if (line[0] == '"') // empty line means new registry key + { + std::string key = line.substr(1, line.find('"', 1) - 1); + if (strcasecmp(key.c_str(), "Installed Path") == 0) + { + std::string::size_type valuePos = line.find('=') + 2; + mwpath = line.substr(valuePos, line.rfind('"') - valuePos); + + std::string::size_type pos = mwpath.find("\\"); + while (pos != std::string::npos) + { + mwpath.replace(pos, 2, "/"); + pos = mwpath.find("\\", pos + 1); + } + break; + } + } + } + } -boost::filesystem::path MacOsPath::getRuntimeDataPath() const -{ - return boost::filesystem::path("./data/"); + if (!mwpath.empty()) + { + // Change drive letter to lowercase, so we could use ~/.wine/dosdevice symlinks + mwpath[0] = tolower(mwpath[0]); + installPath /= homePath; + installPath /= ".wine/dosdevices/"; + installPath /= mwpath; + + if (!boost::filesystem::is_directory(installPath)) + { + installPath.clear(); + } + } + } + } + + return installPath; } diff --git a/components/files/macospath.hpp b/components/files/macospath.hpp index 26f2c907f..0e9b3402e 100644 --- a/components/files/macospath.hpp +++ b/components/files/macospath.hpp @@ -39,48 +39,35 @@ namespace Files struct MacOsPath { /** - * \brief Return path to the local configuration directory. + * \brief Return path to the local directory. * * \return boost::filesystem::path */ - boost::filesystem::path getLocalConfigPath() const; + boost::filesystem::path getUserPath() const; /** - * \brief Return path to the global (system) configuration directory. + * \brief Return path to the global (system) directory. * * \return boost::filesystem::path */ - boost::filesystem::path getGlobalConfigPath() const; + boost::filesystem::path getGlobalPath() const; /** - * \brief Return path to the runtime configuration directory which is the + * \brief Return path to the runtime directory which is the * place where an application was started. * * \return boost::filesystem::path */ - boost::filesystem::path getRuntimeConfigPath() const; + boost::filesystem::path getLocalPath() const; /** - * \brief Return path to the local data directory. - * - * \return boost::filesystem::path - */ - boost::filesystem::path getLocalDataPath() const; - - /** - * \brief Return path to the global (system) data directory. + * \brief * * \return boost::filesystem::path */ boost::filesystem::path getGlobalDataPath() const; - /** - * \brief Return runtime data path which is a location where - * an application was started with 'data' suffix. - * - * \return boost::filesystem::path - */ - boost::filesystem::path getRuntimeDataPath() const; + boost::filesystem::path getInstallPath() const; }; } /* namespace Files */ diff --git a/components/files/multidircollection.hpp b/components/files/multidircollection.hpp index 391f8b6a4..e8abeb45d 100644 --- a/components/files/multidircollection.hpp +++ b/components/files/multidircollection.hpp @@ -68,7 +68,7 @@ namespace Files /// \param foldCase Ignore filename case boost::filesystem::path getPath (const std::string& file) const; - ///< Return full path (including filename) of \æ file. + ///< Return full path (including filename) of \a file. /// /// If the file does not exist, an exception is thrown. \a file must include /// the extension. diff --git a/components/files/path.hpp b/components/files/path.hpp deleted file mode 100644 index 23c702bbb..000000000 --- a/components/files/path.hpp +++ /dev/null @@ -1,232 +0,0 @@ -/** - * Open Morrowind - an opensource Elder Scrolls III: Morrowind - * engine implementation. - * - * Copyright (C) 2011 Open Morrowind Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** \file components/files/path.hpp */ - -#ifndef COMPONENTS_FILES_PATH_HPP -#define COMPONENTS_FILES_PATH_HPP - -#include -#include - -#if defined(__linux__) - #include - namespace Files { typedef LinuxPath TargetPathType; } - -#elif defined(__WIN32) || defined(__WINDOWS__) || defined (_WINDOWS) - #include - namespace Files { typedef WindowsPath TargetPathType; } - -#elif defined(macintosh) || defined(Macintosh) || defined(__APPLE__) || defined(__MACH__) - #include - namespace Files { typedef MacOsPath TargetPathType; } - -#else - #error "Unknown platform!" -#endif - - -/** - * \namespace Files - */ -namespace Files -{ - -/** - * \struct Path - * - * \tparam P - Path strategy class type (depends on target system) - * - */ -template -< - class P = TargetPathType -> -struct Path -{ - typedef P PathType; - - /** - * \brief Path constructor. - * - * \param [in] application_name - Name of the application - */ - Path(const std::string& application_name) - : mPath() - , mLocalConfigPath(mPath.getLocalConfigPath()) - , mGlobalConfigPath(mPath.getGlobalConfigPath()) - , mRuntimeConfigPath(mPath.getRuntimeConfigPath()) - , mLocalDataPath(mPath.getLocalDataPath()) - , mGlobalDataPath(mPath.getGlobalDataPath()) - , mRuntimeDataPath(mPath.getRuntimeDataPath()) - { - if (!application_name.empty()) - { - boost::filesystem::path suffix(application_name + std::string("/")); - - mLocalConfigPath /= suffix; - mGlobalConfigPath /= suffix; - - mLocalDataPath /= suffix; - mGlobalDataPath /= suffix; - } - } - - /** - * \brief Return path pointing to the user local configuration directory. - * - * \return boost::filesystem::path - */ - const boost::filesystem::path& getLocalConfigPath() const - { - return mLocalConfigPath; - } - - /** - * \brief Sets new local configuration path. - * - * \param [in] path - New path - */ - void setLocalConfigPath(const boost::filesystem::path& path) - { - mLocalConfigPath = path; - } - - /** - * \brief Return path pointing to the global (system) configuration directory. - * - * \return boost::filesystem::path - */ - const boost::filesystem::path& getGlobalConfigPath() const - { - return mGlobalConfigPath; - } - - /** - * \brief Sets new global configuration path. - * - * \param [in] path - New path - */ - void setGlobalConfigPath(const boost::filesystem::path& path) - { - mGlobalConfigPath = path; - } - - /** - * \brief Return path pointing to the directory where application was started. - * - * \return boost::filesystem::path - */ - const boost::filesystem::path& getRuntimeConfigPath() const - { - return mRuntimeConfigPath; - } - - /** - * \brief Sets new runtime configuration path. - * - * \param [in] path - New path - */ - void setRuntimeConfigPath(const boost::filesystem::path& path) - { - mRuntimeConfigPath = path; - } - - /** - * \brief Return path pointing to the user local data directory. - * - * \return boost::filesystem::path - */ - const boost::filesystem::path& getLocalDataPath() const - { - return mLocalDataPath; - } - - /** - * \brief Sets new local data path. - * - * \param [in] path - New path - */ - void setLocalDataPath(const boost::filesystem::path& path) - { - mLocalDataPath = path; - } - - /** - * \brief Return path pointing to the global (system) data directory. - * - * \return boost::filesystem::path - */ - const boost::filesystem::path& getGlobalDataPath() const - { - return mGlobalDataPath; - } - - /** - * \brief Sets new global (system) data directory. - * - * \param [in] path - New path - */ - void setGlobalDataPath(const boost::filesystem::path& path) - { - mGlobalDataPath = path; - } - - /** - * \brief Return path pointing to the directory where application was started. - * - * \return boost::filesystem::path - */ - const boost::filesystem::path& getRuntimeDataPath() const - { - return mRuntimeDataPath; - } - - /** - * \brief Sets new runtime data directory. - * - * \param [in] path - New path - */ - void setRuntimeDataPath(const boost::filesystem::path& path) - { - mRuntimeDataPath = path; - } - - private: - PathType mPath; - - boost::filesystem::path mLocalConfigPath; /**< User local path to the configuration files */ - boost::filesystem::path mGlobalConfigPath; /**< Global path to the configuration files */ - boost::filesystem::path mRuntimeConfigPath; /**< Runtime path to the configuration files. - By default it is the same directory where - application was run */ - - boost::filesystem::path mLocalDataPath; /**< User local application data path (user plugins / mods / etc.) */ - boost::filesystem::path mGlobalDataPath; /**< Global application data path */ - boost::filesystem::path mRuntimeDataPath; /**< Runtime path to the configuration files. - By default it is a 'data' directory in same - directory where application was run */ - -}; - - -} /* namespace Files */ - -#endif /* COMPONENTS_FILES_PATH_HPP */ diff --git a/components/files/windowspath.cpp b/components/files/windowspath.cpp index b971cbbbf..dfa8f20cc 100644 --- a/components/files/windowspath.cpp +++ b/components/files/windowspath.cpp @@ -10,12 +10,19 @@ #pragma comment(lib, "Shlwapi.lib") +/** + * FIXME: Someone with Windows system should check this and correct if necessary + */ + +/** + * \namespace Files + */ namespace Files { -boost::filesystem::path WindowsPath::getLocalConfigPath() const +boost::filesystem::path WindowsPath::getUserPath() const { - boost::filesystem::path localConfigPath("."); + boost::filesystem::path userPath("."); boost::filesystem::path suffix("/"); TCHAR path[MAX_PATH]; @@ -24,17 +31,17 @@ boost::filesystem::path WindowsPath::getLocalConfigPath() const if(SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL | CSIDL_FLAG_CREATE, NULL, 0, path))) { PathAppend(path, TEXT("My Games")); - localConfigPath = boost::filesystem::path(path); + userPath = boost::filesystem::path(path); } - localConfigPath /= suffix; + userPath /= suffix; - return localConfigPath; + return userPath; } -boost::filesystem::path WindowsPath::getGlobalConfigPath() const +boost::filesystem::path WindowsPath::getGlobalPath() const { - boost::filesystem::path globalConfigPath("."); + boost::filesystem::path globalPath("."); boost::filesystem::path suffix("/"); TCHAR path[MAX_PATH]; @@ -42,32 +49,54 @@ boost::filesystem::path WindowsPath::getGlobalConfigPath() const if(SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PROGRAM_FILES | CSIDL_FLAG_CREATE, NULL, 0, path))) { - globalConfigPath = boost::filesystem::path(path); + globalPath = boost::filesystem::path(path); } - globalConfigPath /= suffix; + globalPath /= suffix; - return globalConfigPath; + return globalPath; } -boost::filesystem::path WindowsPath::getRuntimeConfigPath() const +boost::filesystem::path WindowsPath::getLocalPath() const { return boost::filesystem::path("./"); } -boost::filesystem::path WindowsPath::getLocalDataPath() const -{ - return getLocalConfigPath(); -} - boost::filesystem::path WindowsPath::getGlobalDataPath() const { - return getGlobalConfigPath(); + return getGlobalPath(); } -boost::filesystem::path WindowsPath::getRuntimeDataPath() const +boost::filesystem::path WindowsPath::getInstallPath() const { - return boost::filesystem::path("./data/"); + boost::filesystem::path installPath(""); + + HKEY hKey; + + BOOL f64 = FALSE; + LPCTSTR regkey; + if ((IsWow64Process(GetCurrentProcess(), &f64) && f64) || sizeof(void*) == 8) + { + regkey = "SOFTWARE\\Wow6432Node\\Bethesda Softworks\\Morrowind"; + } + else + { + regkey = "SOFTWARE\\Bethesda Softworks\\Morrowind"; + } + + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT(regkey), 0, KEY_ALL_ACCESS, &hKey) == ERROR_SUCCESS) + { + //Key existed, let's try to read the install dir + std::vector buf(512); + int len = 512; + + if (RegQueryValueEx(hKey, TEXT("Installed Path"), NULL, NULL, (LPBYTE)&buf[0], (LPDWORD)&len) == ERROR_SUCCESS) + { + installPath = &buf[0]; + } + } + + return installPath; } } /* namespace Files */ diff --git a/components/files/windowspath.hpp b/components/files/windowspath.hpp index 47dfc08d8..d4c716c50 100644 --- a/components/files/windowspath.hpp +++ b/components/files/windowspath.hpp @@ -39,48 +39,41 @@ namespace Files struct WindowsPath { /** - * \brief Returns "X:\Documents And Settings\\My Documents\My Games\" + * \brief Returns user path i.e.: + * "X:\Documents And Settings\\My Documents\My Games\" * * \return boost::filesystem::path */ - boost::filesystem::path getLocalConfigPath() const; + boost::filesystem::path getUserPath() const; /** * \brief Returns "X:\Program Files\" * * \return boost::filesystem::path */ - boost::filesystem::path getGlobalConfigPath() const; + boost::filesystem::path getGlobalPath() const; /** - * \brief Return runtime configuration path which is a location where + * \brief Return local path which is a location where * an application was started * * \return boost::filesystem::path */ - boost::filesystem::path getRuntimeConfigPath() const; + boost::filesystem::path getLocalPath() const; /** - * \brief Return same path like getLocalConfigPath - * - * \return boost::filesystem::path - */ - boost::filesystem::path getLocalDataPath() const; - - /** - * \brief Return same path like getGlobalConfigPath + * \brief Return same path like getGlobalPath * * \return boost::filesystem::path */ boost::filesystem::path getGlobalDataPath() const; /** - * \brief Return runtime data path which is a location where - * an application was started with 'data' suffix. + * \brief Gets the path of the installed Morrowind version if there is one. * * \return boost::filesystem::path */ - boost::filesystem::path getRuntimeDataPath() const; + boost::filesystem::path getInstallPath() const; }; } /* namespace Files */ diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 8b5540019..e576afb25 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -243,6 +243,8 @@ void NIFLoader::createMaterial(const String &name, /*TextureUnitState *txt =*/ pass->createTextureUnitState(texName); + pass->setVertexColourTracking(TVC_DIFFUSE); + // As of yet UNTESTED code from Chris: /*pass->setTextureFiltering(Ogre::TFO_ANISOTROPIC); pass->setDepthFunction(Ogre::CMPF_LESS_EQUAL); @@ -1328,7 +1330,10 @@ void NIFLoader::loadResource(Resource *resource) (*iter)->addBoneAssignment(vba); } - mesh->_notifySkeleton(mSkel); + //Don't link on npc parts to eliminate redundant skeletons + //Will have to be changed later slightly for robes/skirts + if(triname == "") + mesh->_notifySkeleton(mSkel); } } diff --git a/files/openmw.cfg b/files/openmw.cfg index f5322ae8c..0af096e6a 100644 --- a/files/openmw.cfg +++ b/files/openmw.cfg @@ -1,3 +1,2 @@ -data=${MORROWIND_DATA_FILES} +data="?mw?Data Files" resources=${MORROWIND_RESOURCE_FILES} - diff --git a/libs/mangle b/libs/mangle deleted file mode 160000 index 14b2851e7..000000000 --- a/libs/mangle +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 14b2851e72f610ae81dd296598867e6fb0babd2a diff --git a/libs/mangle/.gitignore b/libs/mangle/.gitignore new file mode 100644 index 000000000..cd24d7897 --- /dev/null +++ b/libs/mangle/.gitignore @@ -0,0 +1,3 @@ +upload_docs.sh +docs +*~ diff --git a/libs/mangle/Doxyfile b/libs/mangle/Doxyfile new file mode 100644 index 000000000..f3e018002 --- /dev/null +++ b/libs/mangle/Doxyfile @@ -0,0 +1,1510 @@ +# Doxyfile 1.5.8 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = Mangle + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = 1 + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Farsi, Finnish, French, German, Greek, +# Hungarian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, Polish, +# Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, Slovene, +# Spanish, Swedish, and Ukrainian. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it parses. +# With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this tag. +# The format is ext=language, where ext is a file extension, and language is one of +# the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP, +# Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat +# .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C + +EXTENSION_MAPPING = + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = YES + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen to replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penality. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will rougly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols + +SYMBOL_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespace are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = NO + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. +# This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by +# doxygen. The layout file controls the global structure of the generated output files +# in an output format independent way. The create the layout file that represents +# doxygen's defaults, run doxygen with the -l option. You can optionally specify a +# file name after the option, if omitted DoxygenLayout.xml will be used as the name +# of the layout file. + +LAYOUT_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be abled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = sound stream vfs input + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx +# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 + +FILE_PATTERNS = *.h + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix filesystem feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = */tests/* + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. +# If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. +# Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. +# The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER +# is applied to all files. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. +# Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = docs + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +HTML_DYNAMIC_SECTIONS = NO + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER +# are set, an additional index file will be generated that can be used as input for +# Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated +# HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add. +# For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's +# filter section matches. +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to FRAME, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, +# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are +# probably better off using the HTML help feature. Other possible values +# for this tag are: HIERARCHIES, which will generate the Groups, Directories, +# and Class Hierarchy pages using a tree view instead of an ordered list; +# ALL, which combines the behavior of FRAME and HIERARCHIES; and NONE, which +# disables this behavior completely. For backwards compatibility with previous +# releases of Doxygen, the values YES and NO are equivalent to FRAME and NONE +# respectively. + +GENERATE_TREEVIEW = NONE + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. +# This is useful +# if you want to understand what is going on. +# On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse +# the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option is superseded by the HAVE_DOT option below. This is only a +# fallback. It is recommended to install and use dot, since it yields more +# powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = YES + +# By default doxygen will write a font called FreeSans.ttf to the output +# directory and reference it in all dot files that doxygen generates. This +# font does not include all possible unicode characters however, so when you need +# these (or just want a differently looking font) you can specify the font name +# using DOT_FONTNAME. You need need to make sure dot is able to find the font, +# which can be done by putting it in a standard location or by setting the +# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory +# containing the font. + +DOT_FONTNAME = FreeSans + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the output directory to look for the +# FreeSans.ttf font (which doxygen will put there itself). If you specify a +# different font using DOT_FONTNAME you can set the path where dot +# can find it using this tag. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES + +#--------------------------------------------------------------------------- +# Options related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = NO diff --git a/libs/mangle/LICENSE.txt b/libs/mangle/LICENSE.txt new file mode 100644 index 000000000..ccfcc9f22 --- /dev/null +++ b/libs/mangle/LICENSE.txt @@ -0,0 +1,26 @@ +Minimal Abstraction Game Layer (Mangle) is licensed under the +'zlib/libpng' license: + +---- + +Copyright (c) 2009 Nicolay Korslund + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. + diff --git a/libs/mangle/README.txt b/libs/mangle/README.txt new file mode 100644 index 000000000..f4849bebd --- /dev/null +++ b/libs/mangle/README.txt @@ -0,0 +1,129 @@ +Welcome to Mangle v0.1 +---------------------- + +Written by: Nicolay Korslund (korslund@gmail.com) +License: zlib/png (see LICENSE.txt) +WWW: http://asm-soft.com/mangle/ +Documentation: http://asm-soft.com/mangle/docs + + + +Mangle is the project name for a small set of generic interfaces for +various game middleware libraries, such as sound, input, graphics, and +so on. You can imagine that it stands for "Minimal Abstraction Game +Layer", if you like. It will consist of several more or less +independent modules, one for each of these areas. These may be used +together to build an entire game engine, or they can be used +individually as separate libraries. + +However, Mangle does NOT actually implement a game engine, or any new +fundamental functionality. More on that below. + +Currently there's modules for sound and streams / archives (virtual +file systems.) More will come in the future (including input, 2D/3D +graphics, GUI, physics, and more.) + + +Main idea +--------- + +The idea behind Mangle is to provide a uniform, consistent interface +to other game libraries. The library does not provide ANY +functionality on its own. Instead it connects to a backend +implementation of your choice (or of your making.) + +The Sound module, for example, currently has backends for OpenAL +(output only), FFmpeg (input only) and for Audiere. Hopefully we'll +add IrrKlang, FMod, DirectSound, Miles and more in the future. It can +combine libraries to get more complete functionality (like using +OpenAL for output and FFmpeg to decode sound files), and it's also +easy to write your own backend if you're using a different (or +home-brewed) sound system. + +Regardless of what backend you use, the front-end interfaces (found +eg. in sound/output.h) is identical, and as a library user you +shouldn't notice much difference at all if you swap one backend for +another at a later point. It should Just Work. + +The interfaces themselves are also quite simple. Setting up a sound +stream from FFmpeg or other decoder into OpenAL can be quite hairy - +but with Mangle the hairy parts have already been written for you. You +just plug the parts together. + +The goal in the long run is to support a wide variety of game-related +libraries, and as many backend libraries (free and commercial) as +possible, so that you the user will have to write as little code as +possible. + + + +What is it good for +------------------- + +The main point of Mangle, as we said above, is that it connects to any +library of your choice "behind the scenes" but provides the same, +super-simple interface front-end for all of them. There can benefit +you in many ways: + +- If you want to use a new library that Mangle support. You don't have + to scour the net for tutorials and usage examples, since much of the + common usage code is already included in the implementation classes. + +- If you don't want to pollute your code with library-specific code. + The Mangle interfaces can help you keep your code clean, and its + user interface is often simpler than the exteral library one. + +- If you want to quickly connect different libraries together, it + really helps if they speak a common language. The Mangle interfaces + are exactly that - a common language between libraries. Do you need + Audiere to load sounds from a weird archive format only implemented + for PhysFS, all channeled through the OGRE resource system? No + problem! + +- If you are creating a library that depends on a specific feature + (such as sound), but you don't want to lock your users into any + specific sound library. Mangle works as an abstraction that lets + your users select their own implementation. + +- If you want to support multiple backends for your game/app, or want + to make it possible to easily switch backends later. You can select + backends at compile time or even at runtime. For example you might + want to switch to to a commercial sound library at a later stage in + development, or you may want to use a different input library on + console platforms than on PC. + +The Mangle implementations are extremely light-weight - often just one +or two cpp/h pairs per module. You can plug them directly into your +program, there's no separate library building step required. + +Since the library aims to be very modularly put together, you can +also, in many cases, just copy-and-paste the parts you need and ignore +the rest. Or modify stuff without fearing that the whole 'system' will +come crashing down, because there is no big 'system' to speak of. + + +Past and future +--------------- + +Mangle started out as (and still is) a spin-off from OpenMW, another +project I am personally working on ( http://openmw.com/ ). OpenMW is +an attempt to recreate the engine behind the commercial game +Morrowind, using only open source software. + +The projects are still tightly interlinked, and they will continue to +be until OpenMW is finished. Most near-future work on Mangle will be +focused chiefly on OpenMW at the moment. However I will gladly include +external contributions and suggestions that are not OpenMW-related if +someone sends them to me. + + +Conclusion +---------- + +As you might have guessed, Mangle is more a concept in development +than a finished library right now. + +All feedback, ideas, concepts, questions and code are very +welcome. Send them to: korslund@gmail.com + +I will put up a forum later as well if there's enough interest. diff --git a/libs/mangle/input/clients/ogre_input_capture.hpp b/libs/mangle/input/clients/ogre_input_capture.hpp new file mode 100644 index 000000000..2e77dc10b --- /dev/null +++ b/libs/mangle/input/clients/ogre_input_capture.hpp @@ -0,0 +1,29 @@ +#ifndef MANGLE_INPUT_OGREINPUTFRAME_H +#define MANGLE_INPUT_OGREINPUTFRAME_H + +/* + This Ogre FrameListener calls capture() on an input driver every frame. + */ + +#include +#include "../driver.hpp" + +namespace Mangle { +namespace Input { + + struct OgreInputCapture : Ogre::FrameListener + { + Mangle::Input::Driver &driver; + + OgreInputCapture(Mangle::Input::Driver &drv) + : driver(drv) {} + + bool frameStarted(const Ogre::FrameEvent &evt) + { + driver.capture(); + return true; + } + }; +}} + +#endif diff --git a/libs/mangle/input/driver.hpp b/libs/mangle/input/driver.hpp new file mode 100644 index 000000000..f4ba159c5 --- /dev/null +++ b/libs/mangle/input/driver.hpp @@ -0,0 +1,69 @@ +#ifndef MANGLE_INPUT_DRIVER_H +#define MANGLE_INPUT_DRIVER_H + +#include "event.hpp" + +namespace Mangle +{ + namespace Input + { + /** Input::Driver is the main interface to any input system that + handles keyboard and/or mouse input, along with any other + input source like joysticks. + + It is really a generalized event system, and could also be + used for non-input related events. The definition of the event + codes and structures are entirely dependent on the + implementation. + + A system-independent key code list will be found in keys.hpp, + and input drivers should privide optional translations to/from + this list for full compatibility. + */ + struct Driver + { + Driver() {} + virtual ~Driver() {} + + /** Captures input and produces the relevant events from it. An + event callback must be set with setEvent(), or all events + will be ignored. + */ + virtual void capture() = 0; + + /** Check the state of a given key or button. The key/button + definitions depends on the driver. + */ + virtual bool isDown(int index) = 0; + + /** Show or hide system mouse cursor + */ + virtual void showMouse(bool show) = 0; + + /** Set the event handler for input events. The evt->event() + function is called for each event. The meaning of the index + and *p parameters will be specific to each driver and to + each input system. + */ + void setEvent(EventPtr evt) + { event = evt; } + + /** Instigate an event. Is used internally for all events, but + can also be called from the outside to "fake" events from + this driver. + */ + void makeEvent(Event::Type type, int index, const void *p=NULL) + { + if(event) + event->event(type,index,p); + } + + private: + /// Holds the event callback set byt setEvent() + EventPtr event; + }; + + typedef boost::shared_ptr DriverPtr; + } +} +#endif diff --git a/libs/mangle/input/event.hpp b/libs/mangle/input/event.hpp new file mode 100644 index 000000000..dc7b47088 --- /dev/null +++ b/libs/mangle/input/event.hpp @@ -0,0 +1,46 @@ +#ifndef MANGLE_INPUT_EVENT_H +#define MANGLE_INPUT_EVENT_H + +#include "../tools/shared_ptr.hpp" + +namespace Mangle +{ + namespace Input + { + /** Generic callback for input events. The meaning of the + parameters depend on the system producing the events. + */ + struct Event + { + /// Event types + enum Type + { + EV_Unknown = 1, // Unknown event type + EV_KeyDown = 2, // Keyboard button was pressed + EV_KeyUp = 4, // Keyboard button was released + EV_Keyboard = 6, // All keyboard events + + EV_MouseMove = 8, // Mouse movement + EV_MouseDown = 16, // Mouse button pressed + EV_MouseUp = 32, // Mouse button released + EV_Mouse = 56, // All mouse events + + EV_ALL = 63 // All events + }; + + /** + Called upon all events. The first parameter give the event + type, the second gives additional data (usually the local + keysym or button index as defined by the driver), and the + pointer points to the full custom event structure provided by + the driver (the type may vary depending on the EventType, + this is defined in the Driver documentation.) + */ + virtual void event(Type type, int index, const void *p) = 0; + virtual ~Event() {} + }; + + typedef boost::shared_ptr EventPtr; + } +} +#endif diff --git a/libs/mangle/input/filters/eventlist.hpp b/libs/mangle/input/filters/eventlist.hpp new file mode 100644 index 000000000..b3e2ff8f2 --- /dev/null +++ b/libs/mangle/input/filters/eventlist.hpp @@ -0,0 +1,47 @@ +#ifndef MANGLE_INPUT_EVENTLIST_H +#define MANGLE_INPUT_EVENTLIST_H + +#include "../event.hpp" +#include + +namespace Mangle +{ + namespace Input + { + /** And Event handler that distributes each event to a list of + other handlers. Supports filtering events by their Type + parameter. + */ + struct EventList : Event + { + struct Filter + { + EventPtr evt; + int flags; + }; + std::vector list; + + void add(EventPtr e, int flags = EV_ALL) + { + Filter f; + f.evt = e; + f.flags = flags; + list.push_back(f); + } + + virtual void event(Type type, int index, const void *p) + { + std::vector::iterator it; + + for(it=list.begin(); it!=list.end(); it++) + { + if(type & it->flags) + it->evt->event(type,index,p); + } + } + }; + + typedef boost::shared_ptr EventListPtr; + } +} +#endif diff --git a/libs/mangle/input/servers/ois_driver.cpp b/libs/mangle/input/servers/ois_driver.cpp new file mode 100644 index 000000000..2071b91ea --- /dev/null +++ b/libs/mangle/input/servers/ois_driver.cpp @@ -0,0 +1,148 @@ +#include "ois_driver.hpp" + +#include +#include +#include +#include + +#ifdef __APPLE_CC__ +#include +#endif + +using namespace Mangle::Input; +using namespace OIS; + +struct Mangle::Input::OISListener : OIS::KeyListener, OIS::MouseListener +{ + OISDriver &drv; + + OISListener(OISDriver &driver) + : drv(driver) {} + + bool keyPressed( const OIS::KeyEvent &arg ) + { + drv.makeEvent(Event::EV_KeyDown, arg.key, &arg); + return true; + } + + bool keyReleased( const OIS::KeyEvent &arg ) + { + drv.makeEvent(Event::EV_KeyUp, arg.key, &arg); + return true; + } + + bool mousePressed( const OIS::MouseEvent &arg, OIS::MouseButtonID id ) + { + // Mouse button events are handled as key events + // TODO: Translate mouse buttons into pseudo-keysyms + drv.makeEvent(Event::EV_MouseDown, id, &arg); + return true; + } + + bool mouseReleased( const OIS::MouseEvent &arg, OIS::MouseButtonID id ) + { + // TODO: ditto + drv.makeEvent(Event::EV_MouseUp, id, &arg); + return true; + } + + bool mouseMoved( const OIS::MouseEvent &arg ) + { + drv.makeEvent(Event::EV_MouseMove, -1, &arg); + return true; + } +}; + +OISDriver::OISDriver(Ogre::RenderWindow *window, bool exclusive) +{ + assert(window); + + size_t windowHnd; + + window->getCustomAttribute("WINDOW", &windowHnd); + + std::ostringstream windowHndStr; + ParamList pl; + + windowHndStr << windowHnd; + pl.insert(std::make_pair(std::string("WINDOW"), windowHndStr.str())); + + // Set non-exclusive mouse and keyboard input if the user requested + // it. + if(!exclusive) + { +#if defined OIS_WIN32_PLATFORM + pl.insert(std::make_pair(std::string("w32_mouse"), + std::string("DISCL_FOREGROUND" ))); + pl.insert(std::make_pair(std::string("w32_mouse"), + std::string("DISCL_NONEXCLUSIVE"))); + pl.insert(std::make_pair(std::string("w32_keyboard"), + std::string("DISCL_FOREGROUND"))); + pl.insert(std::make_pair(std::string("w32_keyboard"), + std::string("DISCL_NONEXCLUSIVE"))); +#elif defined OIS_LINUX_PLATFORM + pl.insert(std::make_pair(std::string("x11_mouse_grab"), + std::string("false"))); + pl.insert(std::make_pair(std::string("x11_mouse_hide"), + std::string("false"))); + pl.insert(std::make_pair(std::string("x11_keyboard_grab"), + std::string("false"))); + pl.insert(std::make_pair(std::string("XAutoRepeatOn"), + std::string("true"))); +#endif + } + +#ifdef __APPLE_CC__ + // Give the application window focus to receive input events + ProcessSerialNumber psn = { 0, kCurrentProcess }; + TransformProcessType(&psn, kProcessTransformToForegroundApplication); + SetFrontProcess(&psn); +#endif + + inputMgr = InputManager::createInputSystem( pl ); + + // Create all devices + keyboard = static_cast(inputMgr->createInputObject + ( OISKeyboard, true )); + mouse = static_cast(inputMgr->createInputObject + ( OISMouse, true )); + + // Set mouse region + const MouseState &ms = mouse->getMouseState(); + ms.width = window->getWidth(); + ms.height = window->getHeight(); + + // Set up the input listener + listener = new OISListener(*this); + keyboard-> setEventCallback(listener); + mouse-> setEventCallback(listener); +} + +OISDriver::~OISDriver() +{ + // Delete the listener object + if(listener) + delete listener; + + if(inputMgr == NULL) return; + + // Kill the input systems. This will reset input options such as key + // repeat rate. + inputMgr->destroyInputObject(keyboard); + inputMgr->destroyInputObject(mouse); + InputManager::destroyInputSystem(inputMgr); + inputMgr = NULL; +} + +void OISDriver::capture() +{ + // Capture keyboard and mouse events + keyboard->capture(); + mouse->capture(); +} + +bool OISDriver::isDown(int index) +{ + // TODO: Extend to mouse buttons as well + return keyboard->isKeyDown((OIS::KeyCode)index); +} diff --git a/libs/mangle/input/servers/ois_driver.hpp b/libs/mangle/input/servers/ois_driver.hpp new file mode 100644 index 000000000..ba780c39e --- /dev/null +++ b/libs/mangle/input/servers/ois_driver.hpp @@ -0,0 +1,48 @@ +#ifndef MANGLE_INPUT_OIS_DRIVER_H +#define MANGLE_INPUT_OIS_DRIVER_H + +#include "../driver.hpp" + +namespace OIS +{ + class InputManager; + class Mouse; + class Keyboard; +} + +namespace Ogre +{ + class RenderWindow; +} + +namespace Mangle +{ + namespace Input + { + struct OISListener; + + /** Input driver for OIS, the input manager typically used with + Ogre. + */ + struct OISDriver : Driver + { + /// If exclusive=true, then we capture mouse and keyboard from + /// the OS. + OISDriver(Ogre::RenderWindow *window, bool exclusive=true); + ~OISDriver(); + + void capture(); + bool isDown(int index); + /// Not currently supported. + void showMouse(bool) {} + + private: + OIS::InputManager *inputMgr; + OIS::Mouse *mouse; + OIS::Keyboard *keyboard; + + OISListener *listener; + }; + } +} +#endif diff --git a/libs/mangle/input/servers/sdl_driver.cpp b/libs/mangle/input/servers/sdl_driver.cpp new file mode 100644 index 000000000..93884a6e6 --- /dev/null +++ b/libs/mangle/input/servers/sdl_driver.cpp @@ -0,0 +1,54 @@ +#include "sdl_driver.hpp" + +#include + +using namespace Mangle::Input; + +void SDLDriver::capture() +{ + // Poll for events + SDL_Event evt; + while(SDL_PollEvent(&evt)) + { + Event::Type type = Event::EV_Unknown; + int index = -1; + + switch(evt.type) + { + // For key events, send the keysym as the index. + case SDL_KEYDOWN: + type = Event::EV_KeyDown; + index = evt.key.keysym.sym; + break; + case SDL_KEYUP: + type = Event::EV_KeyUp; + index = evt.key.keysym.sym; + break; + case SDL_MOUSEMOTION: + type = Event::EV_MouseMove; + break; + // Add more event types later + } + + // Pass the event along, using -1 as index for unidentified + // event types. + makeEvent(type, index, &evt); + } +} + +bool SDLDriver::isDown(int index) +{ + int num; + Uint8 *keys = SDL_GetKeyState(&num); + assert(index >= 0 && index < num); + + // The returned array from GetKeyState is indexed by the + // SDLK_KEYNAME enums and is just a list of bools. If the indexed + // value is true, the button is down. + return keys[index]; +} + +void SDLDriver::showMouse(bool show) +{ + SDL_ShowCursor(show?SDL_ENABLE:SDL_DISABLE); +} diff --git a/libs/mangle/input/servers/sdl_driver.hpp b/libs/mangle/input/servers/sdl_driver.hpp new file mode 100644 index 000000000..b71346cba --- /dev/null +++ b/libs/mangle/input/servers/sdl_driver.hpp @@ -0,0 +1,27 @@ +#ifndef MANGLE_INPUT_SDL_DRIVER_H +#define MANGLE_INPUT_SDL_DRIVER_H + +#include "../driver.hpp" + +namespace Mangle +{ + namespace Input + { + /** Input driver for SDL. As the input system of SDL is seldomly + used alone (most often along with the video system), it is + assumed that you do your own initialization and cleanup of SDL + before and after using this driver. + + The Event.event() calls will be given the proper EV_ type, the + key index (for key up/down events), and a pointer to the full + SDL_Event structure. + */ + struct SDLDriver : Driver + { + void capture(); + bool isDown(int index); + void showMouse(bool); + }; + } +} +#endif diff --git a/libs/mangle/input/tests/.gitignore b/libs/mangle/input/tests/.gitignore new file mode 100644 index 000000000..460c76f00 --- /dev/null +++ b/libs/mangle/input/tests/.gitignore @@ -0,0 +1,2 @@ +*_test +ogre.cfg diff --git a/libs/mangle/input/tests/Makefile b/libs/mangle/input/tests/Makefile new file mode 100644 index 000000000..8760adfe7 --- /dev/null +++ b/libs/mangle/input/tests/Makefile @@ -0,0 +1,15 @@ +GCC=g++ -Wall + +all: sdl_driver_test ois_driver_test evtlist_test + +sdl_driver_test: sdl_driver_test.cpp + $(GCC) $< ../servers/sdl_driver.cpp -o $@ -I/usr/include/SDL/ -lSDL + +ois_driver_test: ois_driver_test.cpp + $(GCC) $< ../servers/ois_driver.cpp -o $@ -I/usr/local/include/OGRE/ -lOgreMain -lOIS -lboost_filesystem + +evtlist_test: evtlist_test.cpp ../filters/eventlist.hpp ../event.hpp + $(GCC) $< -o $@ + +clean: + rm *_test diff --git a/libs/mangle/input/tests/common.cpp b/libs/mangle/input/tests/common.cpp new file mode 100644 index 000000000..0c7c76466 --- /dev/null +++ b/libs/mangle/input/tests/common.cpp @@ -0,0 +1,35 @@ +#include +#include "../driver.hpp" +#include +using namespace std; +using namespace Mangle::Input; + +Driver *input; + +struct MyCB : Event +{ + void event(Event::Type type, int i, const void *p) + { + cout << "got event: type=" << type << " index=" << i << endl; + } +}; + +void mainLoop(int argc, int quitKey) +{ + cout << "Hold the Q key to quit:\n"; + input->setEvent(EventPtr(new MyCB)); + while(!input->isDown(quitKey)) + { + input->capture(); + usleep(20000); + + if(argc == 1) + { + cout << "You are running in script mode, aborting. Run this test with a parameter (any at all) to test the input loop properly\n"; + break; + } + } + + delete input; + cout << "\nBye bye!\n"; +} diff --git a/libs/mangle/input/tests/evtlist_test.cpp b/libs/mangle/input/tests/evtlist_test.cpp new file mode 100644 index 000000000..fbd980cbd --- /dev/null +++ b/libs/mangle/input/tests/evtlist_test.cpp @@ -0,0 +1,45 @@ +#include +#include "../filters/eventlist.hpp" + +using namespace std; +using namespace Mangle::Input; + +struct MyEvent : Event +{ + int ii; + MyEvent(int i) : ii(i) {} + + void event(Event::Type type, int i, const void *p) + { + cout << " #" << ii << " got event: type=" << type << " index=" << i << endl; + } +}; + +EventList lst; + +int iii=1; +void make(int flags) +{ + lst.add(EventPtr(new MyEvent(iii++)), flags); +} + +void send(Event::Type type) +{ + cout << "Sending type " << type << endl; + lst.event(type,0,NULL); +} + +int main() +{ + make(Event::EV_ALL); + make(Event::EV_KeyDown); + make(Event::EV_KeyUp | Event::EV_MouseDown); + + send(Event::EV_Unknown); + send(Event::EV_KeyDown); + send(Event::EV_KeyUp); + send(Event::EV_MouseDown); + + cout << "Enough of that\n"; + return 0; +} diff --git a/libs/mangle/input/tests/ois_driver_test.cpp b/libs/mangle/input/tests/ois_driver_test.cpp new file mode 100644 index 000000000..386f24055 --- /dev/null +++ b/libs/mangle/input/tests/ois_driver_test.cpp @@ -0,0 +1,51 @@ +#include "common.cpp" + +#include "../servers/ois_driver.hpp" +#include +#include +#include + +bool isFile(const char *name) +{ + boost::filesystem::path cfg_file_path(name); + return boost::filesystem::exists(cfg_file_path); +} + +using namespace Ogre; +using namespace OIS; + +Root *root; +RenderWindow *window; + +void setupOgre() +{ + // Disable logging + new LogManager; + Log *log = LogManager::getSingleton().createLog(""); + log->setDebugOutputEnabled(false); + + bool useConfig = isFile("ogre.cfg"); + + // Set up Root + root = new Root("plugins.cfg", "ogre.cfg", ""); + + // Configure + if(!useConfig) + root->showConfigDialog(); + else + root->restoreConfig(); + + // Initialize OGRE window + window = root->initialise(true, "test", ""); +} + +int main(int argc, char** argv) +{ + setupOgre(); + input = new OISDriver(window); + + mainLoop(argc, KC_Q); + + delete root; + return 0; +} diff --git a/libs/mangle/input/tests/output/evtlist_test.out b/libs/mangle/input/tests/output/evtlist_test.out new file mode 100644 index 000000000..180dcc58a --- /dev/null +++ b/libs/mangle/input/tests/output/evtlist_test.out @@ -0,0 +1,12 @@ +Sending type 1 + #1 got event: type=1 index=0 +Sending type 2 + #1 got event: type=2 index=0 + #2 got event: type=2 index=0 +Sending type 4 + #1 got event: type=4 index=0 + #3 got event: type=4 index=0 +Sending type 16 + #1 got event: type=16 index=0 + #3 got event: type=16 index=0 +Enough of that diff --git a/libs/mangle/input/tests/output/ois_driver_test.out b/libs/mangle/input/tests/output/ois_driver_test.out new file mode 100644 index 000000000..7d273fd46 --- /dev/null +++ b/libs/mangle/input/tests/output/ois_driver_test.out @@ -0,0 +1,5 @@ +Hold the Q key to quit: +got event: type=8 index=-1 +You are running in script mode, aborting. Run this test with a parameter (any at all) to test the input loop properly + +Bye bye! diff --git a/libs/mangle/input/tests/output/sdl_driver_test.out b/libs/mangle/input/tests/output/sdl_driver_test.out new file mode 100644 index 000000000..2df2e4014 --- /dev/null +++ b/libs/mangle/input/tests/output/sdl_driver_test.out @@ -0,0 +1,5 @@ +Hold the Q key to quit: +got event: type=1 index=-1 +You are running in script mode, aborting. Run this test with a parameter (any at all) to test the input loop properly + +Bye bye! diff --git a/libs/mangle/input/tests/plugins.cfg b/libs/mangle/input/tests/plugins.cfg new file mode 100644 index 000000000..57ec54e1a --- /dev/null +++ b/libs/mangle/input/tests/plugins.cfg @@ -0,0 +1,12 @@ +# Defines plugins to load + +# Define plugin folder +PluginFolder=/usr/local/lib/OGRE/ + +# Define plugins +Plugin=RenderSystem_GL +Plugin=Plugin_ParticleFX +Plugin=Plugin_OctreeSceneManager +# Plugin=Plugin_CgProgramManager + + diff --git a/libs/mangle/input/tests/sdl_driver_test.cpp b/libs/mangle/input/tests/sdl_driver_test.cpp new file mode 100644 index 000000000..5db6dbba8 --- /dev/null +++ b/libs/mangle/input/tests/sdl_driver_test.cpp @@ -0,0 +1,16 @@ +#include "common.cpp" + +#include "../servers/sdl_driver.hpp" +#include + +int main(int argc, char** argv) +{ + SDL_Init(SDL_INIT_VIDEO); + SDL_SetVideoMode(640, 480, 0, SDL_SWSURFACE); + input = new SDLDriver(); + + mainLoop(argc, SDLK_q); + + SDL_Quit(); + return 0; +} diff --git a/libs/mangle/input/tests/test.sh b/libs/mangle/input/tests/test.sh new file mode 100755 index 000000000..2d07708ad --- /dev/null +++ b/libs/mangle/input/tests/test.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +make || exit + +mkdir -p output + +PROGS=*_test + +for a in $PROGS; do + if [ -f "output/$a.out" ]; then + echo "Running $a:" + ./$a | diff output/$a.out - + else + echo "Creating $a.out" + ./$a > "output/$a.out" + git add "output/$a.out" + fi +done diff --git a/libs/mangle/rend2d/driver.hpp b/libs/mangle/rend2d/driver.hpp new file mode 100644 index 000000000..08a15b0ae --- /dev/null +++ b/libs/mangle/rend2d/driver.hpp @@ -0,0 +1,63 @@ +#ifndef MANGLE_REND2D_DRIVER_H +#define MANGLE_REND2D_DRIVER_H + +#include +#include "sprite.hpp" + +namespace Mangle +{ + namespace Rend2D + { + /** + The driver is the connection to the backend system that powers + 2D sprite rendering. For example the backend could be SDL or + any other 2D-capable graphics library. + */ + struct Driver + { + /// Get the screen sprite + virtual Sprite *getScreen() = 0; + + /// Sets the video mode. + virtual void setVideoMode(int width, int height, int bpp=32, bool fullscreen=false) = 0; + + /** Update the screen. Until this function is called, none of + the changes written to the screen sprite will be visible. + */ + virtual void update() = 0; + + /// Set the window title, as well as the title of the window + /// when "iconified" + virtual void setWindowTitle(const std::string &title, + const std::string &icon) = 0; + + /// Set the window title + void setWindowTitle(const std::string &title) { setWindowTitle(title,title); } + + /// Load sprite from an image file. Thows an exception on + /// failure. + virtual Sprite* loadImage(const std::string &file) = 0; + + /// Load a sprite from an image file stored in memory. Throws + /// exception on failure. + virtual Sprite* loadImage(const void* data, size_t size) = 0; + + /** @brief Set gamma value for all colors. + + Note: Setting this in windowed mode will affect the ENTIRE + SCREEN! + */ + virtual void setGamma(float gamma) = 0; + + /// Set gamma individually for red, green, blue + virtual void setGamma(float red, float green, float blue) = 0; + + /// Get screen width + virtual int width() = 0; + + /// Get screen height + virtual int height() = 0; + }; + } +} +#endif diff --git a/libs/mangle/rend2d/servers/sdl_driver.cpp b/libs/mangle/rend2d/servers/sdl_driver.cpp new file mode 100644 index 000000000..aa1ff6c6d --- /dev/null +++ b/libs/mangle/rend2d/servers/sdl_driver.cpp @@ -0,0 +1,259 @@ +#include "sdl_driver.hpp" + +#include +#include +#include +#include + +using namespace Mangle::Rend2D; + +const SpriteData *SDL_Sprite::lock() +{ + // Make sure we aren't already locked + assert(!data.pixels); + + // Lock the surface and set up the data structure + SDL_LockSurface(surface); + + data.pixels = surface->pixels; + data.w = surface->w; + data.h = surface->h; + data.pitch = surface->pitch; + data.bypp = surface->format->BytesPerPixel; + + return &data; +} + +void SDL_Sprite::unlock() +{ + if(data.pixels) + { + SDL_UnlockSurface(surface); + data.pixels = NULL; + } +} + +// This is a really crappy and slow implementation, only intended for +// testing purposes. Use lock/unlock for faster pixel drawing. +void SDL_Sprite::pixel(int x, int y, int color) +{ + SDL_LockSurface(surface); + + int bpp = surface->format->BytesPerPixel; + char *p = (char*)surface->pixels + y*surface->pitch + x*bpp; + + switch(bpp) + { + case 1: *p = color; break; + case 3: + if(SDL_BYTEORDER == SDL_BIG_ENDIAN) + { + p[0] = (color >> 16) & 0xff; + p[1] = (color >> 8) & 0xff; + p[2] = color & 0xff; + } + else + { + p[0] = color & 0xff; + p[1] = (color >> 8) & 0xff; + p[2] = (color >> 16) & 0xff; + } + break; + case 4: + *(int*)p = color; + break; + } + SDL_UnlockSurface(surface); +} + +void SDL_Sprite::draw(Sprite *s, // Must be SDL_Sprite + int x, int y, // Destination position + int sx, int sy, // Source position + int w, int h // Amount to draw. -1 means remainder. + ) +{ + // Get source surface + SDL_Sprite *other = dynamic_cast(s); + assert(other != NULL); + SDL_Surface *img = other->getSurface(); + + // Check coordinate validity + assert(sx <= img->w && sy <= img->h); + assert(x <= surface->w && y <= surface->h); + assert(sx >= 0 && sy >= 0); + + // Compute width and height if necessary + if(w == -1) w = img->w - sx; + if(h == -1) h = img->h - sy; + + // Check them if they're valid + assert(w >= 0 && w <= img->w); + assert(h >= 0 && h <= img->h); + + SDL_Rect dest; + dest.x = x; + dest.y = y; + dest.w = w; + dest.h = h; + + SDL_Rect src; + src.x = sx; + src.y = sy; + src.w = w; + src.h = h; + + // Do the Blitman + SDL_BlitSurface(img, &src, surface, &dest); +} + +SDL_Sprite::SDL_Sprite(SDL_Surface *s, bool autoDelete) + : surface(s), autoDel(autoDelete) +{ + assert(surface != NULL); + data.pixels = NULL; +} + +SDL_Sprite::~SDL_Sprite() +{ + if(autoDel) + SDL_FreeSurface(surface); +} + +void SDL_Sprite::fill(int value) +{ + SDL_FillRect(surface, NULL, value); +} + +int SDL_Sprite::width() { return surface->w; } +int SDL_Sprite::height() { return surface->h; } + +SDLDriver::SDLDriver() : display(NULL), realDisp(NULL), softDouble(false) +{ + if (SDL_InitSubSystem( SDL_INIT_VIDEO ) == -1) + throw std::runtime_error("Error initializing SDL video"); +} +SDLDriver::~SDLDriver() +{ + if(display) delete display; + SDL_Quit(); +} + +void SDLDriver::setVideoMode(int width, int height, int bpp, bool fullscreen) +{ + unsigned int flags; + + if(display) delete display; + + if (fullscreen) + // Assume fullscreen mode allows a double-bufferd hardware + // mode. We need more test code for this to be safe though. + flags = SDL_FULLSCREEN | SDL_HWSURFACE | SDL_DOUBLEBUF; + else + flags = SDL_SWSURFACE; + + // Create the surface and check it + realDisp = SDL_SetVideoMode(width, height, bpp, flags); + if(realDisp == NULL) + throw std::runtime_error("Failed setting SDL video mode"); + + // Code for software double buffering. I haven't found this to be + // any speed advantage at all in windowed mode (it's slower, as one + // would expect.) Not properly tested in fullscreen mode with + // hardware buffers, but it will probably only be an improvement if + // we do excessive writing (ie. write each pixel on average more + // than once) or try to read from the display buffer. + if(softDouble) + { + // Make a new surface with the same attributes as the real + // display surface. + SDL_Surface *back = SDL_DisplayFormat(realDisp); + assert(back != NULL); + + // Create a sprite representing the double buffer + display = new SDL_Sprite(back); + } + else + { + // Create a sprite directly representing the display surface. + // The 'false' parameter means do not autodelete the screen + // surface upon exit (since SDL manages it) + display = new SDL_Sprite(realDisp, false); + } +} + +/// Update the screen +void SDLDriver::update() +{ + // Blit the soft double buffer onto the real display buffer + if(softDouble) + SDL_BlitSurface(display->getSurface(), NULL, realDisp, NULL ); + + if(realDisp) + SDL_Flip(realDisp); +} + +/// Set the window title, as well as the title of the window when +/// "iconified" +void SDLDriver::setWindowTitle(const std::string &title, + const std::string &icon) +{ + SDL_WM_SetCaption( title.c_str(), icon.c_str() ); +} + +// Convert the given surface to display format. +static SDL_Surface* convertImage(SDL_Surface* surf) +{ + if(surf != NULL) + { + // Convert the image to the display buffer format, for faster + // blitting + SDL_Surface *surf2 = SDL_DisplayFormat(surf); + SDL_FreeSurface(surf); + surf = surf2; + } + return surf; +} + +/// Load sprite from an image file, using SDL_image. +Sprite* SDLDriver::loadImage(const std::string &file) +{ + SDL_Surface *surf = IMG_Load(file.c_str()); + surf = convertImage(surf); + if(surf == NULL) + throw std::runtime_error("SDL failed to load image file '" + file + "'"); + return spriteFromSDL(surf); +} + +/// Load sprite from an SDL_RWops structure. autoFree determines +/// whether the RWops struct should be closed/freed after use. +Sprite* SDLDriver::loadImage(SDL_RWops *src, bool autoFree) +{ + SDL_Surface *surf = IMG_Load_RW(src, autoFree); + surf = convertImage(surf); + if(surf == NULL) + throw std::runtime_error("SDL failed to load image"); + return spriteFromSDL(surf); +} + +/// Load a sprite from an image file stored in memory. Uses +/// SDL_image. +Sprite* SDLDriver::loadImage(const void* data, size_t size) +{ + SDL_RWops *rw = SDL_RWFromConstMem(data, size); + return loadImage(rw, true); +} + +void SDLDriver::setGamma(float red, float green, float blue) +{ + SDL_SetGamma(red,green,blue); +} + +/// Convert an existing SDL surface into a sprite +Sprite* SDLDriver::spriteFromSDL(SDL_Surface *surf, bool autoFree) +{ + assert(surf); + return new SDL_Sprite(surf, autoFree); +} + +void SDLDriver::sleep(int ms) { SDL_Delay(ms); } +unsigned int SDLDriver::ticks() { return SDL_GetTicks(); } diff --git a/libs/mangle/rend2d/servers/sdl_driver.hpp b/libs/mangle/rend2d/servers/sdl_driver.hpp new file mode 100644 index 000000000..0f205ba34 --- /dev/null +++ b/libs/mangle/rend2d/servers/sdl_driver.hpp @@ -0,0 +1,125 @@ +#ifndef MANGLE_DRAW2D_SDL_H +#define MANGLE_DRAW2D_SDL_H + +#include "../driver.hpp" + +// Predeclarations keep the streets safe at night +struct SDL_Surface; +struct SDL_RWops; + +namespace Mangle +{ + namespace Rend2D + { + /// SDL-implementation of Sprite + struct SDL_Sprite : Sprite + { + /** Draw a sprite in the given position. Can only draw other SDL + sprites. + */ + void draw(Sprite *s, // Must be SDL_Sprite + int x, int y, // Destination position + int sx=0, int sy=0, // Source position + int w=-1, int h=-1 // Amount to draw. -1 means remainder. + ); + + SDL_Sprite(SDL_Surface *s, bool autoDelete=true); + ~SDL_Sprite(); + + // Information retrieval + int width(); + int height(); + SDL_Surface *getSurface() { return surface; } + + // Fill with a given pixel value + void fill(int value); + + // Set one pixel + void pixel(int x, int y, int value); + + const SpriteData *lock(); + void unlock(); + + private: + // The SDL surface + SDL_Surface* surface; + + // Used for locking + SpriteData data; + + // If true, delete this surface when the canvas is destructed + bool autoDel; + }; + + class SDLDriver : public Driver + { + // The main display surface + SDL_Sprite *display; + + // The actual display surface. May or may not be the same + // surface pointed to by 'display' above, depending on the + // softDouble flag. + SDL_Surface *realDisp; + + // If true, we do software double buffering. + bool softDouble; + + public: + SDLDriver(); + ~SDLDriver(); + + /// Sets the video mode. Will create the window if it is not + /// already set up. Note that for SDL, bpp=0 means use current + /// bpp. + void setVideoMode(int width, int height, int bpp=0, bool fullscreen=false); + + /// Update the screen + void update(); + + /// Set the window title, as well as the title of the window + /// when "iconified" + void setWindowTitle(const std::string &title, + const std::string &icon); + + // Include overloads from our Glorious parent + using Driver::setWindowTitle; + + /// Load sprite from an image file, using SDL_image. + Sprite* loadImage(const std::string &file); + + /// Load sprite from an SDL_RWops structure. autoFree determines + /// whether the RWops struct should be closed/freed after use. + Sprite* loadImage(SDL_RWops *src, bool autoFree=false); + + /// Load a sprite from an image file stored in memory. Uses + /// SDL_image. + Sprite* loadImage(const void* data, size_t size); + + /// Set gamma value + void setGamma(float gamma) { setGamma(gamma,gamma,gamma); } + + /// Set gamma individually for red, green, blue + void setGamma(float red, float green, float blue); + + /// Convert an existing SDL surface into a sprite + Sprite* spriteFromSDL(SDL_Surface *surf, bool autoFree = true); + + // Get width and height + int width() { return display ? display->width() : 0; } + int height() { return display ? display->height() : 0; } + + /// Get the screen sprite + Sprite *getScreen() { return display; } + + /// Not really a graphic-related function, but very + /// handly. Sleeps the given number of milliseconds using + /// SDL_Delay(). + void sleep(int ms); + + /// Get the number of ticks since SDL initialization, using + /// SDL_GetTicks(). + unsigned int ticks(); + }; + } +} +#endif diff --git a/libs/mangle/rend2d/servers/sdl_gl_driver.cpp b/libs/mangle/rend2d/servers/sdl_gl_driver.cpp new file mode 100644 index 000000000..2bcb1d677 --- /dev/null +++ b/libs/mangle/rend2d/servers/sdl_gl_driver.cpp @@ -0,0 +1,311 @@ +#include "sdl_gl_driver.hpp" + +#include +#include +#include +#include +#include + +using namespace Mangle::Rend2D; + +void SDLGL_Sprite::draw(Sprite *s, // Must be SDLGL_Sprite + int x, int y, // Destination position + int sx, int sy, // Source position + int w, int h // Amount to draw. -1 means remainder. + ) +{ + // Get source surface + SDLGL_Sprite *other = dynamic_cast(s); + assert(other != NULL); + SDL_Surface *img = other->getSurface(); + + // Check coordinate validity + assert(sx <= img->w && sy <= img->h); + assert(x <= surface->w && y <= surface->h); + assert(sx >= 0 && sy >= 0); + + // Compute width and height if necessary + if(w == -1) w = img->w - sx; + if(h == -1) h = img->h - sy; + + // Check them if they're valid + assert(w >= 0 && w <= img->w); + assert(h >= 0 && h <= img->h); + + SDL_Rect dest; + dest.x = x; + dest.y = y; + dest.w = w; + dest.h = h; + + SDL_Rect src; + src.x = sx; + src.y = sy; + src.w = w; + src.h = h; + + // Do the Blitman + SDL_BlitSurface(img, &src, surface, &dest); +} + +SDLGL_Sprite::SDLGL_Sprite(SDL_Surface *s, bool autoDelete) + : surface(s), autoDel(autoDelete) +{ + assert(surface != NULL); +} + +SDLGL_Sprite::~SDLGL_Sprite() +{ + if(autoDel) + SDL_FreeSurface(surface); +} + +void SDLGL_Sprite::fill(int value) +{ + SDL_FillRect(surface, NULL, value); +} + +int SDLGL_Sprite::width() { return surface->w; } +int SDLGL_Sprite::height() { return surface->h; } + +SDLGLDriver::SDLGLDriver() : display(NULL), realDisp(NULL) +{ + if (SDL_InitSubSystem( SDL_INIT_VIDEO ) == -1) + throw std::runtime_error("Error initializing SDL video"); +} +SDLGLDriver::~SDLGLDriver() +{ + if(display) delete display; + SDL_Quit(); +} + +// Surface used for the screen. Since OpenGL surfaces must have sizes +// that are powers of 2, we have to "fake" the returned display size +// to match the screen, not the surface itself. If we don't use this, +// the client program will get confused about the actual size of our +// screen, thinking it is bigger than it is. +struct FakeSizeSprite : SDLGL_Sprite +{ + int fakeW, fakeH; + + FakeSizeSprite(SDL_Surface *s, int fw, int fh) + : SDLGL_Sprite(s), fakeW(fw), fakeH(fh) + {} + + int width() { return fakeW; } + int height() { return fakeH; } +}; + +static int makePow2(int num) +{ + assert(num); + if((num & (num-1)) != 0) + { + int cnt = 0; + while(num) + { + num >>= 1; + cnt++; + } + num = 1 << cnt; + } + return num; +} + +void SDLGLDriver::setVideoMode(int width, int height, int bpp, bool fullscreen) +{ + unsigned int flags; + + if(display) delete display; + + flags = SDL_OPENGL; + + if (fullscreen) + flags |= SDL_FULLSCREEN; + + SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 ); + SDL_GL_SetAttribute( SDL_GL_SWAP_CONTROL, 1 ); + + // Create the surface and check it + screen = SDL_SetVideoMode(width, height, bpp, flags); + if(screen == NULL) + throw std::runtime_error("Failed setting SDL video mode"); + + // Expand width and height to be powers of 2 + int width2 = makePow2(width); + int height2 = makePow2(height); + + // Create a new SDL surface of this size + const SDL_PixelFormat& fmt = *(screen->format); + realDisp = SDL_CreateRGBSurface(SDL_SWSURFACE,width2,height2, + fmt.BitsPerPixel, + fmt.Rmask,fmt.Gmask,fmt.Bmask,fmt.Amask); + + // Create a sprite directly representing the display surface. This + // allows the user to blit to it directly. + display = new FakeSizeSprite(realDisp, width, height); + + // Set up the OpenGL format + nOfColors = fmt.BytesPerPixel; + + if(nOfColors == 4) + { + if (fmt.Rmask == 0x000000ff) + texture_format = GL_RGBA; + else + texture_format = GL_BGRA; + } + else if(nOfColors == 3) + { + if (fmt.Rmask == 0x000000ff) + texture_format = GL_RGB; + else + texture_format = GL_BGR; + } + else + assert(0 && "unsupported screen format"); + + glEnable(GL_TEXTURE_2D); + + // Have OpenGL generate a texture object handle for us + glGenTextures( 1, &texture ); + + // Bind the texture object + glBindTexture( GL_TEXTURE_2D, texture ); + + // Set the texture's stretching properties + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); +} + +void SDLGLDriver::updateNoSwap() +{ + if(!realDisp) return; + + // Fist, set up the screen texture: + + // Bind the texture object + glBindTexture( GL_TEXTURE_2D, texture ); + + // Edit the texture object's image data + glTexImage2D( GL_TEXTURE_2D, 0, nOfColors, realDisp->w, realDisp->h, 0, + texture_format, GL_UNSIGNED_BYTE, realDisp->pixels ); + + glClear(GL_COLOR_BUFFER_BIT| GL_DEPTH_BUFFER_BIT); + glLoadIdentity(); + + // OpenGL barf. Set up the projection to match our screen + int vPort[4]; + glGetIntegerv(GL_VIEWPORT, vPort); + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + glOrtho(0, vPort[2], 0, vPort[3], -1, 1); + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + + glBegin( GL_QUADS ); + + // Needed to move the screen into the right place + int diff = screen->h - realDisp->h; + + // Bottom-left vertex (corner) + glTexCoord2i( 0, 1 ); + glVertex3f(0,diff,0); + + // Bottom-right vertex (corner) + glTexCoord2i( 1, 1 ); + glVertex3f( realDisp->w, diff, 0.f ); + + // Top-right vertex (corner) + glTexCoord2i( 1, 0 ); + glVertex3f( realDisp->w, screen->h, 0.f ); + + // Top-left vertex (corner) + glTexCoord2i( 0, 0 ); + glVertex3f( 0, screen->h, 0.f ); + glEnd(); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); +} + +void SDLGLDriver::swap() +{ + SDL_GL_SwapBuffers(); +} + +void SDLGLDriver::update() +{ + updateNoSwap(); + swap(); +} + +/// Set the window title, as well as the title of the window when +/// "iconified" +void SDLGLDriver::setWindowTitle(const std::string &title, + const std::string &icon) +{ + SDL_WM_SetCaption( title.c_str(), icon.c_str() ); +} + +// Convert the given surface to display format. +static SDL_Surface* convertImage(SDL_Surface* surf) +{ + if(surf != NULL) + { + // Convert the image to the display buffer format, for faster + // blitting + SDL_Surface *surf2 = SDL_DisplayFormat(surf); + SDL_FreeSurface(surf); + surf = surf2; + } + return surf; +} + +/// Load sprite from an image file, using SDL_image. +Sprite* SDLGLDriver::loadImage(const std::string &file) +{ + SDL_Surface *surf = IMG_Load(file.c_str()); + surf = convertImage(surf); + if(surf == NULL) + throw std::runtime_error("SDL failed to load image file '" + file + "'"); + return spriteFromSDL(surf); +} + +/// Load sprite from an SDL_RWops structure. autoFree determines +/// whether the RWops struct should be closed/freed after use. +Sprite* SDLGLDriver::loadImage(SDL_RWops *src, bool autoFree) +{ + SDL_Surface *surf = IMG_Load_RW(src, autoFree); + surf = convertImage(surf); + if(surf == NULL) + throw std::runtime_error("SDL failed to load image"); + return spriteFromSDL(surf); +} + +/// Load a sprite from an image file stored in memory. Uses +/// SDL_image. +Sprite* SDLGLDriver::loadImage(const void* data, size_t size) +{ + SDL_RWops *rw = SDL_RWFromConstMem(data, size); + return loadImage(rw, true); +} + +void SDLGLDriver::setGamma(float red, float green, float blue) +{ + SDL_SetGamma(red,green,blue); +} + +/// Convert an existing SDL surface into a sprite +Sprite* SDLGLDriver::spriteFromSDL(SDL_Surface *surf, bool autoFree) +{ + assert(surf); + return new SDLGL_Sprite(surf, autoFree); +} + +void SDLGLDriver::sleep(int ms) { SDL_Delay(ms); } +unsigned int SDLGLDriver::ticks() { return SDL_GetTicks(); } diff --git a/libs/mangle/rend2d/servers/sdl_gl_driver.hpp b/libs/mangle/rend2d/servers/sdl_gl_driver.hpp new file mode 100644 index 000000000..d116e3659 --- /dev/null +++ b/libs/mangle/rend2d/servers/sdl_gl_driver.hpp @@ -0,0 +1,132 @@ +#ifndef MANGLE_DRAW2D_SDLGL_H +#define MANGLE_DRAW2D_SDLGL_H + +/** This driver is similar to SDLDriver, except that it uses SDL on + top of OpenGL. + + I've decided to make it a separate file instead of just adding + optional OpenGL support to the original, so that pure SDL users + don't have to add OpenGL as a dependency. + */ + +#include "../driver.hpp" + +// Predeclarations keep the streets safe at night +struct SDL_Surface; +struct SDL_RWops; + +namespace Mangle +{ + namespace Rend2D + { + /// SDL-implementation of Sprite + struct SDLGL_Sprite : Sprite + { + /** Draw a sprite in the given position. Can only draw other SDL + sprites. + */ + void draw(Sprite *s, // Must be SDLGL_Sprite + int x, int y, // Destination position + int sx=0, int sy=0, // Source position + int w=-1, int h=-1 // Amount to draw. -1 means remainder. + ); + + SDLGL_Sprite(SDL_Surface *s, bool autoDelete=true); + ~SDLGL_Sprite(); + + // Information retrieval + virtual int width(); + virtual int height(); + SDL_Surface *getSurface() { return surface; } + + // Fill with a given pixel value + void fill(int value); + + private: + // The SDL surface + SDL_Surface* surface; + + // If true, delete this surface when the canvas is destructed + bool autoDel; + }; + + class SDLGLDriver : public Driver + { + // The main display surface + SDLGL_Sprite *display; + + // The screen surface. This is completely unused. + SDL_Surface *screen; + + // The display surface and main GL texture. These are used when + // drawing the entire screen as one surface, as a drop-in + // replacement for SDLDriver. + SDL_Surface *realDisp; + unsigned int texture; + int nOfColors, texture_format; + + public: + SDLGLDriver(); + ~SDLGLDriver(); + + /// Sets the video mode. Will create the window if it is not + /// already set up. Note that for SDL, bpp=0 means use current + /// bpp. + void setVideoMode(int width, int height, int bpp=0, bool fullscreen=false); + + /// Update the screen + void update(); + + /// Calls SDL_GL_SwapBuffers + void swap(); + + /// Draw surface to screen but do not call SDL_GL_SwapBuffers() + void updateNoSwap(); + + /// Set the window title, as well as the title of the window + /// when "iconified" + void setWindowTitle(const std::string &title, + const std::string &icon); + + // Include overloads from our Glorious parent + using Driver::setWindowTitle; + + /// Load sprite from an image file, using SDL_image. + Sprite* loadImage(const std::string &file); + + /// Load sprite from an SDL_RWops structure. autoFree determines + /// whether the RWops struct should be closed/freed after use. + Sprite* loadImage(SDL_RWops *src, bool autoFree=false); + + /// Load a sprite from an image file stored in memory. Uses + /// SDL_image. + Sprite* loadImage(const void* data, size_t size); + + /// Set gamma value + void setGamma(float gamma) { setGamma(gamma,gamma,gamma); } + + /// Set gamma individually for red, green, blue + void setGamma(float red, float green, float blue); + + /// Convert an existing SDL surface into a sprite + Sprite* spriteFromSDL(SDL_Surface *surf, bool autoFree = true); + + // Get width and height + int width() { return display ? display->width() : 0; } + int height() { return display ? display->height() : 0; } + + /// Get the screen sprite + Sprite *getScreen() { return display; } + + /// Not really a graphic-related function, but very + /// handly. Sleeps the given number of milliseconds using + /// SDL_Delay(). + void sleep(int ms); + + /// Get the number of ticks since SDL initialization, using + /// SDL_GetTicks(). + unsigned int ticks(); + }; + } +} +#endif diff --git a/libs/mangle/rend2d/sprite.hpp b/libs/mangle/rend2d/sprite.hpp new file mode 100644 index 000000000..f49da6cb6 --- /dev/null +++ b/libs/mangle/rend2d/sprite.hpp @@ -0,0 +1,57 @@ +#ifndef MANGLE_REND2D_SPRITE_H +#define MANGLE_REND2D_SPRITE_H + +namespace Mangle +{ + namespace Rend2D + { + /** + A pointer to sprite data for direct drawing. Only to be used + while the corresponding sprite is locked. + */ + struct SpriteData + { + void *pixels; // Pixel data + int w, h; // Width and height + int pitch, bypp; // Pitch (bytes) and bytes per pixel + }; + + /** + A Sprite is either a bitmap to be drawn or an output of area + for blitting other bitmaps, or both. They are created by the + Driver. + */ + struct Sprite + { + /// Draw a sprite in the given position + virtual void draw(Sprite *s, // The sprite to draw + int x, int y, // Destination position + int sx=0, int sy=0, // Source position + int w=-1, int h=-1 // Amount to draw. -1 means remainder. + ) = 0; + + virtual ~Sprite() {} + + // Information retrieval + virtual int width() = 0; + virtual int height() = 0; + + /// Fill the sprite with the given pixel value. The pixel format + /// depends on the format of the sprite. + virtual void fill(int value) = 0; + + /// Set one pixel value. The pixel format depends on the sprite + /// format. This is not expected to be fast, and in some + /// implementations may not work at all. + virtual void pixel(int x, int y, int value) {} + + /// Lock sprite for direct drawing, and return a struct + /// containing the necessary pointer. When finished, unlock the + /// sprite with unlock(). May return NULL, if so then direct + /// drawing is not possible. + virtual const SpriteData *lock() { return NULL; } + virtual void unlock() {} + }; + } +} +#endif diff --git a/libs/mangle/rend2d/tests/.gitignore b/libs/mangle/rend2d/tests/.gitignore new file mode 100644 index 000000000..814490404 --- /dev/null +++ b/libs/mangle/rend2d/tests/.gitignore @@ -0,0 +1 @@ +*_test diff --git a/libs/mangle/rend2d/tests/Makefile b/libs/mangle/rend2d/tests/Makefile new file mode 100644 index 000000000..d430f60a9 --- /dev/null +++ b/libs/mangle/rend2d/tests/Makefile @@ -0,0 +1,15 @@ +GCC=g++ -Wall -Werror + +all: sdl_test sdl_move_test sdlgl_move_test + +sdl_test: sdl_test.cpp + $(GCC) $< ../servers/sdl_driver.cpp -o $@ -I/usr/include/SDL/ -lSDL -lSDL_image + +sdl_move_test: sdl_move_test.cpp ../servers/sdl_driver.cpp + $(GCC) $^ -o $@ -I/usr/include/SDL/ -lSDL -lSDL_image + +sdlgl_move_test: sdlgl_move_test.cpp ../servers/sdl_gl_driver.cpp + $(GCC) $^ -o $@ -I/usr/include/SDL/ -lSDL -lSDL_image -lGL + +clean: + rm *_test diff --git a/libs/mangle/rend2d/tests/output/sdl_move_test.out b/libs/mangle/rend2d/tests/output/sdl_move_test.out new file mode 100644 index 000000000..e69de29bb diff --git a/libs/mangle/rend2d/tests/output/sdl_test.out b/libs/mangle/rend2d/tests/output/sdl_test.out new file mode 100644 index 000000000..4528e1a98 --- /dev/null +++ b/libs/mangle/rend2d/tests/output/sdl_test.out @@ -0,0 +1,11 @@ +Loading SDL driver. +Creating window. +Current mode: 640x480 +Setting fancy title, cause we like fancy titles. +Loading tile1-blue.png from file. +Loading tile1-yellow.png from memory. +Going bananas. +Taking a breather. +WOW DID YOU SEE THAT!? +Mucking about with the gamma settings +Done. diff --git a/libs/mangle/rend2d/tests/output/sdlgl_move_test.out b/libs/mangle/rend2d/tests/output/sdlgl_move_test.out new file mode 100644 index 000000000..e69de29bb diff --git a/libs/mangle/rend2d/tests/sdl_move_test.cpp b/libs/mangle/rend2d/tests/sdl_move_test.cpp new file mode 100644 index 000000000..bfbca98fa --- /dev/null +++ b/libs/mangle/rend2d/tests/sdl_move_test.cpp @@ -0,0 +1,30 @@ +#include +#include + +using namespace std; + +#include "../servers/sdl_driver.hpp" + +using namespace Mangle::Rend2D; + +int main() +{ + SDLDriver sdl; + + sdl.setVideoMode(640,480,0,false); + sdl.setWindowTitle("Testing 123"); + Sprite *screen = sdl.getScreen(); + const char* imgName = "tile1-blue.png"; + Sprite *image = sdl.loadImage(imgName); + + for(int frames=0; frames<170; frames++) + { + screen->fill(0); + for(int j=0; j<10; j++) + for(int i=0; i<25; i++) + screen->draw(image, 2*frames+30*j, 20*i); + sdl.update(); + sdl.sleep(10); + } + return 0; +} diff --git a/libs/mangle/rend2d/tests/sdl_test.cpp b/libs/mangle/rend2d/tests/sdl_test.cpp new file mode 100644 index 000000000..0355112e6 --- /dev/null +++ b/libs/mangle/rend2d/tests/sdl_test.cpp @@ -0,0 +1,65 @@ +#include +#include + +using namespace std; + +#include "../servers/sdl_driver.hpp" + +using namespace Mangle::Rend2D; + +int main() +{ + cout << "Loading SDL driver.\n"; + SDLDriver sdl; + + cout << "Creating window.\n"; + sdl.setVideoMode(640,480); + cout << "Current mode: " << sdl.width() << "x" << sdl.height() << endl; + + cout << "Setting fancy title, cause we like fancy titles.\n"; + sdl.setWindowTitle("Chief executive window"); + + // Display surface + Sprite *screen = sdl.getScreen(); + + const char* imgName = "tile1-blue.png"; + cout << "Loading " << imgName << " from file.\n"; + Sprite *image = sdl.loadImage(imgName); + + const char* imgName2 = "tile1-yellow.png"; + cout << "Loading " << imgName2 << " from memory.\n"; + Sprite *image2; + { + // This is hard-coded for file sizes below 500 bytes, so obviously + // you shouldn't mess with the image files. + ifstream file(imgName2, ios::binary); + char buf[500]; + file.read(buf, 500); + int size = file.gcount(); + image2 = sdl.loadImage(buf, size); + } + + cout << "Going bananas.\n"; + for(int i=1; i<20; i++) + screen->draw(image, 30*i, 20*i); + + cout << "Taking a breather.\n"; + sdl.update(); + for(int i=1; i<20; i++) + screen->draw(image2, 30*(20-i), 20*i); + sdl.sleep(800); + sdl.update(); + cout << "WOW DID YOU SEE THAT!?\n"; + sdl.sleep(800); + + cout << "Mucking about with the gamma settings\n"; + sdl.setGamma(2.0, 0.1, 0.8); + sdl.sleep(100); + sdl.setGamma(0.6, 2.1, 2.1); + sdl.sleep(100); + sdl.setGamma(1.6); + sdl.sleep(100); + + cout << "Done.\n"; + return 0; +} diff --git a/libs/mangle/rend2d/tests/sdlgl_move_test.cpp b/libs/mangle/rend2d/tests/sdlgl_move_test.cpp new file mode 100644 index 000000000..b769ee837 --- /dev/null +++ b/libs/mangle/rend2d/tests/sdlgl_move_test.cpp @@ -0,0 +1,31 @@ +#include +#include + +using namespace std; + +#include "../servers/sdl_gl_driver.hpp" + +using namespace Mangle::Rend2D; + +int main() +{ + SDLGLDriver sdl; + + sdl.setVideoMode(640,480,0,false); + sdl.setWindowTitle("Testing 123"); + Sprite *screen = sdl.getScreen(); + const char* imgName = "tile1-blue.png"; + Sprite *image = sdl.loadImage(imgName); + + for(int frames=0; frames<170; frames++) + { + screen->fill(0); + for(int j=0; j<10; j++) + for(int i=0; i<25; i++) + screen->draw(image, 2*frames+30*j, 20*i); + sdl.update(); + sdl.sleep(5); + } + + return 0; +} diff --git a/libs/mangle/rend2d/tests/test.sh b/libs/mangle/rend2d/tests/test.sh new file mode 100755 index 000000000..2d07708ad --- /dev/null +++ b/libs/mangle/rend2d/tests/test.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +make || exit + +mkdir -p output + +PROGS=*_test + +for a in $PROGS; do + if [ -f "output/$a.out" ]; then + echo "Running $a:" + ./$a | diff output/$a.out - + else + echo "Creating $a.out" + ./$a > "output/$a.out" + git add "output/$a.out" + fi +done diff --git a/libs/mangle/rend2d/tests/tile1-blue.png b/libs/mangle/rend2d/tests/tile1-blue.png new file mode 100644 index 000000000..066e6f8eb Binary files /dev/null and b/libs/mangle/rend2d/tests/tile1-blue.png differ diff --git a/libs/mangle/rend2d/tests/tile1-yellow.png b/libs/mangle/rend2d/tests/tile1-yellow.png new file mode 100644 index 000000000..2aaf9015d Binary files /dev/null and b/libs/mangle/rend2d/tests/tile1-yellow.png differ diff --git a/libs/mangle/sound/.gitignore b/libs/mangle/sound/.gitignore new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/libs/mangle/sound/.gitignore @@ -0,0 +1 @@ + diff --git a/libs/mangle/sound/clients/ogre_listener_mover.hpp b/libs/mangle/sound/clients/ogre_listener_mover.hpp new file mode 100644 index 000000000..739c08a13 --- /dev/null +++ b/libs/mangle/sound/clients/ogre_listener_mover.hpp @@ -0,0 +1,79 @@ +#ifndef MANGLE_SOUND_OGRELISTENERMOVER_H +#define MANGLE_SOUND_OGRELISTENERMOVER_H + +#include +#include +#include "../output.hpp" + +namespace Mangle { +namespace Sound { + + /** This class lets a sound listener (ie. the SoundFactory) track a + given camera in Ogre3D. The position and orientation of the + listener will be updated to match the camera whenever the camera + is moved. + */ + struct OgreListenerMover : Ogre::Camera::Listener + { + OgreListenerMover(Mangle::Sound::SoundFactoryPtr snd) + : soundFact(snd), camera(NULL) + {} + + /// Follow a camera. WARNING: This will OVERRIDE any other + /// MovableObject::Listener you may have attached to the camera. + void followCamera(Ogre::Camera *cam) + { + camera = cam; + camera->addListener(this); + } + + void unfollowCamera() + { + // If the camera is null, this object wasn't following a camera. + // It doesn't make sense to call unfollow + assert(camera != NULL); + + camera->removeListener(this); + camera = NULL; + } + + private: + Mangle::Sound::SoundFactoryPtr soundFact; + Ogre::Camera *camera; + Ogre::Vector3 pos, dir, up; + + /// From Camera::Listener. This is called once per + /// frame. Unfortunately, Ogre doesn't allow us to be notified + /// only when the camera itself has moved, so we must poll every + /// frame. + void cameraPreRenderScene(Ogre::Camera *cam) + { + assert(cam == camera); + + Ogre::Vector3 nPos, nDir, nUp; + + nPos = camera->getPosition(); + nDir = camera->getDirection(); + nUp = camera->getUp(); + + // Don't bother the sound system needlessly + if(nDir != dir || nPos != pos || nUp != up) + { + pos = nPos; + dir = nDir; + up = nUp; + + soundFact->setListenerPos(pos.x, pos.y, pos.z, + dir.x, dir.y, dir.z, + up.x, up.y, up.z); + } + } + + void cameraDestroyed(Ogre::Camera *cam) + { + assert(cam == camera); + camera = NULL; + } + }; +}} +#endif diff --git a/libs/mangle/sound/clients/ogre_output_updater.hpp b/libs/mangle/sound/clients/ogre_output_updater.hpp new file mode 100644 index 000000000..b73168c75 --- /dev/null +++ b/libs/mangle/sound/clients/ogre_output_updater.hpp @@ -0,0 +1,31 @@ +#ifndef MANGLE_SOUND_OGREUPDATER_H +#define MANGLE_SOUND_OGREUPDATER_H + +/* + This Ogre FrameListener calls update on a SoundFactory + */ + +#include +#include "../output.hpp" +#include + +namespace Mangle { +namespace Sound { + + struct OgreOutputUpdater : Ogre::FrameListener + { + Mangle::Sound::SoundFactoryPtr driver; + + OgreOutputUpdater(Mangle::Sound::SoundFactoryPtr drv) + : driver(drv) + { assert(drv->needsUpdate); } + + bool frameStarted(const Ogre::FrameEvent &evt) + { + driver->update(); + return true; + } + }; +}} + +#endif diff --git a/libs/mangle/sound/filters/input_filter.hpp b/libs/mangle/sound/filters/input_filter.hpp new file mode 100644 index 000000000..00ee18766 --- /dev/null +++ b/libs/mangle/sound/filters/input_filter.hpp @@ -0,0 +1,68 @@ +#ifndef MANGLE_INPUT_FILTER_H +#define MANGLE_INPUT_FILTER_H + +#include "../output.hpp" + +#include + +namespace Mangle { +namespace Sound { + +/** + @brief This filter class adds file loading capabilities to a + Sound::SoundFactory class, by associating a SampleSourceLoader with + it. + + The class takes an existing SoundFactory able to load streams, and + associates a SampleSourceLoader with it. The combined class is able + to load files directly. */ +class InputFilter : public SoundFactory +{ + protected: + SoundFactoryPtr snd; + SampleSourceLoaderPtr inp; + + public: + /// Empty constructor + InputFilter() {} + + /// Assign an input manager and a sound manager to this object + InputFilter(SoundFactoryPtr _snd, SampleSourceLoaderPtr _inp) + { set(_snd, _inp); } + + /// Assign an input manager and a sound manager to this object + void set(SoundFactoryPtr _snd, SampleSourceLoaderPtr _inp) + { + inp = _inp; + snd = _snd; + + // Set capabilities + needsUpdate = snd->needsUpdate; + has3D = snd->has3D; + canLoadStream = inp->canLoadStream; + + // Both these should be true, or the use of this class is pretty + // pointless + canLoadSource = snd->canLoadSource; + canLoadFile = inp->canLoadFile; + assert(canLoadSource && canLoadFile); + } + + virtual SoundPtr load(const std::string &file) + { return loadRaw(inp->load(file)); } + + virtual SoundPtr load(Stream::StreamPtr input) + { return loadRaw(inp->load(input)); } + + virtual SoundPtr loadRaw(SampleSourcePtr input) + { return snd->loadRaw(input); } + + virtual void update() { snd->update(); } + virtual void setListenerPos(float x, float y, float z, + float fx, float fy, float fz, + float ux, float uy, float uz) + { snd->setListenerPos(x,y,z,fx,fy,fz,ux,uy,uz); } +}; + +}} +#endif diff --git a/libs/mangle/sound/filters/openal_audiere.hpp b/libs/mangle/sound/filters/openal_audiere.hpp new file mode 100644 index 000000000..5b9b51824 --- /dev/null +++ b/libs/mangle/sound/filters/openal_audiere.hpp @@ -0,0 +1,24 @@ +#ifndef MANGLE_AUDIERE_OPENAL_H +#define MANGLE_AUDIERE_OPENAL_H + +#include "input_filter.hpp" +#include "../sources/audiere_source.hpp" +#include "../outputs/openal_out.hpp" + +namespace Mangle { +namespace Sound { + +/// A InputFilter that adds audiere decoding to OpenAL. Audiere has +/// it's own output, but OpenAL sports 3D and other advanced features. +class OpenAL_Audiere_Factory : public InputFilter +{ + public: + OpenAL_Audiere_Factory() + { + set(SoundFactoryPtr(new OpenAL_Factory), + SampleSourceLoaderPtr(new AudiereLoader)); + } +}; + +}} +#endif diff --git a/libs/mangle/sound/filters/openal_ffmpeg.hpp b/libs/mangle/sound/filters/openal_ffmpeg.hpp new file mode 100644 index 000000000..42c76af0c --- /dev/null +++ b/libs/mangle/sound/filters/openal_ffmpeg.hpp @@ -0,0 +1,23 @@ +#ifndef MANGLE_FFMPEG_OPENAL_H +#define MANGLE_FFMPEG_OPENAL_H + +#include "input_filter.hpp" +#include "../sources/ffmpeg_source.hpp" +#include "../outputs/openal_out.hpp" + +namespace Mangle { +namespace Sound { + +/// A InputFilter that adds ffmpeg decoding to OpenAL. +class OpenAL_FFMpeg_Factory : public InputFilter +{ + public: + OpenAL_FFMpeg_Factory() + { + set(SoundFactoryPtr(new OpenAL_Factory), + SampleSourceLoaderPtr(new FFMpegLoader)); + } +}; + +}} +#endif diff --git a/libs/mangle/sound/filters/openal_mpg123.hpp b/libs/mangle/sound/filters/openal_mpg123.hpp new file mode 100644 index 000000000..bfd926c0b --- /dev/null +++ b/libs/mangle/sound/filters/openal_mpg123.hpp @@ -0,0 +1,24 @@ +#ifndef MANGLE_MPG123_OPENAL_H +#define MANGLE_MPG123_OPENAL_H + +#include "input_filter.hpp" +#include "../sources/mpg123_source.hpp" +#include "../outputs/openal_out.hpp" + +namespace Mangle { +namespace Sound { + +/// A InputFilter that adds mpg123 decoding to OpenAL. Only supports +/// MP3 files. +class OpenAL_Mpg123_Factory : public InputFilter +{ + public: + OpenAL_Mpg123_Factory() + { + set(SoundFactoryPtr(new OpenAL_Factory), + SampleSourceLoaderPtr(new Mpg123Loader)); + } +}; + +}} +#endif diff --git a/libs/mangle/sound/filters/openal_sndfile.hpp b/libs/mangle/sound/filters/openal_sndfile.hpp new file mode 100644 index 000000000..fd7e78025 --- /dev/null +++ b/libs/mangle/sound/filters/openal_sndfile.hpp @@ -0,0 +1,24 @@ +#ifndef MANGLE_SNDFILE_OPENAL_H +#define MANGLE_SNDFILE_OPENAL_H + +#include "input_filter.hpp" +#include "../sources/libsndfile.hpp" +#include "../outputs/openal_out.hpp" + +namespace Mangle { +namespace Sound { + +/// A InputFilter that adds libsnd decoding to OpenAL. libsndfile +/// supports most formats except MP3. +class OpenAL_SndFile_Factory : public InputFilter +{ + public: + OpenAL_SndFile_Factory() + { + set(SoundFactoryPtr(new OpenAL_Factory), + SampleSourceLoaderPtr(new SndFileLoader)); + } +}; + +}} +#endif diff --git a/libs/mangle/sound/filters/openal_sndfile_mpg123.hpp b/libs/mangle/sound/filters/openal_sndfile_mpg123.hpp new file mode 100644 index 000000000..6e5db4d0e --- /dev/null +++ b/libs/mangle/sound/filters/openal_sndfile_mpg123.hpp @@ -0,0 +1,33 @@ +#ifndef MANGLE_SNDFILE_MPG123_OPENAL_H +#define MANGLE_SNDFILE_MPG123_OPENAL_H + +#include "input_filter.hpp" +#include "source_splicer.hpp" +#include "../sources/mpg123_source.hpp" +#include "../sources/libsndfile.hpp" +#include "../outputs/openal_out.hpp" + +namespace Mangle { +namespace Sound { + +/// A InputFilter that uses OpenAL for output, and mpg123 (for MP3) + +/// libsndfile (for everything else) to decode files. Can only load +/// from the file system, and uses the file name to differentiate +/// between mp3 and non-mp3 types. +class OpenAL_SndFile_Mpg123_Factory : public InputFilter +{ + public: + OpenAL_SndFile_Mpg123_Factory() + { + SourceSplicer *splice = new SourceSplicer; + + splice->add("mp3", SampleSourceLoaderPtr(new Mpg123Loader)); + splice->setDefault(SampleSourceLoaderPtr(new SndFileLoader)); + + set(SoundFactoryPtr(new OpenAL_Factory), + SampleSourceLoaderPtr(splice)); + } +}; + +}} +#endif diff --git a/libs/mangle/sound/filters/openal_various.hpp b/libs/mangle/sound/filters/openal_various.hpp new file mode 100644 index 000000000..945b3dabd --- /dev/null +++ b/libs/mangle/sound/filters/openal_various.hpp @@ -0,0 +1,39 @@ +#ifndef MANGLE_VARIOUS_OPENAL_H +#define MANGLE_VARIOUS_OPENAL_H + +#include "input_filter.hpp" +#include "source_splicer.hpp" +#include "../sources/mpg123_source.hpp" +#include "../sources/wav_source.hpp" +#include "../outputs/openal_out.hpp" + +namespace Mangle { +namespace Sound { + +/** A InputFilter that uses OpenAL for output, and load input from + various individual sources, depending on file extension. Currently + supports: + + MP3: mpg123 + WAV: custom wav loader (PCM only) + + This could be an alternative to using eg. 3rd party decoder + libraries like libsndfile. + */ +class OpenAL_Various_Factory : public InputFilter +{ + public: + OpenAL_Various_Factory() + { + SourceSplicer *splice = new SourceSplicer; + + splice->add("mp3", SampleSourceLoaderPtr(new Mpg123Loader)); + splice->add("wav", SampleSourceLoaderPtr(new WavLoader)); + + set(SoundFactoryPtr(new OpenAL_Factory), + SampleSourceLoaderPtr(splice)); + } +}; + +}} +#endif diff --git a/libs/mangle/sound/filters/pure_filter.hpp b/libs/mangle/sound/filters/pure_filter.hpp new file mode 100644 index 000000000..1e8b9f92c --- /dev/null +++ b/libs/mangle/sound/filters/pure_filter.hpp @@ -0,0 +1,72 @@ +#ifndef MANGLE_SOUND_OUTPUT_PUREFILTER_H +#define MANGLE_SOUND_OUTPUT_PUREFILTER_H + +#include "../output.hpp" + +namespace Mangle +{ + namespace Sound + { + // For use in writing other filters + class SoundFilter : public Sound + { + protected: + SoundPtr client; + + public: + SoundFilter(SoundPtr c) : client(c) {} + void play() { client->play(); } + void stop() { client->stop(); } + void pause() { client->pause(); } + bool isPlaying() const { return client->isPlaying(); } + void setVolume(float f) { client->setVolume(f); } + void setPan(float f) { client->setPan(f); } + void setPos(float x, float y, float z) + { client->setPos(x,y,z); } + void setPitch(float p) { client->setPitch(p); } + void setRepeat(bool b) { client->setRepeat(b); } + void setRange(float a, float b=0, float c=0) + { client->setRange(a,b,c); } + void setStreaming(bool b) { client->setStreaming(b); } + + // The clone() function is not implemented here, as you will + // almost certainly want to override it yourself + }; + + class FactoryFilter : public SoundFactory + { + protected: + SoundFactoryPtr client; + + public: + FactoryFilter(SoundFactoryPtr c) : client(c) + { + needsUpdate = client->needsUpdate; + has3D = client->has3D; + canLoadFile = client->canLoadFile; + canLoadStream = client->canLoadStream; + canLoadSource = client->canLoadSource; + } + + SoundPtr loadRaw(SampleSourcePtr input) + { return client->loadRaw(input); } + + SoundPtr load(Stream::StreamPtr input) + { return client->load(input); } + + SoundPtr load(const std::string &file) + { return client->load(file); } + + void update() + { client->update(); } + + void setListenerPos(float x, float y, float z, + float fx, float fy, float fz, + float ux, float uy, float uz) + { + client->setListenerPos(x,y,z,fx,fy,fz,ux,uy,uz); + } + }; + } +} +#endif diff --git a/libs/mangle/sound/filters/source_splicer.hpp b/libs/mangle/sound/filters/source_splicer.hpp new file mode 100644 index 000000000..9c7623086 --- /dev/null +++ b/libs/mangle/sound/filters/source_splicer.hpp @@ -0,0 +1,90 @@ +#ifndef MANGLE_SOUND_SOURCE_SPLICE_H +#define MANGLE_SOUND_SOURCE_SPLICE_H + +#include "../source.hpp" +#include +#include +#include +#include + +namespace Mangle +{ + namespace Sound + { + class SourceSplicer : public SampleSourceLoader + { + struct SourceType + { + std::string type; + SampleSourceLoaderPtr loader; + }; + + typedef std::list TypeList; + TypeList list; + SampleSourceLoaderPtr catchAll; + + static bool isMatch(char a, char b) + { + if(a >= 'A' && a <= 'Z') + a += 'a' - 'A'; + if(b >= 'A' && b <= 'Z') + b += 'a' - 'A'; + return a == b; + } + + public: + SourceSplicer() + { + canLoadStream = false; + canLoadFile = true; + } + + void add(const std::string &type, SampleSourceLoaderPtr fact) + { + SourceType tp; + tp.type = type; + tp.loader = fact; + list.push_back(tp); + } + + void setDefault(SampleSourceLoaderPtr def) + { + catchAll = def; + } + + SampleSourcePtr load(const std::string &file) + { + // Search the list for this file type. + for(TypeList::iterator it = list.begin(); + it != list.end(); it++) + { + const std::string &t = it->type; + + int diff = file.size() - t.size(); + if(diff < 0) continue; + + bool match = true; + for(unsigned i=0; iloader->load(file); + } + // If not found, use the catch-all + if(catchAll) + return catchAll->load(file); + + throw std::runtime_error("No handler for sound file " + file); + } + + SampleSourcePtr load(Stream::StreamPtr input) { assert(0); } + }; + } +} + +#endif diff --git a/libs/mangle/sound/output.hpp b/libs/mangle/sound/output.hpp new file mode 100644 index 000000000..9bbdebb2c --- /dev/null +++ b/libs/mangle/sound/output.hpp @@ -0,0 +1,180 @@ +#ifndef MANGLE_SOUND_OUTPUT_H +#define MANGLE_SOUND_OUTPUT_H + +#include +#include + +#include "source.hpp" +#include "../stream/stream.hpp" + +namespace Mangle { +namespace Sound { + +/// Abstract interface for a single playable sound +/** This class represents one sound outlet, which may be played, + stopped, paused and so on. + + Sound instances are created from the SoundFactory class. Sounds + may be connected to a SampleSource or read directly from a file, + and they may support 3d sounds, looping and other features + depending on the capabilities of the backend system. + + To create multiple instances of one sound, it is recommended to + 'clone' an existing instance instead of reloading it from + file. Cloned sounds will often (depending on the back-end) use + less memory due to shared buffers. +*/ +class Sound; +typedef boost::shared_ptr SoundPtr; +typedef boost::weak_ptr WSoundPtr; + +class Sound +{ + public: + /// Play or resume the sound + virtual void play() = 0; + + /// Stop the sound + virtual void stop() = 0; + + /// Pause the sound, may be resumed later + virtual void pause() = 0; + + /// Check if the sound is still playing + virtual bool isPlaying() const = 0; + + /// Set the volume. The parameter must be between 0.0 and 1.0. + virtual void setVolume(float) = 0; + + /// Set left/right pan. -1.0 is left, 0.0 is center and 1.0 is right. + virtual void setPan(float) = 0; + + /// Set pitch (1.0 is normal speed) + virtual void setPitch(float) = 0; + + /// Set range factors for 3D sounds. The meaning of the fields + /// depend on implementation. + virtual void setRange(float a, float b=0.0, float c=0.0) = 0; + + /// Set the position. May not work with all backends. + virtual void setPos(float x, float y, float z) = 0; + + /// Set loop mode + virtual void setRepeat(bool) = 0; + + /// Set streaming mode. + /** This may be used by implementations to optimize for very large + files. If streaming mode is off (default), most implementations + will load the entire file into memory before starting playback. + */ + virtual void setStreaming(bool) = 0; + + /// Create a new instance of this sound. + /** Playback status is not cloned, only the sound data + itself. Back-ends can use this as a means of sharing data and + saving memory. */ + virtual SoundPtr clone() = 0; + + /// Virtual destructor + virtual ~Sound() {} +}; + +/// Factory interface for creating Sound objects +/** The SoundFactory is the main entry point to a given sound output + system. It is used to create Sound objects, which may be connected + to a sound file or stream, and which may be individually played, + paused, and so on. + + The class also contains a set of public bools which describe the + capabilities the particular system. These should be set by + implementations (base classes) in their respective constructors. + */ +class SoundFactory +{ + public: + /// Virtual destructor + virtual ~SoundFactory() {} + + /** @brief If set to true, you should call update() regularly (every frame + or so) on this sound manager. If false, update() should not be + called. + */ + bool needsUpdate; + + /** @brief true if 3D functions are available. If false, all use of + 3D sounds and calls to setPos / setListenerPos will result in + undefined behavior. + */ + bool has3D; + + /// true if we can load sounds directly from file (containing encoded data) + bool canLoadFile; + + /// If true, we can lound sound files from a Stream (containing encoded data) + bool canLoadStream; + + /// true if we can load sounds from a SampleSource (containing raw data) + bool canLoadSource; + + /** + @brief Load a sound from a sample source. Only valid if + canLoadSource is true. + + This function loads a sound from a given stream as defined by + SampleSource. + + @param input the input source + @param stream true if the file should be streamed. + Implementations may use this for optimizing playback of + large files, but they are not required to. + @return a new Sound object + */ + virtual SoundPtr loadRaw(SampleSourcePtr input) = 0; + + /** + @brief Load a sound file from stream. Only valid if canLoadStream + is true. + + @param input audio file stream + @param stream true if the file should be streamed + @see load(InputSource*,bool) + */ + virtual SoundPtr load(Stream::StreamPtr input) = 0; + + /** + @brief Load a sound directly from file. Only valid if canLoadFile + is true. + + @param file filename + @param stream true if the file should be streamed + @see load(InputSource*,bool) + */ + virtual SoundPtr load(const std::string &file) = 0; + + /// Call this every frame if needsUpdate is true + /** + This should be called regularly (about every frame in a normal + game setting.) Implementions may use this for filling streaming + buffers and similar tasks. Implementations that do not need this + should set needsUpdate to false. + */ + virtual void update() { assert(0); } + + /// Set listener position (coordinates, front and up vectors) + /** + Only valid if has3D is true. + + @param x,y,z listener position + @param fx,fy,fz listener's looking direction + @param ux,uy,uz listener's up direction + */ + virtual void setListenerPos(float x, float y, float z, + float fx, float fy, float fz, + float ux, float uy, float uz) = 0; +}; + +typedef boost::shared_ptr SoundFactoryPtr; + +}} // Namespaces + +#endif diff --git a/libs/mangle/sound/outputs/openal_out.cpp b/libs/mangle/sound/outputs/openal_out.cpp new file mode 100644 index 000000000..f1cf39838 --- /dev/null +++ b/libs/mangle/sound/outputs/openal_out.cpp @@ -0,0 +1,492 @@ +#include "openal_out.hpp" +#include +#include + +#include "../../stream/filters/buffer_stream.hpp" + +#ifdef _WIN32 +#include +#include +#elif defined(__APPLE__) +#include +#include +#else +#include +#include +#endif + +using namespace Mangle::Sound; + +// ---- Helper functions and classes ---- + +// Static buffer used to shuffle sound data from the input into +// OpenAL. The data is only stored temporarily and then immediately +// shuffled off to the library. This is not thread safe, but it works +// fine with multiple sounds in one thread. It could be made thread +// safe simply by using thread local storage. +const size_t BSIZE = 32*1024; +static char tmp_buffer[BSIZE]; + +// Number of buffers used (per sound) for streaming sounds. Each +// buffer is of size BSIZE. Increasing this will make streaming sounds +// more fault tolerant against temporary lapses in call to update(), +// but will also increase memory usage. +// This was changed from 4 to 150 for an estimated 30 seconds tolerance. +// At some point we should replace it with a more multithreading-ish +// solution. +const int STREAM_BUF_NUM = 150; + +static void fail(const std::string &msg) +{ throw std::runtime_error("OpenAL exception: " + msg); } + +/* + Check for AL error. Since we're always calling this with string + literals, and it only makes sense to optimize for the non-error + case, the parameter is const char* rather than std::string. + + This way we don't force the compiler to create a string object each + time we're called (since the string is never used unless there's an + error), although a good compiler might have optimized that away in + any case. + */ +static void checkALError(const char *where) +{ + ALenum err = alGetError(); + if(err != AL_NO_ERROR) + { + std::string msg = where; + + const ALchar* errmsg = alGetString(err); + if(errmsg) + fail("\"" + std::string(alGetString(err)) + "\" while " + msg); + else + fail("non-specified error while " + msg + " (did you forget to initialize OpenAL?)"); + } +} + +static void getALFormat(SampleSourcePtr inp, int &fmt, int &rate) +{ + boost::int32_t rate_, ch, bits; + inp->getInfo(&rate_, &ch, &bits); + rate = rate_; + + fmt = 0; + + if(bits == 8) + { + if(ch == 1) fmt = AL_FORMAT_MONO8; + if(ch == 2) fmt = AL_FORMAT_STEREO8; + if(alIsExtensionPresent("AL_EXT_MCFORMATS")) + { + if(ch == 4) fmt = alGetEnumValue("AL_FORMAT_QUAD8"); + if(ch == 6) fmt = alGetEnumValue("AL_FORMAT_51CHN8"); + } + } + if(bits == 16) + { + if(ch == 1) fmt = AL_FORMAT_MONO16; + if(ch == 2) fmt = AL_FORMAT_STEREO16; + if(ch == 4) fmt = alGetEnumValue("AL_FORMAT_QUAD16"); + if(alIsExtensionPresent("AL_EXT_MCFORMATS")) + { + if(ch == 4) fmt = alGetEnumValue("AL_FORMAT_QUAD16"); + if(ch == 6) fmt = alGetEnumValue("AL_FORMAT_51CHN16"); + } + } + + if(fmt == 0) + fail("Unsupported input format"); +} + +/// OpenAL sound output +class Mangle::Sound::OpenAL_Sound : public Sound +{ + ALuint inst; + + // Buffers. Only the first is used for non-streaming sounds. + ALuint bufferID[STREAM_BUF_NUM]; + + // Number of buffers used + int bufNum; + + // Parameters used for filling buffers + int fmt, rate; + + // Poor mans reference counting. Might improve this later. When + // NULL, the buffer has not been set up yet. + int *refCnt; + + bool streaming; + + // Input stream + SampleSourcePtr input; + + OpenAL_Factory *owner; + bool ownerAlive; + + // Used for streamed sound list + OpenAL_Sound *next, *prev; + + void setupBuffer(); + + // Fill data into the given buffer and queue it, if there is any + // data left to queue. Assumes the buffer is already unqueued, if + // necessary. + void queueBuffer(ALuint buf) + { + // If there is no more data, do nothing + if(!input) return; + if(input->eof()) + { + input.reset(); + return; + } + + // Get some new data + size_t bytes = input->read(tmp_buffer, BSIZE); + if(bytes == 0) + { + input.reset(); + return; + } + + // Move data into the OpenAL buffer + alBufferData(buf, fmt, tmp_buffer, bytes, rate); + // Queue it + alSourceQueueBuffers(inst, 1, &buf); + checkALError("Queueing buffer data"); + } + + public: + /// Read samples from the given input buffer + OpenAL_Sound(SampleSourcePtr input, OpenAL_Factory *fact); + + /// Play an existing buffer, with a given ref counter. Used + /// internally for cloning. + OpenAL_Sound(ALuint buf, int *ref, OpenAL_Factory *fact); + + ~OpenAL_Sound(); + + // Must be called regularly on streamed sounds + void update() + { + if(!streaming) return; + if(!input) return; + + // Get the number of processed buffers + ALint count; + alGetSourcei(inst, AL_BUFFERS_PROCESSED, &count); + checkALError("getting number of unprocessed buffers"); + + for(int i=0; iupdate(); +} + +void OpenAL_Factory::notifyStreaming(OpenAL_Sound *snd) +{ + // Add the sound to the streaming list + streaming.push_back(snd); +} + +void OpenAL_Factory::notifyDelete(OpenAL_Sound *snd) +{ + // Remove the sound from the stream list + streaming.remove(snd); +} + +OpenAL_Factory::~OpenAL_Factory() +{ + // Notify remaining streamed sounds that we're dying + StreamList::iterator it = streaming.begin(); + for(;it != streaming.end(); it++) + (*it)->notifyOwnerDeath(); + + // Deinitialize sound system + if(didSetup) + { + alcMakeContextCurrent(NULL); + if(context) alcDestroyContext((ALCcontext*)context); + if(device) alcCloseDevice((ALCdevice*)device); + } +} + +// ---- OpenAL_Sound ---- + +void OpenAL_Sound::play() +{ + setupBuffer(); + alSourcePlay(inst); + checkALError("starting playback"); +} + +void OpenAL_Sound::stop() +{ + alSourceStop(inst); + checkALError("stopping"); +} + +void OpenAL_Sound::pause() +{ + alSourcePause(inst); + checkALError("pausing"); +} + +bool OpenAL_Sound::isPlaying() const +{ + ALint state; + alGetSourcei(inst, AL_SOURCE_STATE, &state); + + return state == AL_PLAYING; +} + +void OpenAL_Sound::setVolume(float volume) +{ + if(volume > 1.0) volume = 1.0; + if(volume < 0.0) volume = 0.0; + alSourcef(inst, AL_GAIN, volume); + checkALError("setting volume"); +} + +void OpenAL_Sound::setRange(float a, float b, float) +{ + alSourcef(inst, AL_REFERENCE_DISTANCE, a); + alSourcef(inst, AL_MAX_DISTANCE, b); + checkALError("setting sound ranges"); +} + +void OpenAL_Sound::setPos(float x, float y, float z) +{ + alSource3f(inst, AL_POSITION, x, y, z); + checkALError("setting position"); +} + +void OpenAL_Sound::setPitch(float pitch) +{ + alSourcef(inst, AL_PITCH, pitch); + checkALError("setting pitch"); +} + +void OpenAL_Sound::setRepeat(bool rep) +{ + alSourcei(inst, AL_LOOPING, rep?AL_TRUE:AL_FALSE); +} + +SoundPtr OpenAL_Sound::clone() +{ + setupBuffer(); + assert(!streaming && "cloning streamed sounds not supported"); + return SoundPtr(new OpenAL_Sound(bufferID[0], refCnt, owner)); +} + +// Constructor used for cloned sounds +OpenAL_Sound::OpenAL_Sound(ALuint buf, int *ref, OpenAL_Factory *fact) + : refCnt(ref), streaming(false), owner(fact), ownerAlive(false) +{ + // Increase the reference count + assert(ref != NULL); + (*refCnt)++; + + // Set up buffer + bufferID[0] = buf; + bufNum = 1; + + // Create a source + alGenSources(1, &inst); + checkALError("creating instance (clone)"); + alSourcei(inst, AL_BUFFER, bufferID[0]); + checkALError("assigning buffer (clone)"); +} + +// Constructor used for original (non-cloned) sounds +OpenAL_Sound::OpenAL_Sound(SampleSourcePtr _input, OpenAL_Factory *fact) + : refCnt(NULL), streaming(false), input(_input), owner(fact), ownerAlive(false) +{ + // Create a source + alGenSources(1, &inst); + checkALError("creating source"); + + // By default, the sound starts out in a buffer-less mode. We don't + // create a buffer until the sound is played. This gives the user + // the chance to call setStreaming(true) first. +} + +void OpenAL_Sound::setupBuffer() +{ + if(refCnt != NULL) return; + + assert(input); + + // Get the format + getALFormat(input, fmt, rate); + + // Create a cheap reference counter for the buffer + refCnt = new int; + *refCnt = 1; + + if(streaming) bufNum = STREAM_BUF_NUM; + else bufNum = 1; + + // Set up the OpenAL buffer(s) + alGenBuffers(bufNum, bufferID); + checkALError("generating buffer(s)"); + assert(bufferID[0] != 0); + + // STREAMING. + if(streaming) + { + // Just queue all the buffers with data and exit. queueBuffer() + // will work correctly also in the case where there is not + // enough data to fill all the buffers. + for(int i=0; inotifyStreaming(this); + ownerAlive = true; + + return; + } + + // NON-STREAMING. We have to load all the data and shove it into the + // buffer. + + // Does the stream support pointer operations? + if(input->hasPtr) + { + // If so, we can read the data directly from the stream + alBufferData(bufferID[0], fmt, input->getPtr(), input->size(), rate); + } + else + { + // Read the entire stream into a temporary buffer first + Mangle::Stream::BufferStream buf(input, 128*1024); + + // Then copy that into OpenAL + alBufferData(bufferID[0], fmt, buf.getPtr(), buf.size(), rate); + } + checkALError("loading sound data"); + + // We're done with the input stream, release the pointer + input.reset(); + + alSourcei(inst, AL_BUFFER, bufferID[0]); + checkALError("assigning buffer"); +} + +OpenAL_Sound::~OpenAL_Sound() +{ + // Stop + alSourceStop(inst); + + // Return sound + alDeleteSources(1, &inst); + + // Notify the factory that we quit. You will hear from our union + // rep. The bool check is to handle cases where the manager goes out + // of scope before the sounds do. In that case, don't try to contact + // the factory. + if(ownerAlive) + owner->notifyDelete(this); + + // Decrease the reference counter + if((-- (*refCnt)) == 0) + { + // We're the last owner. Delete the buffer(s) and the counter + // itself. + alDeleteBuffers(bufNum, bufferID); + checkALError("deleting buffer"); + delete refCnt; + } +} diff --git a/libs/mangle/sound/outputs/openal_out.hpp b/libs/mangle/sound/outputs/openal_out.hpp new file mode 100644 index 000000000..44d03ecf8 --- /dev/null +++ b/libs/mangle/sound/outputs/openal_out.hpp @@ -0,0 +1,44 @@ +#ifndef MANGLE_SOUND_OPENAL_OUT_H +#define MANGLE_SOUND_OPENAL_OUT_H + +#include "../output.hpp" +#include + +namespace Mangle { +namespace Sound { + +class OpenAL_Sound; + +class OpenAL_Factory : public SoundFactory +{ + void *device; + void *context; + bool didSetup; + + // List of streaming sounds that need to be updated every frame. + typedef std::list StreamList; + StreamList streaming; + + friend class OpenAL_Sound; + void notifyStreaming(OpenAL_Sound*); + void notifyDelete(OpenAL_Sound*); + + public: + /// Initialize object. Pass true (default) if you want the + /// constructor to set up the current ALCdevice and ALCcontext for + /// you. + OpenAL_Factory(bool doSetup = true); + ~OpenAL_Factory(); + + SoundPtr load(const std::string &file) { assert(0); return SoundPtr(); } + SoundPtr load(Stream::StreamPtr input) { assert(0); return SoundPtr(); } + SoundPtr loadRaw(SampleSourcePtr input); + + void update(); + void setListenerPos(float x, float y, float z, + float fx, float fy, float fz, + float ux, float uy, float uz); +}; + +}} // namespaces +#endif diff --git a/libs/mangle/sound/source.hpp b/libs/mangle/sound/source.hpp new file mode 100644 index 000000000..fbe7cf958 --- /dev/null +++ b/libs/mangle/sound/source.hpp @@ -0,0 +1,62 @@ +#ifndef MANGLE_SOUND_SOURCE_H +#define MANGLE_SOUND_SOURCE_H + +#include +#include +#include + +#include "../stream/stream.hpp" + +namespace Mangle { +namespace Sound { + +typedef boost::int32_t int32_t; + +/// A stream containing raw sound data and information about the format +class SampleSource : public Stream::Stream +{ + protected: + bool isEof; + + public: + SampleSource() : isEof(false) {} + + /// Get the sample rate, number of channels, and bits per + /// sample. NULL parameters are ignored. + virtual void getInfo(int32_t *rate, int32_t *channels, int32_t *bits) = 0; + + bool eof() const { return isEof; } + + // Disabled functions by default. You can still override them in + // subclasses. + void seek(size_t pos) { assert(0); } + size_t tell() const { assert(0); return 0; } + size_t size() const { assert(0); return 0; } +}; + +typedef boost::shared_ptr SampleSourcePtr; + +/// A factory interface for loading SampleSources from file or stream +class SampleSourceLoader +{ + public: + /// If true, the stream version of load() works + bool canLoadStream; + + /// If true, the file version of load() works + bool canLoadFile; + + /// Load a sound input source from file (if canLoadFile is true) + virtual SampleSourcePtr load(const std::string &file) = 0; + + /// Load a sound input source from stream (if canLoadStream is true) + virtual SampleSourcePtr load(Stream::StreamPtr input) = 0; + + /// Virtual destructor + virtual ~SampleSourceLoader() {} +}; + +typedef boost::shared_ptr SampleSourceLoaderPtr; + +}} // namespaces +#endif diff --git a/libs/mangle/sound/sources/audiere_source.cpp b/libs/mangle/sound/sources/audiere_source.cpp new file mode 100644 index 000000000..faaa3c8c5 --- /dev/null +++ b/libs/mangle/sound/sources/audiere_source.cpp @@ -0,0 +1,77 @@ +#include "audiere_source.hpp" + +#include "../../stream/clients/audiere_file.hpp" + +#include + +using namespace Mangle::Stream; + +static void fail(const std::string &msg) +{ throw std::runtime_error("Audiere exception: " + msg); } + +using namespace audiere; +using namespace Mangle::Sound; + +// --- SampleSource --- + +void AudiereSource::getInfo(Mangle::Sound::int32_t *rate, + Mangle::Sound::int32_t *channels, Mangle::Sound::int32_t *bits) +{ + SampleFormat fmt; + int channels_, rate_; + sample->getFormat(channels_, rate_, fmt); + *channels = channels_; + *rate = rate_; + if(bits) + { + if(fmt == SF_U8) + *bits = 8; + else if(fmt == SF_S16) + *bits = 16; + else assert(0); + } +} + +// --- Constructors --- + +AudiereSource::AudiereSource(const std::string &file) +{ + sample = OpenSampleSource(file.c_str()); + + if(!sample) + fail("Couldn't load file " + file); + + doSetup(); +} + +AudiereSource::AudiereSource(StreamPtr input) +{ + // Use our Stream::AudiereFile implementation to convert a Mangle + // 'Stream' to an Audiere 'File' + sample = OpenSampleSource(new AudiereFile(input)); + if(!sample) + fail("Couldn't load stream"); + + doSetup(); +} + +AudiereSource::AudiereSource(audiere::SampleSourcePtr src) + : sample(src) +{ assert(sample); doSetup(); } + +// Common function called from all constructors +void AudiereSource::doSetup() +{ + assert(sample); + + SampleFormat fmt; + int channels, rate; + sample->getFormat(channels, rate, fmt); + + // Calculate the size of one frame, and pass it to SampleReader. + setup(GetSampleSize(fmt) * channels); + + isSeekable = sample->isSeekable(); + hasPosition = true; + hasSize = true; +} diff --git a/libs/mangle/sound/sources/audiere_source.hpp b/libs/mangle/sound/sources/audiere_source.hpp new file mode 100644 index 000000000..d797c55c8 --- /dev/null +++ b/libs/mangle/sound/sources/audiere_source.hpp @@ -0,0 +1,48 @@ +#ifndef MANGLE_SOUND_AUDIERE_SOURCE_H +#define MANGLE_SOUND_AUDIERE_SOURCE_H + +#include "sample_reader.hpp" + +// audiere.h from 1.9.4 (latest) release uses +// cstring routines like strchr() and strlen() without +// including cstring itself. +#include +#include + +namespace Mangle { +namespace Sound { + +/// A sample source that decodes files using Audiere +class AudiereSource : public SampleReader +{ + audiere::SampleSourcePtr sample; + + size_t readSamples(void *data, size_t length) + { return sample->read(length, data); } + + void doSetup(); + + public: + /// Decode the given sound file + AudiereSource(const std::string &file); + + /// Decode the given sound stream + AudiereSource(Mangle::Stream::StreamPtr src); + + /// Read directly from an existing audiere::SampleSource + AudiereSource(audiere::SampleSourcePtr src); + + void getInfo(int32_t *rate, int32_t *channels, int32_t *bits); + + void seek(size_t pos) { sample->setPosition(pos/frameSize); } + size_t tell() const { return sample->getPosition()*frameSize; } + size_t size() const { return sample->getLength()*frameSize; } +}; + +#include "loadertemplate.hpp" + +/// A factory that loads AudiereSources from file and stream +typedef SSL_Template AudiereLoader; + +}} // Namespace +#endif diff --git a/libs/mangle/sound/sources/ffmpeg_source.cpp b/libs/mangle/sound/sources/ffmpeg_source.cpp new file mode 100644 index 000000000..6349be691 --- /dev/null +++ b/libs/mangle/sound/sources/ffmpeg_source.cpp @@ -0,0 +1,189 @@ +#include "ffmpeg_source.hpp" + +#include + +using namespace Mangle::Sound; + +// Static output buffer. Not thread safe, but supports multiple +// streams operated from the same thread. +static uint8_t outBuf[AVCODEC_MAX_AUDIO_FRAME_SIZE]; + +static void fail(const std::string &msg) +{ throw std::runtime_error("FFMpeg exception: " + msg); } + +// --- Loader --- + +static bool init = false; + +FFMpegLoader::FFMpegLoader(bool setup) +{ + if(setup && !init) + { + av_register_all(); + av_log_set_level(AV_LOG_ERROR); + init = true; + } +} + +// --- Source --- + +FFMpegSource::FFMpegSource(const std::string &file) +{ + std::string msg; + AVCodec *codec; + + if(av_open_input_file(&FmtCtx, file.c_str(), NULL, 0, NULL) != 0) + fail("Error loading audio file " + file); + + if(av_find_stream_info(FmtCtx) < 0) + { + msg = "Error in file stream " + file; + goto err; + } + + // Pick the first audio stream, if any + for(StreamNum = 0; StreamNum < FmtCtx->nb_streams; StreamNum++) + { + // Pick the first audio stream + if(FmtCtx->streams[StreamNum]->codec->codec_type == CODEC_TYPE_AUDIO) + break; + } + + if(StreamNum == FmtCtx->nb_streams) + fail("File '" + file + "' didn't contain any audio streams"); + + // Open the decoder + CodecCtx = FmtCtx->streams[StreamNum]->codec; + codec = avcodec_find_decoder(CodecCtx->codec_id); + + if(!codec || avcodec_open(CodecCtx, codec) < 0) + { + msg = "Error loading '" + file + "': "; + if(codec) + msg += "coded error"; + else + msg += "no codec found"; + goto err; + } + + // No errors, we're done + return; + + // Handle errors + err: + av_close_input_file(FmtCtx); + fail(msg); +} + +FFMpegSource::~FFMpegSource() +{ + avcodec_close(CodecCtx); + av_close_input_file(FmtCtx); +} + +void FFMpegSource::getInfo(int32_t *rate, int32_t *channels, int32_t *bits) +{ + if(rate) *rate = CodecCtx->sample_rate; + if(channels) *channels = CodecCtx->channels; + if(bits) *bits = 16; +} + +size_t FFMpegSource::read(void *data, size_t length) +{ + if(isEof) return 0; + + size_t left = length; + uint8_t *outPtr = (uint8_t*)data; + + // First, copy over any stored data we might be sitting on + { + size_t s = storage.size(); + size_t copy = s; + if(s) + { + // Make sure there's room + if(copy > left) + copy = left; + + // Copy + memcpy(outPtr, &storage[0], copy); + outPtr += copy; + left -= copy; + + // Is there anything left in the storage? + assert(s>= copy); + s -= copy; + if(s) + { + assert(left == 0); + + // Move it to the start and resize + memmove(&storage[0], &storage[copy], s); + storage.resize(s); + } + } + } + + // Next, get more input data from stream, and decode it + while(left) + { + AVPacket packet; + + // Get the next packet, if any + if(av_read_frame(FmtCtx, &packet) < 0) + break; + + // We only allow one stream per file at the moment + assert((int)StreamNum == packet.stream_index); + + // Decode the packet + int len = AVCODEC_MAX_AUDIO_FRAME_SIZE; + int tmp = avcodec_decode_audio2(CodecCtx, (int16_t*)outBuf, + &len, packet.data, packet.size); + assert(tmp < 0 || tmp == packet.size); + + // We don't need the input packet any longer + av_free_packet(&packet); + + if(tmp < 0) + fail("Error decoding audio stream"); + + // Copy whatever data we got, and advance the pointer + if(len > 0) + { + // copy = how many bytes do we copy now + size_t copy = len; + if(copy > left) + copy = left; + + // len = how many bytes are left uncopied + len -= copy; + + // copy data + memcpy(outPtr, outBuf, copy); + + // left = how much space is left in the caller output + // buffer. This loop repeats as long left is > 0 + left -= copy; + outPtr += copy; + assert(left >= 0); + + if(len > 0) + { + // There were uncopied bytes. Store them for later. + assert(left == 0); + storage.resize(len); + memcpy(&storage[0], outBuf, len); + } + } + } + + // End of loop. Return the number of bytes copied. + assert(left <= length); + + // If we're returning less than asked for, then we're done + if(left > 0) + isEof = true; + + return length - left; +} diff --git a/libs/mangle/sound/sources/ffmpeg_source.hpp b/libs/mangle/sound/sources/ffmpeg_source.hpp new file mode 100644 index 000000000..d422b9809 --- /dev/null +++ b/libs/mangle/sound/sources/ffmpeg_source.hpp @@ -0,0 +1,52 @@ +#ifndef MANGLE_SOUND_FFMPEG_H +#define MANGLE_SOUND_FFMPEG_H + +#include "../source.hpp" +#include +#include + +extern "C" +{ +#include +#include +} + +namespace Mangle { +namespace Sound { + +class FFMpegSource : public SampleSource +{ + AVFormatContext *FmtCtx; + AVCodecContext *CodecCtx; + unsigned int StreamNum; + + std::vector storage; + + public: + /// Decode the given sound file + FFMpegSource(const std::string &file); + + /// Decode the given sound stream (not supported by FFmpeg) + FFMpegSource(Mangle::Stream::StreamPtr src) { assert(0); } + + ~FFMpegSource(); + + // Overrides + void getInfo(int32_t *rate, int32_t *channels, int32_t *bits); + size_t read(void *data, size_t length); +}; + +#include "loadertemplate.hpp" + +/// A factory that loads FFMpegSources from file +class FFMpegLoader : public SSL_Template +{ + public: + + /// Sets up the libavcodec library. If you want to do your own + /// setup, send a setup=false parameter. + FFMpegLoader(bool setup=true); +}; + +}} // namespaces +#endif diff --git a/libs/mangle/sound/sources/libsndfile.cpp b/libs/mangle/sound/sources/libsndfile.cpp new file mode 100644 index 000000000..b69a2d436 --- /dev/null +++ b/libs/mangle/sound/sources/libsndfile.cpp @@ -0,0 +1,48 @@ +#include "libsndfile.hpp" + +#include +#include + +using namespace Mangle::Stream; + +static void fail(const std::string &msg) +{ throw std::runtime_error("Mangle::libsndfile: " + msg); } + +using namespace Mangle::Sound; + +void SndFileSource::getInfo(int32_t *_rate, int32_t *_channels, int32_t *_bits) +{ + *_rate = rate; + *_channels = channels; + *_bits = bits; +} + +size_t SndFileSource::readSamples(void *data, size_t length) +{ + // readf_* reads entire frames, including channels + return sf_readf_short((SNDFILE*)handle, (short*)data, length); +} + +SndFileSource::SndFileSource(const std::string &file) +{ + SF_INFO info; + info.format = 0; + handle = sf_open(file.c_str(), SFM_READ, &info); + if(handle == NULL) + fail("Failed to open " + file); + + // I THINK that using sf_read_short forces the library to convert to + // 16 bits no matter what, but the libsndfile docs aren't exactly + // very clear on this point. + channels = info.channels; + rate = info.samplerate; + bits = 16; + + // 16 bits per sample times number of channels + setup(2*channels); +} + +SndFileSource::~SndFileSource() +{ + sf_close((SNDFILE*)handle); +} diff --git a/libs/mangle/sound/sources/libsndfile.hpp b/libs/mangle/sound/sources/libsndfile.hpp new file mode 100644 index 000000000..7286cf0fe --- /dev/null +++ b/libs/mangle/sound/sources/libsndfile.hpp @@ -0,0 +1,36 @@ +#ifndef MANGLE_SOUND_SNDFILE_SOURCE_H +#define MANGLE_SOUND_SNDFILE_SOURCE_H + +#include "sample_reader.hpp" + +namespace Mangle { +namespace Sound { + +/// A sample source that decodes files using libsndfile. Supports most +/// formats except mp3. +class SndFileSource : public SampleReader +{ + void *handle; + int channels, rate, bits; + + size_t readSamples(void *data, size_t length); + + public: + /// Decode the given sound file + SndFileSource(const std::string &file); + + /// Decode the given sound stream (not supported) + SndFileSource(Mangle::Stream::StreamPtr src) { assert(0); } + + ~SndFileSource(); + + void getInfo(int32_t *rate, int32_t *channels, int32_t *bits); +}; + +#include "loadertemplate.hpp" + +/// A factory that loads SndFileSources from file and stream +typedef SSL_Template SndFileLoader; + +}} // Namespace +#endif diff --git a/libs/mangle/sound/sources/loadertemplate.hpp b/libs/mangle/sound/sources/loadertemplate.hpp new file mode 100644 index 000000000..a27a77d10 --- /dev/null +++ b/libs/mangle/sound/sources/loadertemplate.hpp @@ -0,0 +1,28 @@ +#ifndef SSL_TEMPL_H +#define SSL_TEMPL_H + +template +class SSL_Template : public SampleSourceLoader +{ + public: + + SSL_Template() + { + canLoadStream = stream; + canLoadFile = file; + } + + SampleSourcePtr load(const std::string &filename) + { + assert(canLoadFile); + return SampleSourcePtr(new SourceT(filename)); + } + + SampleSourcePtr load(Stream::StreamPtr input) + { + assert(canLoadStream); + return SampleSourcePtr(new SourceT(input)); + } +}; + +#endif diff --git a/libs/mangle/sound/sources/mpg123_source.cpp b/libs/mangle/sound/sources/mpg123_source.cpp new file mode 100644 index 000000000..24d6ecce1 --- /dev/null +++ b/libs/mangle/sound/sources/mpg123_source.cpp @@ -0,0 +1,115 @@ +#include "mpg123_source.hpp" + +#include + +#include + +using namespace Mangle::Stream; + +/* + TODOs: + + - mpg123 impressively enough supports custom stream reading. Which + means we could (and SHOULD!) support reading from Mangle::Streams + as well. But I'll save it til I need it. + + An alternative way to do this is through feeding (which they also + support), but that's more messy. + + - the library also supports output, via various other sources, + including ALSA, OSS, PortAudio, PulseAudio and SDL. Using this + library as a pure output library (if that is possible) would be a + nice shortcut over using those libraries - OTOH it's another + dependency. + + - we could implement seek(), tell() and size(), but they aren't + really necessary. Furthermore, since the returned size is only a + guess, it is not safe to rely on it. + */ + +static void fail(const std::string &msg) +{ throw std::runtime_error("Mangle::Mpg123 exception: " + msg); } + +static void checkError(int err, void *mh = NULL) +{ + if(err != MPG123_OK) + { + std::string msg; + if(mh) msg = mpg123_strerror((mpg123_handle*)mh); + else msg = mpg123_plain_strerror(err); + fail(msg); + } +} + +using namespace Mangle::Sound; + +void Mpg123Source::getInfo(int32_t *pRate, int32_t *pChannels, int32_t *pBits) +{ + // Use the values we found in the constructor + *pRate = rate; + *pChannels = channels; + *pBits = bits; +} + +size_t Mpg123Source::read(void *data, size_t length) +{ + size_t done; + // This is extraordinarily nice. I like this library. + int err = mpg123_read((mpg123_handle*)mh, (unsigned char*)data, length, &done); + assert(done <= length); + if(err == MPG123_DONE) + isEof = true; + else + checkError(err, mh); + return done; +} + +Mpg123Loader::Mpg123Loader(bool setup) +{ + // Do as we're told + if(setup) + { + int err = mpg123_init(); + checkError(err); + } + didSetup = setup; +} + +Mpg123Loader::~Mpg123Loader() +{ + // Deinitialize the library on exit + if(didSetup) + mpg123_exit(); +} + +Mpg123Source::Mpg123Source(const std::string &file) +{ + int err; + + // Create a new handle + mh = mpg123_new(NULL, &err); + if(mh == NULL) + checkError(err, mh); + + mpg123_handle *mhh = (mpg123_handle*)mh; + + // Open the file (hack around constness) + err = mpg123_open(mhh, (char*)file.c_str()); + checkError(err, mh); + + // Get the format + int encoding; + err = mpg123_getformat(mhh, &rate, &channels, &encoding); + checkError(err, mh); + if(encoding != MPG123_ENC_SIGNED_16) + fail("Unsupported encoding in " + file); + + // This is the only bit size we support. + bits = 16; +} + +Mpg123Source::~Mpg123Source() +{ + mpg123_close((mpg123_handle*)mh); + mpg123_delete((mpg123_handle*)mh); +} diff --git a/libs/mangle/sound/sources/mpg123_source.hpp b/libs/mangle/sound/sources/mpg123_source.hpp new file mode 100644 index 000000000..1ac16b530 --- /dev/null +++ b/libs/mangle/sound/sources/mpg123_source.hpp @@ -0,0 +1,47 @@ +#ifndef MANGLE_SOUND_MPG123_SOURCE_H +#define MANGLE_SOUND_MPG123_SOURCE_H + +#include "../source.hpp" +#include + +namespace Mangle { +namespace Sound { + +/// A sample source that decodes files using libmpg123. Only supports +/// MP3 files. +class Mpg123Source : public SampleSource +{ + void *mh; + long int rate; + int channels, bits; + + public: + /// Decode the given sound file + Mpg123Source(const std::string &file); + + /// Needed by SSL_Template but not yet supported + Mpg123Source(Mangle::Stream::StreamPtr data) + { assert(0); } + + ~Mpg123Source(); + + void getInfo(int32_t *rate, int32_t *channels, int32_t *bits); + size_t read(void *data, size_t length); +}; + +#include "loadertemplate.hpp" + +/// A factory that loads Mpg123Sources from file and stream +struct Mpg123Loader : SSL_Template +{ + /** Sets up libmpg123 for you, and closes it on destruction. If you + want to do this yourself, send setup=false. + */ + Mpg123Loader(bool setup=true); + ~Mpg123Loader(); +private: + bool didSetup; +}; + +}} // Namespace +#endif diff --git a/libs/mangle/sound/sources/sample_reader.cpp b/libs/mangle/sound/sources/sample_reader.cpp new file mode 100644 index 000000000..c30de654a --- /dev/null +++ b/libs/mangle/sound/sources/sample_reader.cpp @@ -0,0 +1,99 @@ +#include "sample_reader.hpp" + +#include + +using namespace Mangle::Sound; + +void SampleReader::setup(int size) +{ + pullSize = 0; + frameSize = size; + pullOver = new char[size]; +} + +SampleReader::~SampleReader() +{ + if(pullOver) + delete[] pullOver; +} + +size_t SampleReader::read(void *_data, size_t length) +{ + if(isEof) return 0; + char *data = (char*)_data; + + // Pullsize holds the number of bytes that were copied "extra" at + // the end of LAST round. If non-zero, it also means there is data + // left in the pullOver buffer. + if(pullSize) + { + // Amount of data left + size_t doRead = frameSize - pullSize; + assert(doRead > 0); + + // Make sure we don't read more than we're supposed to + if(doRead > length) doRead = length; + + memcpy(data, pullOver+pullSize, doRead); + + // Update the number of bytes now copied + pullSize += doRead; + assert(pullSize <= frameSize); + + if(pullSize < frameSize) + { + // There is STILL data left in the pull buffer, and we've + // done everything we were supposed to. Leave it and return. + assert(doRead == length); + return doRead; + } + + // Set up variables for further reading below. No need to update + // pullSize, it is overwritten anyway. + length -= doRead; + data += doRead; + } + + // Number of whole frames + size_t frames = length / frameSize; + + // Read the data + size_t res = readSamples(data, frames); + assert(res <= frames); + + // Total bytes read + size_t num = res*frameSize; + data += num; + + if(res < frames) + { + // End of stream. + isEof = true; + // Determine how much we read + return data-(char*)_data; + } + + // Determine the overshoot + pullSize = length - num; + assert(pullSize < frameSize && pullSize >= 0); + + // Are we missing data? + if(pullSize) + { + // Fill in one sample + res = readSamples(pullOver,1); + assert(res == 1 || res == 0); + if(res) + { + // Move as much as we can into the output buffer + memcpy(data, pullOver, pullSize); + data += pullSize; + } + else + // Failed reading, we're out of data + isEof = true; + } + + // Return the total number of bytes stored + return data-(char*)_data; +} diff --git a/libs/mangle/sound/sources/sample_reader.hpp b/libs/mangle/sound/sources/sample_reader.hpp new file mode 100644 index 000000000..89ddf1f65 --- /dev/null +++ b/libs/mangle/sound/sources/sample_reader.hpp @@ -0,0 +1,48 @@ +#ifndef MANGLE_SOUND_SAMPLE_READER_H +#define MANGLE_SOUND_SAMPLE_READER_H + +#include "../source.hpp" + +namespace Mangle { +namespace Sound { + + /* This is a helper base class for other SampleSource + implementations. Certain sources (like Audiere and libsndfile) + insist on reading whole samples rather than bytes. This class + compensates for that, and allows you to read bytes rather than + samples. + + There are two ways for subclasses to use this class. EITHER call + setup() with the size of frameSize. This will allocate a buffer, + which the destructor frees. OR set frameSize manually and + manipulate the pullOver pointer yourself. In that case you MUST + reset it to NULL if you don't want the destructor to call + delete[] on it. + */ +class SampleReader : public SampleSource +{ + // How much of the above buffer is in use. + int pullSize; + +protected: + // Pullover buffer + char* pullOver; + + // Size of one frame, in bytes. This is also the size of the + // pullOver buffer. + int frameSize; + + // The parameter gives the size of one sample/frame, in bytes. + void setup(int); + + // Read the given number of samples, in multiples of frameSize. Does + // not have to set or respect isEof. + virtual size_t readSamples(void *data, size_t num) = 0; + + public: + SampleReader() : pullSize(0), pullOver(NULL) {} + ~SampleReader(); + size_t read(void *data, size_t length); +}; +}} // Namespace +#endif diff --git a/libs/mangle/sound/sources/stream_source.hpp b/libs/mangle/sound/sources/stream_source.hpp new file mode 100644 index 000000000..43c605a00 --- /dev/null +++ b/libs/mangle/sound/sources/stream_source.hpp @@ -0,0 +1,47 @@ +#ifndef MANGLE_SOUND_STREAMSOURCE_H +#define MANGLE_SOUND_STREAMSOURCE_H + +#include "../source.hpp" + +namespace Mangle { +namespace Sound { + +/// A class for reading raw samples directly from a stream. +class Stream2Samples : public SampleSource +{ + Mangle::Stream::StreamPtr inp; + int32_t rate, channels, bits; + + public: + Stream2Samples(Mangle::Stream::StreamPtr _inp, int32_t _rate, int32_t _channels, int32_t _bits) + : inp(_inp), rate(_rate), channels(_channels), bits(_bits) + { + isSeekable = inp->isSeekable; + hasPosition = inp->hasPosition; + hasSize = inp->hasSize; + hasPtr = inp->hasPtr; + } + + /// Get the sample rate, number of channels, and bits per + /// sample. NULL parameters are ignored. + void getInfo(int32_t *_rate, int32_t *_channels, int32_t *_bits) + { + if(_rate) *_rate = rate; + if(_channels) *_channels = channels; + if(_bits) *_bits = bits; + } + + size_t read(void *out, size_t count) + { return inp->read(out, count); } + + void seek(size_t pos) { inp->seek(pos); } + size_t tell() const { return inp->tell(); } + size_t size() const { return inp->size(); } + bool eof() const { return inp->eof(); } + const void *getPtr() { return inp->getPtr(); } + const void *getPtr(size_t size) { return inp->getPtr(size); } + const void *getPtr(size_t pos, size_t size) { return inp->getPtr(pos, size); } +}; + +}} // namespaces +#endif diff --git a/libs/mangle/sound/sources/wav_source.cpp b/libs/mangle/sound/sources/wav_source.cpp new file mode 100644 index 000000000..a46b3d27e --- /dev/null +++ b/libs/mangle/sound/sources/wav_source.cpp @@ -0,0 +1,99 @@ +#include "wav_source.hpp" + +#include "../../stream/servers/file_stream.hpp" + +#include + +using namespace Mangle::Stream; +using namespace Mangle::Sound; + +static void fail(const std::string &msg) +{ throw std::runtime_error("Mangle::Wav exception: " + msg); } + +void WavSource::getInfo(int32_t *pRate, int32_t *pChannels, int32_t *pBits) +{ + // Use the values we found in the constructor + *pRate = rate; + *pChannels = channels; + *pBits = bits; +} + +void WavSource::seek(size_t pos) +{ + // Seek the stream and set 'left' + assert(isSeekable); + if(pos > total) pos = total; + input->seek(dataOffset + pos); + left = total-pos; +} + +size_t WavSource::read(void *data, size_t length) +{ + if(length > left) + length = left; + size_t read = input->read(data, length); + if(read < length) + // Something went wrong + fail("WAV read error"); + return length; +} + +void WavSource::open(Mangle::Stream::StreamPtr data) +{ + input = data; + + hasPosition = true; + hasSize = true; + // If we can check position and seek in the input stream, then we + // can seek the wav data too. + isSeekable = input->isSeekable && input->hasPosition; + + // Read header + unsigned int val; + + input->read(&val,4); // header + if(val != 0x46464952) // "RIFF" + fail("Not a WAV file"); + + input->read(&val,4); // size (ignored) + input->read(&val,4); // file format + if(val != 0x45564157) // "WAVE" + fail("Not a valid WAV file"); + + input->read(&val,4); // "fmt " + input->read(&val,4); // chunk size (must be 16) + if(val != 16) + fail("Unsupported WAV format"); + + input->read(&val,2); + if(val != 1) + fail("Non-PCM (compressed) WAV files not supported"); + + // Sound data specification + channels = 0; + input->read(&channels,2); + input->read(&rate, 4); + + // Skip next 6 bytes + input->read(&val, 4); + input->read(&val, 2); + + // Bits per sample + bits = 0; + input->read(&bits,2); + + input->read(&val,4); // Data header + if(val != 0x61746164) // "data" + fail("Expected data block"); + + // Finally, read the data size + input->read(&total,4); + left = total; + + // Store the beginning of the data block for later + if(input->hasPosition) + dataOffset = input->tell(); +} + +WavSource::WavSource(const std::string &file) +{ open(StreamPtr(new FileStream(file))); } diff --git a/libs/mangle/sound/sources/wav_source.hpp b/libs/mangle/sound/sources/wav_source.hpp new file mode 100644 index 000000000..227f4da73 --- /dev/null +++ b/libs/mangle/sound/sources/wav_source.hpp @@ -0,0 +1,49 @@ +#ifndef MANGLE_SOUND_WAV_SOURCE_H +#define MANGLE_SOUND_WAV_SOURCE_H + +#include "../source.hpp" +#include + +namespace Mangle { +namespace Sound { + +/// WAV file decoder. Has no external library dependencies. +class WavSource : public SampleSource +{ + // Sound info + uint32_t rate, channels, bits; + + // Total size (of output) and bytes left + uint32_t total, left; + + // Offset in input of the beginning of the data block + size_t dataOffset; + + Mangle::Stream::StreamPtr input; + + void open(Mangle::Stream::StreamPtr); + + public: + /// Decode the given sound file + WavSource(const std::string&); + + /// Decode from stream + WavSource(Mangle::Stream::StreamPtr s) + { open(s); } + + void getInfo(int32_t *rate, int32_t *channels, int32_t *bits); + size_t read(void *data, size_t length); + + void seek(size_t); + size_t tell() const { return total-left; } + size_t size() const { return total; } + bool eof() const { return left > 0; } +}; + +#include "loadertemplate.hpp" + +/// A factory that loads WavSources from file and stream +typedef SSL_Template WavLoader; + +}} // Namespace +#endif diff --git a/libs/mangle/sound/tests/.gitignore b/libs/mangle/sound/tests/.gitignore new file mode 100644 index 000000000..814490404 --- /dev/null +++ b/libs/mangle/sound/tests/.gitignore @@ -0,0 +1 @@ +*_test diff --git a/libs/mangle/sound/tests/Makefile b/libs/mangle/sound/tests/Makefile new file mode 100644 index 000000000..6fcac72da --- /dev/null +++ b/libs/mangle/sound/tests/Makefile @@ -0,0 +1,38 @@ +GCC=g++ -I../ -Wall + +all: audiere_source_test ffmpeg_source_test openal_output_test openal_audiere_test openal_ffmpeg_test openal_mpg123_test openal_sndfile_test wav_source_test openal_various_test + +L_FFMPEG=$(shell pkg-config --libs libavcodec libavformat) +I_FFMPEG=-I/usr/include/libavcodec -I/usr/include/libavformat +L_OPENAL=$(shell pkg-config --libs openal) +L_AUDIERE=-laudiere + +wav_source_test: wav_source_test.cpp ../sources/wav_source.cpp + $(GCC) $^ -o $@ + +openal_various_test: openal_various_test.cpp ../sources/mpg123_source.cpp ../sources/wav_source.cpp ../outputs/openal_out.cpp + $(GCC) $^ -o $@ -lmpg123 ${L_OPENAL} + +openal_audiere_test: openal_audiere_test.cpp ../sources/audiere_source.cpp ../sources/sample_reader.cpp ../outputs/openal_out.cpp ../../stream/clients/audiere_file.cpp + $(GCC) $^ -o $@ $(L_AUDIERE) $(L_OPENAL) + +openal_ffmpeg_test: openal_ffmpeg_test.cpp ../sources/ffmpeg_source.cpp ../outputs/openal_out.cpp + $(GCC) $^ -o $@ $(L_FFMPEG) $(L_OPENAL) $(I_FFMPEG) + +openal_mpg123_test: openal_mpg123_test.cpp ../sources/mpg123_source.cpp ../outputs/openal_out.cpp + $(GCC) $^ -o $@ -lmpg123 ${L_OPENAL} + +openal_sndfile_test: openal_sndfile_test.cpp ../sources/libsndfile.cpp ../sources/sample_reader.cpp ../outputs/openal_out.cpp + $(GCC) $^ -o $@ -lsndfile ${L_OPENAL} + +openal_output_test: openal_output_test.cpp ../outputs/openal_out.cpp + $(GCC) $^ -o $@ $(L_OPENAL) + +audiere_source_test: audiere_source_test.cpp ../sources/audiere_source.cpp ../../stream/clients/audiere_file.cpp ../sources/sample_reader.cpp + $(GCC) $^ -o $@ $(L_AUDIERE) + +ffmpeg_source_test: ffmpeg_source_test.cpp ../sources/ffmpeg_source.cpp + $(GCC) $^ -o $@ $(L_FFMPEG) $(I_FFMPEG) + +clean: + rm *_test diff --git a/libs/mangle/sound/tests/audiere_source_test.cpp b/libs/mangle/sound/tests/audiere_source_test.cpp new file mode 100644 index 000000000..637d743b2 --- /dev/null +++ b/libs/mangle/sound/tests/audiere_source_test.cpp @@ -0,0 +1,68 @@ +#include + +#include "../../stream/servers/file_stream.hpp" +#include "../sources/audiere_source.hpp" + +#include +#include + +using namespace std; +using namespace Mangle::Stream; +using namespace Mangle::Sound; + +// Contents and size of cow.raw +void *orig; +size_t orig_size; + +void run(SampleSourcePtr &src) +{ + size_t ss = src->size(); + assert(ss == orig_size); + + cout << "Source size: " << ss << endl; + int rate, channels, bits; + src->getInfo(&rate, &channels, &bits); + cout << "rate=" << rate << "\nchannels=" << channels + << "\nbits=" << bits << endl; + + cout << "Reading entire buffer into memory\n"; + void *buf = malloc(ss); + src->read(buf, ss); + + cout << "Comparing...\n"; + if(memcmp(buf, orig, ss) != 0) + { + cout << "Oops!\n"; + assert(0); + } + + cout << "Done\n"; +} + +int main() +{ + { + cout << "Reading cow.raw first\n"; + FileStream tmp("cow.raw"); + orig_size = tmp.size(); + cout << "Size: " << orig_size << endl; + orig = malloc(orig_size); + tmp.read(orig, orig_size); + cout << "Done\n"; + } + + { + cout << "\nLoading cow.wav by filename:\n"; + SampleSourcePtr cow_file( new AudiereSource("cow.wav") ); + run(cow_file); + } + + { + cout << "\nLoading cow.wav by stream:\n"; + StreamPtr inp( new FileStream("cow.wav") ); + SampleSourcePtr cow_stream( new AudiereSource(inp) ); + run(cow_stream); + } + + return 0; +} diff --git a/libs/mangle/sound/tests/cow.raw b/libs/mangle/sound/tests/cow.raw new file mode 100644 index 000000000..c4d155bbf Binary files /dev/null and b/libs/mangle/sound/tests/cow.raw differ diff --git a/libs/mangle/sound/tests/cow.wav b/libs/mangle/sound/tests/cow.wav new file mode 100644 index 000000000..494e6c4ac Binary files /dev/null and b/libs/mangle/sound/tests/cow.wav differ diff --git a/libs/mangle/sound/tests/ffmpeg_source_test.cpp b/libs/mangle/sound/tests/ffmpeg_source_test.cpp new file mode 100644 index 000000000..f03b15b99 --- /dev/null +++ b/libs/mangle/sound/tests/ffmpeg_source_test.cpp @@ -0,0 +1,62 @@ +#include + +#include "../../stream/servers/file_stream.hpp" +#include "../sources/ffmpeg_source.hpp" + +#include +#include + +using namespace std; +using namespace Mangle::Stream; +using namespace Mangle::Sound; + +// Contents and size of cow.raw +void *orig; +size_t orig_size; + +void run(SampleSourcePtr &src) +{ + int rate, channels, bits; + src->getInfo(&rate, &channels, &bits); + cout << "rate=" << rate << "\nchannels=" << channels + << "\nbits=" << bits << endl; + + cout << "Reading entire buffer into memory\n"; + void *buf = malloc(orig_size); + size_t ss = src->read(buf, orig_size); + cout << "Actually read: " << ss << endl; + assert(ss == orig_size); + + cout << "Comparing...\n"; + if(memcmp(buf, orig, ss) != 0) + { + cout << "Oops!\n"; + assert(0); + } + + cout << "Done\n"; +} + +int main() +{ + { + cout << "Reading cow.raw first\n"; + FileStream tmp("cow.raw"); + orig_size = tmp.size(); + cout << "Size: " << orig_size << endl; + orig = malloc(orig_size); + tmp.read(orig, orig_size); + cout << "Done\n"; + } + + // Initializes the library, not used for anything else. + FFMpegLoader fm; + + { + cout << "\nLoading cow.wav by filename:\n"; + SampleSourcePtr cow_file( new FFMpegSource("cow.wav") ); + run(cow_file); + } + + return 0; +} diff --git a/libs/mangle/sound/tests/openal_audiere_test.cpp b/libs/mangle/sound/tests/openal_audiere_test.cpp new file mode 100644 index 000000000..ced7fe5d2 --- /dev/null +++ b/libs/mangle/sound/tests/openal_audiere_test.cpp @@ -0,0 +1,52 @@ +#include +#include + +#include "../../stream/servers/file_stream.hpp" +#include "../filters/openal_audiere.hpp" + +using namespace std; +using namespace Mangle::Stream; +using namespace Mangle::Sound; + +OpenAL_Audiere_Factory mg; + +void play(const char* name, bool stream=false) +{ + // Only load streams if the backend supports it + if(stream && !mg.canLoadStream) + return; + + cout << "Playing " << name; + if(stream) cout << " (from stream)"; + cout << "\n"; + + SoundPtr snd; + + try + { + if(stream) + snd = mg.load(StreamPtr(new FileStream(name))); + else + snd = mg.load(name); + + snd->play(); + + while(snd->isPlaying()) + { + usleep(10000); + if(mg.needsUpdate) mg.update(); + } + } + catch(exception &e) + { + cout << " ERROR: " << e.what() << "\n"; + } +} + +int main() +{ + play("cow.wav"); + play("owl.ogg"); + play("cow.wav", true); + return 0; +} diff --git a/libs/mangle/sound/tests/openal_ffmpeg_test.cpp b/libs/mangle/sound/tests/openal_ffmpeg_test.cpp new file mode 100644 index 000000000..d4b8e9300 --- /dev/null +++ b/libs/mangle/sound/tests/openal_ffmpeg_test.cpp @@ -0,0 +1,52 @@ +#include +#include + +#include "../../stream/servers/file_stream.hpp" +#include "../filters/openal_ffmpeg.hpp" + +using namespace std; +using namespace Mangle::Stream; +using namespace Mangle::Sound; + +OpenAL_FFMpeg_Factory mg; + +void play(const char* name, bool stream=false) +{ + // Only load streams if the backend supports it + if(stream && !mg.canLoadStream) + return; + + cout << "Playing " << name; + if(stream) cout << " (from stream)"; + cout << "\n"; + + SoundPtr snd; + + try + { + if(stream) + snd = mg.load(StreamPtr(new FileStream(name))); + else + snd = mg.load(name); + + snd->play(); + + while(snd->isPlaying()) + { + usleep(10000); + if(mg.needsUpdate) mg.update(); + } + } + catch(exception &e) + { + cout << " ERROR: " << e.what() << "\n"; + } +} + +int main() +{ + play("cow.wav"); + play("owl.ogg"); + play("cow.wav", true); + return 0; +} diff --git a/libs/mangle/sound/tests/openal_mpg123_test.cpp b/libs/mangle/sound/tests/openal_mpg123_test.cpp new file mode 100644 index 000000000..fef1a5605 --- /dev/null +++ b/libs/mangle/sound/tests/openal_mpg123_test.cpp @@ -0,0 +1,54 @@ +#include +#include + +#include "../../stream/servers/file_stream.hpp" +#include "../filters/openal_mpg123.hpp" + +using namespace std; +using namespace Mangle::Stream; +using namespace Mangle::Sound; + +OpenAL_Mpg123_Factory mg; + +void play(const char* name, bool stream=false) +{ + // Only load streams if the backend supports it + if(stream && !mg.canLoadStream) + return; + + cout << "Playing " << name; + if(stream) cout << " (from stream)"; + cout << "\n"; + + SoundPtr snd; + + try + { + if(stream) + snd = mg.load(StreamPtr(new FileStream(name))); + else + snd = mg.load(name); + + snd->setStreaming(true); + snd->play(); + + while(snd->isPlaying()) + { + usleep(10000); + if(mg.needsUpdate) mg.update(); + } + } + catch(exception &e) + { + cout << " ERROR: " << e.what() << "\n"; + } +} + +int main(int argc, char**argv) +{ + if(argc != 2) + cout << "Please specify an MP3 file\n"; + else + play(argv[1]); + return 0; +} diff --git a/libs/mangle/sound/tests/openal_output_test.cpp b/libs/mangle/sound/tests/openal_output_test.cpp new file mode 100644 index 000000000..a8059ec65 --- /dev/null +++ b/libs/mangle/sound/tests/openal_output_test.cpp @@ -0,0 +1,59 @@ +#include +#include + +#include "../../stream/servers/file_stream.hpp" +#include "../sources/stream_source.hpp" +#include "../outputs/openal_out.hpp" + +using namespace std; +using namespace Mangle::Stream; +using namespace Mangle::Sound; + +int main() +{ + cout << "Loading cow.raw\n"; + + int rate = 11025; + int chan = 1; + int bits = 16; + + cout << " rate=" << rate << "\n channels=" << chan + << "\n bits=" << bits << endl; + + StreamPtr file( new FileStream("cow.raw") ); + SampleSourcePtr source( new Stream2Samples( file, rate, chan, bits)); + + cout << "Playing\n"; + + OpenAL_Factory mg; + + SoundPtr snd = mg.loadRaw(source); + + try + { + // Try setting all kinds of stuff before playing. OpenAL_Sound + // uses delayed buffer loading, but these should still work + // without a buffer. + snd->stop(); + snd->pause(); + snd->setVolume(0.8); + snd->setPitch(0.9); + + // Also test streaming, since all the other examples test + // non-streaming sounds. + snd->setStreaming(true); + + snd->play(); + + while(snd->isPlaying()) + { + usleep(10000); + mg.update(); + } + } + catch(exception &e) + { + cout << " ERROR: " << e.what() << "\n"; + } + return 0; +} diff --git a/libs/mangle/sound/tests/openal_sndfile_test.cpp b/libs/mangle/sound/tests/openal_sndfile_test.cpp new file mode 100644 index 000000000..bd5f117a5 --- /dev/null +++ b/libs/mangle/sound/tests/openal_sndfile_test.cpp @@ -0,0 +1,52 @@ +#include +#include + +#include "../../stream/servers/file_stream.hpp" +#include "../filters/openal_sndfile.hpp" + +using namespace std; +using namespace Mangle::Stream; +using namespace Mangle::Sound; + +OpenAL_SndFile_Factory mg; + +void play(const char* name, bool stream=false) +{ + // Only load streams if the backend supports it + if(stream && !mg.canLoadStream) + return; + + cout << "Playing " << name; + if(stream) cout << " (from stream)"; + cout << "\n"; + + SoundPtr snd; + + try + { + if(stream) + snd = mg.load(StreamPtr(new FileStream(name))); + else + snd = mg.load(name); + + snd->play(); + + while(snd->isPlaying()) + { + usleep(10000); + if(mg.needsUpdate) mg.update(); + } + } + catch(exception &e) + { + cout << " ERROR: " << e.what() << "\n"; + } +} + +int main() +{ + play("cow.wav"); + play("owl.ogg"); + play("cow.wav", true); + return 0; +} diff --git a/libs/mangle/sound/tests/openal_various_test.cpp b/libs/mangle/sound/tests/openal_various_test.cpp new file mode 100644 index 000000000..9426a672e --- /dev/null +++ b/libs/mangle/sound/tests/openal_various_test.cpp @@ -0,0 +1,51 @@ +#include +#include + +#include "../../stream/servers/file_stream.hpp" +#include "../filters/openal_various.hpp" + +using namespace std; +using namespace Mangle::Stream; +using namespace Mangle::Sound; + +OpenAL_Various_Factory mg; + +void play(const char* name, bool stream=false) +{ + // Only load streams if the backend supports it + if(stream && !mg.canLoadStream) + return; + + cout << "Playing " << name; + if(stream) cout << " (from stream)"; + cout << "\n"; + + SoundPtr snd; + + try + { + if(stream) + snd = mg.load(StreamPtr(new FileStream(name))); + else + snd = mg.load(name); + + snd->play(); + + while(snd->isPlaying()) + { + usleep(10000); + if(mg.needsUpdate) mg.update(); + } + } + catch(exception &e) + { + cout << " ERROR: " << e.what() << "\n"; + } +} + +int main() +{ + play("cow.wav"); + play("cow.wav", true); + return 0; +} diff --git a/libs/mangle/sound/tests/output/audiere_source_test.out b/libs/mangle/sound/tests/output/audiere_source_test.out new file mode 100644 index 000000000..47a5a9e41 --- /dev/null +++ b/libs/mangle/sound/tests/output/audiere_source_test.out @@ -0,0 +1,21 @@ +Reading cow.raw first +Size: 37502 +Done + +Loading cow.wav by filename: +Source size: 37502 +rate=11025 +channels=1 +bits=16 +Reading entire buffer into memory +Comparing... +Done + +Loading cow.wav by stream: +Source size: 37502 +rate=11025 +channels=1 +bits=16 +Reading entire buffer into memory +Comparing... +Done diff --git a/libs/mangle/sound/tests/output/ffmpeg_source_test.out b/libs/mangle/sound/tests/output/ffmpeg_source_test.out new file mode 100644 index 000000000..1c7d49113 --- /dev/null +++ b/libs/mangle/sound/tests/output/ffmpeg_source_test.out @@ -0,0 +1,12 @@ +Reading cow.raw first +Size: 37502 +Done + +Loading cow.wav by filename: +rate=11025 +channels=1 +bits=16 +Reading entire buffer into memory +Actually read: 37502 +Comparing... +Done diff --git a/libs/mangle/sound/tests/output/openal_audiere_test.out b/libs/mangle/sound/tests/output/openal_audiere_test.out new file mode 100644 index 000000000..4fe01eac2 --- /dev/null +++ b/libs/mangle/sound/tests/output/openal_audiere_test.out @@ -0,0 +1,3 @@ +Playing cow.wav +Playing owl.ogg +Playing cow.wav (from stream) diff --git a/libs/mangle/sound/tests/output/openal_ffmpeg_test.out b/libs/mangle/sound/tests/output/openal_ffmpeg_test.out new file mode 100644 index 000000000..96e1db0f9 --- /dev/null +++ b/libs/mangle/sound/tests/output/openal_ffmpeg_test.out @@ -0,0 +1,2 @@ +Playing cow.wav +Playing owl.ogg diff --git a/libs/mangle/sound/tests/output/openal_mpg123_test.out b/libs/mangle/sound/tests/output/openal_mpg123_test.out new file mode 100644 index 000000000..e55dabbb1 --- /dev/null +++ b/libs/mangle/sound/tests/output/openal_mpg123_test.out @@ -0,0 +1 @@ +Please specify an MP3 file diff --git a/libs/mangle/sound/tests/output/openal_output_test.out b/libs/mangle/sound/tests/output/openal_output_test.out new file mode 100644 index 000000000..04392a72e --- /dev/null +++ b/libs/mangle/sound/tests/output/openal_output_test.out @@ -0,0 +1,5 @@ +Loading cow.raw + rate=11025 + channels=1 + bits=16 +Playing diff --git a/libs/mangle/sound/tests/output/openal_sndfile_test.out b/libs/mangle/sound/tests/output/openal_sndfile_test.out new file mode 100644 index 000000000..96e1db0f9 --- /dev/null +++ b/libs/mangle/sound/tests/output/openal_sndfile_test.out @@ -0,0 +1,2 @@ +Playing cow.wav +Playing owl.ogg diff --git a/libs/mangle/sound/tests/output/openal_various_test.out b/libs/mangle/sound/tests/output/openal_various_test.out new file mode 100644 index 000000000..f25a55513 --- /dev/null +++ b/libs/mangle/sound/tests/output/openal_various_test.out @@ -0,0 +1 @@ +Playing cow.wav diff --git a/libs/mangle/sound/tests/output/wav_source_test.out b/libs/mangle/sound/tests/output/wav_source_test.out new file mode 100644 index 000000000..b6fc8e6fc --- /dev/null +++ b/libs/mangle/sound/tests/output/wav_source_test.out @@ -0,0 +1,12 @@ +Source size: 37502 +rate=11025 +channels=1 +bits=16 +Reading entire buffer into memory + +Reading cow.raw +Size: 37502 + +Comparing... + +Done diff --git a/libs/mangle/sound/tests/owl.ogg b/libs/mangle/sound/tests/owl.ogg new file mode 100644 index 000000000..e992f24d4 Binary files /dev/null and b/libs/mangle/sound/tests/owl.ogg differ diff --git a/libs/mangle/sound/tests/test.sh b/libs/mangle/sound/tests/test.sh new file mode 100755 index 000000000..2d07708ad --- /dev/null +++ b/libs/mangle/sound/tests/test.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +make || exit + +mkdir -p output + +PROGS=*_test + +for a in $PROGS; do + if [ -f "output/$a.out" ]; then + echo "Running $a:" + ./$a | diff output/$a.out - + else + echo "Creating $a.out" + ./$a > "output/$a.out" + git add "output/$a.out" + fi +done diff --git a/libs/mangle/sound/tests/wav_source_test.cpp b/libs/mangle/sound/tests/wav_source_test.cpp new file mode 100644 index 000000000..749af1849 --- /dev/null +++ b/libs/mangle/sound/tests/wav_source_test.cpp @@ -0,0 +1,48 @@ +#include + +#include "../sources/wav_source.hpp" +#include "../../stream/servers/file_stream.hpp" + +#include +#include + +using namespace std; +using namespace Mangle::Sound; +using namespace Mangle::Stream; + +int main() +{ + WavSource wav("cow.wav"); + + cout << "Source size: " << wav.size() << endl; + int rate, channels, bits; + wav.getInfo(&rate, &channels, &bits); + cout << "rate=" << rate << "\nchannels=" << channels + << "\nbits=" << bits << endl; + + cout << "Reading entire buffer into memory\n"; + void *buf = malloc(wav.size()); + wav.read(buf, wav.size()); + + cout << "\nReading cow.raw\n"; + FileStream tmp("cow.raw"); + cout << "Size: " << tmp.size() << endl; + void *buf2 = malloc(tmp.size()); + tmp.read(buf2, tmp.size()); + + cout << "\nComparing...\n"; + if(tmp.size() != wav.size()) + { + cout << "SIZE MISMATCH!\n"; + assert(0); + } + + if(memcmp(buf, buf2, wav.size()) != 0) + { + cout << "CONTENT MISMATCH!\n"; + assert(0); + } + + cout << "\nDone\n"; + return 0; +} diff --git a/libs/mangle/stream/clients/audiere_file.cpp b/libs/mangle/stream/clients/audiere_file.cpp new file mode 100644 index 000000000..16bc7891a --- /dev/null +++ b/libs/mangle/stream/clients/audiere_file.cpp @@ -0,0 +1,32 @@ +#include "audiere_file.hpp" + +using namespace audiere; +using namespace Mangle::Stream; + +bool AudiereFile::seek(int pos, SeekMode mode) +{ + assert(inp->isSeekable); + assert(inp->hasPosition); + + size_t newPos; + + switch(mode) + { + case BEGIN: newPos = pos; break; + case CURRENT: newPos = pos+tell(); break; + case END: + // Seeking from the end. This requires that we're able to get + // the entire size of the stream. The pos also has to be + // non-positive. + assert(inp->hasSize); + assert(pos <= 0); + newPos = inp->size() + pos; + break; + default: + assert(0 && "invalid seek mode"); + } + + inp->seek(newPos); + return inp->tell() == newPos; + +} diff --git a/libs/mangle/stream/clients/audiere_file.hpp b/libs/mangle/stream/clients/audiere_file.hpp new file mode 100644 index 000000000..61e26f21b --- /dev/null +++ b/libs/mangle/stream/clients/audiere_file.hpp @@ -0,0 +1,38 @@ +#ifndef MANGLE_STREAM_AUDIERECLIENT_H +#define MANGLE_STREAM_AUDIERECLIENT_H + +#include +#include + +#include "../stream.hpp" + +namespace Mangle { +namespace Stream { + +/** @brief An Audiere::File that wraps a Mangle::Stream input. + + This lets Audiere read sound files from any generic archive or + file manager that supports Mangle streams. + */ +class AudiereFile : public audiere::RefImplementation +{ + StreamPtr inp; + + public: + AudiereFile(StreamPtr _inp) + : inp(_inp) {} + + /// Read 'count' bytes, return bytes successfully read + int ADR_CALL read(void *buf, int count) + { return inp->read(buf,count); } + + /// Seek, relative to specified seek mode. Returns true if successful. + bool ADR_CALL seek(int pos, audiere::File::SeekMode mode); + + /// Get current position + int ADR_CALL tell() + { assert(inp->hasPosition); return inp->tell(); } +}; + +}} // namespaces +#endif diff --git a/libs/mangle/stream/clients/io_stream.cpp b/libs/mangle/stream/clients/io_stream.cpp new file mode 100644 index 000000000..5f1edc221 --- /dev/null +++ b/libs/mangle/stream/clients/io_stream.cpp @@ -0,0 +1,221 @@ +#include "io_stream.hpp" + +// This seems to work +#ifndef EOF +#define EOF -1 +#endif + +using namespace Mangle::Stream; + +#define BSIZE 1024 + +// Streambuf for normal stream reading +class _istreambuf : public std::streambuf +{ + StreamPtr client; + char buf[BSIZE]; + +public: + _istreambuf(StreamPtr strm) : client(strm) + { + // Make sure we picked the right class + assert(client->isReadable); + assert(!client->hasPtr); + + // Tell streambuf to delegate reading operations to underflow() + setg(NULL,NULL,NULL); + + // Disallow writing + setp(NULL,NULL); + } + + /* Underflow is called when there is no more info to read in the + input buffer. We need to refill buf with new data (if any), and + set up the internal pointers with setg() to reflect the new + state. + */ + int underflow() + { + // Read some more data + size_t read = client->read(buf, BSIZE); + assert(read <= BSIZE); + + // If we're out of data, then EOF + if(read == 0) + return EOF; + + // Otherwise, set up input buffer + setg(buf, buf, buf+read); + + // Return the first char + return *((unsigned char*)buf); + } + + // Seek stream, if the source supports it. Ignores the second + // parameter as Mangle doesn't separate input and output pointers. + std::streampos seekpos(std::streampos pos, std::ios_base::openmode = std::ios_base::in) + { + // Does this stream know how to seek? + if(!client->isSeekable || !client->hasPosition) + // If not, signal an error. + return -1; + + // Set stream position and reset the buffer. + client->seek(pos); + setg(NULL,NULL,NULL); + + return client->tell(); + } +}; + +// Streambuf optimized for pointer-based input streams +class _ptrstreambuf : public std::streambuf +{ + StreamPtr client; + +public: + _ptrstreambuf(StreamPtr strm) : client(strm) + { + // Make sure we picked the right class + assert(client->isReadable); + assert(client->hasPtr); + + // seekpos() does all the work + seekpos(0); + } + + // Underflow is only called when we're at the end of the file + int underflow() { return EOF; } + + // Seek to a new position within the memory stream. This bypasses + // client->seek() entirely so isSeekable doesn't have to be set. + std::streampos seekpos(std::streampos pos, std::ios_base::openmode = std::ios_base::in) + { + // All pointer streams need a size + assert(client->hasSize); + + // Figure out how much will be left of the stream after seeking + size_t size = client->size() - pos; + + // Get a pointer + char* ptr = (char*)client->getPtr(pos,size); + + // And use it + setg(ptr,ptr,ptr+size); + + return pos; + } +}; + +// Streambuf for stream writing +class _ostreambuf : public std::streambuf +{ + StreamPtr client; + char buf[BSIZE]; + +public: + _ostreambuf(StreamPtr strm) : client(strm) + { + // Make sure we picked the right class + assert(client->isWritable); + + // Inform streambuf about our nice buffer + setp(buf, buf+BSIZE); + + // Disallow reading + setg(NULL,NULL,NULL); + } + + /* Sync means to flush (write) all current data to the output + stream. It will also set up the entire output buffer to be usable + again. + */ + int sync() + { + // Get the number of bytes that streambuf wants us to write + int num = pptr() - pbase(); + assert(num >= 0); + + // Is there any work to do? + if(num == 0) return 0; + + if((int)client->write(pbase(), num) != num) + // Inform caller that writing failed + return -1; + + // Reset output buffer pointers + setp(buf, buf+BSIZE); + + // No error + return 0; + } + + /* Called whenever the output buffer is full. + */ + int overflow(int c) + { + // First, write all existing data + if(sync()) return EOF; + + // Put the requested character in the next round of output + if(c != EOF) + { + *pptr() = c; + pbump(1); + } + + // No error + return 0; + } + + // Seek stream, if the source supports it. + std::streampos seekpos(std::streampos pos, std::ios_base::openmode = std::ios_base::out) + { + if(!client->isSeekable || !client->hasPosition) + return -1; + + // Flush data and reset buffers + sync(); + + // Set stream position + client->seek(pos); + + return client->tell(); + } +}; + +MangleIStream::MangleIStream(StreamPtr inp) + : std::istream(NULL) +{ + assert(inp->isReadable); + + // Pick the right streambuf implementation based on whether the + // input supports pointers or not. + if(inp->hasPtr) + buf = new _ptrstreambuf(inp); + else + buf = new _istreambuf(inp); + + rdbuf(buf); +} + +MangleIStream::~MangleIStream() +{ + delete buf; +} + +MangleOStream::MangleOStream(StreamPtr out) + : std::ostream(NULL) +{ + assert(out->isWritable); + buf = new _ostreambuf(out); + + rdbuf(buf); +} + +MangleOStream::~MangleOStream() +{ + // Make sure we don't have lingering data on exit + flush(); + delete buf; +} diff --git a/libs/mangle/stream/clients/io_stream.hpp b/libs/mangle/stream/clients/io_stream.hpp new file mode 100644 index 000000000..98c6252ed --- /dev/null +++ b/libs/mangle/stream/clients/io_stream.hpp @@ -0,0 +1,43 @@ +#ifndef MANGLE_STREAM_IOSTREAM_H +#define MANGLE_STREAM_IOSTREAM_H + +#include +#include "../stream.hpp" +#include + +namespace Mangle { +namespace Stream { + + /** This file contains classes for wrapping an std::istream or + std::ostream around a Mangle::Stream. + + This allows you to use Mangle streams in places that require std + streams. + + This is much easier than trying to make your own custom streams + into iostreams. The std::iostream interface is horrible and NOT + designed for easy subclassing. Create a Mangle::Stream instead, + and use this wrapper. + */ + + // An istream wrapping a readable Mangle::Stream. Has extra + // optimizations for pointer-based streams. + class MangleIStream : public std::istream + { + std::streambuf *buf; + public: + MangleIStream(StreamPtr inp); + ~MangleIStream(); + }; + + // An ostream wrapping a writable Mangle::Stream. + class MangleOStream : public std::ostream + { + std::streambuf *buf; + public: + MangleOStream(StreamPtr inp); + ~MangleOStream(); + }; + +}} // namespaces +#endif diff --git a/libs/mangle/stream/clients/ogre_datastream.hpp b/libs/mangle/stream/clients/ogre_datastream.hpp new file mode 100644 index 000000000..76a6f20cf --- /dev/null +++ b/libs/mangle/stream/clients/ogre_datastream.hpp @@ -0,0 +1,68 @@ +#ifndef MANGLE_STREAM_OGRECLIENT_H +#define MANGLE_STREAM_OGRECLIENT_H + +#include +#include +#include "../stream.hpp" + +namespace Mangle { +namespace Stream { + +/** An OGRE DataStream that wraps a Mangle::Stream input. + + This has been built and tested against OGRE 1.6.2. You might have + to make your own modifications if you're working with newer (or + older) versions. + */ +class Mangle2OgreStream : public Ogre::DataStream +{ + StreamPtr inp; + + void init() + { + // Get the size, if possible + if(inp->hasSize) + mSize = inp->size(); + + // Allow writing if inp supports it + if(inp->isWritable) + mAccess |= Ogre::DataStream::WRITE; + } + + public: + /// Constructor without name + Mangle2OgreStream(StreamPtr _inp) + : inp(_inp) { init(); } + + /// Constructor for a named data stream + Mangle2OgreStream(const Ogre::String &name, StreamPtr _inp) + : Ogre::DataStream(name), inp(_inp) { init(); } + + // Only implement the DataStream functions we have to implement + + size_t read(void *buf, size_t count) + { return inp->read(buf,count); } + + size_t write(const void *buf, size_t count) + { assert(inp->isWritable); return inp->write(buf,count); } + + void skip(long count) + { + assert(inp->isSeekable && inp->hasPosition); + inp->seek(inp->tell() + count); + } + + void seek(size_t pos) + { assert(inp->isSeekable); inp->seek(pos); } + + size_t tell() const + { assert(inp->hasPosition); return inp->tell(); } + + bool eof() const { return inp->eof(); } + + /// Does nothing + void close() {} +}; + +}} // namespaces +#endif diff --git a/libs/mangle/stream/filters/buffer_stream.hpp b/libs/mangle/stream/filters/buffer_stream.hpp new file mode 100644 index 000000000..f037212a3 --- /dev/null +++ b/libs/mangle/stream/filters/buffer_stream.hpp @@ -0,0 +1,74 @@ +#ifndef MANGLE_STREAM_BUFFER_H +#define MANGLE_STREAM_BUFFER_H + +#include "../servers/memory_stream.hpp" +#include + +namespace Mangle { +namespace Stream { + +/** A Stream that reads another Stream into a buffer, and serves it as + a MemoryStream. Might be expanded with other capabilities later. + */ + +class BufferStream : public MemoryStream +{ + std::vector buffer; + + public: + /* + input = stream to copy + ADD = each read increment (for streams without size()) + */ + BufferStream(StreamPtr input, size_t ADD = 32*1024) + { + assert(input); + + // Allocate memory, read the stream into it. Then call set() + if(input->hasSize) + { + // We assume that we can get the position as well + assert(input->hasPosition); + + // Calculate how much is left of the stream + size_t left = input->size() - input->tell(); + + // Allocate the buffer and fill it + buffer.resize(left); + input->read(&buffer[0], left); + } + else + { + // We DON'T know how big the stream is. We'll have to read + // it in increments. + size_t len=0, newlen; + + while(!input->eof()) + { + // Read one block + newlen = len + ADD; + buffer.resize(newlen); + size_t read = input->read(&buffer[len], ADD); + + // Increase the total length + len += read; + + // If we read less than expected, we should be at the + // end of the stream + assert(read == ADD || (read < ADD && input->eof())); + } + + // Downsize to match the real length + buffer.resize(len); + } + + // After the buffer has been filled, set up our MemoryStream + // ancestor to reference it. + set(&buffer[0], buffer.size()); + } +}; + +typedef boost::shared_ptr BufferStreamPtr; + +}} // namespaces +#endif diff --git a/libs/mangle/stream/filters/pure_filter.hpp b/libs/mangle/stream/filters/pure_filter.hpp new file mode 100644 index 000000000..f0ce91f87 --- /dev/null +++ b/libs/mangle/stream/filters/pure_filter.hpp @@ -0,0 +1,46 @@ +#ifndef MANGLE_STREAM_FILTER_H +#define MANGLE_STREAM_FILTER_H + +#include "../stream.hpp" + +namespace Mangle { +namespace Stream { + +/** A stream that filters another stream with no changes. Intended as + a base class for other filters. + */ +class PureFilter : public Stream +{ + protected: + StreamPtr src; + + public: + PureFilter() {} + PureFilter(StreamPtr _src) + { setStream(_src); } + + void setStream(StreamPtr _src) + { + src = _src; + isSeekable = src->isSeekable; + isWritable = src->isWritable; + hasPosition = src->hasPosition; + hasSize = src->hasSize; + hasPtr = src->hasPtr; + } + + size_t read(void *buf, size_t count) { return src->read(buf, count); } + size_t write(const void *buf, size_t count) { return src->write(buf,count); } + void flush() { src->flush(); } + void seek(size_t pos) { src->seek(pos); } + size_t tell() const { return src->tell(); } + size_t size() const { return src->size(); } + bool eof() const { return src->eof(); } + const void *getPtr() { return src->getPtr(); } + const void *getPtr(size_t size) { return src->getPtr(size); } + const void *getPtr(size_t pos, size_t size) + { return src->getPtr(pos, size); } +}; + +}} // namespaces +#endif diff --git a/libs/mangle/stream/filters/slice_stream.hpp b/libs/mangle/stream/filters/slice_stream.hpp new file mode 100644 index 000000000..6337b9d57 --- /dev/null +++ b/libs/mangle/stream/filters/slice_stream.hpp @@ -0,0 +1,101 @@ +#ifndef MANGLE_STREAM_SLICE_H +#define MANGLE_STREAM_SLICE_H + +#include "../stream.hpp" + +namespace Mangle { +namespace Stream { + +/** A Stream that represents a subset (called a slice) of another stream. + */ +class SliceStream : public Stream +{ + StreamPtr src; + size_t offset, length, pos; + + public: + SliceStream(StreamPtr _src, size_t _offset, size_t _length) + : src(_src), offset(_offset), length(_length), pos(0) + { + assert(src->hasSize); + assert(src->isSeekable); + + // Make sure we can actually fit inside the source stream + assert(src->size() >= offset+length); + + isSeekable = true; + hasPosition = true; + hasSize = true; + hasPtr = src->hasPtr; + isWritable = src->isWritable; + } + + size_t read(void *buf, size_t count) + { + // Check that we're not reading past our slice + if(count > length-pos) + count = length-pos; + + // Seek into place and start reading + src->seek(offset+pos); + count = src->read(buf, count); + + pos += count; + assert(pos <= length); + return count; + } + + // Note that writing to a slice does not allow you to append data, + // you may only overwrite existing data. + size_t write(const void *buf, size_t count) + { + assert(isWritable); + // Check that we're not reading past our slice + if(count > length-pos) + count = length-pos; + + // Seek into place and action + src->seek(offset+pos); + count = src->write(buf, count); + + pos += count; + assert(pos <= length); + return count; + } + + void seek(size_t _pos) + { + pos = _pos; + if(pos > length) pos = length; + } + + bool eof() const { return pos == length; } + size_t tell() const { return pos; } + size_t size() const { return length; } + void flush() { src->flush(); } + + const void *getPtr() { return getPtr(0, length); } + const void *getPtr(size_t size) + { + const void *ptr = getPtr(pos, size); + seek(pos+size); + return ptr; + } + const void *getPtr(size_t pos, size_t size) + { + // Boundry checks on pos and size. Bounding the size is + // important even when getting pointers, as the source stream + // may use the size parameter for something (such as memory + // mapping or buffering.) + if(pos > length) pos = length; + if(pos+size > length) size = length-pos; + + // Ask the source to kindly give us a pointer + return src->getPtr(offset+pos, size); + } +}; + +typedef boost::shared_ptr SliceStreamPtr; + +}} // namespaces +#endif diff --git a/libs/mangle/stream/servers/file_stream.hpp b/libs/mangle/stream/servers/file_stream.hpp new file mode 100644 index 000000000..314a49642 --- /dev/null +++ b/libs/mangle/stream/servers/file_stream.hpp @@ -0,0 +1,32 @@ +#ifndef MANGLE_STREAM_FILESERVER_H +#define MANGLE_STREAM_FILESERVER_H + +#include "std_stream.hpp" +#include +#include + +namespace Mangle { +namespace Stream { + +/** Very simple file input stream, based on std::ifstream + */ +class FileStream : public StdStream +{ + std::ifstream file; + + public: + FileStream(const std::string &name) + : StdStream(&file) + { + file.open(name.c_str(), std::ios::binary); + + if(file.fail()) + throw std::runtime_error("FileStream: failed to open file " + name); + } + ~FileStream() { file.close(); } +}; + +typedef boost::shared_ptr FileStreamPtr; + +}} // namespaces +#endif diff --git a/libs/mangle/stream/servers/memory_stream.hpp b/libs/mangle/stream/servers/memory_stream.hpp new file mode 100644 index 000000000..0849e4a3c --- /dev/null +++ b/libs/mangle/stream/servers/memory_stream.hpp @@ -0,0 +1,116 @@ +#ifndef MANGLE_STREAM_MEMSERVER_H +#define MANGLE_STREAM_MEMSERVER_H + +#include +#include "../stream.hpp" +#include + +namespace Mangle { +namespace Stream { + +// Do this before the class declaration, since the class itself +// uses it. +class MemoryStream; +typedef boost::shared_ptr MemoryStreamPtr; + +/** A Stream wrapping a memory buffer + + This will create a fully seekable stream out of any pointer/length + pair you give it. + */ +class MemoryStream : public Stream +{ + const void *data; + size_t length, pos; + + public: + MemoryStream(const void *ptr, size_t len) + : data(ptr), length(len), pos(0) + { + isSeekable = true; + hasPosition = true; + hasSize = true; + hasPtr = true; + } + + MemoryStream() + : data(NULL), length(0), pos(0) + { + isSeekable = true; + hasPosition = true; + hasSize = true; + hasPtr = true; + } + + size_t read(void *buf, size_t count) + { + assert(data != NULL); + assert(pos <= length); + + // Don't read more than we have + if(count > (length - pos)) + count = length - pos; + + // Copy data + if(count) + memcpy(buf, ((char*)data)+pos, count); + + // aaand remember to increase the count + pos += count; + + return count; + } + + void seek(size_t _pos) + { + pos = _pos; + if(pos > length) + pos = length; + } + + size_t tell() const { return pos; } + size_t size() const { return length; } + bool eof() const { return pos == length; } + + const void *getPtr() { return data; } + const void *getPtr(size_t size) + { + // This variant of getPtr must move the position pointer + size_t opos = pos; + pos += size; + if(pos > length) pos = length; + return ((char*)data)+opos; + } + const void *getPtr(size_t pos, size_t size) + { + if(pos > length) pos = length; + return ((char*)data)+pos; + } + + // New members in MemoryStream: + + /// Set a new buffer and length. This will rewind the position to zero. + void set(const void* _data, size_t _length) + { + data = _data; + length = _length; + pos = 0; + } + + /// Clone this memory stream + /** Make a new stream of the same buffer. If setPos is true, we also + set the clone's position to be the same as ours. + + No memory is copied during this operation, the new stream is + just another 'view' into the same shared memory buffer. + */ + MemoryStreamPtr clone(bool setPos=false) const + { + MemoryStreamPtr res(new MemoryStream(data, length)); + if(setPos) res->seek(pos); + return res; + } +}; + +}} // namespaces +#endif diff --git a/libs/mangle/stream/servers/ogre_datastream.hpp b/libs/mangle/stream/servers/ogre_datastream.hpp new file mode 100644 index 000000000..a5be98c84 --- /dev/null +++ b/libs/mangle/stream/servers/ogre_datastream.hpp @@ -0,0 +1,37 @@ +#ifndef MANGLE_STREAM_OGRESERVER_H +#define MANGLE_STREAM_OGRESERVER_H + +#include + +namespace Mangle { +namespace Stream { + +/** A Stream wrapping an OGRE DataStream. + + This has been built and tested against OGRE 1.6.2. You might have + to make your own modifications if you're working with newer (or + older) versions. + */ +class OgreStream : public Stream +{ + Ogre::DataStreamPtr inp; + + public: + OgreStream(Ogre::DataStreamPtr _inp) : inp(_inp) + { + isSeekable = true; + hasPosition = true; + hasSize = true; + } + + size_t read(void *buf, size_t count) { return inp->read(buf,count); } + void seek(size_t pos) { inp->seek(pos); } + size_t tell() const { return inp->tell(); } + size_t size() const { return inp->size(); } + bool eof() const { return inp->eof(); } +}; + +typedef boost::shared_ptr OgreStreamPtr; + +}} // namespaces +#endif diff --git a/libs/mangle/stream/servers/outfile_stream.hpp b/libs/mangle/stream/servers/outfile_stream.hpp new file mode 100644 index 000000000..8d953d904 --- /dev/null +++ b/libs/mangle/stream/servers/outfile_stream.hpp @@ -0,0 +1,41 @@ +#ifndef MANGLE_OSTREAM_FILESERVER_H +#define MANGLE_OSTREAM_FILESERVER_H + +#include "std_ostream.hpp" +#include + +namespace Mangle { +namespace Stream { + +/** File stream based on std::ofstream, only supports writing. + */ +class OutFileStream : public StdOStream +{ + std::ofstream file; + + public: + /** + By default we overwrite the file. If append=true, then we will + open an existing file and seek to the end instead. + */ + OutFileStream(const std::string &name, bool append=false) + : StdOStream(&file) + { + std::ios::openmode mode = std::ios::binary; + if(append) + mode |= std::ios::app; + else + mode |= std::ios::trunc; + + file.open(name.c_str(), mode); + + if(file.fail()) + throw std::runtime_error("OutFileStream: failed to open file " + name); + } + ~OutFileStream() { file.close(); } +}; + +typedef boost::shared_ptr OutFileStreamPtr; + +}} // namespaces +#endif diff --git a/libs/mangle/stream/servers/phys_stream.hpp b/libs/mangle/stream/servers/phys_stream.hpp new file mode 100644 index 000000000..4312ac041 --- /dev/null +++ b/libs/mangle/stream/servers/phys_stream.hpp @@ -0,0 +1,36 @@ +#ifndef MANGLE_STREAM_OGRESERVER_H +#define MANGLE_STREAM_OGRESERVER_H + +#include + +namespace Mangle { +namespace Stream { + +/// A Stream wrapping a PHYSFS_file stream from the PhysFS library. +class PhysFile : public Stream +{ + PHYSFS_file *file; + + public: + PhysFile(PHYSFS_file *inp) : file(inp) + { + isSeekable = true; + hasPosition = true; + hasSize = true; + } + + ~PhysFile() { PHYSFS_close(file); } + + size_t read(void *buf, size_t count) + { return PHYSFS_read(file, buf, 1, count); } + + void seek(size_t pos) { PHYSFS_seek(file, pos); } + size_t tell() const { return PHYSFS_tell(file); } + size_t size() const { return PHYSFS_fileLength(file); } + bool eof() const { return PHYSFS_eof(file); } +}; + +typedef boost::shared_ptr PhysFilePtr; + +}} // namespaces +#endif diff --git a/libs/mangle/stream/servers/std_ostream.hpp b/libs/mangle/stream/servers/std_ostream.hpp new file mode 100644 index 000000000..f406e1a93 --- /dev/null +++ b/libs/mangle/stream/servers/std_ostream.hpp @@ -0,0 +1,78 @@ +#ifndef MANGLE_OSTREAM_STDIOSERVER_H +#define MANGLE_OSTREAM_STDIOSERVER_H + +#include "../stream.hpp" +#include +#include + +namespace Mangle { +namespace Stream { + +/** Simple wrapper for std::ostream, only supports output. + */ +class StdOStream : public Stream +{ + std::ostream *inf; + + static void fail(const std::string &msg) + { throw std::runtime_error("StdOStream: " + msg); } + + public: + StdOStream(std::ostream *_inf) + : inf(_inf) + { + isSeekable = true; + hasPosition = true; + hasSize = true; + isWritable = true; + isReadable = false; + } + + size_t write(const void* buf, size_t len) + { + inf->write((const char*)buf, len); + if(inf->fail()) + fail("error writing to stream"); + // Just return len, but that is ok. The only cases where we would + // return less than len is when an error occured. + return len; + } + + void flush() + { + inf->flush(); + } + + void seek(size_t pos) + { + inf->seekp(pos); + if(inf->fail()) + fail("seek error"); + } + + size_t tell() const + // Hack around the fact that ifstream->tellp() isn't const + { return ((StdOStream*)this)->inf->tellp(); } + + size_t size() const + { + // Use the standard iostream size hack, terrible as it is. + std::streampos pos = inf->tellp(); + inf->seekp(0, std::ios::end); + size_t res = inf->tellp(); + inf->seekp(pos); + + if(inf->fail()) + fail("could not get stream size"); + + return res; + } + + bool eof() const + { return inf->eof(); } +}; + +typedef boost::shared_ptr StdOStreamPtr; + +}} // namespaces +#endif diff --git a/libs/mangle/stream/servers/std_stream.hpp b/libs/mangle/stream/servers/std_stream.hpp new file mode 100644 index 000000000..163f023f6 --- /dev/null +++ b/libs/mangle/stream/servers/std_stream.hpp @@ -0,0 +1,70 @@ +#ifndef MANGLE_STREAM_STDIOSERVER_H +#define MANGLE_STREAM_STDIOSERVER_H + +#include "../stream.hpp" +#include +#include + +namespace Mangle { +namespace Stream { + +/** Simple wrapper for std::istream. + */ +class StdStream : public Stream +{ + std::istream *inf; + + static void fail(const std::string &msg) + { throw std::runtime_error("StdStream: " + msg); } + + public: + StdStream(std::istream *_inf) + : inf(_inf) + { + isSeekable = true; + hasPosition = true; + hasSize = true; + } + + size_t read(void* buf, size_t len) + { + inf->read((char*)buf, len); + if(inf->bad()) + fail("error reading from stream"); + return inf->gcount(); + } + + void seek(size_t pos) + { + inf->clear(); + inf->seekg(pos); + if(inf->fail()) + fail("seek error"); + } + + size_t tell() const + // Hack around the fact that ifstream->tellg() isn't const + { return ((StdStream*)this)->inf->tellg(); } + + size_t size() const + { + // Use the standard iostream size hack, terrible as it is. + std::streampos pos = inf->tellg(); + inf->seekg(0, std::ios::end); + size_t res = inf->tellg(); + inf->seekg(pos); + + if(inf->fail()) + fail("could not get stream size"); + + return res; + } + + bool eof() const + { return inf->eof(); } +}; + +typedef boost::shared_ptr StdStreamPtr; + +}} // namespaces +#endif diff --git a/libs/mangle/stream/stream.hpp b/libs/mangle/stream/stream.hpp new file mode 100644 index 000000000..2ee4fcbd8 --- /dev/null +++ b/libs/mangle/stream/stream.hpp @@ -0,0 +1,103 @@ +#ifndef MANGLE_STREAM_INPUT_H +#define MANGLE_STREAM_INPUT_H + +#include +#include "../tools/shared_ptr.hpp" +#include + +namespace Mangle { +namespace Stream { + +/// An abstract interface for a stream data. +class Stream +{ + public: + // Feature options. These should be set in the constructor. + + /// If true, seek() works + bool isSeekable; + + /// If true, tell() works + bool hasPosition; + + /// If true, size() works + bool hasSize; + + /// If true, write() works. Writing through pointer operations is + /// not (yet) supported. + bool isWritable; + + /// If true, read() and eof() works. + bool isReadable; + + /// If true, the getPtr() functions work + bool hasPtr; + + /// Initialize all bools to false by default, except isReadable. + Stream() : + isSeekable(false), hasPosition(false), hasSize(false), + isWritable(false), isReadable(true), hasPtr(false) {} + + /// Virtual destructor + virtual ~Stream() {} + + /** Read a given number of bytes from the stream. Returns the actual + number read. If the return value is less than count, then the + stream is empty or an error occured. Only required for readable + streams. + */ + virtual size_t read(void* buf, size_t count) { assert(0); return 0; } + + /** Write a given number of bytes from the stream. Semantics is + similar to read(). Only valid if isWritable is true. + + The returned value is the number of bytes written. However in + most cases, unlike for read(), a write-count less than requested + usually indicates an error. The implementation should throw such + errors as exceptions rather than expect the caller to handle + them. + + Since most implementations do NOT support writing we default to + an assert(0) here. + */ + virtual size_t write(const void *buf, size_t count) { assert(0); return 0; } + + /// Flush an output stream. Does nothing for non-writing streams. + virtual void flush() {} + + /// Seek to an absolute position in this stream. Not all streams are + /// seekable. + virtual void seek(size_t pos) { assert(0); } + + /// Get the current position in the stream. Non-seekable streams are + /// not required to keep track of this. + virtual size_t tell() const { assert(0); return 0; } + + /// Return the total size of the stream. For streams hasSize is + /// false, size() should fail in some way, since it is an error to + /// call it in those cases. + virtual size_t size() const { assert(0); return 0; } + + /// Returns true if the stream is empty. Required for readable + /// streams. + virtual bool eof() const { assert(0); return 0; } + + /// Return a pointer to the entire stream. This function (and the + /// other getPtr() variants below) should only be implemented for + /// memory-based streams where using them would be an optimization. + virtual const void *getPtr() { assert(0); return NULL; } + + /// Get a pointer to a memory region of 'size' bytes starting from + /// position 'pos' + virtual const void *getPtr(size_t pos, size_t size) { assert(0); return NULL; } + + /// Get a pointer to a memory region of 'size' bytes from the + /// current position. Unlike the two other getPtr variants, this + /// will advance the position past the returned area. + virtual const void *getPtr(size_t size) { assert(0); return NULL; } +}; + +typedef boost::shared_ptr StreamPtr; + +}} // namespaces +#endif diff --git a/libs/mangle/stream/tests/.gitignore b/libs/mangle/stream/tests/.gitignore new file mode 100644 index 000000000..9dfd618e2 --- /dev/null +++ b/libs/mangle/stream/tests/.gitignore @@ -0,0 +1,2 @@ +*_test +test.file diff --git a/libs/mangle/stream/tests/Makefile b/libs/mangle/stream/tests/Makefile new file mode 100644 index 000000000..5f4397819 --- /dev/null +++ b/libs/mangle/stream/tests/Makefile @@ -0,0 +1,34 @@ +GCC=g++ -I../ -Wall -Werror + +all: ogre_client_test audiere_client_test memory_server_test buffer_filter_test file_server_test slice_filter_test file_write_test iostream_test + +I_OGRE=$(shell pkg-config --cflags OGRE) +L_OGRE=$(shell pkg-config --libs OGRE) +L_AUDIERE=-laudiere + +ogre_client_test: ogre_client_test.cpp ../stream.hpp ../clients/ogre_datastream.hpp + $(GCC) $< -o $@ $(I_OGRE) $(L_OGRE) + +audiere_client_test: audiere_client_test.cpp ../stream.hpp ../clients/audiere_file.hpp ../clients/audiere_file.cpp + $(GCC) $< -o $@ ../clients/audiere_file.cpp $(L_AUDIERE) + +iostream_test: iostream_test.cpp ../clients/io_stream.cpp + $(GCC) $^ -o $@ + +file_server_test: file_server_test.cpp ../stream.hpp ../servers/file_stream.hpp ../servers/std_stream.hpp + $(GCC) $< -o $@ + +file_write_test: file_write_test.cpp ../stream.hpp ../servers/outfile_stream.hpp ../servers/std_ostream.hpp + $(GCC) $< -o $@ + +memory_server_test: memory_server_test.cpp ../stream.hpp ../servers/memory_stream.hpp + $(GCC) $< -o $@ + +buffer_filter_test: buffer_filter_test.cpp ../stream.hpp ../servers/memory_stream.hpp ../filters/buffer_stream.hpp + $(GCC) $< -o $@ + +slice_filter_test: slice_filter_test.cpp ../stream.hpp ../servers/memory_stream.hpp ../filters/slice_stream.hpp + $(GCC) $< -o $@ + +clean: + rm *_test diff --git a/libs/mangle/stream/tests/audiere_client_test.cpp b/libs/mangle/stream/tests/audiere_client_test.cpp new file mode 100644 index 000000000..82be569cc --- /dev/null +++ b/libs/mangle/stream/tests/audiere_client_test.cpp @@ -0,0 +1,34 @@ +#include "../servers/memory_stream.hpp" +#include "../clients/audiere_file.hpp" +#include +#include + +using namespace Mangle::Stream; +using namespace audiere; +using namespace std; + +int main() +{ + char str[12]; + memset(str, 0, 12); + StreamPtr inp(new MemoryStream("hello world", 11)); + FilePtr p(new AudiereFile(inp)); + cout << "pos=" << p->tell() << endl; + p->read(str, 2); + cout << "2 bytes: " << str << endl; + cout << "pos=" << p->tell() << endl; + p->seek(4, File::BEGIN); + cout << "pos=" << p->tell() << endl; + p->read(str, 3); + cout << "3 bytes: " << str << endl; + p->seek(-1, File::CURRENT); + cout << "pos=" << p->tell() << endl; + p->seek(-4, File::END); + cout << "pos=" << p->tell() << endl; + p->read(str, 4); + cout << "last 4 bytes: " << str << endl; + p->seek(0, File::BEGIN); + p->read(str, 11); + cout << "entire stream: " << str << endl; + return 0; +} diff --git a/libs/mangle/stream/tests/buffer_filter_test.cpp b/libs/mangle/stream/tests/buffer_filter_test.cpp new file mode 100644 index 000000000..e53bda651 --- /dev/null +++ b/libs/mangle/stream/tests/buffer_filter_test.cpp @@ -0,0 +1,41 @@ +#include +#include + +#include "../filters/buffer_stream.hpp" + +using namespace Mangle::Stream; +using namespace std; + +int main() +{ + StreamPtr orig (new MemoryStream("hello world", 11)); + StreamPtr inp (new BufferStream(orig)); + + cout << "Size: " << inp->size() << endl; + cout << "Pos: " << inp->tell() << "\nSeeking...\n"; + inp->seek(3); + cout << "Pos: " << inp->tell() << endl; + char data[12]; + memset(data, 0, 12); + cout << "Reading: " << inp->read(data, 4) << endl; + cout << "Four bytes: " << data << endl; + cout << "Eof: " << inp->eof() << endl; + cout << "Pos: " << inp->tell() << "\nSeeking again...\n"; + inp->seek(33); + cout << "Pos: " << inp->tell() << endl; + cout << "Eof: " << inp->eof() << "\nSeek to 6\n"; + inp->seek(6); + cout << "Eof: " << inp->eof() << endl; + cout << "Pos: " << inp->tell() << endl; + cout << "Over-reading: " << inp->read(data, 200) << endl; + cout << "Result: " << data << endl; + cout << "Eof: " << inp->eof() << endl; + cout << "Pos: " << inp->tell() << endl; + inp->seek(0); + cout << "Finally, reading the entire string: " << inp->read(data,11) << endl; + cout << "Result: " << data << endl; + cout << "Eof: " << inp->eof() << endl; + cout << "Pos: " << inp->tell() << endl; + + return 0; +} diff --git a/libs/mangle/stream/tests/file_server_test.cpp b/libs/mangle/stream/tests/file_server_test.cpp new file mode 100644 index 000000000..3e1e3cfa5 --- /dev/null +++ b/libs/mangle/stream/tests/file_server_test.cpp @@ -0,0 +1,18 @@ +#include "../servers/file_stream.hpp" +#include + +using namespace Mangle::Stream; +using namespace std; + +int main() +{ + StreamPtr inp(new FileStream("file_server_test.cpp")); + + char buf[21]; + buf[20] = 0; + cout << "pos=" << inp->tell() << " eof=" << inp->eof() << endl; + inp->read(buf, 20); + cout << "First 20 bytes: " << buf << endl; + cout << "pos=" << inp->tell() << " eof=" << inp->eof() << endl; + return 0; +} diff --git a/libs/mangle/stream/tests/file_write_test.cpp b/libs/mangle/stream/tests/file_write_test.cpp new file mode 100644 index 000000000..c6c61fcca --- /dev/null +++ b/libs/mangle/stream/tests/file_write_test.cpp @@ -0,0 +1,41 @@ +#include "../servers/outfile_stream.hpp" +#include + +using namespace Mangle::Stream; +using namespace std; + +void print(Stream &str) +{ + cout << "size=" << str.size() + << " pos=" << str.tell() + << " eof=" << str.eof() + << endl; +} + +int main() +{ + { + cout << "\nCreating file\n"; + OutFileStream out("test.file"); + print(out); + out.write("hello",5); + print(out); + } + + { + cout << "\nAppending to file\n"; + OutFileStream out("test.file", true); + print(out); + out.write(" again\n",7); + print(out); + } + + { + cout << "\nOverwriting file\n"; + OutFileStream out("test.file"); + print(out); + out.write("overwrite!\n",11); + print(out); + } + return 0; +} diff --git a/libs/mangle/stream/tests/iostream_test.cpp b/libs/mangle/stream/tests/iostream_test.cpp new file mode 100644 index 000000000..60648c6c5 --- /dev/null +++ b/libs/mangle/stream/tests/iostream_test.cpp @@ -0,0 +1,176 @@ +#include +#include "../clients/io_stream.hpp" +#include "../servers/memory_stream.hpp" + +using namespace Mangle::Stream; +using namespace std; + +void test1() +{ + cout << "Testing ASCII reading from memory:\n"; + StreamPtr input(new MemoryStream("hello you world you", 19)); + MangleIStream inp(input); + + string str; + while(!inp.eof()) + { + inp >> str; + cout << "Got: " << str << endl; + } +} + +class Dummy : public Stream +{ + int count; + +public: + + Dummy() : count(0) + { + } + + size_t read(void *ptr, size_t num) + { + char *p = (char*)ptr; + char *start = p; + for(; (count < 2560) && (p-start < (int)num); count++) + { + *p = count / 10; + p++; + } + return p-start; + } + + bool eof() const { return count == 2560; } +}; + +void test2() +{ + cout << "\nTesting binary reading from non-memory:\n"; + + StreamPtr input(new Dummy); + MangleIStream inp(input); + + int x = 0; + while(!inp.eof()) + { + unsigned char buf[5]; + inp.read((char*)buf,5); + + // istream doesn't set eof() until we read _beyond_ the end of + // the stream, so we need an extra check. + if(inp.gcount() == 0) break; + + /* + for(int i=0;i<5;i++) + cout << (int)buf[i] << " "; + cout << endl; + */ + + assert(buf[4] == buf[0]); + assert(buf[0] == x/2); + x++; + } + cout << " Done\n"; +} + +struct Dummy2 : Stream +{ + Dummy2() + { + isWritable = true; + isReadable = false; + } + + size_t write(const void *ptr, size_t num) + { + const char *p = (const char*)ptr; + cout << " Got: "; + for(unsigned i=0;iwrite("testing", 7); + + cout << " Running through MangleOStream:\n"; + MangleOStream out(output); + out << "hello"; + out << " - are you ok?"; + cout << " Flushing:\n"; + out.flush(); + + cout << " Writing a hell of a lot of characters:\n"; + for(int i=0; i<127; i++) + out << "xxxxxxxx"; // 127 * 8 = 1016 + out << "fffffff"; // +7 = 1023 + cout << " Just one more:\n"; + out << "y"; + cout << " And oooone more:\n"; + out << "z"; + + cout << " Flushing again:\n"; + out.flush(); + cout << " Writing some more and exiting:\n"; + out << "blah bleh blob"; +} + +struct Dummy3 : Stream +{ + int pos; + + Dummy3() : pos(0) + { + hasPosition = true; + isSeekable = true; + } + + size_t read(void*, size_t num) + { + cout << " Reading " << num << " bytes from " << pos << endl; + pos += num; + return num; + } + + void seek(size_t npos) { pos = npos; } + size_t tell() const { return pos; } +}; + +void test4() +{ + cout << "\nTesting seeking;\n"; + StreamPtr input(new Dummy3); + + cout << " Direct reading:\n"; + input->read(0,10); + input->read(0,5); + + MangleIStream inp(input); + + cout << " Reading from istream:\n"; + char buf[20]; + inp.read(buf, 20); + inp.read(buf, 20); + inp.read(buf, 20); + + cout << " Seeking to 30 and reading again:\n"; + inp.seekg(30); + inp.read(buf, 20); +} + +int main() +{ + test1(); + test2(); + test3(); + test4(); + return 0; +} diff --git a/libs/mangle/stream/tests/memory_server_test.cpp b/libs/mangle/stream/tests/memory_server_test.cpp new file mode 100644 index 000000000..24d3bb17e --- /dev/null +++ b/libs/mangle/stream/tests/memory_server_test.cpp @@ -0,0 +1,42 @@ +#include +#include + +#include "../servers/memory_stream.hpp" + +using namespace Mangle::Stream; +using namespace std; + +int main() +{ + Stream* inp = new MemoryStream("hello world\0", 12); + + cout << "Size: " << inp->size() << endl; + cout << "Pos: " << inp->tell() << "\nSeeking...\n"; + inp->seek(3); + cout << "Pos: " << inp->tell() << endl; + char data[12]; + memset(data, 0, 12); + cout << "Reading: " << inp->read(data, 4) << endl; + cout << "Four bytes: " << data << endl; + cout << "Eof: " << inp->eof() << endl; + cout << "Pos: " << inp->tell() << "\nSeeking again...\n"; + inp->seek(33); + cout << "Pos: " << inp->tell() << endl; + cout << "Eof: " << inp->eof() << "\nSeek to 6\n"; + inp->seek(6); + cout << "Eof: " << inp->eof() << endl; + cout << "Pos: " << inp->tell() << endl; + cout << "Over-reading: " << inp->read(data, 200) << endl; + cout << "Result: " << data << endl; + cout << "Eof: " << inp->eof() << endl; + cout << "Pos: " << inp->tell() << endl; + inp->seek(0); + cout << "Finally, reading the entire string: " << inp->read(data,11) << endl; + cout << "Result: " << data << endl; + cout << "Eof: " << inp->eof() << endl; + cout << "Pos: " << inp->tell() << endl; + + cout << "Entire stream from pointer: " << (char*)inp->getPtr() << endl; + + return 0; +} diff --git a/libs/mangle/stream/tests/ogre_client_test.cpp b/libs/mangle/stream/tests/ogre_client_test.cpp new file mode 100644 index 000000000..c8d0442c0 --- /dev/null +++ b/libs/mangle/stream/tests/ogre_client_test.cpp @@ -0,0 +1,22 @@ +#include "../servers/memory_stream.hpp" +#include "../clients/ogre_datastream.hpp" +#include + +using namespace Mangle::Stream; +using namespace Ogre; +using namespace std; + +int main() +{ + StreamPtr inp(new MemoryStream("hello world", 11)); + DataStreamPtr p(new Mangle2OgreStream("hello", inp)); + cout << "Name: " << p->getName() << endl; + cout << "As string: " << p->getAsString() << endl; + cout << "pos=" << p->tell() << " eof=" << p->eof() << endl; + p->seek(0); + cout << "pos=" << p->tell() << " eof=" << p->eof() << endl; + p->skip(5); + p->skip(-2); + cout << "pos=" << p->tell() << " eof=" << p->eof() << endl; + return 0; +} diff --git a/libs/mangle/stream/tests/output/audiere_client_test.out b/libs/mangle/stream/tests/output/audiere_client_test.out new file mode 100644 index 000000000..2130db9fd --- /dev/null +++ b/libs/mangle/stream/tests/output/audiere_client_test.out @@ -0,0 +1,9 @@ +pos=0 +2 bytes: he +pos=2 +pos=4 +3 bytes: o w +pos=6 +pos=7 +last 4 bytes: orld +entire stream: hello world diff --git a/libs/mangle/stream/tests/output/buffer_filter_test.out b/libs/mangle/stream/tests/output/buffer_filter_test.out new file mode 100644 index 000000000..0ca252f25 --- /dev/null +++ b/libs/mangle/stream/tests/output/buffer_filter_test.out @@ -0,0 +1,22 @@ +Size: 11 +Pos: 0 +Seeking... +Pos: 3 +Reading: 4 +Four bytes: lo w +Eof: 0 +Pos: 7 +Seeking again... +Pos: 11 +Eof: 1 +Seek to 6 +Eof: 0 +Pos: 6 +Over-reading: 5 +Result: world +Eof: 1 +Pos: 11 +Finally, reading the entire string: 11 +Result: hello world +Eof: 1 +Pos: 11 diff --git a/libs/mangle/stream/tests/output/file_server_test.out b/libs/mangle/stream/tests/output/file_server_test.out new file mode 100644 index 000000000..240f066a3 --- /dev/null +++ b/libs/mangle/stream/tests/output/file_server_test.out @@ -0,0 +1,3 @@ +pos=0 eof=0 +First 20 bytes: #include "../servers +pos=20 eof=0 diff --git a/libs/mangle/stream/tests/output/file_write_test.out b/libs/mangle/stream/tests/output/file_write_test.out new file mode 100644 index 000000000..34b07c49f --- /dev/null +++ b/libs/mangle/stream/tests/output/file_write_test.out @@ -0,0 +1,12 @@ + +Creating file +size=0 pos=0 eof=0 +size=5 pos=5 eof=0 + +Appending to file +size=5 pos=5 eof=0 +size=12 pos=12 eof=0 + +Overwriting file +size=0 pos=0 eof=0 +size=11 pos=11 eof=0 diff --git a/libs/mangle/stream/tests/output/iostream_test.out b/libs/mangle/stream/tests/output/iostream_test.out new file mode 100644 index 000000000..b6da80c80 --- /dev/null +++ b/libs/mangle/stream/tests/output/iostream_test.out @@ -0,0 +1,32 @@ +Testing ASCII reading from memory: +Got: hello +Got: you +Got: world +Got: you + +Testing binary reading from non-memory: + Done + +Writing to dummy stream: + Pure dummy test: + Got: t e s t i n g + Running through MangleOStream: + Flushing: + Got: h e l l o - a r e y o u o k ? + Writing a hell of a lot of characters: + Just one more: + And oooone more: + Got: x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x f f f f f f f y + Flushing again: + Got: z + Writing some more and exiting: + Got: b l a h b l e h b l o b + +Testing seeking; + Direct reading: + Reading 10 bytes from 0 + Reading 5 bytes from 10 + Reading from istream: + Reading 1024 bytes from 15 + Seeking to 30 and reading again: + Reading 1024 bytes from 30 diff --git a/libs/mangle/stream/tests/output/memory_server_test.out b/libs/mangle/stream/tests/output/memory_server_test.out new file mode 100644 index 000000000..7cd9533e7 --- /dev/null +++ b/libs/mangle/stream/tests/output/memory_server_test.out @@ -0,0 +1,23 @@ +Size: 12 +Pos: 0 +Seeking... +Pos: 3 +Reading: 4 +Four bytes: lo w +Eof: 0 +Pos: 7 +Seeking again... +Pos: 12 +Eof: 1 +Seek to 6 +Eof: 0 +Pos: 6 +Over-reading: 6 +Result: world +Eof: 1 +Pos: 12 +Finally, reading the entire string: 11 +Result: hello world +Eof: 0 +Pos: 11 +Entire stream from pointer: hello world diff --git a/libs/mangle/stream/tests/output/ogre_client_test.out b/libs/mangle/stream/tests/output/ogre_client_test.out new file mode 100644 index 000000000..62cd14604 --- /dev/null +++ b/libs/mangle/stream/tests/output/ogre_client_test.out @@ -0,0 +1,5 @@ +Name: hello +As string: hello world +pos=11 eof=1 +pos=0 eof=0 +pos=3 eof=0 diff --git a/libs/mangle/stream/tests/output/slice_filter_test.out b/libs/mangle/stream/tests/output/slice_filter_test.out new file mode 100644 index 000000000..6d84704a7 --- /dev/null +++ b/libs/mangle/stream/tests/output/slice_filter_test.out @@ -0,0 +1,36 @@ + +Slice 1: +-------- +Size: 6 +Pos: 0 +Seeking... +Reading 6 bytes +Result: hello +Pos: 6 +Eof: 1 +Seeking: +Pos: 2 +Eof: 0 +Reading 4 bytes +Result: llo +Pos: 6 +Eof: 1 +Entire stream as pointer: hello + +Slice 2: +-------- +Size: 6 +Pos: 0 +Seeking... +Reading 6 bytes +Result: world +Pos: 6 +Eof: 1 +Seeking: +Pos: 2 +Eof: 0 +Reading 4 bytes +Result: rld +Pos: 6 +Eof: 1 +Entire stream as pointer: world diff --git a/libs/mangle/stream/tests/slice_filter_test.cpp b/libs/mangle/stream/tests/slice_filter_test.cpp new file mode 100644 index 000000000..da90e24ad --- /dev/null +++ b/libs/mangle/stream/tests/slice_filter_test.cpp @@ -0,0 +1,42 @@ +#include +#include + +#include "../filters/slice_stream.hpp" +#include "../servers/memory_stream.hpp" + +using namespace Mangle::Stream; +using namespace std; + +void test(StreamPtr inp) +{ + cout << "Size: " << inp->size() << endl; + cout << "Pos: " << inp->tell() << "\nSeeking...\n"; + char data[6]; + memset(data, 0, 6); + cout << "Reading " << inp->read(data, 6) << " bytes\n"; + cout << "Result: " << data << endl; + cout << "Pos: " << inp->tell() << endl; + cout << "Eof: " << inp->eof() << endl; + inp->seek(2); + cout << "Seeking:\nPos: " << inp->tell() << endl; + cout << "Eof: " << inp->eof() << endl; + cout << "Reading " << inp->read(data, 6) << " bytes\n"; + cout << "Result: " << data << endl; + cout << "Pos: " << inp->tell() << endl; + cout << "Eof: " << inp->eof() << endl; + cout << "Entire stream as pointer: " << (char*)inp->getPtr() << endl; +} + +int main() +{ + StreamPtr orig (new MemoryStream("hello\0world\0", 12)); + StreamPtr slice1 (new SliceStream(orig,0,6)); + StreamPtr slice2 (new SliceStream(orig,6,6)); + + cout << "\nSlice 1:\n--------\n"; + test(slice1); + cout << "\nSlice 2:\n--------\n"; + test(slice2); + + return 0; +} diff --git a/libs/mangle/stream/tests/test.sh b/libs/mangle/stream/tests/test.sh new file mode 100755 index 000000000..2d07708ad --- /dev/null +++ b/libs/mangle/stream/tests/test.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +make || exit + +mkdir -p output + +PROGS=*_test + +for a in $PROGS; do + if [ -f "output/$a.out" ]; then + echo "Running $a:" + ./$a | diff output/$a.out - + else + echo "Creating $a.out" + ./$a > "output/$a.out" + git add "output/$a.out" + fi +done diff --git a/libs/mangle/testall.sh b/libs/mangle/testall.sh new file mode 100755 index 000000000..b93fee215 --- /dev/null +++ b/libs/mangle/testall.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +function run() +{ + echo "TESTING $1" + cd "$1/tests/" + ./test.sh + cd ../../ +} + +run stream +run vfs +run sound +run input +run rend2d +run . diff --git a/libs/mangle/tests/.gitignore b/libs/mangle/tests/.gitignore new file mode 100644 index 000000000..814490404 --- /dev/null +++ b/libs/mangle/tests/.gitignore @@ -0,0 +1 @@ +*_test diff --git a/libs/mangle/tests/Makefile b/libs/mangle/tests/Makefile new file mode 100644 index 000000000..d912c0784 --- /dev/null +++ b/libs/mangle/tests/Makefile @@ -0,0 +1,14 @@ +GCC=g++ -I../ + +all: ogrevfs_audiere_openal_test + +I_OGRE=$(shell pkg-config --cflags OGRE) +L_OGRE=$(shell pkg-config --libs OGRE) +L_OPENAL=$(shell pkg-config --libs openal) +L_AUDIERE=-laudiere + +ogrevfs_audiere_openal_test: ogrevfs_audiere_openal_test.cpp ../vfs/servers/ogre_vfs.cpp ../sound/sources/audiere_source.cpp ../sound/outputs/openal_out.cpp ../stream/clients/audiere_file.cpp + $(GCC) $^ -o $@ $(I_OGRE) $(L_OGRE) $(L_OPENAL) $(L_AUDIERE) + +clean: + rm *_test diff --git a/libs/mangle/tests/ogrevfs_audiere_openal_test.cpp b/libs/mangle/tests/ogrevfs_audiere_openal_test.cpp new file mode 100644 index 000000000..4936538c5 --- /dev/null +++ b/libs/mangle/tests/ogrevfs_audiere_openal_test.cpp @@ -0,0 +1,49 @@ +/* + This example combines: + + - the OGRE VFS system (to read from zip) + - Audiere (for decoding sound data) + - OpenAL (for sound playback) + + */ + +#include "sound/filters/openal_audiere.hpp" +#include "vfs/servers/ogre_vfs.hpp" +#include +#include + +using namespace Ogre; +using namespace Mangle; +using namespace std; + +int main() +{ + // Disable Ogre logging + new LogManager; + Log *log = LogManager::getSingleton().createLog(""); + log->setDebugOutputEnabled(false); + + // Set up Root + Root *root = new Root("","",""); + + // Add zip file with a sound in it + root->addResourceLocation("sound.zip", "Zip", "General"); + + // Ogre file system + VFS::OgreVFS vfs; + + // The main sound system + Sound::OpenAL_Audiere_Factory mg; + Sound::SoundPtr snd = mg.load(vfs.open("owl.ogg")); + + cout << "Playing 'owl.ogg' from 'sound.zip'\n"; + snd->play(); + + while(snd->isPlaying()) + { + usleep(10000); + if(mg.needsUpdate) mg.update(); + } + + return 0; +} diff --git a/libs/mangle/tests/output/ogrevfs_audiere_openal_test.out b/libs/mangle/tests/output/ogrevfs_audiere_openal_test.out new file mode 100644 index 000000000..28ea8a71b --- /dev/null +++ b/libs/mangle/tests/output/ogrevfs_audiere_openal_test.out @@ -0,0 +1 @@ +Playing 'owl.ogg' from 'sound.zip' diff --git a/libs/mangle/tests/sound.zip b/libs/mangle/tests/sound.zip new file mode 100644 index 000000000..fd32b3529 Binary files /dev/null and b/libs/mangle/tests/sound.zip differ diff --git a/libs/mangle/tests/test.sh b/libs/mangle/tests/test.sh new file mode 100755 index 000000000..2d07708ad --- /dev/null +++ b/libs/mangle/tests/test.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +make || exit + +mkdir -p output + +PROGS=*_test + +for a in $PROGS; do + if [ -f "output/$a.out" ]; then + echo "Running $a:" + ./$a | diff output/$a.out - + else + echo "Creating $a.out" + ./$a > "output/$a.out" + git add "output/$a.out" + fi +done diff --git a/libs/mangle/tools/shared_ptr.hpp b/libs/mangle/tools/shared_ptr.hpp new file mode 100644 index 000000000..3d073fc24 --- /dev/null +++ b/libs/mangle/tools/shared_ptr.hpp @@ -0,0 +1,3 @@ +// This file should include whatever it needs to define the boost/tr1 +// shared_ptr<> and weak_ptr<> templates. +#include diff --git a/libs/mangle/vfs/clients/ogre_archive.cpp b/libs/mangle/vfs/clients/ogre_archive.cpp new file mode 100644 index 000000000..2d3f7c520 --- /dev/null +++ b/libs/mangle/vfs/clients/ogre_archive.cpp @@ -0,0 +1,85 @@ +#include "ogre_archive.hpp" + +#include "../../stream/clients/ogre_datastream.hpp" + +using namespace Mangle::VFS; +using namespace Mangle::Stream; + +Ogre::DataStreamPtr MangleArchive::open(const Ogre::String& filename) const +{ + return Ogre::DataStreamPtr(new Mangle2OgreStream + (filename, vfs->open(filename))); +} + +static void fill(Ogre::FileInfoList &out, FileInfoList &in) +{ + int size = in.size(); + out.resize(size); + + for(int i=0; ihasList); + FileInfoListPtr lst = vfs->list("", recursive, dirs); + Ogre::StringVector *res = new Ogre::StringVector; + + fill(*res, *lst); + + return Ogre::StringVectorPtr(res); +} + +Ogre::FileInfoListPtr MangleArchive::listFileInfo(bool recursive, bool dirs) +{ + assert(vfs->hasList); + FileInfoListPtr lst = vfs->list("", recursive, dirs); + Ogre::FileInfoList *res = new Ogre::FileInfoList; + + fill(*res, *lst); + + return Ogre::FileInfoListPtr(res); +} + +// Find functions will only work if vfs->hasFind is set. +Ogre::StringVectorPtr MangleArchive::find(const Ogre::String& pattern, + bool recursive, + bool dirs) +{ + assert(vfs->hasFind); + FileInfoListPtr lst = vfs->find(pattern, recursive, dirs); + Ogre::StringVector *res = new Ogre::StringVector; + + fill(*res, *lst); + + return Ogre::StringVectorPtr(res); +} + +Ogre::FileInfoListPtr MangleArchive::findFileInfo(const Ogre::String& pattern, + bool recursive, + bool dirs) +{ + assert(vfs->hasFind); + FileInfoListPtr lst = vfs->find(pattern, recursive, dirs); + Ogre::FileInfoList *res = new Ogre::FileInfoList; + + fill(*res, *lst); + + return Ogre::FileInfoListPtr(res); +} diff --git a/libs/mangle/vfs/clients/ogre_archive.hpp b/libs/mangle/vfs/clients/ogre_archive.hpp new file mode 100644 index 000000000..7b371c6ef --- /dev/null +++ b/libs/mangle/vfs/clients/ogre_archive.hpp @@ -0,0 +1,59 @@ +#ifndef MANGLE_VFS_OGRECLIENT_H +#define MANGLE_VFS_OGRECLIENT_H + +#include +#include +#include "../vfs.hpp" + +namespace Mangle { +namespace VFS { + +/** An OGRE Archive implementation that wraps a Mangle::VFS + filesystem. + + This has been built and tested against OGRE 1.6.2, and has been + extended for OGRE 1.7. You might have to make your own + modifications if you're working with newer (or older) versions. + */ +class MangleArchive : public Ogre::Archive +{ + VFSPtr vfs; + + public: + MangleArchive(VFSPtr _vfs, const std::string &name, + const std::string &archType = "Mangle") + : Ogre::Archive(name, archType) + , vfs(_vfs) + {} + + bool isCaseSensitive() const { return vfs->isCaseSensitive; } + + // These do nothing. You have to load / unload the archive in the + // constructor/destructor. + void load() {} + void unload() {} + + bool exists(const Ogre::String& filename) + { return vfs->isFile(filename); } + + time_t getModifiedTime(const Ogre::String& filename) + { return vfs->stat(filename)->time; } + + // With both these we support both OGRE 1.6 and 1.7 + Ogre::DataStreamPtr open(const Ogre::String& filename) const; + Ogre::DataStreamPtr open(const Ogre::String& filename, bool readOnly) const + { return open(filename); } + + Ogre::StringVectorPtr list(bool recursive = true, bool dirs = false); + Ogre::FileInfoListPtr listFileInfo(bool recursive = true, bool dirs = false); + + // Find functions will only work if vfs->hasFind is set. + Ogre::StringVectorPtr find(const Ogre::String& pattern, bool recursive = true, + bool dirs = false); + Ogre::FileInfoListPtr findFileInfo(const Ogre::String& pattern, + bool recursive = true, + bool dirs = false); +}; + +}} // namespaces +#endif diff --git a/libs/mangle/vfs/servers/ogre_vfs.cpp b/libs/mangle/vfs/servers/ogre_vfs.cpp new file mode 100644 index 000000000..7f728c1b0 --- /dev/null +++ b/libs/mangle/vfs/servers/ogre_vfs.cpp @@ -0,0 +1,61 @@ +#include "ogre_vfs.hpp" +#include "../../stream/servers/ogre_datastream.hpp" + +using namespace Mangle::VFS; +using namespace Mangle::Stream; + +OgreVFS::OgreVFS(const std::string &_group) + : group(_group) +{ + hasList = true; + hasFind = true; + isCaseSensitive = true; + + // Get the group manager once + gm = Ogre::ResourceGroupManager::getSingletonPtr(); + + // Use the default group if none was specified + if(group.empty()) + group = gm->getWorldResourceGroupName(); +} + +StreamPtr OgreVFS::open(const std::string &name) +{ + Ogre::DataStreamPtr data = gm->openResource(name, group); + return StreamPtr(new OgreStream(data)); +} + +static void fill(FileInfoList &out, Ogre::FileInfoList &in, bool dirs) +{ + int size = in.size(); + out.resize(size); + + for(int i=0; ilistResourceFileInfo(group, dirs); + FileInfoListPtr res(new FileInfoList); + fill(*res, *olist, dirs); + return res; +} + +FileInfoListPtr OgreVFS::find(const std::string& pattern, + bool recursive, + bool dirs) const +{ + Ogre::FileInfoListPtr olist = gm->findResourceFileInfo(group, pattern, dirs); + FileInfoListPtr res(new FileInfoList); + fill(*res, *olist, dirs); + return res; +} diff --git a/libs/mangle/vfs/servers/ogre_vfs.hpp b/libs/mangle/vfs/servers/ogre_vfs.hpp new file mode 100644 index 000000000..7ab64169d --- /dev/null +++ b/libs/mangle/vfs/servers/ogre_vfs.hpp @@ -0,0 +1,70 @@ +#ifndef MANGLE_VFS_OGRESERVER_H +#define MANGLE_VFS_OGRESERVER_H + +#include "../vfs.hpp" +#include + +namespace Mangle { +namespace VFS { + +/** @brief An interface into the OGRE VFS system. + + This class does NOT wrap a single Ogre::Archive, but rather an + entire resource group in Ogre. You can use this class to tap into + all paths, Zip files, custom archives on so on that have been + inserted into Ogre as resource locations. + + This has been built and tested against OGRE 1.6.2. You might have + to make your own modifications if you're working with newer (or + older) versions. + */ +class OgreVFS : public VFS +{ + std::string group; + Ogre::ResourceGroupManager *gm; + + public: + /** @brief Constructor + + OGRE must be initialized (ie. you must have created an + Ogre::Root object somewhere) before calling this. + + @param group Optional resource group name. If none is given, + OGRE's default (or 'World') resource group is used. + */ + OgreVFS(const std::string &_group = ""); + + /// Open a new data stream. Deleting the object should be enough to + /// close it. + virtual Stream::StreamPtr open(const std::string &name); + + /// Check for the existence of a file + virtual bool isFile(const std::string &name) const + { return gm->resourceExists(group, name); } + + /// This doesn't work, always returns false. + virtual bool isDir(const std::string &name) const + { return false; } + + /// This doesn't work. + virtual FileInfoPtr stat(const std::string &name) const + { return FileInfoPtr(); } + + /// List all entries in a given directory. A blank dir should be + /// interpreted as a the root/current directory of the archive. If + /// dirs is true, list directories instead of files. OGRE note: The + /// ogre resource systemd does not support recursive listing of + /// files. We might make a separate filter for this later. + virtual FileInfoListPtr list(const std::string& dir = "", + bool recurse=true, + bool dirs=false) const; + + /// Find files after a given pattern. Wildcards (*) are + /// supported. + virtual FileInfoListPtr find(const std::string& pattern, + bool recursive=true, + bool dirs=false) const; +}; + +}} // namespaces +#endif diff --git a/libs/mangle/vfs/servers/physfs_vfs.hpp b/libs/mangle/vfs/servers/physfs_vfs.hpp new file mode 100644 index 000000000..8535088e0 --- /dev/null +++ b/libs/mangle/vfs/servers/physfs_vfs.hpp @@ -0,0 +1,71 @@ +#ifndef MANGLE_VFS_PHYSFS_SERVER_H +#define MANGLE_VFS_PHYSFS_SERVER_H + +#include "../vfs.hpp" +#include "../../stream/servers/phys_stream.hpp" + +#include +#include + +namespace Mangle { +namespace VFS { + +/** @brief An interface into the PhysFS virtual file system library + + You have to set up PhysFS on your own before using this class. + */ +class PhysVFS : public VFS +{ + public: + PhysVFS() + { + hasList = true; + hasFind = false; + isCaseSensitive = true; + } + + /// Open a new data stream. Deleting the object should be enough to + /// close it. + virtual Stream::StreamPtr open(const std::string &name) + { return Stream::StreamPtr(new Stream::PhysFile(PHYSFS_openRead(name.c_str()))); } + + /// Check for the existence of a file + virtual bool isFile(const std::string &name) const + { return PHYSFS_exists(name.c_str()); } + + /// Checks for a directory + virtual bool isDir(const std::string &name) const + { return PHYSFS_isDirectory(name.c_str()); } + + /// This doesn't work + virtual FileInfoPtr stat(const std::string &name) const + { assert(0); return FileInfoPtr(); } + + virtual FileInfoListPtr list(const std::string& dir = "", + bool recurse=true, + bool dirs=false) const + { + char **files = PHYSFS_enumerateFiles(dir.c_str()); + FileInfoListPtr lst(new FileInfoList); + + // Add all teh files + int i = 0; + while(files[i] != NULL) + { + FileInfo fi; + fi.name = files[i]; + fi.isDir = false; + + lst->push_back(fi); + } + return lst; + } + + virtual FileInfoListPtr find(const std::string& pattern, + bool recursive=true, + bool dirs=false) const + { assert(0); } +}; + +}} // namespaces +#endif diff --git a/libs/mangle/vfs/tests/.gitignore b/libs/mangle/vfs/tests/.gitignore new file mode 100644 index 000000000..814490404 --- /dev/null +++ b/libs/mangle/vfs/tests/.gitignore @@ -0,0 +1 @@ +*_test diff --git a/libs/mangle/vfs/tests/Makefile b/libs/mangle/vfs/tests/Makefile new file mode 100644 index 000000000..a5d1d1712 --- /dev/null +++ b/libs/mangle/vfs/tests/Makefile @@ -0,0 +1,25 @@ +GCC=g++ -I../ -Wall + +all: dummy_test ogre_client_test ogre_resource_test ogre_server_test physfs_server_test + +I_OGRE=$(shell pkg-config --cflags OGRE) +L_OGRE=$(shell pkg-config --libs OGRE) +L_PHYSFS=-lphysfs + +ogre_client_test: ogre_client_test.cpp dummy_vfs.cpp ../vfs.hpp ../clients/ogre_archive.hpp ../clients/ogre_archive.cpp + $(GCC) $< ../clients/ogre_archive.cpp -o $@ $(I_OGRE) $(L_OGRE) + +ogre_resource_test: ogre_resource_test.cpp + $(GCC) $< -o $@ $(I_OGRE) $(L_OGRE) + +ogre_server_test: ogre_server_test.cpp ../vfs.hpp ../servers/ogre_vfs.hpp ../servers/ogre_vfs.cpp + $(GCC) $< -o $@ $(I_OGRE) $(L_OGRE) ../servers/ogre_vfs.cpp + +physfs_server_test: physfs_server_test.cpp ../vfs.hpp ../servers/physfs_vfs.hpp + $(GCC) $< -o $@ $(L_PHYSFS) + +dummy_test: dummy_test.cpp dummy_vfs.cpp ../vfs.hpp + $(GCC) $< -o $@ + +clean: + rm *_test diff --git a/libs/mangle/vfs/tests/dummy_test.cpp b/libs/mangle/vfs/tests/dummy_test.cpp new file mode 100644 index 000000000..541010c6a --- /dev/null +++ b/libs/mangle/vfs/tests/dummy_test.cpp @@ -0,0 +1,42 @@ +#include "dummy_vfs.cpp" + +#include +#include + +using namespace std; + +void print(FileInfo &inf) +{ + cout << "name: " << inf.name << endl; + cout << "basename: " << inf.basename << endl; + cout << "isDir: " << inf.isDir << endl; + cout << "size: " << inf.size << endl; + cout << "time: " << inf.time << endl; +} +void print(FileInfoPtr inf) { print(*inf); } + +void print(FileInfoList &lst) +{ + for(unsigned i=0; isize() << endl; + + return 0; +} diff --git a/libs/mangle/vfs/tests/dummy_vfs.cpp b/libs/mangle/vfs/tests/dummy_vfs.cpp new file mode 100644 index 000000000..0448e96a5 --- /dev/null +++ b/libs/mangle/vfs/tests/dummy_vfs.cpp @@ -0,0 +1,117 @@ +// This file is shared between several test programs +#include "vfs.hpp" +#include +#include + +#include "../../stream/servers/memory_stream.hpp" + +using namespace Mangle::VFS; +using namespace Mangle::Stream; + +class DummyVFS : public VFS +{ +public: + DummyVFS() + { + hasFind = false; + hasList = true; + isCaseSensitive = true; + } + + // We only support opening 'file1' at the moment. + StreamPtr open(const std::string &name) + { + assert(name == "file1"); + return StreamPtr(new MemoryStream("hello world", 11)); + } + + bool isFile(const std::string &name) const + { + return (name == "file1" || + name == "dir/file2"); + } + + bool isDir(const std::string &name) const + { + return name == "dir"; + } + + /// Get info about a single file + FileInfoPtr stat(const std::string &name) const + { + FileInfoPtr fi(new FileInfo); + fi->name = name; + fi->time = 0; + + if(isFile(name)) + { + if(name == "dir/file2") + { + fi->basename = "file2"; + fi->size = 2; + } + else + { + fi->basename = "file1"; + fi->size = 1; + } + fi->isDir = false; + } + else if(isDir(name)) + { + fi->basename = "dir"; + fi->isDir = true; + fi->size = 0; + } + else assert(0); + + return fi; + } + + /// List all entries in a given directory. A blank dir should be + /// interpreted as a the root/current directory of the archive. If + /// dirs is true, list directories instead of files. + virtual FileInfoListPtr list(const std::string& dir = "", + bool recurse=true, + bool dirs=false) const + { + assert(dir == ""); + + FileInfoListPtr fl(new FileInfoList); + + FileInfo fi; + + if(!dirs) + { + fi.name = "file1"; + fi.basename = "file1"; + fi.isDir = false; + fi.size = 1; + fi.time = 0; + fl->push_back(fi); + + if(recurse) + { + fi.name = "dir/file2"; + fi.basename = "file2"; + fi.size = 2; + fl->push_back(fi); + } + } + else + { + fi.name = "dir"; + fi.basename = "dir"; + fi.isDir = true; + fi.size = 0; + fi.time = 0; + fl->push_back(fi); + } + return fl; + } + + FileInfoListPtr find(const std::string& pattern, + bool recursive=true, + bool dirs=false) const + { assert(0); } +}; diff --git a/libs/mangle/vfs/tests/ogre_client_test.cpp b/libs/mangle/vfs/tests/ogre_client_test.cpp new file mode 100644 index 000000000..8e1269b56 --- /dev/null +++ b/libs/mangle/vfs/tests/ogre_client_test.cpp @@ -0,0 +1,39 @@ +#include "dummy_vfs.cpp" +#include "../clients/ogre_archive.hpp" +#include + +using namespace Ogre; +using namespace std; + +void print(StringVectorPtr lst) +{ + int s = lst->size(); + + for(int i=0; isize() << endl; + cout << "contents: " << file->getAsString() << endl; + + return 0; +} diff --git a/libs/mangle/vfs/tests/ogre_resource_test.cpp b/libs/mangle/vfs/tests/ogre_resource_test.cpp new file mode 100644 index 000000000..8965adaa1 --- /dev/null +++ b/libs/mangle/vfs/tests/ogre_resource_test.cpp @@ -0,0 +1,61 @@ +#include +#include +#include + +/* + This isn't really a test of our implementation, but a test of using + the Ogre resource system to find files. If the Ogre interface + changes and you have to change this test, you will have to change + the servers/ogre_vfs.cpp implementation equivalently. + + */ + +using namespace std; +using namespace Ogre; + +ResourceGroupManager *gm; +String group; + +void find(const std::string &fileName) +{ + cout << "\nFile: " << fileName << endl; + + if(!gm->resourceExists(group, fileName)) + { + cout << "Does not exist\n"; + return; + } + + DataStreamPtr data = gm->openResource(fileName, group); + + cout << "Size: " << data->size() << endl; + cout << "First line: " << data->getLine() << "\n"; +} + +int main() +{ + // Disable logging + new LogManager; + Log *log = LogManager::getSingleton().createLog(""); + log->setDebugOutputEnabled(false); + + // Set up Ogre + Root root("","",""); + + root.addResourceLocation("./", "FileSystem", "General"); + + gm = ResourceGroupManager::getSingletonPtr(); + group = gm->getWorldResourceGroupName(); + + find("Makefile"); + find("ogre_resource_test.cpp"); + find("bleh"); + + cout << "\nAll source files:\n"; + FileInfoListPtr list = gm->findResourceFileInfo(group, "*.cpp"); + FileInfoList::iterator it, end; + it = list->begin(); + end = list->end(); + for(; it != end; it++) + cout << " " << it->filename << endl; +} diff --git a/libs/mangle/vfs/tests/ogre_server_test.cpp b/libs/mangle/vfs/tests/ogre_server_test.cpp new file mode 100644 index 000000000..b846eef96 --- /dev/null +++ b/libs/mangle/vfs/tests/ogre_server_test.cpp @@ -0,0 +1,38 @@ +#include "../servers/ogre_vfs.hpp" + +#include + +#include "server_common.cpp" + +Ogre::Root *root; + +void setupOgre() +{ + using namespace Ogre; + + // Disable logging + new LogManager; + Log *log = LogManager::getSingleton().createLog(""); + log->setDebugOutputEnabled(false); + + // Set up Root + root = new Root("","",""); + + // Add a zip file and the current directory + root->addResourceLocation("test.zip", "Zip", "General"); + root->addResourceLocation("./", "FileSystem", "General"); +} + +int main() +{ + // Set up the engine + setupOgre(); + + // This is our entry point into the resource file system + OgreVFS vfs("General"); + + // Run the test + testAll(vfs); + + return 0; +} diff --git a/libs/mangle/vfs/tests/output/dummy_test.out b/libs/mangle/vfs/tests/output/dummy_test.out new file mode 100644 index 000000000..61f1fda80 --- /dev/null +++ b/libs/mangle/vfs/tests/output/dummy_test.out @@ -0,0 +1,31 @@ +Listing all files: +name: file1 +basename: file1 +isDir: 0 +size: 1 +time: 0 +name: dir/file2 +basename: file2 +isDir: 0 +size: 2 +time: 0 + +Stat for single files: +name: file1 +basename: file1 +isDir: 0 +size: 1 +time: 0 + +name: dir/file2 +basename: file2 +isDir: 0 +size: 2 +time: 0 + +name: dir +basename: dir +isDir: 1 +size: 0 +time: 0 +filesize: 11 diff --git a/libs/mangle/vfs/tests/output/ogre_client_test.out b/libs/mangle/vfs/tests/output/ogre_client_test.out new file mode 100644 index 000000000..bc10b1e5c --- /dev/null +++ b/libs/mangle/vfs/tests/output/ogre_client_test.out @@ -0,0 +1,12 @@ +Case: 1 +Name: dummy +Type: Mangle +All files: + file1 + dir/file2 +Non-recursive: + file1 +Dirs: + dir +filesize: 11 +contents: hello world diff --git a/libs/mangle/vfs/tests/output/ogre_resource_test.out b/libs/mangle/vfs/tests/output/ogre_resource_test.out new file mode 100644 index 000000000..9bfc4ff5b --- /dev/null +++ b/libs/mangle/vfs/tests/output/ogre_resource_test.out @@ -0,0 +1,20 @@ + +File: Makefile +Size: 828 +First line: GCC=g++ -I../ + +File: ogre_resource_test.cpp +Size: 1437 +First line: #include + +File: bleh +Does not exist + +All source files: + physfs_server_test.cpp + dummy_test.cpp + ogre_resource_test.cpp + server_common.cpp + dummy_vfs.cpp + ogre_client_test.cpp + ogre_server_test.cpp diff --git a/libs/mangle/vfs/tests/output/ogre_server_test.out b/libs/mangle/vfs/tests/output/ogre_server_test.out new file mode 100644 index 000000000..74f350844 --- /dev/null +++ b/libs/mangle/vfs/tests/output/ogre_server_test.out @@ -0,0 +1,11 @@ + +File: Makefile +Size: 828 +First 12 bytes: GCC=g++ -I.. + +File: testfile.txt +Size: 13 +First 12 bytes: hello world! + +File: blah_bleh +File doesn't exist diff --git a/libs/mangle/vfs/tests/output/physfs_server_test.out b/libs/mangle/vfs/tests/output/physfs_server_test.out new file mode 100644 index 000000000..74f350844 --- /dev/null +++ b/libs/mangle/vfs/tests/output/physfs_server_test.out @@ -0,0 +1,11 @@ + +File: Makefile +Size: 828 +First 12 bytes: GCC=g++ -I.. + +File: testfile.txt +Size: 13 +First 12 bytes: hello world! + +File: blah_bleh +File doesn't exist diff --git a/libs/mangle/vfs/tests/physfs_server_test.cpp b/libs/mangle/vfs/tests/physfs_server_test.cpp new file mode 100644 index 000000000..9e9d088cd --- /dev/null +++ b/libs/mangle/vfs/tests/physfs_server_test.cpp @@ -0,0 +1,21 @@ +#include "../servers/physfs_vfs.hpp" + +#include "server_common.cpp" + +#include + +int main() +{ + // Set up the library and paths + PHYSFS_init("blah"); + PHYSFS_addToSearchPath("test.zip", 1); + PHYSFS_addToSearchPath("./", 1); + + // Create our interface + PhysVFS vfs; + + // Run the test + testAll(vfs); + + return 0; +} diff --git a/libs/mangle/vfs/tests/server_common.cpp b/libs/mangle/vfs/tests/server_common.cpp new file mode 100644 index 000000000..1834bc25a --- /dev/null +++ b/libs/mangle/vfs/tests/server_common.cpp @@ -0,0 +1,33 @@ +#include + +using namespace Mangle::VFS; +using namespace Mangle::Stream; +using namespace std; + +void find(VFS &vfs, const std::string &file) +{ + cout << "\nFile: " << file << endl; + + if(!vfs.isFile(file)) + { + cout << "File doesn't exist\n"; + return; + } + + StreamPtr data = vfs.open(file); + + cout << "Size: " << data->size() << endl; + + char buf[13]; + buf[12] = 0; + data->read(buf, 12); + + cout << "First 12 bytes: " << buf << "\n"; +} + +void testAll(VFS &vfs) +{ + find(vfs, "Makefile"); // From the file system + find(vfs, "testfile.txt"); // From the zip + find(vfs, "blah_bleh"); // Doesn't exist +} diff --git a/libs/mangle/vfs/tests/test.sh b/libs/mangle/vfs/tests/test.sh new file mode 100755 index 000000000..2d07708ad --- /dev/null +++ b/libs/mangle/vfs/tests/test.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +make || exit + +mkdir -p output + +PROGS=*_test + +for a in $PROGS; do + if [ -f "output/$a.out" ]; then + echo "Running $a:" + ./$a | diff output/$a.out - + else + echo "Creating $a.out" + ./$a > "output/$a.out" + git add "output/$a.out" + fi +done diff --git a/libs/mangle/vfs/tests/test.zip b/libs/mangle/vfs/tests/test.zip new file mode 100644 index 000000000..ec82f8bc6 Binary files /dev/null and b/libs/mangle/vfs/tests/test.zip differ diff --git a/libs/mangle/vfs/vfs.hpp b/libs/mangle/vfs/vfs.hpp new file mode 100644 index 000000000..258a6b0a0 --- /dev/null +++ b/libs/mangle/vfs/vfs.hpp @@ -0,0 +1,87 @@ +#ifndef MANGLE_VFS_H +#define MANGLE_VFS_H + +#include "../stream/stream.hpp" +#include +#include + +namespace Mangle { +namespace VFS { + +/// Generic file info structure +struct FileInfo +{ + /// Full name, including path + std::string name; + + /// Base name, not including path + std::string basename; + + /// Is this a directory? + bool isDir; + + /// File size + size_t size; + + /// Last modification date + time_t time; +}; + +typedef std::vector FileInfoList; + +typedef boost::shared_ptr FileInfoPtr; +typedef boost::shared_ptr FileInfoListPtr; + +/** An interface to any file system or other provider of named data + streams +*/ +class VFS +{ + public: + // Feature options. These should be set in the constructor. + + /// If true, the list() function work + bool hasList; + + /// If true, the find() function work + bool hasFind; + + /// If true, the file system is case sensitive + bool isCaseSensitive; + + /// Virtual destructor + virtual ~VFS() {} + + /// Open a new data stream. Deleting the object (letting all the + /// pointers to it go out of scope) should be enough to close it. + virtual Stream::StreamPtr open(const std::string &name) = 0; + + /// Check for the existence of a file + virtual bool isFile(const std::string &name) const = 0; + + /// Check for the existence of a directory + virtual bool isDir(const std::string &name) const = 0; + + /// Get info about a single file + virtual FileInfoPtr stat(const std::string &name) const = 0; + + /// List all entries in a given directory. A blank dir should be + /// interpreted as a the root/current directory of the archive. If + /// dirs is true, list directories instead of files. + virtual FileInfoListPtr list(const std::string& dir = "", + bool recurse=true, + bool dirs=false) const = 0; + + /// Find files after a given pattern. Wildcards (*) are + /// supported. Only valid if 'hasFind' is true. Don't implement your + /// own pattern matching here if the backend doesn't support it + /// natively; use a filter instead. + virtual FileInfoListPtr find(const std::string& pattern, + bool recursive=true, + bool dirs=false) const = 0; +}; + +typedef boost::shared_ptr VFSPtr; + +}} // namespaces +#endif diff --git a/libs/openengine b/libs/openengine deleted file mode 160000 index 21b845645..000000000 --- a/libs/openengine +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 21b8456453242e132c85f92047cf9bce535c1b22 diff --git a/libs/openengine/.gitignore b/libs/openengine/.gitignore new file mode 100644 index 000000000..23a5e931b --- /dev/null +++ b/libs/openengine/.gitignore @@ -0,0 +1,3 @@ +*~ +*.o +*_test diff --git a/libs/openengine/README b/libs/openengine/README new file mode 100644 index 000000000..621fe8d60 --- /dev/null +++ b/libs/openengine/README @@ -0,0 +1,12 @@ +OpenEngine README +================= + +OpenEngine is a bunch of stand-alone game engine modules collected from the OpenMW project (see http://github.com/korslund/openmw or http://openmw.com ) and from certain other projects. + +It is currently a very early work in progress, and development will follow OpenMW closely for a while forward. + +OpenEngine will depend heavily on Mangle ( http://github.com/korslund/mangle/ ) and will thus aim to be backend agnostic. When finished it should work with a variety for free and commercial middleware libraries as backends for graphics, sound, physics, input and so on. + +All questions can be directed to Nicolay Korslund at korslund@gmail.com + +- Nicolay diff --git a/libs/openengine/bullet/BtOgre.cpp b/libs/openengine/bullet/BtOgre.cpp new file mode 100644 index 000000000..618739083 --- /dev/null +++ b/libs/openengine/bullet/BtOgre.cpp @@ -0,0 +1,1090 @@ +/* + * ============================================================================================= + * + * Filename: BtOgre.cpp + * + * Description: BtOgre implementation. + * + * Version: 1.0 + * Created: 27/12/2008 01:47:56 PM + * + * Author: Nikhilesh (nikki) + * + * ============================================================================================= + */ + +#include "BtOgrePG.h" +#include "BtOgreGP.h" +#include "BtOgreExtras.h" + +using namespace Ogre; + +namespace BtOgre { + +/* + * ============================================================================================= + * BtOgre::VertexIndexToShape + * ============================================================================================= + */ + + void VertexIndexToShape::addStaticVertexData(const VertexData *vertex_data) + { + if (!vertex_data) + return; + + const VertexData *data = vertex_data; + + const unsigned int prev_size = mVertexCount; + mVertexCount += (unsigned int)data->vertexCount; + + Ogre::Vector3* tmp_vert = new Ogre::Vector3[mVertexCount]; + if (mVertexBuffer) + { + memcpy(tmp_vert, mVertexBuffer, sizeof(Vector3) * prev_size); + delete[] mVertexBuffer; + } + mVertexBuffer = tmp_vert; + + // Get the positional buffer element + { + const Ogre::VertexElement* posElem = data->vertexDeclaration->findElementBySemantic(Ogre::VES_POSITION); + Ogre::HardwareVertexBufferSharedPtr vbuf = data->vertexBufferBinding->getBuffer(posElem->getSource()); + const unsigned int vSize = (unsigned int)vbuf->getVertexSize(); + + unsigned char* vertex = static_cast(vbuf->lock(Ogre::HardwareBuffer::HBL_READ_ONLY)); + float* pReal; + Ogre::Vector3 * curVertices = &mVertexBuffer[prev_size]; + const unsigned int vertexCount = (unsigned int)data->vertexCount; + for(unsigned int j = 0; j < vertexCount; ++j) + { + posElem->baseVertexPointerToElement(vertex, &pReal); + vertex += vSize; + + curVertices->x = (*pReal++); + curVertices->y = (*pReal++); + curVertices->z = (*pReal++); + + *curVertices = mTransform * (*curVertices); + + curVertices++; + } + vbuf->unlock(); + } + } + + //------------------------------------------------------------------------------------------------ + void VertexIndexToShape::addAnimatedVertexData(const Ogre::VertexData *vertex_data, + const Ogre::VertexData *blend_data, + const Ogre::Mesh::IndexMap *indexMap) + { + // Get the bone index element + assert(vertex_data); + + const VertexData *data = blend_data; + const unsigned int prev_size = mVertexCount; + mVertexCount += (unsigned int)data->vertexCount; + Ogre::Vector3* tmp_vert = new Ogre::Vector3[mVertexCount]; + if (mVertexBuffer) + { + memcpy(tmp_vert, mVertexBuffer, sizeof(Vector3) * prev_size); + delete[] mVertexBuffer; + } + mVertexBuffer = tmp_vert; + + // Get the positional buffer element + { + const Ogre::VertexElement* posElem = data->vertexDeclaration->findElementBySemantic(Ogre::VES_POSITION); + assert (posElem); + Ogre::HardwareVertexBufferSharedPtr vbuf = data->vertexBufferBinding->getBuffer(posElem->getSource()); + const unsigned int vSize = (unsigned int)vbuf->getVertexSize(); + + unsigned char* vertex = static_cast(vbuf->lock(Ogre::HardwareBuffer::HBL_READ_ONLY)); + float* pReal; + Ogre::Vector3 * curVertices = &mVertexBuffer[prev_size]; + const unsigned int vertexCount = (unsigned int)data->vertexCount; + for(unsigned int j = 0; j < vertexCount; ++j) + { + posElem->baseVertexPointerToElement(vertex, &pReal); + vertex += vSize; + + curVertices->x = (*pReal++); + curVertices->y = (*pReal++); + curVertices->z = (*pReal++); + + *curVertices = mTransform * (*curVertices); + + curVertices++; + } + vbuf->unlock(); + } + + { + const Ogre::VertexElement* bneElem = vertex_data->vertexDeclaration->findElementBySemantic(Ogre::VES_BLEND_INDICES); + assert (bneElem); + + Ogre::HardwareVertexBufferSharedPtr vbuf = vertex_data->vertexBufferBinding->getBuffer(bneElem->getSource()); + const unsigned int vSize = (unsigned int)vbuf->getVertexSize(); + unsigned char* vertex = static_cast(vbuf->lock(Ogre::HardwareBuffer::HBL_READ_ONLY)); + + unsigned char* pBone; + + if (!mBoneIndex) + mBoneIndex = new BoneIndex(); + BoneIndex::iterator i; + + Ogre::Vector3 * curVertices = &mVertexBuffer[prev_size]; + + const unsigned int vertexCount = (unsigned int)vertex_data->vertexCount; + for(unsigned int j = 0; j < vertexCount; ++j) + { + bneElem->baseVertexPointerToElement(vertex, &pBone); + vertex += vSize; + + const unsigned char currBone = (indexMap) ? (*indexMap)[*pBone] : *pBone; + i = mBoneIndex->find (currBone); + Vector3Array* l = 0; + if (i == mBoneIndex->end()) + { + l = new Vector3Array; + mBoneIndex->insert(BoneKeyIndex(currBone, l)); + } + else + { + l = i->second; + } + + l->push_back(*curVertices); + + curVertices++; + } + vbuf->unlock(); + } + } + + //------------------------------------------------------------------------------------------------ + void VertexIndexToShape::addIndexData(IndexData *data, const unsigned int offset) + { + const unsigned int prev_size = mIndexCount; + mIndexCount += (unsigned int)data->indexCount; + + unsigned int* tmp_ind = new unsigned int[mIndexCount]; + if (mIndexBuffer) + { + memcpy (tmp_ind, mIndexBuffer, sizeof(unsigned int) * prev_size); + delete[] mIndexBuffer; + } + mIndexBuffer = tmp_ind; + + const unsigned int numTris = (unsigned int) data->indexCount / 3; + HardwareIndexBufferSharedPtr ibuf = data->indexBuffer; + const bool use32bitindexes = (ibuf->getType() == HardwareIndexBuffer::IT_32BIT); + unsigned int index_offset = prev_size; + + if (use32bitindexes) + { + const unsigned int* pInt = static_cast(ibuf->lock(HardwareBuffer::HBL_READ_ONLY)); + for(unsigned int k = 0; k < numTris; ++k) + { + mIndexBuffer[index_offset ++] = offset + *pInt++; + mIndexBuffer[index_offset ++] = offset + *pInt++; + mIndexBuffer[index_offset ++] = offset + *pInt++; + } + ibuf->unlock(); + } + else + { + const unsigned short* pShort = static_cast(ibuf->lock(HardwareBuffer::HBL_READ_ONLY)); + for(unsigned int k = 0; k < numTris; ++k) + { + mIndexBuffer[index_offset ++] = offset + static_cast (*pShort++); + mIndexBuffer[index_offset ++] = offset + static_cast (*pShort++); + mIndexBuffer[index_offset ++] = offset + static_cast (*pShort++); + } + ibuf->unlock(); + } + + } + + //------------------------------------------------------------------------------------------------ + Real VertexIndexToShape::getRadius() + { + if (mBoundRadius == (-1)) + { + getSize(); + mBoundRadius = (std::max(mBounds.x,std::max(mBounds.y,mBounds.z)) * 0.5); + } + return mBoundRadius; + } + + //------------------------------------------------------------------------------------------------ + Vector3 VertexIndexToShape::getSize() + { + const unsigned int vCount = getVertexCount(); + if (mBounds == Ogre::Vector3(-1,-1,-1) && vCount > 0) + { + + const Ogre::Vector3 * const v = getVertices(); + + Ogre::Vector3 vmin(v[0]); + Ogre::Vector3 vmax(v[0]); + + for(unsigned int j = 1; j < vCount; j++) + { + vmin.x = std::min(vmin.x, v[j].x); + vmin.y = std::min(vmin.y, v[j].y); + vmin.z = std::min(vmin.z, v[j].z); + + vmax.x = std::max(vmax.x, v[j].x); + vmax.y = std::max(vmax.y, v[j].y); + vmax.z = std::max(vmax.z, v[j].z); + } + + mBounds.x = vmax.x - vmin.x; + mBounds.y = vmax.y - vmin.y; + mBounds.z = vmax.z - vmin.z; + } + + return mBounds; + } + + //------------------------------------------------------------------------------------------------ + const Ogre::Vector3* VertexIndexToShape::getVertices() + { + return mVertexBuffer; + } + + //------------------------------------------------------------------------------------------------ + unsigned int VertexIndexToShape::getVertexCount() + { + return mVertexCount; + } + + //------------------------------------------------------------------------------------------------ + const unsigned int* VertexIndexToShape::getIndices() + { + return mIndexBuffer; + } + + //------------------------------------------------------------------------------------------------ + unsigned int VertexIndexToShape::getIndexCount() + { + return mIndexCount; + } + + //------------------------------------------------------------------------------------------------ + btSphereShape* VertexIndexToShape::createSphere() + { + const Ogre::Real rad = getRadius(); + assert((rad > 0.0) && + ("Sphere radius must be greater than zero")); + btSphereShape* shape = new btSphereShape(rad); + + shape->setLocalScaling(Convert::toBullet(mScale)); + + return shape; + } + + //------------------------------------------------------------------------------------------------ + btBoxShape* VertexIndexToShape::createBox() + { + const Ogre::Vector3 sz = getSize(); + + assert((sz.x > 0.0) && (sz.y > 0.0) && (sz.y > 0.0) && + ("Size of box must be greater than zero on all axes")); + + btBoxShape* shape = new btBoxShape(Convert::toBullet(sz * 0.5)); + + shape->setLocalScaling(Convert::toBullet(mScale)); + + return shape; + } + + //------------------------------------------------------------------------------------------------ + btCylinderShape* VertexIndexToShape::createCylinder() + { + const Ogre::Vector3 sz = getSize(); + + assert((sz.x > 0.0) && (sz.y > 0.0) && (sz.y > 0.0) && + ("Size of Cylinder must be greater than zero on all axes")); + + btCylinderShape* shape = new btCylinderShapeX(Convert::toBullet(sz * 0.5)); + + shape->setLocalScaling(Convert::toBullet(mScale)); + + return shape; + } + + //------------------------------------------------------------------------------------------------ + btConvexHullShape* VertexIndexToShape::createConvex() + { + assert(mVertexCount && (mIndexCount >= 6) && + ("Mesh must have some vertices and at least 6 indices (2 triangles)")); + + return new btConvexHullShape((btScalar*) &mVertexBuffer[0].x, mVertexCount, sizeof(Vector3)); + } + + //------------------------------------------------------------------------------------------------ + btBvhTriangleMeshShape* VertexIndexToShape::createTrimesh() + { + assert(mVertexCount && (mIndexCount >= 6) && + ("Mesh must have some vertices and at least 6 indices (2 triangles)")); + + unsigned int numFaces = mIndexCount / 3; + + btTriangleMesh *trimesh = new btTriangleMesh(); + unsigned int *indices = mIndexBuffer; + Vector3 *vertices = mVertexBuffer; + + btVector3 vertexPos[3]; + for (unsigned int n = 0; n < numFaces; ++n) + { + { + const Vector3 &vec = vertices[*indices]; + vertexPos[0][0] = vec.x; + vertexPos[0][1] = vec.y; + vertexPos[0][2] = vec.z; + } + { + const Vector3 &vec = vertices[*(indices + 1)]; + vertexPos[1][0] = vec.x; + vertexPos[1][1] = vec.y; + vertexPos[1][2] = vec.z; + } + { + const Vector3 &vec = vertices[*(indices + 2)]; + vertexPos[2][0] = vec.x; + vertexPos[2][1] = vec.y; + vertexPos[2][2] = vec.z; + } + + indices += 3; + + trimesh->addTriangle(vertexPos[0], vertexPos[1], vertexPos[2]); + } + + const bool useQuantizedAABB = true; + btBvhTriangleMeshShape *shape = new btBvhTriangleMeshShape(trimesh, useQuantizedAABB); + + shape->setLocalScaling(Convert::toBullet(mScale)); + + return shape; + } + + //------------------------------------------------------------------------------------------------ + VertexIndexToShape::~VertexIndexToShape() + { + delete[] mVertexBuffer; + delete[] mIndexBuffer; + + if (mBoneIndex) + { + for(BoneIndex::iterator i = mBoneIndex->begin(); + i != mBoneIndex->end(); + ++i) + { + delete i->second; + } + delete mBoneIndex; + } + } + + //------------------------------------------------------------------------------------------------ + VertexIndexToShape::VertexIndexToShape(const Matrix4 &transform) : + mVertexBuffer (0), + mIndexBuffer (0), + mVertexCount (0), + mIndexCount (0), + mTransform (transform), + mBoundRadius (-1), + mBounds (Vector3(-1,-1,-1)), + mBoneIndex (0), + mScale(1) + { + } + +/* + * ============================================================================================= + * BtOgre::StaticMeshToShapeConverter + * ============================================================================================= + */ + + StaticMeshToShapeConverter::StaticMeshToShapeConverter() : + VertexIndexToShape(), + mEntity (0), + mNode (0) + { + } + + //------------------------------------------------------------------------------------------------ + StaticMeshToShapeConverter::~StaticMeshToShapeConverter() + { + } + + //------------------------------------------------------------------------------------------------ + StaticMeshToShapeConverter::StaticMeshToShapeConverter(Entity *entity, const Matrix4 &transform) : + VertexIndexToShape(transform), + mEntity (0), + mNode (0) + { + addEntity(entity, transform); + } + + //------------------------------------------------------------------------------------------------ + StaticMeshToShapeConverter::StaticMeshToShapeConverter(Renderable *rend, const Matrix4 &transform) : + VertexIndexToShape(transform), + mEntity (0), + mNode (0) + { + RenderOperation op; + rend->getRenderOperation(op); + VertexIndexToShape::addStaticVertexData(op.vertexData); + if(op.useIndexes) + VertexIndexToShape::addIndexData(op.indexData); + + } + + //------------------------------------------------------------------------------------------------ + void StaticMeshToShapeConverter::addEntity(Entity *entity,const Matrix4 &transform) + { + // Each entity added need to reset size and radius + // next time getRadius and getSize are asked, they're computed. + mBounds = Ogre::Vector3(-1,-1,-1); + mBoundRadius = -1; + + mEntity = entity; + mNode = (SceneNode*)(mEntity->getParentNode()); + mTransform = transform; + mScale = mNode->getScale(); + + if (mEntity->getMesh()->sharedVertexData) + { + VertexIndexToShape::addStaticVertexData (mEntity->getMesh()->sharedVertexData); + } + + for (unsigned int i = 0;i < mEntity->getNumSubEntities();++i) + { + SubMesh *sub_mesh = mEntity->getSubEntity(i)->getSubMesh(); + + if (!sub_mesh->useSharedVertices) + { + VertexIndexToShape::addIndexData(sub_mesh->indexData, mVertexCount); + VertexIndexToShape::addStaticVertexData (sub_mesh->vertexData); + } + else + { + VertexIndexToShape::addIndexData (sub_mesh->indexData); + } + + } + } + + //------------------------------------------------------------------------------------------------ + void StaticMeshToShapeConverter::addMesh(const MeshPtr &mesh, const Matrix4 &transform) + { + // Each entity added need to reset size and radius + // next time getRadius and getSize are asked, they're computed. + mBounds = Ogre::Vector3(-1,-1,-1); + mBoundRadius = -1; + + //_entity = entity; + //_node = (SceneNode*)(_entity->getParentNode()); + mTransform = transform; + + if (mesh->hasSkeleton ()) + Ogre::LogManager::getSingleton().logMessage("MeshToShapeConverter::addMesh : Mesh " + mesh->getName () + " as skeleton but added to trimesh non animated"); + + if (mesh->sharedVertexData) + { + VertexIndexToShape::addStaticVertexData (mesh->sharedVertexData); + } + + for(unsigned int i = 0;i < mesh->getNumSubMeshes();++i) + { + SubMesh *sub_mesh = mesh->getSubMesh(i); + + if (!sub_mesh->useSharedVertices) + { + VertexIndexToShape::addIndexData(sub_mesh->indexData, mVertexCount); + VertexIndexToShape::addStaticVertexData (sub_mesh->vertexData); + } + else + { + VertexIndexToShape::addIndexData (sub_mesh->indexData); + } + + } + } + +/* + * ============================================================================================= + * BtOgre::AnimatedMeshToShapeConverter + * ============================================================================================= + */ + + AnimatedMeshToShapeConverter::AnimatedMeshToShapeConverter(Entity *entity,const Matrix4 &transform) : + VertexIndexToShape(transform), + mEntity (0), + mNode (0), + mTransformedVerticesTemp(0), + mTransformedVerticesTempSize(0) + { + addEntity(entity, transform); + } + + //------------------------------------------------------------------------------------------------ + AnimatedMeshToShapeConverter::AnimatedMeshToShapeConverter() : + VertexIndexToShape(), + mEntity (0), + mNode (0), + mTransformedVerticesTemp(0), + mTransformedVerticesTempSize(0) + { + } + + //------------------------------------------------------------------------------------------------ + AnimatedMeshToShapeConverter::~AnimatedMeshToShapeConverter() + { + delete[] mTransformedVerticesTemp; + } + + //------------------------------------------------------------------------------------------------ + void AnimatedMeshToShapeConverter::addEntity(Entity *entity,const Matrix4 &transform) + { + // Each entity added need to reset size and radius + // next time getRadius and getSize are asked, they're computed. + mBounds = Ogre::Vector3(-1,-1,-1); + mBoundRadius = -1; + + mEntity = entity; + mNode = (SceneNode*)(mEntity->getParentNode()); + mTransform = transform; + + assert (entity->getMesh()->hasSkeleton ()); + + mEntity->addSoftwareAnimationRequest(false); + mEntity->_updateAnimation(); + + if (mEntity->getMesh()->sharedVertexData) + { + VertexIndexToShape::addAnimatedVertexData (mEntity->getMesh()->sharedVertexData, + mEntity->_getSkelAnimVertexData(), + &mEntity->getMesh()->sharedBlendIndexToBoneIndexMap); + } + + for (unsigned int i = 0;i < mEntity->getNumSubEntities();++i) + { + SubMesh *sub_mesh = mEntity->getSubEntity(i)->getSubMesh(); + + if (!sub_mesh->useSharedVertices) + { + VertexIndexToShape::addIndexData(sub_mesh->indexData, mVertexCount); + + VertexIndexToShape::addAnimatedVertexData (sub_mesh->vertexData, + mEntity->getSubEntity(i)->_getSkelAnimVertexData(), + &sub_mesh->blendIndexToBoneIndexMap); + } + else + { + VertexIndexToShape::addIndexData (sub_mesh->indexData); + } + + } + + mEntity->removeSoftwareAnimationRequest(false); + } + + //------------------------------------------------------------------------------------------------ + void AnimatedMeshToShapeConverter::addMesh(const MeshPtr &mesh, const Matrix4 &transform) + { + // Each entity added need to reset size and radius + // next time getRadius and getSize are asked, they're computed. + mBounds = Ogre::Vector3(-1,-1,-1); + mBoundRadius = -1; + + //_entity = entity; + //_node = (SceneNode*)(_entity->getParentNode()); + mTransform = transform; + + assert (mesh->hasSkeleton ()); + + if (mesh->sharedVertexData) + { + VertexIndexToShape::addAnimatedVertexData (mesh->sharedVertexData, + 0, + &mesh->sharedBlendIndexToBoneIndexMap); + } + + for(unsigned int i = 0;i < mesh->getNumSubMeshes();++i) + { + SubMesh *sub_mesh = mesh->getSubMesh(i); + + if (!sub_mesh->useSharedVertices) + { + VertexIndexToShape::addIndexData(sub_mesh->indexData, mVertexCount); + + VertexIndexToShape::addAnimatedVertexData (sub_mesh->vertexData, + 0, + &sub_mesh->blendIndexToBoneIndexMap); + } + else + { + VertexIndexToShape::addIndexData (sub_mesh->indexData); + } + + } + } + + //------------------------------------------------------------------------------------------------ + bool AnimatedMeshToShapeConverter::getBoneVertices(unsigned char bone, + unsigned int &vertex_count, + Ogre::Vector3* &vertices, + const Vector3 &bonePosition) + { + BoneIndex::iterator i = mBoneIndex->find(bone); + + if (i == mBoneIndex->end()) + return false; + + if (i->second->empty()) + return false; + + vertex_count = (unsigned int) i->second->size() + 1; + if (vertex_count > mTransformedVerticesTempSize) + { + if (mTransformedVerticesTemp) + delete[] mTransformedVerticesTemp; + + mTransformedVerticesTemp = new Ogre::Vector3[vertex_count]; + + } + + vertices = mTransformedVerticesTemp; + vertices[0] = bonePosition; + //mEntity->_getParentNodeFullTransform() * + //mEntity->getSkeleton()->getBone(bone)->_getDerivedPosition(); + + //mEntity->getSkeleton()->getBone(bone)->_getDerivedOrientation() + unsigned int currBoneVertex = 1; + Vector3Array::iterator j = i->second->begin(); + while(j != i->second->end()) + { + vertices[currBoneVertex] = (*j); + ++j; + ++currBoneVertex; + } + return true; + } + + //------------------------------------------------------------------------------------------------ + btBoxShape* AnimatedMeshToShapeConverter::createAlignedBox(unsigned char bone, + const Vector3 &bonePosition, + const Quaternion &boneOrientation) + { + unsigned int vertex_count; + Vector3* vertices; + + if (!getBoneVertices(bone, vertex_count, vertices, bonePosition)) + return 0; + + Vector3 min_vec(vertices[0]); + Vector3 max_vec(vertices[0]); + + for(unsigned int j = 1; j < vertex_count ;j++) + { + min_vec.x = std::min(min_vec.x,vertices[j].x); + min_vec.y = std::min(min_vec.y,vertices[j].y); + min_vec.z = std::min(min_vec.z,vertices[j].z); + + max_vec.x = std::max(max_vec.x,vertices[j].x); + max_vec.y = std::max(max_vec.y,vertices[j].y); + max_vec.z = std::max(max_vec.z,vertices[j].z); + } + const Ogre::Vector3 maxMinusMin(max_vec - min_vec); + btBoxShape* box = new btBoxShape(Convert::toBullet(maxMinusMin)); + + /*const Ogre::Vector3 pos + (min_vec.x + (maxMinusMin.x * 0.5), + min_vec.y + (maxMinusMin.y * 0.5), + min_vec.z + (maxMinusMin.z * 0.5));*/ + + //box->setPosition(pos); + + return box; + } + + //------------------------------------------------------------------------------------------------ + bool AnimatedMeshToShapeConverter::getOrientedBox(unsigned char bone, + const Vector3 &bonePosition, + const Quaternion &boneOrientation, + Vector3 &box_afExtent, + Vector3 *box_akAxis, + Vector3 &box_kCenter) + { + unsigned int vertex_count; + Vector3* vertices; + + if (!getBoneVertices(bone, vertex_count, vertices, bonePosition)) + return false; + + box_kCenter = Vector3::ZERO; + + { + for(unsigned int c = 0 ;c < vertex_count;c++) + { + box_kCenter += vertices[c]; + } + const Ogre::Real invVertexCount = 1.0 / vertex_count; + box_kCenter *= invVertexCount; + } + Quaternion orient = boneOrientation; + orient.ToAxes(box_akAxis); + + // Let C be the box center and let U0, U1, and U2 be the box axes. Each + // input point is of the form X = C + y0*U0 + y1*U1 + y2*U2. The + // following code computes min(y0), max(y0), min(y1), max(y1), min(y2), + // and max(y2). The box center is then adjusted to be + // C' = C + 0.5*(min(y0)+max(y0))*U0 + 0.5*(min(y1)+max(y1))*U1 + + // 0.5*(min(y2)+max(y2))*U2 + + Ogre::Vector3 kDiff (vertices[1] - box_kCenter); + Ogre::Real fY0Min = kDiff.dotProduct(box_akAxis[0]), fY0Max = fY0Min; + Ogre::Real fY1Min = kDiff.dotProduct(box_akAxis[1]), fY1Max = fY1Min; + Ogre::Real fY2Min = kDiff.dotProduct(box_akAxis[2]), fY2Max = fY2Min; + + for (unsigned int i = 2; i < vertex_count; i++) + { + kDiff = vertices[i] - box_kCenter; + + const Ogre::Real fY0 = kDiff.dotProduct(box_akAxis[0]); + if ( fY0 < fY0Min ) + fY0Min = fY0; + else if ( fY0 > fY0Max ) + fY0Max = fY0; + + const Ogre::Real fY1 = kDiff.dotProduct(box_akAxis[1]); + if ( fY1 < fY1Min ) + fY1Min = fY1; + else if ( fY1 > fY1Max ) + fY1Max = fY1; + + const Ogre::Real fY2 = kDiff.dotProduct(box_akAxis[2]); + if ( fY2 < fY2Min ) + fY2Min = fY2; + else if ( fY2 > fY2Max ) + fY2Max = fY2; + } + + box_afExtent.x = ((Real)0.5)*(fY0Max - fY0Min); + box_afExtent.y = ((Real)0.5)*(fY1Max - fY1Min); + box_afExtent.z = ((Real)0.5)*(fY2Max - fY2Min); + + box_kCenter += (0.5*(fY0Max+fY0Min))*box_akAxis[0] + + (0.5*(fY1Max+fY1Min))*box_akAxis[1] + + (0.5*(fY2Max+fY2Min))*box_akAxis[2]; + + box_afExtent *= 2.0; + + return true; + } + + //------------------------------------------------------------------------------------------------ + btBoxShape *AnimatedMeshToShapeConverter::createOrientedBox(unsigned char bone, + const Vector3 &bonePosition, + const Quaternion &boneOrientation) + { + Ogre::Vector3 box_akAxis[3]; + Ogre::Vector3 box_afExtent; + Ogre::Vector3 box_afCenter; + + if (!getOrientedBox(bone, bonePosition, boneOrientation, + box_afExtent, + box_akAxis, + box_afCenter)) + return 0; + + btBoxShape *geom = new btBoxShape(Convert::toBullet(box_afExtent)); + //geom->setOrientation(Quaternion(box_akAxis[0],box_akAxis[1],box_akAxis[2])); + //geom->setPosition(box_afCenter); + return geom; + } + +/* + * ============================================================================================= + * BtOgre::DynamicRenderable + * ============================================================================================= + */ + + DynamicRenderable::DynamicRenderable() + { + } + + //------------------------------------------------------------------------------------------------ + DynamicRenderable::~DynamicRenderable() + { + delete mRenderOp.vertexData; + delete mRenderOp.indexData; + } + + //------------------------------------------------------------------------------------------------ + void DynamicRenderable::initialize(RenderOperation::OperationType operationType, + bool useIndices) + { + // Initialize render operation + mRenderOp.operationType = operationType; + mRenderOp.useIndexes = useIndices; + mRenderOp.vertexData = new VertexData; + if (mRenderOp.useIndexes) + mRenderOp.indexData = new IndexData; + + // Reset buffer capacities + mVertexBufferCapacity = 0; + mIndexBufferCapacity = 0; + + // Create vertex declaration + createVertexDeclaration(); + } + + //------------------------------------------------------------------------------------------------ + void DynamicRenderable::prepareHardwareBuffers(size_t vertexCount, + size_t indexCount) + { + // Prepare vertex buffer + size_t newVertCapacity = mVertexBufferCapacity; + if ((vertexCount > mVertexBufferCapacity) || + (!mVertexBufferCapacity)) + { + // vertexCount exceeds current capacity! + // It is necessary to reallocate the buffer. + + // Check if this is the first call + if (!newVertCapacity) + newVertCapacity = 1; + + // Make capacity the next power of two + while (newVertCapacity < vertexCount) + newVertCapacity <<= 1; + } + else if (vertexCount < mVertexBufferCapacity>>1) { + // Make capacity the previous power of two + while (vertexCount < newVertCapacity>>1) + newVertCapacity >>= 1; + } + if (newVertCapacity != mVertexBufferCapacity) + { + mVertexBufferCapacity = newVertCapacity; + // Create new vertex buffer + HardwareVertexBufferSharedPtr vbuf = + HardwareBufferManager::getSingleton().createVertexBuffer( + mRenderOp.vertexData->vertexDeclaration->getVertexSize(0), + mVertexBufferCapacity, + HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY); // TODO: Custom HBU_? + + // Bind buffer + mRenderOp.vertexData->vertexBufferBinding->setBinding(0, vbuf); + } + // Update vertex count in the render operation + mRenderOp.vertexData->vertexCount = vertexCount; + + if (mRenderOp.useIndexes) + { + OgreAssert(indexCount <= std::numeric_limits::max(), "indexCount exceeds 16 bit"); + + size_t newIndexCapacity = mIndexBufferCapacity; + // Prepare index buffer + if ((indexCount > newIndexCapacity) || + (!newIndexCapacity)) + { + // indexCount exceeds current capacity! + // It is necessary to reallocate the buffer. + + // Check if this is the first call + if (!newIndexCapacity) + newIndexCapacity = 1; + + // Make capacity the next power of two + while (newIndexCapacity < indexCount) + newIndexCapacity <<= 1; + + } + else if (indexCount < newIndexCapacity>>1) + { + // Make capacity the previous power of two + while (indexCount < newIndexCapacity>>1) + newIndexCapacity >>= 1; + } + + if (newIndexCapacity != mIndexBufferCapacity) + { + mIndexBufferCapacity = newIndexCapacity; + // Create new index buffer + mRenderOp.indexData->indexBuffer = + HardwareBufferManager::getSingleton().createIndexBuffer( + HardwareIndexBuffer::IT_16BIT, + mIndexBufferCapacity, + HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY); // TODO: Custom HBU_? + } + + // Update index count in the render operation + mRenderOp.indexData->indexCount = indexCount; + } + } + + //------------------------------------------------------------------------------------------------ + Real DynamicRenderable::getBoundingRadius(void) const + { + return Math::Sqrt(std::max(mBox.getMaximum().squaredLength(), mBox.getMinimum().squaredLength())); + } + + //------------------------------------------------------------------------------------------------ + Real DynamicRenderable::getSquaredViewDepth(const Camera* cam) const + { + Vector3 vMin, vMax, vMid, vDist; + vMin = mBox.getMinimum(); + vMax = mBox.getMaximum(); + vMid = ((vMax - vMin) * 0.5) + vMin; + vDist = cam->getDerivedPosition() - vMid; + + return vDist.squaredLength(); + } + +/* + * ============================================================================================= + * BtOgre::DynamicLines + * ============================================================================================= + */ + + enum { + POSITION_BINDING, + TEXCOORD_BINDING + }; + + //------------------------------------------------------------------------------------------------ + DynamicLines::DynamicLines(OperationType opType) + { + initialize(opType,false); + setMaterial("BaseWhiteNoLighting"); + mDirty = true; + } + + //------------------------------------------------------------------------------------------------ + DynamicLines::~DynamicLines() + { + } + + //------------------------------------------------------------------------------------------------ + void DynamicLines::setOperationType(OperationType opType) + { + mRenderOp.operationType = opType; + } + + //------------------------------------------------------------------------------------------------ + RenderOperation::OperationType DynamicLines::getOperationType() const + { + return mRenderOp.operationType; + } + + //------------------------------------------------------------------------------------------------ + void DynamicLines::addPoint(const Vector3 &p) + { + mPoints.push_back(p); + mDirty = true; + } + + //------------------------------------------------------------------------------------------------ + void DynamicLines::addPoint(Real x, Real y, Real z) + { + mPoints.push_back(Vector3(x,y,z)); + mDirty = true; + } + + //------------------------------------------------------------------------------------------------ + const Vector3& DynamicLines::getPoint(unsigned short index) const + { + assert(index < mPoints.size() && "Point index is out of bounds!!"); + return mPoints[index]; + } + + //------------------------------------------------------------------------------------------------ + unsigned short DynamicLines::getNumPoints(void) const + { + return (unsigned short)mPoints.size(); + } + + //------------------------------------------------------------------------------------------------ + void DynamicLines::setPoint(unsigned short index, const Vector3 &value) + { + assert(index < mPoints.size() && "Point index is out of bounds!!"); + + mPoints[index] = value; + mDirty = true; + } + + //------------------------------------------------------------------------------------------------ + void DynamicLines::clear() + { + mPoints.clear(); + mDirty = true; + } + + //------------------------------------------------------------------------------------------------ + void DynamicLines::update() + { + if (mDirty) fillHardwareBuffers(); + } + + //------------------------------------------------------------------------------------------------ + void DynamicLines::createVertexDeclaration() + { + VertexDeclaration *decl = mRenderOp.vertexData->vertexDeclaration; + decl->addElement(POSITION_BINDING, 0, VET_FLOAT3, VES_POSITION); + } + + //------------------------------------------------------------------------------------------------ + void DynamicLines::fillHardwareBuffers() + { + int size = mPoints.size(); + + prepareHardwareBuffers(size,0); + + if (!size) { + mBox.setExtents(Vector3::ZERO,Vector3::ZERO); + mDirty=false; + return; + } + + Vector3 vaabMin = mPoints[0]; + Vector3 vaabMax = mPoints[0]; + + HardwareVertexBufferSharedPtr vbuf = + mRenderOp.vertexData->vertexBufferBinding->getBuffer(0); + + Real *prPos = static_cast(vbuf->lock(HardwareBuffer::HBL_DISCARD)); + { + for(int i = 0; i < size; i++) + { + *prPos++ = mPoints[i].x; + *prPos++ = mPoints[i].y; + *prPos++ = mPoints[i].z; + + if(mPoints[i].x < vaabMin.x) + vaabMin.x = mPoints[i].x; + if(mPoints[i].y < vaabMin.y) + vaabMin.y = mPoints[i].y; + if(mPoints[i].z < vaabMin.z) + vaabMin.z = mPoints[i].z; + + if(mPoints[i].x > vaabMax.x) + vaabMax.x = mPoints[i].x; + if(mPoints[i].y > vaabMax.y) + vaabMax.y = mPoints[i].y; + if(mPoints[i].z > vaabMax.z) + vaabMax.z = mPoints[i].z; + } + } + vbuf->unlock(); + + mBox.setExtents(vaabMin, vaabMax); + + mDirty = false; + } +} diff --git a/libs/openengine/bullet/BtOgreExtras.h b/libs/openengine/bullet/BtOgreExtras.h new file mode 100644 index 000000000..f3e1aa87a --- /dev/null +++ b/libs/openengine/bullet/BtOgreExtras.h @@ -0,0 +1,280 @@ +/* + * ===================================================================================== + * + * Filename: BtOgreExtras.h + * + * Description: Contains the Ogre Mesh to Bullet Shape converters. + * + * Version: 1.0 + * Created: 27/12/2008 01:45:56 PM + * + * Author: Nikhilesh (nikki) + * + * ===================================================================================== + */ + +#ifndef _BtOgreShapes_H_ +#define _BtOgreShapes_H_ + +#include "btBulletDynamicsCommon.h" +#include "OgreSimpleRenderable.h" +#include "OgreCamera.h" +#include "OgreHardwareBufferManager.h" +#include "OgreMaterialManager.h" +#include "OgreTechnique.h" +#include "OgrePass.h" + +#include "OgreLogManager.h" + +namespace BtOgre +{ + +typedef std::vector Vector3Array; + +//Converts from and to Bullet and Ogre stuff. Pretty self-explanatory. +class Convert +{ +public: + Convert() {}; + ~Convert() {}; + + static btQuaternion toBullet(const Ogre::Quaternion &q) + { + return btQuaternion(q.x, q.y, q.z, q.w); + } + static btVector3 toBullet(const Ogre::Vector3 &v) + { + return btVector3(v.x, v.y, v.z); + } + + static Ogre::Quaternion toOgre(const btQuaternion &q) + { + return Ogre::Quaternion(q.w(), q.x(), q.y(), q.z()); + } + static Ogre::Vector3 toOgre(const btVector3 &v) + { + return Ogre::Vector3(v.x(), v.y(), v.z()); + } +}; + +//From here on its debug-drawing stuff. ------------------------------------------------------------------ + +class DynamicRenderable : public Ogre::SimpleRenderable +{ +public: + /// Constructor + DynamicRenderable(); + /// Virtual destructor + virtual ~DynamicRenderable(); + + /** Initializes the dynamic renderable. + @remarks + This function should only be called once. It initializes the + render operation, and calls the abstract function + createVertexDeclaration(). + @param operationType The type of render operation to perform. + @param useIndices Specifies whether to use indices to determine the + vertices to use as input. */ + void initialize(Ogre::RenderOperation::OperationType operationType, + bool useIndices); + + /// Implementation of Ogre::SimpleRenderable + virtual Ogre::Real getBoundingRadius(void) const; + /// Implementation of Ogre::SimpleRenderable + virtual Ogre::Real getSquaredViewDepth(const Ogre::Camera* cam) const; + +protected: + /// Maximum capacity of the currently allocated vertex buffer. + size_t mVertexBufferCapacity; + /// Maximum capacity of the currently allocated index buffer. + size_t mIndexBufferCapacity; + + /** Creates the vertex declaration. + @remarks + Override and set mRenderOp.vertexData->vertexDeclaration here. + mRenderOp.vertexData will be created for you before this method + is called. */ + virtual void createVertexDeclaration() = 0; + + /** Prepares the hardware buffers for the requested vertex and index counts. + @remarks + This function must be called before locking the buffers in + fillHardwareBuffers(). It guarantees that the hardware buffers + are large enough to hold at least the requested number of + vertices and indices (if using indices). The buffers are + possibly reallocated to achieve this. + @par + The vertex and index count in the render operation are set to + the values of vertexCount and indexCount respectively. + @param vertexCount The number of vertices the buffer must hold. + + @param indexCount The number of indices the buffer must hold. This + parameter is ignored if not using indices. */ + void prepareHardwareBuffers(size_t vertexCount, size_t indexCount); + + /** Fills the hardware vertex and index buffers with data. + @remarks + This function must call prepareHardwareBuffers() before locking + the buffers to ensure the they are large enough for the data to + be written. Afterwards the vertex and index buffers (if using + indices) can be locked, and data can be written to them. */ + virtual void fillHardwareBuffers() = 0; +}; + +class DynamicLines : public DynamicRenderable +{ + typedef Ogre::Vector3 Vector3; + typedef Ogre::Quaternion Quaternion; + typedef Ogre::Camera Camera; + typedef Ogre::Real Real; + typedef Ogre::RenderOperation::OperationType OperationType; + +public: + /// Constructor - see setOperationType() for description of argument. + DynamicLines(OperationType opType=Ogre::RenderOperation::OT_LINE_STRIP); + virtual ~DynamicLines(); + + /// Add a point to the point list + void addPoint(const Ogre::Vector3 &p); + /// Add a point to the point list + void addPoint(Real x, Real y, Real z); + + /// Change the location of an existing point in the point list + void setPoint(unsigned short index, const Vector3 &value); + + /// Return the location of an existing point in the point list + const Vector3& getPoint(unsigned short index) const; + + /// Return the total number of points in the point list + unsigned short getNumPoints(void) const; + + /// Remove all points from the point list + void clear(); + + /// Call this to update the hardware buffer after making changes. + void update(); + + /** Set the type of operation to draw with. + * @param opType Can be one of + * - RenderOperation::OT_LINE_STRIP + * - RenderOperation::OT_LINE_LIST + * - RenderOperation::OT_POINT_LIST + * - RenderOperation::OT_TRIANGLE_LIST + * - RenderOperation::OT_TRIANGLE_STRIP + * - RenderOperation::OT_TRIANGLE_FAN + * The default is OT_LINE_STRIP. + */ + void setOperationType(OperationType opType); + OperationType getOperationType() const; + +protected: + /// Implementation DynamicRenderable, creates a simple vertex-only decl + virtual void createVertexDeclaration(); + /// Implementation DynamicRenderable, pushes point list out to hardware memory + virtual void fillHardwareBuffers(); + +private: + std::vector mPoints; + bool mDirty; +}; + +class DebugDrawer : public btIDebugDraw +{ +protected: + Ogre::SceneNode *mNode; + btDynamicsWorld *mWorld; + DynamicLines *mLineDrawer; + bool mDebugOn; + +public: + + DebugDrawer(Ogre::SceneNode *node, btDynamicsWorld *world) + : mNode(node), + mWorld(world), + mDebugOn(true) + { + mLineDrawer = new DynamicLines(Ogre::RenderOperation::OT_LINE_LIST); + mNode->attachObject(mLineDrawer); + + if (!Ogre::ResourceGroupManager::getSingleton().resourceGroupExists("BtOgre")) + Ogre::ResourceGroupManager::getSingleton().createResourceGroup("BtOgre"); + if (!Ogre::MaterialManager::getSingleton().resourceExists("BtOgre/DebugLines")) + { + Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().create("BtOgre/DebugLines", "BtOgre"); + mat->setReceiveShadows(false); + mat->setSelfIllumination(1,1,1); + } + + mLineDrawer->setMaterial("BtOgre/DebugLines"); + } + + ~DebugDrawer() + { + Ogre::MaterialManager::getSingleton().remove("BtOgre/DebugLines"); + Ogre::ResourceGroupManager::getSingleton().destroyResourceGroup("BtOgre"); + delete mLineDrawer; + } + + void step() + { + if (mDebugOn) + { + mWorld->debugDrawWorld(); + mLineDrawer->update(); + mNode->needUpdate(); + mLineDrawer->clear(); + } + else + { + mLineDrawer->clear(); + mLineDrawer->update(); + mNode->needUpdate(); + } + } + + void drawLine(const btVector3& from,const btVector3& to,const btVector3& color) + { + mLineDrawer->addPoint(Convert::toOgre(from)); + mLineDrawer->addPoint(Convert::toOgre(to)); + } + + void drawContactPoint(const btVector3& PointOnB,const btVector3& normalOnB,btScalar distance,int lifeTime,const btVector3& color) + { + mLineDrawer->addPoint(Convert::toOgre(PointOnB)); + mLineDrawer->addPoint(Convert::toOgre(PointOnB) + (Convert::toOgre(normalOnB) * distance * 20)); + } + + void reportErrorWarning(const char* warningString) + { + Ogre::LogManager::getSingleton().logMessage(warningString); + } + + void draw3dText(const btVector3& location,const char* textString) + { + } + + //0 for off, anything else for on. + void setDebugMode(int isOn) + { + mDebugOn = (isOn == 0) ? false : true; + + if (!mDebugOn) + mLineDrawer->clear(); + } + + //0 for off, anything else for on. + int getDebugMode() const + { + return mDebugOn; + } + +}; + +} + +#endif + + + + + diff --git a/libs/openengine/bullet/BtOgreGP.h b/libs/openengine/bullet/BtOgreGP.h new file mode 100644 index 000000000..f0534de4b --- /dev/null +++ b/libs/openengine/bullet/BtOgreGP.h @@ -0,0 +1,142 @@ +/* + * ===================================================================================== + * + * Filename: BtOgreGP.h + * + * Description: The part of BtOgre that handles information transfer from Ogre to + * Bullet (like mesh data for making trimeshes). + * + * Version: 1.0 + * Created: 27/12/2008 03:29:56 AM + * + * Author: Nikhilesh (nikki) + * + * ===================================================================================== + */ + +#ifndef _BtOgrePG_H_ +#define _BtOgrePG_H_ + +#include "btBulletDynamicsCommon.h" +#include "BtOgreExtras.h" +#include "Ogre.h" + +namespace BtOgre { + +typedef std::map BoneIndex; +typedef std::pair BoneKeyIndex; + +class VertexIndexToShape +{ +public: + VertexIndexToShape(const Ogre::Matrix4 &transform = Ogre::Matrix4::IDENTITY); + ~VertexIndexToShape(); + + Ogre::Real getRadius(); + Ogre::Vector3 getSize(); + + + btSphereShape* createSphere(); + btBoxShape* createBox(); + btBvhTriangleMeshShape* createTrimesh(); + btCylinderShape* createCylinder(); + btConvexHullShape* createConvex(); + + const Ogre::Vector3* getVertices(); + unsigned int getVertexCount(); + const unsigned int* getIndices(); + unsigned int getIndexCount(); + +protected: + + void addStaticVertexData(const Ogre::VertexData *vertex_data); + + void addAnimatedVertexData(const Ogre::VertexData *vertex_data, + const Ogre::VertexData *blended_data, + const Ogre::Mesh::IndexMap *indexMap); + + void addIndexData(Ogre::IndexData *data, const unsigned int offset = 0); + + +protected: + Ogre::Vector3* mVertexBuffer; + unsigned int* mIndexBuffer; + unsigned int mVertexCount; + unsigned int mIndexCount; + + Ogre::Matrix4 mTransform; + + Ogre::Real mBoundRadius; + Ogre::Vector3 mBounds; + + BoneIndex *mBoneIndex; + + Ogre::Vector3 mScale; +}; + +//For static (non-animated) meshes. +class StaticMeshToShapeConverter : public VertexIndexToShape +{ +public: + + StaticMeshToShapeConverter(Ogre::Renderable *rend, const Ogre::Matrix4 &transform = Ogre::Matrix4::IDENTITY); + StaticMeshToShapeConverter(Ogre::Entity *entity, const Ogre::Matrix4 &transform = Ogre::Matrix4::IDENTITY); + StaticMeshToShapeConverter(); + + ~StaticMeshToShapeConverter(); + + void addEntity(Ogre::Entity *entity,const Ogre::Matrix4 &transform = Ogre::Matrix4::IDENTITY); + + void addMesh(const Ogre::MeshPtr &mesh, const Ogre::Matrix4 &transform = Ogre::Matrix4::IDENTITY); + + +protected: + + Ogre::Entity* mEntity; + Ogre::SceneNode* mNode; +}; + +//For animated meshes. +class AnimatedMeshToShapeConverter : public VertexIndexToShape +{ +public: + + AnimatedMeshToShapeConverter(Ogre::Entity *entity, const Ogre::Matrix4 &transform = Ogre::Matrix4::IDENTITY); + AnimatedMeshToShapeConverter(); + ~AnimatedMeshToShapeConverter(); + + void addEntity(Ogre::Entity *entity,const Ogre::Matrix4 &transform = Ogre::Matrix4::IDENTITY); + void addMesh(const Ogre::MeshPtr &mesh, const Ogre::Matrix4 &transform); + + btBoxShape* createAlignedBox(unsigned char bone, + const Ogre::Vector3 &bonePosition, + const Ogre::Quaternion &boneOrientation); + + btBoxShape* createOrientedBox(unsigned char bone, + const Ogre::Vector3 &bonePosition, + const Ogre::Quaternion &boneOrientation); + +protected: + + bool getBoneVertices(unsigned char bone, + unsigned int &vertex_count, + Ogre::Vector3* &vertices, + const Ogre::Vector3 &bonePosition); + + bool getOrientedBox(unsigned char bone, + const Ogre::Vector3 &bonePosition, + const Ogre::Quaternion &boneOrientation, + Ogre::Vector3 &extents, + Ogre::Vector3 *axis, + Ogre::Vector3 ¢er); + + Ogre::Entity* mEntity; + Ogre::SceneNode* mNode; + + Ogre::Vector3 *mTransformedVerticesTemp; + size_t mTransformedVerticesTempSize; +}; + +} + +#endif diff --git a/libs/openengine/bullet/BtOgrePG.h b/libs/openengine/bullet/BtOgrePG.h new file mode 100644 index 000000000..9ff069a8f --- /dev/null +++ b/libs/openengine/bullet/BtOgrePG.h @@ -0,0 +1,81 @@ +/* + * ===================================================================================== + * + * Filename: BtOgrePG.h + * + * Description: The part of BtOgre that handles information transfer from Bullet to + * Ogre (like updating graphics object positions). + * + * Version: 1.0 + * Created: 27/12/2008 03:40:56 AM + * + * Author: Nikhilesh (nikki) + * + * ===================================================================================== + */ + +#ifndef _BtOgreGP_H_ +#define _BtOgreGP_H_ + +#include "btBulletDynamicsCommon.h" +#include "OgreSceneNode.h" +#include "BtOgreExtras.h" + +namespace BtOgre { + +//A MotionState is Bullet's way of informing you about updates to an object. +//Pass this MotionState to a btRigidBody to have your SceneNode updated automaticaly. +class RigidBodyState : public btMotionState +{ + protected: + btTransform mTransform; + btTransform mCenterOfMassOffset; + + Ogre::SceneNode *mNode; + + public: + RigidBodyState(Ogre::SceneNode *node, const btTransform &transform, const btTransform &offset = btTransform::getIdentity()) + : mTransform(transform), + mCenterOfMassOffset(offset), + mNode(node) + { + } + + RigidBodyState(Ogre::SceneNode *node) + : mTransform(((node != NULL) ? BtOgre::Convert::toBullet(node->getOrientation()) : btQuaternion(0,0,0,1)), + ((node != NULL) ? BtOgre::Convert::toBullet(node->getPosition()) : btVector3(0,0,0))), + mCenterOfMassOffset(btTransform::getIdentity()), + mNode(node) + { + } + + virtual void getWorldTransform(btTransform &ret) const + { + ret = mCenterOfMassOffset.inverse() * mTransform; + } + + virtual void setWorldTransform(const btTransform &in) + { + if (mNode == NULL) + return; + + mTransform = in; + btTransform transform = in * mCenterOfMassOffset; + + btQuaternion rot = transform.getRotation(); + btVector3 pos = transform.getOrigin(); + mNode->setOrientation(rot.w(), rot.x(), rot.y(), rot.z()); + mNode->setPosition(pos.x(), pos.y(), pos.z()); + } + + void setNode(Ogre::SceneNode *node) + { + mNode = node; + } +}; + +//Softbody-Ogre connection goes here! + +} + +#endif diff --git a/libs/openengine/bullet/BulletShapeLoader.cpp b/libs/openengine/bullet/BulletShapeLoader.cpp new file mode 100644 index 000000000..48b87e1e8 --- /dev/null +++ b/libs/openengine/bullet/BulletShapeLoader.cpp @@ -0,0 +1,124 @@ +#include "BulletShapeLoader.h" + + + +BulletShape::BulletShape(Ogre::ResourceManager* creator, const Ogre::String &name, + Ogre::ResourceHandle handle, const Ogre::String &group, bool isManual, + Ogre::ManualResourceLoader *loader) : +Ogre::Resource(creator, name, handle, group, isManual, loader) +{ + /* If you were storing a pointer to an object, then you would set that pointer to NULL here. + */ + + /* For consistency with StringInterface, but we don't add any parameters here + That's because the Resource implementation of StringInterface is to + list all the options that need to be set before loading, of which + we have none as such. Full details can be set through scripts. + */ + Shape = NULL; + collide = true; + createParamDictionary("BulletShape"); +} + +BulletShape::~BulletShape() +{ +} + +// farm out to BulletShapeLoader +void BulletShape::loadImpl() +{ + mLoader->loadResource(this); +} + +void BulletShape::deleteShape(btCollisionShape* mShape) +{ + if(mShape!=NULL) + { + if(mShape->isCompound()) + { + btCompoundShape* ms = static_cast(Shape); + int a = ms->getNumChildShapes(); + for(int i=0; i getChildShape(i)); + } + } + delete mShape; + } + mShape = NULL; +} + +void BulletShape::unloadImpl() +{ + deleteShape(Shape); +} + +//TODO:change this? +size_t BulletShape::calculateSize() const +{ + return 1; +} + + + +//============================================================================================================= +template<> BulletShapeManager *Ogre::Singleton::ms_Singleton = 0; + +BulletShapeManager *BulletShapeManager::getSingletonPtr() +{ + return ms_Singleton; +} + +BulletShapeManager &BulletShapeManager::getSingleton() +{ + assert(ms_Singleton); + return(*ms_Singleton); +} + +BulletShapeManager::BulletShapeManager() +{ + mResourceType = "BulletShape"; + + // low, because it will likely reference other resources + mLoadOrder = 30.0f; + + // this is how we register the ResourceManager with OGRE + Ogre::ResourceGroupManager::getSingleton()._registerResourceManager(mResourceType, this); +} + +BulletShapeManager::~BulletShapeManager() +{ + // and this is how we unregister it + Ogre::ResourceGroupManager::getSingleton()._unregisterResourceManager(mResourceType); +} + +BulletShapePtr BulletShapeManager::load(const Ogre::String &name, const Ogre::String &group) +{ + BulletShapePtr textf = getByName(name); + + if (textf.isNull()) + textf = create(name, group); + + textf->load(); + return textf; +} + +Ogre::Resource *BulletShapeManager::createImpl(const Ogre::String &name, Ogre::ResourceHandle handle, + const Ogre::String &group, bool isManual, Ogre::ManualResourceLoader *loader, + const Ogre::NameValuePairList *createParams) +{ + BulletShape* res = new BulletShape(this, name, handle, group, isManual, loader); + //if(isManual) + //{ + //loader->loadResource(res); + //} + return res; +} + + +//==================================================================== +void BulletShapeLoader::loadResource(Ogre::Resource *resource) +{} + +void BulletShapeLoader::load(const std::string &name,const std::string &group) +{} diff --git a/libs/openengine/bullet/BulletShapeLoader.h b/libs/openengine/bullet/BulletShapeLoader.h new file mode 100644 index 000000000..316ee523c --- /dev/null +++ b/libs/openengine/bullet/BulletShapeLoader.h @@ -0,0 +1,138 @@ +#ifndef _BULLET_SHAPE_LOADER_H_ +#define _BULLET_SHAPE_LOADER_H_ + +#include +#include +#include + +//For some reason, Ogre Singleton cannot be used in another namespace, that's why there is no namespace here. +//But the risk of name collision seems pretty low here. + +/** +*Define a new resource which describe a Shape usable by bullet.See BulletShapeManager for how to get/use them. +*/ +class BulletShape : public Ogre::Resource +{ + Ogre::String mString; + +protected: + void loadImpl(); + void unloadImpl(); + size_t calculateSize() const; + + void deleteShape(btCollisionShape* mShape); + +public: + + BulletShape(Ogre::ResourceManager *creator, const Ogre::String &name, + Ogre::ResourceHandle handle, const Ogre::String &group, bool isManual = false, + Ogre::ManualResourceLoader *loader = 0); + + virtual ~BulletShape(); + + btCollisionShape* Shape; + //this flag indicate if the shape is used for collision or if it's for raycasting only. + bool collide; +}; + +/** +* +*/ +class BulletShapePtr : public Ogre::SharedPtr +{ +public: + BulletShapePtr() : Ogre::SharedPtr() {} + explicit BulletShapePtr(BulletShape *rep) : Ogre::SharedPtr(rep) {} + BulletShapePtr(const BulletShapePtr &r) : Ogre::SharedPtr(r) {} + BulletShapePtr(const Ogre::ResourcePtr &r) : Ogre::SharedPtr() + { + if( r.isNull() ) + return; + // lock & copy other mutex pointer + OGRE_LOCK_MUTEX(*r.OGRE_AUTO_MUTEX_NAME) + OGRE_COPY_AUTO_SHARED_MUTEX(r.OGRE_AUTO_MUTEX_NAME) + pRep = static_cast(r.getPointer()); + pUseCount = r.useCountPointer(); + useFreeMethod = r.freeMethod(); + if (pUseCount) + { + ++(*pUseCount); + } + } + + /// Operator used to convert a ResourcePtr to a BulletShapePtr + BulletShapePtr& operator=(const Ogre::ResourcePtr& r) + { + if(pRep == static_cast(r.getPointer())) + return *this; + release(); + if( r.isNull() ) + return *this; // resource ptr is null, so the call to release above has done all we need to do. + // lock & copy other mutex pointer + OGRE_LOCK_MUTEX(*r.OGRE_AUTO_MUTEX_NAME) + OGRE_COPY_AUTO_SHARED_MUTEX(r.OGRE_AUTO_MUTEX_NAME) + pRep = static_cast(r.getPointer()); + pUseCount = r.useCountPointer(); + useFreeMethod = r.freeMethod(); + if (pUseCount) + { + ++(*pUseCount); + } + return *this; + } +}; + + + + +/** +*Hold any BulletShape that was created by the ManualBulletShapeLoader. +* +*To get a bulletShape, you must load it first. +*First, create a manualBulletShapeLoader. Then call ManualBulletShapeManager->load(). This create an "empty" resource. +*Then use BulletShapeManager->load(). This will fill the resource with the required info. +*To get the resource,use BulletShapeManager::getByName. +*When you use the resource no more, just use BulletShapeManager->unload(). It won't completly delete the resource, but it will +*"empty" it.This allow a better management of memory: when you are leaving a cell, just unload every useless shape. +* +*Alternatively, you can call BulletShape->load() in order to actually load the resource. +*When you are finished with it, just call BulletShape->unload(). +* +*IMO: prefere the first methode, i am not completly sure about the 2nd. +* +*Important Note: i have no idea of what happen if you try to load two time the same resource without unloading. +*It won't crash, but it might lead to memory leaks(I don't know how Ogre handle this). So don't do it! +*/ +class BulletShapeManager : public Ogre::ResourceManager, public Ogre::Singleton +{ +protected: + + // must implement this from ResourceManager's interface + Ogre::Resource *createImpl(const Ogre::String &name, Ogre::ResourceHandle handle, + const Ogre::String &group, bool isManual, Ogre::ManualResourceLoader *loader, + const Ogre::NameValuePairList *createParams); + +public: + + BulletShapeManager(); + virtual ~BulletShapeManager(); + + virtual BulletShapePtr load(const Ogre::String &name, const Ogre::String &group); + + static BulletShapeManager &getSingleton(); + static BulletShapeManager *getSingletonPtr(); +}; + +class BulletShapeLoader : public Ogre::ManualResourceLoader +{ +public: + + BulletShapeLoader(){}; + virtual ~BulletShapeLoader() {} + + virtual void loadResource(Ogre::Resource *resource); + + virtual void load(const std::string &name,const std::string &group); +}; + +#endif diff --git a/libs/openengine/bullet/CMotionState.cpp b/libs/openengine/bullet/CMotionState.cpp new file mode 100644 index 000000000..5ddef5175 --- /dev/null +++ b/libs/openengine/bullet/CMotionState.cpp @@ -0,0 +1,45 @@ +#include "CMotionState.h" +#include "physic.hpp" + +#include +#include +#include +//#include + +namespace OEngine { +namespace Physic +{ + + CMotionState::CMotionState(PhysicEngine* eng,std::string name) + { + pEng = eng; + tr.setIdentity(); + pName = name; + }; + + void CMotionState::getWorldTransform(btTransform &worldTrans) const + { + worldTrans = tr; + } + + void CMotionState::setWorldTransform(const btTransform &worldTrans) + { + tr = worldTrans; + + PhysicEvent evt; + evt.isNPC = isNPC; + evt.isPC = isPC; + evt.newTransform = tr; + evt.RigidBodyName = pName; + + if(isPC) + { + pEng->PEventList.push_back(evt); + } + else + { + pEng->NPEventList.push_back(evt); + } + } + +}} diff --git a/libs/openengine/bullet/CMotionState.h b/libs/openengine/bullet/CMotionState.h new file mode 100644 index 000000000..3508ab4ef --- /dev/null +++ b/libs/openengine/bullet/CMotionState.h @@ -0,0 +1,52 @@ +#ifndef OENGINE_CMOTIONSTATE_H +#define OENGINE_CMOTIONSTATE_H + +#include +#include + +namespace OEngine { +namespace Physic +{ + class PhysicEngine; + + /** + * A CMotionState is associated with a single RigidBody. + * When the RigidBody is moved by bullet, bullet will call the function setWorldTransform. + * for more info, see the bullet Wiki at btMotionState. + */ + class CMotionState:public btMotionState + { + public: + + CMotionState(PhysicEngine* eng,std::string name); + + /** + * Return the position of the RigidBody. + */ + virtual void getWorldTransform(btTransform &worldTrans) const; + + /** + * Function called by bullet when the RigidBody is moved. + * It add an event to the EventList of the PhysicEngine class. + */ + virtual void setWorldTransform(const btTransform &worldTrans); + + protected: + PhysicEngine* pEng; + btTransform tr; + bool isNPC; + bool isPC; + + std::string pName; + }; + + struct PhysicEvent + { + bool isNPC; + bool isPC; + btTransform newTransform; + std::string RigidBodyName; + }; + +}} +#endif diff --git a/libs/openengine/bullet/btKinematicCharacterController.cpp b/libs/openengine/bullet/btKinematicCharacterController.cpp new file mode 100644 index 000000000..fc4f3278f --- /dev/null +++ b/libs/openengine/bullet/btKinematicCharacterController.cpp @@ -0,0 +1,643 @@ +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2008 Erwin Coumans http://bulletphysics.com + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +#include "LinearMath/btIDebugDraw.h" +#include "BulletCollision/CollisionDispatch/btGhostObject.h" +#include "BulletCollision/CollisionShapes/btMultiSphereShape.h" +#include "BulletCollision/BroadphaseCollision/btOverlappingPairCache.h" +#include "BulletCollision/BroadphaseCollision/btCollisionAlgorithm.h" +#include "BulletCollision/CollisionDispatch/btCollisionWorld.h" +#include "LinearMath/btDefaultMotionState.h" +#include "btKinematicCharacterController.h" + +///@todo Interact with dynamic objects, +///Ride kinematicly animated platforms properly +///Support ducking +class btKinematicClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback +{ +public: + btKinematicClosestNotMeRayResultCallback (btCollisionObject* me) : btCollisionWorld::ClosestRayResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)) + { + m_me[0] = me; + count = 1; + } + + btKinematicClosestNotMeRayResultCallback (btCollisionObject* me[], int count_) : btCollisionWorld::ClosestRayResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)) + { + count = count_; + + for(int i = 0; i < count; i++) + m_me[i] = me[i]; + } + + virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult,bool normalInWorldSpace) + { + for(int i = 0; i < count; i++) + if (rayResult.m_collisionObject == m_me[i]) + return 1.0; + + return ClosestRayResultCallback::addSingleResult (rayResult, normalInWorldSpace); + } +protected: + btCollisionObject* m_me[10]; + int count; +}; + +class btKinematicClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback +{ +public: + btKinematicClosestNotMeConvexResultCallback( btCollisionObject* me, const btVector3& up, btScalar minSlopeDot ) + : btCollisionWorld::ClosestConvexResultCallback( btVector3( 0.0, 0.0, 0.0 ), btVector3( 0.0, 0.0, 0.0 ) ), + m_me( me ), m_up( up ), m_minSlopeDot( minSlopeDot ) + { + } + + virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace) + { + if( convexResult.m_hitCollisionObject == m_me ) + return btScalar( 1 ); + + btVector3 hitNormalWorld; + if( normalInWorldSpace ) + { + hitNormalWorld = convexResult.m_hitNormalLocal; + } + else + { + ///need to transform normal into worldspace + hitNormalWorld = m_hitCollisionObject->getWorldTransform().getBasis()*convexResult.m_hitNormalLocal; + } + + // NOTE : m_hitNormalLocal is not always vertical on the ground with a capsule or a box... + + btScalar dotUp = m_up.dot(hitNormalWorld); + if( dotUp < m_minSlopeDot ) + return btScalar( 1 ); + + return ClosestConvexResultCallback::addSingleResult (convexResult, normalInWorldSpace); + } + +protected: + btCollisionObject* m_me; + const btVector3 m_up; + btScalar m_minSlopeDot; +}; + + + +btKinematicCharacterController::btKinematicCharacterController( btPairCachingGhostObject* externalGhostObject_, + btPairCachingGhostObject* internalGhostObject_, + btScalar stepHeight, + btScalar constantScale, + btScalar gravity, + btScalar fallVelocity, + btScalar jumpVelocity, + btScalar recoveringFactor ) +{ + m_upAxis = btKinematicCharacterController::Y_AXIS; + + m_walkDirection.setValue( btScalar( 0 ), btScalar( 0 ), btScalar( 0 ) ); + + m_useGhostObjectSweepTest = true; + + externalGhostObject = externalGhostObject_; + internalGhostObject = internalGhostObject_; + + m_recoveringFactor = recoveringFactor; + + m_stepHeight = stepHeight; + + m_useWalkDirection = true; // use walk direction by default, legacy behavior + m_velocityTimeInterval = btScalar( 0 ); + m_verticalVelocity = btScalar( 0 ); + m_verticalOffset = btScalar( 0 ); + + m_gravity = constantScale * gravity; + m_fallSpeed = constantScale * fallVelocity; // Terminal velocity of a sky diver in m/s. + + m_jumpSpeed = constantScale * jumpVelocity; // ? + m_wasJumping = false; + + setMaxSlope( btRadians( 45.0 ) ); + + mCollision = true; +} + + +btKinematicCharacterController::~btKinematicCharacterController () +{ +} + +void btKinematicCharacterController::setVerticalVelocity(float z) +{ + m_verticalVelocity = z; +} + +bool btKinematicCharacterController::recoverFromPenetration( btCollisionWorld* collisionWorld ) +{ + bool penetration = false; + + if(!mCollision) return penetration; + + collisionWorld->getDispatcher()->dispatchAllCollisionPairs( internalGhostObject->getOverlappingPairCache(), + collisionWorld->getDispatchInfo(), + collisionWorld->getDispatcher() ); + + btVector3 currentPosition = internalGhostObject->getWorldTransform().getOrigin(); + + btScalar maxPen = btScalar( 0 ); + + for( int i = 0; i < internalGhostObject->getOverlappingPairCache()->getNumOverlappingPairs(); i++ ) + { + m_manifoldArray.resize(0); + + btBroadphasePair* collisionPair = &internalGhostObject->getOverlappingPairCache()->getOverlappingPairArray()[i]; + + if( collisionPair->m_algorithm ) + collisionPair->m_algorithm->getAllContactManifolds( m_manifoldArray ); + + + for( int j = 0; j < m_manifoldArray.size(); j++ ) + { + btPersistentManifold* manifold = m_manifoldArray[j]; + + btScalar directionSign = manifold->getBody0() == internalGhostObject ? btScalar( -1.0 ) : btScalar( 1.0 ); + + for( int p = 0; p < manifold->getNumContacts(); p++ ) + { + const btManifoldPoint&pt = manifold->getContactPoint( p ); + if( (manifold->getBody1() == externalGhostObject && manifold->getBody0() == internalGhostObject) + ||(manifold->getBody0() == externalGhostObject && manifold->getBody1() == internalGhostObject) ) + { + } + else + { + btScalar dist = pt.getDistance(); + + if( dist < 0.0 ) + { + if( dist < maxPen ) + maxPen = dist; + + // NOTE : btScalar affects the stairs but the parkinson... + // 0.0 , the capsule can break the walls... + currentPosition += pt.m_normalWorldOnB * directionSign * dist * m_recoveringFactor; + + penetration = true; + } + } + } + + // ??? + //manifold->clearManifold(); + } + } + + btTransform transform = internalGhostObject->getWorldTransform(); + + transform.setOrigin( currentPosition ); + + internalGhostObject->setWorldTransform( transform ); + externalGhostObject->setWorldTransform( transform ); + + return penetration; +} + + +btVector3 btKinematicCharacterController::stepUp( btCollisionWorld* world, const btVector3& currentPosition, btScalar& currentStepOffset ) +{ + btVector3 targetPosition = currentPosition + getUpAxisDirections()[ m_upAxis ] * ( m_stepHeight + ( m_verticalOffset > btScalar( 0.0 ) ? m_verticalOffset : 0.0 ) ); + + //if the no collisions mode is on, no need to go any further + if(!mCollision) + { + currentStepOffset = m_stepHeight; + return targetPosition; + } + + // Retrieve the collision shape + // + btCollisionShape* collisionShape = externalGhostObject->getCollisionShape(); + btAssert( collisionShape->isConvex() ); + + btConvexShape* convexShape = ( btConvexShape* )collisionShape; + + // FIXME: Handle penetration properly + // + btTransform start; + start.setIdentity(); + start.setOrigin( currentPosition + getUpAxisDirections()[ m_upAxis ] * ( convexShape->getMargin() ) ); + + btTransform end; + end.setIdentity(); + end.setOrigin( targetPosition ); + + btKinematicClosestNotMeConvexResultCallback callback( externalGhostObject, -getUpAxisDirections()[ m_upAxis ], m_maxSlopeCosine ); + callback.m_collisionFilterGroup = externalGhostObject->getBroadphaseHandle()->m_collisionFilterGroup; + callback.m_collisionFilterMask = externalGhostObject->getBroadphaseHandle()->m_collisionFilterMask; + + // Sweep test + // + if( m_useGhostObjectSweepTest ) + externalGhostObject->convexSweepTest( convexShape, start, end, callback, world->getDispatchInfo().m_allowedCcdPenetration ); + + else + world->convexSweepTest( convexShape, start, end, callback ); + + if( callback.hasHit() ) + { + // Only modify the position if the hit was a slope and not a wall or ceiling. + // + if( callback.m_hitNormalWorld.dot(getUpAxisDirections()[m_upAxis]) > btScalar( 0.0 ) ) + { + // We moved up only a fraction of the step height + // + currentStepOffset = m_stepHeight * callback.m_closestHitFraction; + + return currentPosition.lerp( targetPosition, callback.m_closestHitFraction ); + } + + m_verticalVelocity = btScalar( 0.0 ); + m_verticalOffset = btScalar( 0.0 ); + + return currentPosition; + } + else + { + currentStepOffset = m_stepHeight; + return targetPosition; + } +} + + +///Reflect the vector d around the vector r +inline btVector3 reflect( const btVector3& d, const btVector3& r ) +{ + return d - ( btScalar( 2.0 ) * d.dot( r ) ) * r; +} + + +///Project a vector u on another vector v +inline btVector3 project( const btVector3& u, const btVector3& v ) +{ + return v * u.dot( v ); +} + + +///Helper for computing the character sliding +inline btVector3 slide( const btVector3& direction, const btVector3& planeNormal ) +{ + return direction - project( direction, planeNormal ); +} + + + +btVector3 slideOnCollision( const btVector3& fromPosition, const btVector3& toPosition, const btVector3& hitNormal ) +{ + btVector3 moveDirection = toPosition - fromPosition; + btScalar moveLength = moveDirection.length(); + + if( moveLength <= btScalar( SIMD_EPSILON ) ) + return toPosition; + + moveDirection.normalize(); + + btVector3 reflectDir = reflect( moveDirection, hitNormal ); + reflectDir.normalize(); + + return fromPosition + slide( reflectDir, hitNormal ) * moveLength; +} + + +btVector3 btKinematicCharacterController::stepForwardAndStrafe( btCollisionWorld* collisionWorld, const btVector3& currentPosition, const btVector3& walkMove ) +{ + // We go to ! + // + btVector3 targetPosition = currentPosition + walkMove; + + //if the no collisions mode is on, no need to go any further + if(!mCollision) return targetPosition; + + // Retrieve the collision shape + // + btCollisionShape* collisionShape = externalGhostObject->getCollisionShape(); + btAssert( collisionShape->isConvex() ); + + btConvexShape* convexShape = ( btConvexShape* )collisionShape; + + btTransform start; + start.setIdentity(); + + btTransform end; + end.setIdentity(); + + btScalar fraction = btScalar( 1.0 ); + + // This optimization scheme suffers in the corners. + // It basically jumps from a wall to another, then fails to find a new + // position (after 4 iterations here) and finally don't move at all. + // + // The stepping algorithm adds some problems with stairs. It seems + // the treads create some fake corner using capsules for collisions. + // + for( int i = 0; i < 4 && fraction > btScalar( 0.01 ); i++ ) + { + start.setOrigin( currentPosition ); + end.setOrigin( targetPosition ); + + btVector3 sweepDirNegative = currentPosition - targetPosition; + + btKinematicClosestNotMeConvexResultCallback callback( externalGhostObject, sweepDirNegative, btScalar( 0.0 ) ); + callback.m_collisionFilterGroup = externalGhostObject->getBroadphaseHandle()->m_collisionFilterGroup; + callback.m_collisionFilterMask = externalGhostObject->getBroadphaseHandle()->m_collisionFilterMask; + + if( m_useGhostObjectSweepTest ) + externalGhostObject->convexSweepTest( convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration ); + + else + collisionWorld->convexSweepTest( convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration ); + + if( callback.hasHit() ) + { + // Try another target position + // + targetPosition = slideOnCollision( currentPosition, targetPosition, callback.m_hitNormalWorld ); + fraction = callback.m_closestHitFraction; + } + else + + // Move to the valid target position + // + return targetPosition; + } + + // Don't move if you can't find a valid target position... + // It prevents some flickering. + // + return currentPosition; +} + + +///Handle the gravity +btScalar btKinematicCharacterController::addFallOffset( bool wasOnGround, btScalar currentStepOffset, btScalar dt ) +{ + btScalar downVelocity = ( m_verticalVelocity < 0.0 ? -m_verticalVelocity : btScalar( 0.0 ) ) * dt; + + if( downVelocity > btScalar( 0.0 ) && downVelocity < m_stepHeight && ( wasOnGround || !m_wasJumping ) ) + downVelocity = m_stepHeight; + + return currentStepOffset + downVelocity; +} + + +btVector3 btKinematicCharacterController::stepDown( btCollisionWorld* collisionWorld, const btVector3& currentPosition, btScalar currentStepOffset ) +{ + btVector3 stepDrop = getUpAxisDirections()[ m_upAxis ] * currentStepOffset; + + // Be sure we are falling from the last m_currentPosition + // It prevents some flickering + // + btVector3 targetPosition = currentPosition - stepDrop; + + //if the no collisions mode is on, no need to go any further + if(!mCollision) return targetPosition; + + btTransform start; + start.setIdentity(); + start.setOrigin( currentPosition ); + + btTransform end; + end.setIdentity(); + end.setOrigin( targetPosition ); + + btKinematicClosestNotMeConvexResultCallback callback( internalGhostObject, getUpAxisDirections()[ m_upAxis ], m_maxSlopeCosine ); + callback.m_collisionFilterGroup = internalGhostObject->getBroadphaseHandle()->m_collisionFilterGroup; + callback.m_collisionFilterMask = internalGhostObject->getBroadphaseHandle()->m_collisionFilterMask; + + // Retrieve the collision shape + // + btCollisionShape* collisionShape = internalGhostObject->getCollisionShape(); + btAssert( collisionShape->isConvex() ); + btConvexShape* convexShape = ( btConvexShape* )collisionShape; + + if( m_useGhostObjectSweepTest ) + externalGhostObject->convexSweepTest( convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration ); + + else + collisionWorld->convexSweepTest( convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration ); + + if( callback.hasHit() ) + { + m_verticalVelocity = btScalar( 0.0 ); + m_verticalOffset = btScalar( 0.0 ); + m_wasJumping = false; + + // We dropped a fraction of the height -> hit floor + // + return currentPosition.lerp( targetPosition, callback.m_closestHitFraction ); + } + else + + // We dropped the full height + // + return targetPosition; +} + + + +void btKinematicCharacterController::setWalkDirection( const btVector3& walkDirection ) +{ + m_useWalkDirection = true; + m_walkDirection = walkDirection; +} + + +void btKinematicCharacterController::setVelocityForTimeInterval( const btVector3& velocity, btScalar timeInterval ) +{ + m_useWalkDirection = false; + m_walkDirection = velocity; + m_velocityTimeInterval = timeInterval; +} + + +void btKinematicCharacterController::reset() +{ +} + + +void btKinematicCharacterController::warp( const btVector3& origin ) +{ + btTransform transform; + transform.setIdentity(); + transform.setOrigin( -origin ); + + externalGhostObject->setWorldTransform( transform ); + internalGhostObject->setWorldTransform( transform ); +} + + +void btKinematicCharacterController::preStep( btCollisionWorld* collisionWorld ) +{ + BT_PROFILE( "preStep" ); + + for( int i = 0; i < 4 && recoverFromPenetration ( collisionWorld ); i++ ); +} + + +void btKinematicCharacterController::playerStep( btCollisionWorld* collisionWorld, btScalar dt ) +{ + BT_PROFILE( "playerStep" ); + + if( !m_useWalkDirection && m_velocityTimeInterval <= btScalar( 0.0 ) ) + return; + + bool wasOnGround = onGround(); + + // Handle the gravity + // + m_verticalVelocity -= m_gravity * dt; + + if( m_verticalVelocity > 0.0 && m_verticalVelocity > m_jumpSpeed ) + m_verticalVelocity = m_jumpSpeed; + + if( m_verticalVelocity < 0.0 && btFabs( m_verticalVelocity ) > btFabs( m_fallSpeed ) ) + m_verticalVelocity = -btFabs( m_fallSpeed ); + + m_verticalOffset = m_verticalVelocity * dt; + + // This forced stepping up can cause problems when the character + // walks (jump in fact...) under too low ceilings. + // + btVector3 currentPosition = externalGhostObject->getWorldTransform().getOrigin(); + btScalar currentStepOffset; + + currentPosition = stepUp( collisionWorld, currentPosition, currentStepOffset ); + + // Move in the air and slide against the walls ignoring the stair steps. + // + if( m_useWalkDirection ) + currentPosition = stepForwardAndStrafe( collisionWorld, currentPosition, m_walkDirection ); + + else + { + btScalar dtMoving = ( dt < m_velocityTimeInterval ) ? dt : m_velocityTimeInterval; + m_velocityTimeInterval -= dt; + + // How far will we move while we are moving ? + // + btVector3 moveDirection = m_walkDirection * dtMoving; + + currentPosition = stepForwardAndStrafe( collisionWorld, currentPosition, moveDirection ); + } + + // Finally find the ground. + // + currentStepOffset = addFallOffset( wasOnGround, currentStepOffset, dt ); + + currentPosition = stepDown( collisionWorld, currentPosition, currentStepOffset ); + + // Apply the new position to the collision objects. + // + btTransform tranform; + tranform = externalGhostObject->getWorldTransform(); + tranform.setOrigin( currentPosition ); + + externalGhostObject->setWorldTransform( tranform ); + internalGhostObject->setWorldTransform( tranform ); +} + + +void btKinematicCharacterController::setFallSpeed( btScalar fallSpeed ) +{ + m_fallSpeed = fallSpeed; +} + + +void btKinematicCharacterController::setJumpSpeed( btScalar jumpSpeed ) +{ + m_jumpSpeed = jumpSpeed; +} + + +void btKinematicCharacterController::setMaxJumpHeight( btScalar maxJumpHeight ) +{ + m_maxJumpHeight = maxJumpHeight; +} + + +bool btKinematicCharacterController::canJump() const +{ + return onGround(); +} + + +void btKinematicCharacterController::jump() +{ + if( !canJump() ) + return; + + m_verticalVelocity = m_jumpSpeed; + m_wasJumping = true; +} + + +void btKinematicCharacterController::setGravity( btScalar gravity ) +{ + m_gravity = gravity; +} + + +btScalar btKinematicCharacterController::getGravity() const +{ + return m_gravity; +} + + +void btKinematicCharacterController::setMaxSlope( btScalar slopeRadians ) +{ + m_maxSlopeRadians = slopeRadians; + m_maxSlopeCosine = btCos( slopeRadians ); +} + + +btScalar btKinematicCharacterController::getMaxSlope() const +{ + return m_maxSlopeRadians; +} + + +bool btKinematicCharacterController::onGround() const +{ + return btFabs( m_verticalVelocity ) < btScalar( SIMD_EPSILON ) && + btFabs( m_verticalOffset ) < btScalar( SIMD_EPSILON ); +} + + +btVector3* btKinematicCharacterController::getUpAxisDirections() +{ + static btVector3 sUpAxisDirection[] = + { + btVector3( btScalar( 0.0 ), btScalar( 0.0 ), btScalar( 0.0 ) ), + btVector3( btScalar( 0.0 ), btScalar( 1.0 ), btScalar( 0.0 ) ), + btVector3( btScalar( 0.0 ), btScalar( 0.0 ), btScalar( 1.0 ) ) + }; + + return sUpAxisDirection; +} + + +void btKinematicCharacterController::debugDraw( btIDebugDraw* debugDrawer ) +{ +} diff --git a/libs/openengine/bullet/btKinematicCharacterController.h b/libs/openengine/bullet/btKinematicCharacterController.h new file mode 100644 index 000000000..d24cd9722 --- /dev/null +++ b/libs/openengine/bullet/btKinematicCharacterController.h @@ -0,0 +1,168 @@ +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2008 Erwin Coumans http://bulletphysics.com + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef KINEMATIC_CHARACTER_CONTROLLER_H +#define KINEMATIC_CHARACTER_CONTROLLER_H + +#include "LinearMath/btVector3.h" +#include "LinearMath/btQuickprof.h" + +#include "BulletDynamics/Character/btCharacterControllerInterface.h" + +#include "BulletCollision/BroadphaseCollision/btCollisionAlgorithm.h" + + +class btCollisionShape; +class btRigidBody; +class btCollisionWorld; +class btCollisionDispatcher; +class btPairCachingGhostObject; + +///btKinematicCharacterController is an object that supports a sliding motion in a world. +///It uses a ghost object and convex sweep test to test for upcoming collisions. This is combined with discrete collision detection to recover from penetrations. +///Interaction between btKinematicCharacterController and dynamic rigid bodies needs to be explicity implemented by the user. +class btKinematicCharacterController : public btCharacterControllerInterface +{ +public: + enum UpAxis + { + X_AXIS = 0, + Y_AXIS = 1, + Z_AXIS = 2 + }; + +private: + btPairCachingGhostObject* externalGhostObject; // use this for querying collisions for sliding and move + btPairCachingGhostObject* internalGhostObject; // and this for recoreving from penetrations + + btScalar m_verticalVelocity; + btScalar m_verticalOffset; + btScalar m_fallSpeed; + btScalar m_jumpSpeed; + btScalar m_maxJumpHeight; + btScalar m_maxSlopeRadians; // Slope angle that is set (used for returning the exact value) + btScalar m_maxSlopeCosine; // Cosine equivalent of m_maxSlopeRadians (calculated once when set, for optimization) + btScalar m_gravity; + btScalar m_recoveringFactor; + + btScalar m_stepHeight; + + ///this is the desired walk direction, set by the user + btVector3 m_walkDirection; + + ///keep track of the contact manifolds + btManifoldArray m_manifoldArray; + + ///Gravity attributes + bool m_wasJumping; + + bool m_useGhostObjectSweepTest; + bool m_useWalkDirection; + btScalar m_velocityTimeInterval; + + UpAxis m_upAxis; + + static btVector3* getUpAxisDirections(); + + bool recoverFromPenetration ( btCollisionWorld* collisionWorld ); + + btVector3 stepUp( btCollisionWorld* collisionWorld, const btVector3& currentPosition, btScalar& currentStepOffset ); + btVector3 stepForwardAndStrafe( btCollisionWorld* collisionWorld, const btVector3& currentPosition, const btVector3& walkMove ); + btScalar addFallOffset( bool wasJumping, btScalar currentStepOffset, btScalar dt ); + btVector3 stepDown( btCollisionWorld* collisionWorld, const btVector3& currentPosition, btScalar currentStepOffset ); + +public: + /// externalGhostObject is used for querying the collisions for sliding along the wall, + /// and internalGhostObject is used for querying the collisions for recovering from large penetrations. + /// These parameters can point on the same object. + /// Using a smaller internalGhostObject can help for removing some flickering but create some + /// stopping artefacts when sliding along stairs or small walls. + /// Don't forget to scale gravity and fallSpeed if you scale the world. + btKinematicCharacterController( btPairCachingGhostObject* externalGhostObject, + btPairCachingGhostObject* internalGhostObject, + btScalar stepHeight, + btScalar constantScale = btScalar( 1.0 ), + btScalar gravity = btScalar( 9.8 ), + btScalar fallVelocity = btScalar( 55.0 ), + btScalar jumpVelocity = btScalar( 9.8 ), + btScalar recoveringFactor = btScalar( 0.2 ) ); + + ~btKinematicCharacterController (); + + void setVerticalVelocity(float z); + + ///btActionInterface interface + virtual void updateAction( btCollisionWorld* collisionWorld, btScalar deltaTime ) + { + preStep( collisionWorld ); + playerStep( collisionWorld, deltaTime ); + } + + ///btActionInterface interface + void debugDraw( btIDebugDraw* debugDrawer ); + + void setUpAxis( UpAxis axis ) + { + m_upAxis = axis; + } + + /// This should probably be called setPositionIncrementPerSimulatorStep. + /// This is neither a direction nor a velocity, but the amount to + /// increment the position each simulation iteration, regardless + /// of dt. + /// This call will reset any velocity set by setVelocityForTimeInterval(). + virtual void setWalkDirection(const btVector3& walkDirection); + + /// Caller provides a velocity with which the character should move for + /// the given time period. After the time period, velocity is reset + /// to zero. + /// This call will reset any walk direction set by setWalkDirection(). + /// Negative time intervals will result in no motion. + virtual void setVelocityForTimeInterval(const btVector3& velocity, + btScalar timeInterval); + + void reset(); + void warp( const btVector3& origin ); + + void preStep( btCollisionWorld* collisionWorld ); + void playerStep( btCollisionWorld* collisionWorld, btScalar dt ); + + void setFallSpeed( btScalar fallSpeed ); + void setJumpSpeed( btScalar jumpSpeed ); + void setMaxJumpHeight( btScalar maxJumpHeight ); + bool canJump() const; + + void jump(); + + void setGravity( btScalar gravity ); + btScalar getGravity() const; + + /// The max slope determines the maximum angle that the controller can walk up. + /// The slope angle is measured in radians. + void setMaxSlope( btScalar slopeRadians ); + btScalar getMaxSlope() const; + + void setUseGhostSweepTest( bool useGhostObjectSweepTest ) + { + m_useGhostObjectSweepTest = useGhostObjectSweepTest; + } + + bool onGround() const; + + //if set to false, there will be no collision. + bool mCollision; +}; + +#endif // KINEMATIC_CHARACTER_CONTROLLER_H diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp new file mode 100644 index 000000000..6d92d79b3 --- /dev/null +++ b/libs/openengine/bullet/physic.cpp @@ -0,0 +1,432 @@ +#include "physic.hpp" +#include +#include +#include +#include +//#include +#include "CMotionState.h" +#include "OgreRoot.h" +#include "btKinematicCharacterController.h" +#include "BtOgrePG.h" +#include "BtOgreGP.h" +#include "BtOgreExtras.h" + +#include + +#define BIT(x) (1<<(x)) + +namespace OEngine { +namespace Physic +{ + enum collisiontypes { + COL_NOTHING = 0, //setWorldTransform( transform ); + + btScalar externalCapsuleHeight = 120; + btScalar externalCapsuleWidth = 19; + + externalCollisionShape = new btCapsuleShapeZ( externalCapsuleWidth, externalCapsuleHeight ); + externalCollisionShape->setMargin( 0.1 ); + + externalGhostObject->setCollisionShape( externalCollisionShape ); + externalGhostObject->setCollisionFlags( btCollisionObject::CF_CHARACTER_OBJECT ); + + // Internal capsule + internalGhostObject = new PairCachingGhostObject(name); + internalGhostObject->setWorldTransform( transform ); + //internalGhostObject->getBroadphaseHandle()->s + btScalar internalCapsuleHeight = 110; + btScalar internalCapsuleWidth = 17; + + internalCollisionShape = new btCapsuleShapeZ( internalCapsuleWidth, internalCapsuleHeight ); + internalCollisionShape->setMargin( 0.1 ); + + internalGhostObject->setCollisionShape( internalCollisionShape ); + internalGhostObject->setCollisionFlags( btCollisionObject::CF_CHARACTER_OBJECT ); + + mCharacter = new btKinematicCharacterController( externalGhostObject,internalGhostObject,btScalar( 40 ),1,4,20,9.8,0.2 ); + mCharacter->setUpAxis(btKinematicCharacterController::Z_AXIS); + mCharacter->setUseGhostSweepTest(false); + + mCharacter->mCollision = false; + setGravity(0); + + mTranslation = btVector3(0,0,70); + } + + PhysicActor::~PhysicActor() + { + delete mCharacter; + delete internalGhostObject; + delete internalCollisionShape; + delete externalGhostObject; + delete externalCollisionShape; + } + + void PhysicActor::setGravity(float gravity) + { + mCharacter->setGravity(gravity); + //mCharacter-> + } + + void PhysicActor::enableCollisions(bool collision) + { + mCharacter->mCollision = collision; + } + + void PhysicActor::setVerticalVelocity(float z) + { + mCharacter->setVerticalVelocity(z); + } + + bool PhysicActor::getCollisionMode() + { + return mCharacter->mCollision; + } + + void PhysicActor::setWalkDirection(const btVector3& mvt) + { + mCharacter->setWalkDirection( mvt ); + } + + void PhysicActor::Rotate(const btQuaternion& quat) + { + externalGhostObject->getWorldTransform().setRotation( externalGhostObject->getWorldTransform().getRotation() * quat ); + internalGhostObject->getWorldTransform().setRotation( internalGhostObject->getWorldTransform().getRotation() * quat ); + } + + void PhysicActor::setRotation(const btQuaternion& quat) + { + externalGhostObject->getWorldTransform().setRotation( quat ); + internalGhostObject->getWorldTransform().setRotation( quat ); + } + + btVector3 PhysicActor::getPosition(void) + { + return internalGhostObject->getWorldTransform().getOrigin() -mTranslation; + } + + btQuaternion PhysicActor::getRotation(void) + { + return internalGhostObject->getWorldTransform().getRotation(); + } + + void PhysicActor::setPosition(const btVector3& pos) + { + internalGhostObject->getWorldTransform().setOrigin(pos+mTranslation); + externalGhostObject->getWorldTransform().setOrigin(pos+mTranslation); + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + + RigidBody::RigidBody(btRigidBody::btRigidBodyConstructionInfo& CI,std::string name) + :btRigidBody(CI),mName(name) + { + + }; + + + + /////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////// + + + + PhysicEngine::PhysicEngine(BulletShapeLoader* shapeLoader) + { + // Set up the collision configuration and dispatcher + collisionConfiguration = new btDefaultCollisionConfiguration(); + dispatcher = new btCollisionDispatcher(collisionConfiguration); + + // The actual physics solver + solver = new btSequentialImpulseConstraintSolver; + + //TODO: memory leak? + btOverlappingPairCache* pairCache = new btSortedOverlappingPairCache(); + //pairCache->setInternalGhostPairCallback( new btGhostPairCallback() ); + + broadphase = new btDbvtBroadphase(pairCache); + + // The world. + dynamicsWorld = new btDiscreteDynamicsWorld(dispatcher,broadphase,solver,collisionConfiguration); + dynamicsWorld->setGravity(btVector3(0,0,-10)); + + if(BulletShapeManager::getSingletonPtr() == NULL) + { + new BulletShapeManager(); + } + //TODO:singleton? + mShapeLoader = shapeLoader; + + isDebugCreated = false; + } + + void PhysicEngine::createDebugRendering() + { + if(!isDebugCreated) + { + Ogre::SceneManagerEnumerator::SceneManagerIterator iter = Ogre::Root::getSingleton().getSceneManagerIterator(); + iter.begin(); + Ogre::SceneManager* scn = iter.getNext(); + Ogre::SceneNode* node = scn->getRootSceneNode()->createChildSceneNode(); + node->pitch(Ogre::Degree(-90)); + mDebugDrawer = new BtOgre::DebugDrawer(node, dynamicsWorld); + dynamicsWorld->setDebugDrawer(mDebugDrawer); + isDebugCreated = true; + dynamicsWorld->debugDrawWorld(); + } + } + + void PhysicEngine::setDebugRenderingMode(int mode) + { + if(!isDebugCreated) + { + createDebugRendering(); + } + mDebugDrawer->setDebugMode(mode); + } + + PhysicEngine::~PhysicEngine() + { + delete dynamicsWorld; + delete solver; + delete collisionConfiguration; + delete dispatcher; + delete broadphase; + delete mShapeLoader; + } + + void PhysicEngine::addHeightField(float* heights, + int x, int y, float yoffset, + float triSize, float sqrtVerts) + { + const std::string name = "HeightField_" + + boost::lexical_cast(x) + "_" + + boost::lexical_cast(y); + + // find the minimum and maximum heights (needed for bullet) + float minh; + float maxh; + for (int i=0; imaxh) maxh = h; + if (hsetUseDiamondSubdivision(true); + + btVector3 scl(triSize, triSize, 1); + hfShape->setLocalScaling(scl); + + CMotionState* newMotionState = new CMotionState(this,name); + + btRigidBody::btRigidBodyConstructionInfo CI = btRigidBody::btRigidBodyConstructionInfo(0,newMotionState,hfShape); + RigidBody* body = new RigidBody(CI,name); + body->collide = true; + body->getWorldTransform().setOrigin(btVector3( (x+0.5)*triSize*(sqrtVerts-1), (y+0.5)*triSize*(sqrtVerts-1), (maxh+minh)/2.f)); + + addRigidBody(body); + } + + void PhysicEngine::removeHeightField(int x, int y) + { + const std::string name = "HeightField_" + + boost::lexical_cast(x) + "_" + + boost::lexical_cast(y); + + removeRigidBody(name); + deleteRigidBody(name); + } + + RigidBody* PhysicEngine::createRigidBody(std::string mesh,std::string name,float scale) + { + //get the shape from the .nif + mShapeLoader->load(mesh,"General"); + BulletShapeManager::getSingletonPtr()->load(mesh,"General"); + BulletShapePtr shape = BulletShapeManager::getSingleton().getByName(mesh,"General"); + shape->Shape->setLocalScaling(btVector3(scale,scale,scale)); + + //create the motionState + CMotionState* newMotionState = new CMotionState(this,name); + + //create the real body + btRigidBody::btRigidBodyConstructionInfo CI = btRigidBody::btRigidBodyConstructionInfo(0,newMotionState,shape->Shape); + RigidBody* body = new RigidBody(CI,name); + body->collide = shape->collide; + return body; + } + + void PhysicEngine::addRigidBody(RigidBody* body) + { + if(body->collide) + { + dynamicsWorld->addRigidBody(body,COL_WORLD,COL_WORLD|COL_ACTOR_INTERNAL|COL_ACTOR_EXTERNAL); + } + else + { + dynamicsWorld->addRigidBody(body,COL_WORLD,COL_NOTHING); + } + body->setActivationState(DISABLE_DEACTIVATION); + RigidBodyMap[body->mName] = body; + } + + void PhysicEngine::removeRigidBody(std::string name) + { + std::map::iterator it = RigidBodyMap.find(name); + if (it != RigidBodyMap.end() ) + { + RigidBody* body = it->second; + if(body != NULL) + { + // broadphase->getOverlappingPairCache()->removeOverlappingPairsContainingProxy(body->getBroadphaseProxy(),dispatcher); + /*std::map::iterator it2 = PhysicActorMap.begin(); + for(;it2!=PhysicActorMap.end();it++) + { + it2->second->internalGhostObject->getOverlappingPairCache()->removeOverlappingPairsContainingProxy(body->getBroadphaseProxy(),dispatcher); + it2->second->externalGhostObject->getOverlappingPairCache()->removeOverlappingPairsContainingProxy(body->getBroadphaseProxy(),dispatcher); + }*/ + dynamicsWorld->removeRigidBody(RigidBodyMap[name]); + } + } + } + + void PhysicEngine::deleteRigidBody(std::string name) + { + std::map::iterator it = RigidBodyMap.find(name); + if (it != RigidBodyMap.end() ) + { + RigidBody* body = it->second; + if(body != NULL) + { + delete body; + } + RigidBodyMap.erase(it); + } + } + + RigidBody* PhysicEngine::getRigidBody(std::string name) + { + RigidBody* body = RigidBodyMap[name]; + return body; + } + + void PhysicEngine::stepSimulation(double deltaT) + { + dynamicsWorld->stepSimulation(deltaT,1,1/50.); + if(isDebugCreated) + { + mDebugDrawer->step(); + } + } + + void PhysicEngine::addCharacter(std::string name) + { + PhysicActor* newActor = new PhysicActor(name); + dynamicsWorld->addCollisionObject( newActor->externalGhostObject, COL_ACTOR_EXTERNAL, COL_WORLD |COL_ACTOR_EXTERNAL ); + dynamicsWorld->addCollisionObject( newActor->internalGhostObject, COL_ACTOR_INTERNAL, COL_WORLD |COL_ACTOR_INTERNAL ); + dynamicsWorld->addAction( newActor->mCharacter ); + PhysicActorMap[name] = newActor; + } + + void PhysicEngine::removeCharacter(std::string name) + { + //std::cout << "remove"; + std::map::iterator it = PhysicActorMap.find(name); + if (it != PhysicActorMap.end() ) + { + PhysicActor* act = it->second; + if(act != NULL) + { + /*broadphase->getOverlappingPairCache()->removeOverlappingPairsContainingProxy(act->externalGhostObject->getBroadphaseHandle(),dispatcher); + broadphase->getOverlappingPairCache()->removeOverlappingPairsContainingProxy(act->internalGhostObject->getBroadphaseHandle(),dispatcher); + std::map::iterator it2 = PhysicActorMap.begin(); + for(;it2!=PhysicActorMap.end();it++) + { + it->second->internalGhostObject->getOverlappingPairCache()->removeOverlappingPairsContainingProxy(act->externalGhostObject->getBroadphaseHandle(),dispatcher); + it->second->externalGhostObject->getOverlappingPairCache()->removeOverlappingPairsContainingProxy(act->externalGhostObject->getBroadphaseHandle(),dispatcher); + it->second->internalGhostObject->getOverlappingPairCache()->removeOverlappingPairsContainingProxy(act->internalGhostObject->getBroadphaseHandle(),dispatcher); + it->second->externalGhostObject->getOverlappingPairCache()->removeOverlappingPairsContainingProxy(act->internalGhostObject->getBroadphaseHandle(),dispatcher); + }*/ + //act->externalGhostObject-> + dynamicsWorld->removeCollisionObject(act->externalGhostObject); + dynamicsWorld->removeCollisionObject(act->internalGhostObject); + dynamicsWorld->removeAction(act->mCharacter); + delete act; + } + PhysicActorMap.erase(it); + } + //std::cout << "ok"; + } + + PhysicActor* PhysicEngine::getCharacter(std::string name) + { + PhysicActor* act = PhysicActorMap[name]; + return act; + } + + void PhysicEngine::emptyEventLists(void) + { + } + + std::pair PhysicEngine::rayTest(btVector3& from,btVector3& to) + { + std::string name = ""; + float d = -1; + + float d1 = 10000.; + btCollisionWorld::ClosestRayResultCallback resultCallback1(from, to); + resultCallback1.m_collisionFilterMask = COL_WORLD; + dynamicsWorld->rayTest(from, to, resultCallback1); + if (resultCallback1.hasHit()) + { + name = static_cast(*resultCallback1.m_collisionObject).mName; + d1 = resultCallback1.m_closestHitFraction; + d = d1; + } + + btCollisionWorld::ClosestRayResultCallback resultCallback2(from, to); + resultCallback2.m_collisionFilterMask = COL_ACTOR_INTERNAL|COL_ACTOR_EXTERNAL; + dynamicsWorld->rayTest(from, to, resultCallback2); + float d2 = 10000.; + if (resultCallback2.hasHit()) + { + d2 = resultCallback1.m_closestHitFraction; + if(d2<=d1) + { + name = static_cast(*resultCallback2.m_collisionObject).mName; + d = d2; + } + } + + return std::pair(name,d); + } +}}; diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp new file mode 100644 index 000000000..0678d1ae7 --- /dev/null +++ b/libs/openengine/bullet/physic.hpp @@ -0,0 +1,242 @@ +#ifndef OENGINE_BULLET_PHYSIC_H +#define OENGINE_BULLET_PHYSIC_H + +#include +#include "BulletCollision/CollisionDispatch/btGhostObject.h" +#include +#include +#include +#include "BulletShapeLoader.h" + +class btRigidBody; +class btBroadphaseInterface; +class btDefaultCollisionConfiguration; +class btSequentialImpulseConstraintSolver; +class btCollisionDispatcher; +class btDiscreteDynamicsWorld; +class btKinematicCharacterController; + +namespace BtOgre +{ + class DebugDrawer; +} + +namespace MWWorld +{ + class World; +} + +namespace OEngine { +namespace Physic +{ + class CMotionState; + struct PhysicEvent; + + /** + *This is just used to be able to name objects. + */ + class PairCachingGhostObject : public btPairCachingGhostObject + { + public: + PairCachingGhostObject(std::string name) + :btPairCachingGhostObject(),mName(name) + { + } + std::string mName; + }; + + /** + * A physic Actor use a modifed KinematicCharacterController taken in the bullet forum. + */ + class PhysicActor + { + public: + PhysicActor(std::string name); + + ~PhysicActor(); + + /** + * This function set the walkDirection. This is not relative to the actor orientation. + * I think it's also needed to take time into account. A typical call should look like this: + * setWalkDirection( mvt * orientation * dt) + */ + void setWalkDirection(const btVector3& mvt); + + void Rotate(const btQuaternion& quat); + + void setRotation(const btQuaternion& quat); + + void setGravity(float gravity); + + void setVerticalVelocity(float z); + + void enableCollisions(bool collision); + + bool getCollisionMode(); + + btVector3 getPosition(void); + + btQuaternion getRotation(void); + + void setPosition(const btVector3& pos); + + btKinematicCharacterController* mCharacter; + + PairCachingGhostObject* internalGhostObject; + btCollisionShape* internalCollisionShape; + + PairCachingGhostObject* externalGhostObject; + btCollisionShape* externalCollisionShape; + + std::string mName; + + /** + *NPC scenenode is located on there feet, and you can't simply translate a btShape, so this vector is used + *each time get/setposition is called. + */ + btVector3 mTranslation; + }; + + /** + *This class is just an extension of normal btRigidBody in order to add extra info. + *When bullet give back a btRigidBody, you can just do a static_cast to RigidBody, + *so one never should use btRigidBody directly! + */ + class RigidBody: public btRigidBody + { + public: + RigidBody(btRigidBody::btRigidBodyConstructionInfo& CI,std::string name); + std::string mName; + + //is this body used for raycasting only? + bool collide; + }; + + /** + * The PhysicEngine class contain everything which is needed for Physic. + * It's needed that Ogre Resources are set up before the PhysicEngine is created. + * Note:deleting it WILL NOT delete the RigidBody! + * TODO:unload unused resources? + */ + class PhysicEngine + { + public: + /** + * Note that the shapeLoader IS destroyed by the phyic Engine!! + */ + PhysicEngine(BulletShapeLoader* shapeLoader); + + /** + * It DOES destroy the shape loader! + */ + ~PhysicEngine(); + + /** + * Create a RigidBody.It does not add it to the simulation, but it does add it to the rigidBody Map, + * so you can get it with the getRigidBody function. + */ + RigidBody* createRigidBody(std::string mesh,std::string name,float scale); + + /** + * Add a HeightField to the simulation + */ + void addHeightField(float* heights, + int x, int y, float yoffset, + float triSize, float sqrtVerts); + + /** + * Remove a HeightField from the simulation + */ + void removeHeightField(int x, int y); + + /** + * Add a RigidBody to the simulation + */ + void addRigidBody(RigidBody* body); + + /** + * Remove a RigidBody from the simulation. It does not delete it, and does not remove it from the RigidBodyMap. + */ + void removeRigidBody(std::string name); + + /** + * Delete a RigidBody, and remove it from RigidBodyMap. + */ + void deleteRigidBody(std::string name); + + /** + * Return a pointer to a given rigid body. + * TODO:check if exist + */ + RigidBody* getRigidBody(std::string name); + + /** + * Create and add a character to the scene, and add it to the ActorMap. + */ + void addCharacter(std::string name); + + /** + * Remove a character from the scene. TODO:delete it! for now, a small memory leak^^ done? + */ + void removeCharacter(std::string name); + + /** + * Return a pointer to a character + * TODO:check if the actor exist... + */ + PhysicActor* getCharacter(std::string name); + + /** + * This step the simulation of a given time. + */ + void stepSimulation(double deltaT); + + /** + * Empty events lists + */ + void emptyEventLists(void); + + /** + * Create a debug rendering. It is called by setDebgRenderingMode if it's not created yet. + * Important Note: this will crash if the Render is not yet initialise! + */ + void createDebugRendering(); + + /** + * Set the debug rendering mode. 0 to turn it off. + * Important Note: this will crash if the Render is not yet initialise! + */ + void setDebugRenderingMode(int mode); + + /** + * Return the closest object hit by a ray. If there are no objects, it will return ("",-1). + */ + std::pair rayTest(btVector3& from,btVector3& to); + + //event list of non player object + std::list NPEventList; + + //event list affecting the player + std::list PEventList; + + //Bullet Stuff + btBroadphaseInterface* broadphase; + btDefaultCollisionConfiguration* collisionConfiguration; + btSequentialImpulseConstraintSolver* solver; + btCollisionDispatcher* dispatcher; + btDiscreteDynamicsWorld* dynamicsWorld; + + //the NIF file loader. + BulletShapeLoader* mShapeLoader; + + std::map RigidBodyMap; + std::map PhysicActorMap; + + //debug rendering + BtOgre::DebugDrawer* mDebugDrawer; + bool isDebugCreated; + }; + +}} + +#endif diff --git a/libs/openengine/gui/events.cpp b/libs/openengine/gui/events.cpp new file mode 100644 index 000000000..713db8585 --- /dev/null +++ b/libs/openengine/gui/events.cpp @@ -0,0 +1,80 @@ +#include +#include +#include + +#include "events.hpp" + +using namespace OIS; +using namespace OEngine::GUI; + +EventInjector::EventInjector(MyGUI::Gui *g) + : gui(g), mouseX(0), mouseY(0), enabled(true) +{ + assert(gui); + maxX = gui->getViewSize().width; + maxY = gui->getViewSize().height; +} + +template +void setRange(X &x, X min, X max) +{ + if(x < min) x = min; + else if(x > max) x = max; +} + +void EventInjector::event(Type type, int index, const void *p) +{ + if(!enabled) return; + + if(type & EV_Keyboard) + { + KeyEvent *key = (KeyEvent*)p; + MyGUI::KeyCode code = MyGUI::KeyCode::Enum(key->key); + if(type == EV_KeyDown) + { + /* + This is just a first approximation. Apparently, OIS is + unable to provide reliable unicode characters on all + platforms. At least that's what I surmise from the amount + of workaround that the MyGUI folks have put in place for + this. See Common/Input/OIS/InputManager.cpp in the MyGUI + sources for details. + + If the work they have done there is indeed necessary (I + haven't tested that it is, although I have had dubious + experinces with OIS events in the past), then we should + probably adapt all that code here. Or even better, + directly into the OIS input manager in Mangle. + + Note that all this only affects the 'text' field, and + should thus only affect typed text in input boxes (which + is still pretty significant.) + */ + MyGUI::Char text = (MyGUI::Char)key->text; + gui->injectKeyPress(code,text); + } + else + { + gui->injectKeyRelease(code); + } + } + else if(type & EV_Mouse) + { + MouseEvent *mouse = (MouseEvent*)p; + MyGUI::MouseButton id = MyGUI::MouseButton::Enum(index); + + // Update mouse position + mouseX += mouse->state.X.rel; + mouseY += mouse->state.Y.rel; + + setRange(mouseX,0,maxX); + setRange(mouseY,0,maxY); + + if(type == EV_MouseDown) + gui->injectMousePress(mouseX, mouseY, id); + else if(type == EV_MouseUp) + gui->injectMouseRelease(mouseX, mouseY, id); + else + gui->injectMouseMove(mouseX, mouseY, mouse->state.Z.abs); + } +} diff --git a/libs/openengine/gui/events.hpp b/libs/openengine/gui/events.hpp new file mode 100644 index 000000000..6ca83cf75 --- /dev/null +++ b/libs/openengine/gui/events.hpp @@ -0,0 +1,31 @@ +#ifndef OENGINE_MYGUI_EVENTS_H +#define OENGINE_MYGUI_EVENTS_H + +#include + +namespace MyGUI +{ + class Gui; +} + +namespace OEngine { +namespace GUI +{ + /** Event handler that injects OIS events into MyGUI + */ + class EventInjector : public Mangle::Input::Event + { + MyGUI::Gui *gui; + int mouseX, mouseY; + int maxX, maxY; + + public: + bool enabled; + + EventInjector(MyGUI::Gui *g); + void event(Type type, int index, const void *p); + }; + + typedef boost::shared_ptr EventInjectorPtr; +}} +#endif diff --git a/libs/openengine/gui/layout.hpp b/libs/openengine/gui/layout.hpp new file mode 100644 index 000000000..f02ddbdff --- /dev/null +++ b/libs/openengine/gui/layout.hpp @@ -0,0 +1,119 @@ +#ifndef OENGINE_MYGUI_LAYOUT_H +#define OENGINE_MYGUI_LAYOUT_H + +#include +#include + +namespace OEngine { +namespace GUI +{ + /** The Layout class is an utility class used to load MyGUI layouts + from xml files, and to manipulate member widgets. + */ + class Layout + { + public: + Layout(const std::string & _layout, MyGUI::WidgetPtr _parent = nullptr) + : mMainWidget(nullptr) + { initialise(_layout, _parent); } + virtual ~Layout() { shutdown(); } + + template + void getWidget(T * & _widget, const std::string & _name, bool _throw = true) + { + _widget = nullptr; + for (MyGUI::VectorWidgetPtr::iterator iter=mListWindowRoot.begin(); + iter!=mListWindowRoot.end(); ++iter) + { + MyGUI::WidgetPtr find = (*iter)->findWidget(mPrefix + _name); + if (nullptr != find) + { + T * cast = find->castType(false); + if (nullptr != cast) + _widget = cast; + else if (_throw) + { + MYGUI_EXCEPT("Error cast : dest type = '" << T::getClassTypeName() + << "' source name = '" << find->getName() + << "' source type = '" << find->getTypeName() << "' in layout '" << mLayoutName << "'"); + } + return; + } + } + MYGUI_ASSERT( ! _throw, "widget name '" << _name << "' in layout '" << mLayoutName << "' not found."); + } + + void initialise(const std::string & _layout, + MyGUI::WidgetPtr _parent = nullptr) + { + const std::string MAIN_WINDOW = "_Main"; + mLayoutName = _layout; + + if (mLayoutName.empty()) + mMainWidget = _parent; + else + { + mPrefix = MyGUI::utility::toString(this, "_"); + mListWindowRoot = MyGUI::LayoutManager::getInstance().loadLayout(mLayoutName, mPrefix, _parent); + + const std::string main_name = mPrefix + MAIN_WINDOW; + for (MyGUI::VectorWidgetPtr::iterator iter=mListWindowRoot.begin(); iter!=mListWindowRoot.end(); ++iter) + { + if ((*iter)->getName() == main_name) + { + mMainWidget = (*iter); + break; + } + } + MYGUI_ASSERT(mMainWidget, "root widget name '" << MAIN_WINDOW << "' in layout '" << mLayoutName << "' not found."); + } + } + + void shutdown() + { + MyGUI::LayoutManager::getInstance().unloadLayout(mListWindowRoot); + mListWindowRoot.clear(); + } + + void setCoord(int x, int y, int w, int h) + { + mMainWidget->setCoord(x,y,w,h); + } + + void setVisible(bool b) + { + mMainWidget->setVisible(b); + } + + void setText(const std::string& name, const std::string& caption) + { + MyGUI::WidgetPtr pt; + getWidget(pt, name); + pt->setCaption(caption); + } + + void setTextColor(const std::string& name, float r, float g, float b) + { + MyGUI::WidgetPtr pt; + getWidget(pt, name); + MyGUI::StaticText *st = dynamic_cast(pt); + if(st != NULL) + st->setTextColour(MyGUI::Colour(b,g,r)); + } + + void setImage(const std::string& name, const std::string& imgName) + { + MyGUI::StaticImagePtr pt; + getWidget(pt, name); + pt->setImageTexture(imgName); + } + + protected: + + MyGUI::WidgetPtr mMainWidget; + std::string mPrefix; + std::string mLayoutName; + MyGUI::VectorWidgetPtr mListWindowRoot; + }; +}} +#endif diff --git a/libs/openengine/gui/manager.cpp b/libs/openengine/gui/manager.cpp new file mode 100644 index 000000000..b606d7d16 --- /dev/null +++ b/libs/openengine/gui/manager.cpp @@ -0,0 +1,47 @@ +#include +#include +#include + +#include "manager.hpp" + +using namespace OEngine::GUI; + +void MyGUIManager::setup(Ogre::RenderWindow *wnd, Ogre::SceneManager *mgr, bool logging, const std::string& logDir) +{ + assert(wnd); + assert(mgr); + + using namespace MyGUI; + + // Enable/disable MyGUI logging to stdout. (Logging to MyGUI.log is + // still enabled.) In order to do this we have to initialize the log + // manager before the main gui system itself, otherwise the main + // object will get the chance to spit out a few messages before we + // can able to disable it. + LogManager::initialise(); + LogManager::setSTDOutputEnabled(logging); + + std::string theLogFile = std::string(MYGUI_PLATFORM_LOG_FILENAME); + if(!logDir.empty()) + theLogFile.insert(0, logDir); + + // Set up OGRE platform. We might make this more generic later. + mPlatform = new OgrePlatform(); + mPlatform->initialise(wnd, mgr, "General", theLogFile); + + // Create GUI + mGui = new Gui(); + mGui->initialise("core.xml", theLogFile); +} + +void MyGUIManager::shutdown() +{ + if(mGui) delete mGui; + if(mPlatform) + { + mPlatform->shutdown(); + delete mPlatform; + } + mGui = NULL; + mPlatform = NULL; +} diff --git a/libs/openengine/gui/manager.hpp b/libs/openengine/gui/manager.hpp new file mode 100644 index 000000000..781685acf --- /dev/null +++ b/libs/openengine/gui/manager.hpp @@ -0,0 +1,36 @@ +#ifndef OENGINE_MYGUI_MANAGER_H +#define OENGINE_MYGUI_MANAGER_H + +namespace MyGUI +{ + class OgrePlatform; + class Gui; +} + +namespace Ogre +{ + class RenderWindow; + class SceneManager; +} + +namespace OEngine { +namespace GUI +{ + class MyGUIManager + { + MyGUI::OgrePlatform *mPlatform; + MyGUI::Gui *mGui; + + public: + MyGUIManager() : mPlatform(NULL), mGui(NULL) {} + MyGUIManager(Ogre::RenderWindow *wnd, Ogre::SceneManager *mgr, bool logging=false, const std::string& logDir = std::string("")) + { setup(wnd,mgr,logging, logDir); } + ~MyGUIManager() { shutdown(); } + + void setup(Ogre::RenderWindow *wnd, Ogre::SceneManager *mgr, bool logging=false, const std::string& logDir = std::string("")); + void shutdown(); + + MyGUI::Gui *getGui() { return mGui; } + }; +}} +#endif diff --git a/libs/openengine/input/dispatch_map.hpp b/libs/openengine/input/dispatch_map.hpp new file mode 100644 index 000000000..f0d4cabe9 --- /dev/null +++ b/libs/openengine/input/dispatch_map.hpp @@ -0,0 +1,75 @@ +#ifndef OENGINE_INPUT_DISPATCHMAP_H +#define OENGINE_INPUT_DISPATCHMAP_H + +#include +#include +#include + +namespace OEngine { +namespace Input { + +/** + DispatchMap is a simple connection system that connects incomming + signals with outgoing signals. + + The signals can be connected one-to-one, many-to-one, one-to-many + or many-to-many. + + The dispatch map is completely system agnostic. It is a pure data + structure and all signals are just integer indices. It does not + delegate any actions, but used together with Dispatcher it can be + used to build an event system. + */ +struct DispatchMap +{ + typedef std::set OutList; + typedef std::map InMap; + + typedef OutList::iterator Oit; + typedef InMap::iterator Iit; + + InMap map; + + void bind(int in, int out) + { + map[in].insert(out); + } + + void unbind(int in, int out) + { + Iit it = map.find(in); + if(it != map.end()) + { + it->second.erase(out); + + // If there are no more elements, then remove the entire list + if(it->second.empty()) + map.erase(it); + } + } + + /// Check if a given input is bound to anything + bool isBound(int in) const + { + return map.find(in) != map.end(); + } + + /** + Get the list of outputs bound to the given input. Only call this + on inputs that you know are bound to something. + + The returned set is only intended for immediate iteration. Do not + store references to it. + */ + const OutList &getList(int in) const + { + assert(isBound(in)); + InMap::const_iterator it = map.find(in); + assert(it != map.end()); + const OutList &out = it->second; + assert(!out.empty()); + return out; + } +}; +}} +#endif diff --git a/libs/openengine/input/dispatcher.hpp b/libs/openengine/input/dispatcher.hpp new file mode 100644 index 000000000..a8d480d4b --- /dev/null +++ b/libs/openengine/input/dispatcher.hpp @@ -0,0 +1,78 @@ +#ifndef OENGINE_INPUT_DISPATCHER_H +#define OENGINE_INPUT_DISPATCHER_H + +#include +#include + +#include + +#include "dispatch_map.hpp" +#include "func_binder.hpp" + +namespace OEngine { +namespace Input { + +struct Dispatcher : Mangle::Input::Event +{ + DispatchMap map; + FuncBinder funcs; + + /** + Constructor. Takes the number of actions and passes it to + FuncBinder. + */ + Dispatcher(int actions) : funcs(actions) {} + + void bind(unsigned int action, int key) + { + assert(action < funcs.getSize()); + map.bind(key, action); + } + void unbind(unsigned int action, int key) + { + assert(action < funcs.getSize()); + map.unbind(key, action); + } + bool isBound(int key) const { return map.isBound(key); } + + /** + Instigate an event. It is translated through the dispatch map and + sent to the function bindings. + */ + typedef DispatchMap::OutList _O; + void event(Type type, int index, const void* p) + { + // No bindings, nothing happens + if(!isBound(index)) + return; + + // Get the mapped actions and execute them + const _O &list = map.getList(index); + _O::const_iterator it; + for(it = list.begin(); it != list.end(); it++) + { + //catch exceptions thrown in the input handlers so that pressing a key + //doesn't cause OpenMw to crash + try + { + funcs.call(*it, p); + } + catch(const std::exception& e) + { + std::cerr << "Exception in input handler: " << e.what() << std::endl; + } + catch(...) + { + std::cerr << "Unknown exception in input handler" << std::endl; + } + + } + } +}; + +// This helps us play nice with Mangle's EventPtr, but it should +// really be defined for all the classes in OEngine. + typedef boost::shared_ptr DispatcherPtr; + +}} +#endif diff --git a/libs/openengine/input/func_binder.hpp b/libs/openengine/input/func_binder.hpp new file mode 100644 index 000000000..7aa733edf --- /dev/null +++ b/libs/openengine/input/func_binder.hpp @@ -0,0 +1,106 @@ +#ifndef OENGINE_INPUT_FUNCBINDER_H +#define OENGINE_INPUT_FUNCBINDER_H + +#include +#include +#include +#include + +namespace OEngine { +namespace Input { + +/** + An Action defines the user defined action corresponding to a + binding. + + The first parameter is the action index that invoked this call. You + can assign the same function to multiple actions, and this can help + you keep track of which action was invoked. + + The second parameter is an optional user-defined parameter, + represented by a void pointer. In many cases it is practical to + point this to temporaries (stack values), so make sure not to store + permanent references to it unless you've planning for this on the + calling side as well. + */ +typedef boost::function Action; + +/** + The FuncBinder is a simple struct that binds user-defined indices + to functions. It is useful for binding eg. keyboard events to + specific actions in your program, but can potentially have many + other uses as well. + */ +class FuncBinder +{ + struct FuncBinding + { + std::string name; + Action action; + }; + + std::vector bindings; + +public: + /** + Constructor. Initialize the struct by telling it how many action + indices you intend to bind. + + The indices you use should be 0 <= i < number. + */ + FuncBinder(int number) : bindings(number) {} + + unsigned int getSize() { return bindings.size(); } + + /** + Bind an action to an index. + */ + void bind(int index, Action action, const std::string &name="") + { + assert(index >= 0 && index < (int)bindings.size()); + + FuncBinding &fb = bindings[index]; + fb.action = action; + fb.name = name; + } + + /** + Unbind an index, reverting a previous bind(). + */ + void unbind(int index) + { + assert(index >= 0 && index < (int)bindings.size()); + + bindings[index] = FuncBinding(); + } + + /** + Call a specific action. Takes an optional parameter that is + passed to the action. + */ + void call(int index, const void *p=NULL) const + { + assert(index >= 0 && index < (int)bindings.size()); + + const FuncBinding &fb = bindings[index]; + if(fb.action) fb.action(index, p); + } + + /// Check if a given index is bound to anything + bool isBound(int index) const + { + assert(index >= 0 && index < (int)bindings.size()); + + return !bindings[index].action.empty(); + } + + /// Return the name associated with an action (empty if not bound) + const std::string &getName(int index) const + { + assert(index >= 0 && index < (int)bindings.size()); + + return bindings[index].name; + } +}; +}} +#endif diff --git a/libs/openengine/input/poller.hpp b/libs/openengine/input/poller.hpp new file mode 100644 index 000000000..c544aed52 --- /dev/null +++ b/libs/openengine/input/poller.hpp @@ -0,0 +1,46 @@ +#ifndef OENGINE_INPUT_POLLER_H +#define OENGINE_INPUT_POLLER_H + +#include "dispatch_map.hpp" +#include + +namespace OEngine { +namespace Input { + +/** The poller is used to check (poll) for keys rather than waiting + for events. */ +struct Poller +{ + DispatchMap map; + Mangle::Input::Driver &input; + + Poller(Mangle::Input::Driver &drv) + : input(drv) {} + + /** Bind or unbind a given action with a key. The action is the first + parameter, the key is the second. + */ + void bind(int in, int out) { map.bind(in, out); } + void unbind(int in, int out) { map.unbind(in, out); } + bool isBound(int in) const { return map.isBound(in); } + + /// Check whether a given action button is currently pressed. + typedef DispatchMap::OutList _O; + bool isDown(int index) const + { + // No bindings, no action + if(!isBound(index)) + return false; + + // Get all the keys bound to this action, and check them. + const _O &list = map.getList(index); + _O::const_iterator it; + for(it = list.begin(); it != list.end(); it++) + // If there's any match, we're good to go. + if(input.isDown(*it)) return true; + + return false; + } +}; +}} +#endif diff --git a/libs/openengine/input/tests/Makefile b/libs/openengine/input/tests/Makefile new file mode 100644 index 000000000..91a0b2663 --- /dev/null +++ b/libs/openengine/input/tests/Makefile @@ -0,0 +1,18 @@ +GCC=g++ + +all: funcbind_test dispatch_map_test sdl_driver_test sdl_binder_test + +funcbind_test: funcbind_test.cpp ../func_binder.hpp + $(GCC) $< -o $@ + +dispatch_map_test: dispatch_map_test.cpp ../dispatch_map.hpp + $(GCC) $< -o $@ + +sdl_driver_test: sdl_driver_test.cpp + $(GCC) $< ../../mangle/input/servers/sdl_driver.cpp -o $@ -I/usr/include/SDL/ -lSDL -I../../ + +sdl_binder_test: sdl_binder_test.cpp + $(GCC) $< ../../mangle/input/servers/sdl_driver.cpp -o $@ -I/usr/include/SDL/ -lSDL -I../../ + +clean: + rm *_test diff --git a/libs/openengine/input/tests/dispatch_map_test.cpp b/libs/openengine/input/tests/dispatch_map_test.cpp new file mode 100644 index 000000000..5f262494e --- /dev/null +++ b/libs/openengine/input/tests/dispatch_map_test.cpp @@ -0,0 +1,54 @@ +#include +using namespace std; + +#include "../dispatch_map.hpp" + +using namespace OEngine::Input; + +typedef DispatchMap::OutList OutList; +typedef OutList::const_iterator Cit; + +void showList(const DispatchMap::OutList &out) +{ + for(Cit it = out.begin(); + it != out.end(); it++) + { + cout << " " << *it << endl; + } +} + +void showAll(DispatchMap &map) +{ + cout << "\nPrinting everything:\n"; + for(DispatchMap::Iit it = map.map.begin(); + it != map.map.end(); it++) + { + cout << it->first << ":\n"; + showList(map.getList(it->first)); + } +} + +int main() +{ + cout << "Testing the dispatch map\n"; + + DispatchMap dsp; + + dsp.bind(1,9); + dsp.bind(2,-5); + dsp.bind(2,9); + dsp.bind(3,10); + dsp.bind(3,12); + dsp.bind(3,10); + + showAll(dsp); + + dsp.unbind(1,9); + dsp.unbind(5,8); + dsp.unbind(3,11); + dsp.unbind(3,12); + dsp.unbind(3,12); + + showAll(dsp); + return 0; +} diff --git a/libs/openengine/input/tests/funcbind_test.cpp b/libs/openengine/input/tests/funcbind_test.cpp new file mode 100644 index 000000000..bb4ad34e1 --- /dev/null +++ b/libs/openengine/input/tests/funcbind_test.cpp @@ -0,0 +1,47 @@ +#include +using namespace std; + +#include "../func_binder.hpp" + +void f1(int i, const void *p) +{ + cout << " F1 i=" << i << endl; + + if(p) + cout << " Got a nice gift: " + << *((const float*)p) << endl; +} + +void f2(int i, const void *p) +{ + cout << " F2 i=" << i << endl; +} + +using namespace OEngine::Input; + +int main() +{ + cout << "This will test the function binding system\n"; + + FuncBinder bnd(5); + + bnd.bind(0, &f1, "This is action 1"); + bnd.bind(1, &f2); + bnd.bind(2, &f1, "This is action 3"); + bnd.bind(3, &f2, "This is action 4"); + + bnd.unbind(2); + + for(int i=0; i<5; i++) + { + cout << "Calling " << i << ": '" << bnd.getName(i) << "'\n"; + bnd.call(i); + if(!bnd.isBound(i)) cout << " (not bound)\n"; + } + + cout << "\nCalling with parameter:\n"; + float f = 3.1415; + bnd.call(0, &f); + + return 0; +} diff --git a/libs/openengine/input/tests/output/dispatch_map_test.out b/libs/openengine/input/tests/output/dispatch_map_test.out new file mode 100644 index 000000000..01aa9d9d9 --- /dev/null +++ b/libs/openengine/input/tests/output/dispatch_map_test.out @@ -0,0 +1,18 @@ +Testing the dispatch map + +Printing everything: +1: + 9 +2: + -5 + 9 +3: + 10 + 12 + +Printing everything: +2: + -5 + 9 +3: + 10 diff --git a/libs/openengine/input/tests/output/funcbind_test.out b/libs/openengine/input/tests/output/funcbind_test.out new file mode 100644 index 000000000..862c5c972 --- /dev/null +++ b/libs/openengine/input/tests/output/funcbind_test.out @@ -0,0 +1,15 @@ +This will test the function binding system +Calling 0: 'This is action 1' + F1 i=0 +Calling 1: '' + F2 i=1 +Calling 2: '' + (not bound) +Calling 3: 'This is action 4' + F2 i=3 +Calling 4: '' + (not bound) + +Calling with parameter: + F1 i=0 + Got a nice gift: 3.1415 diff --git a/libs/openengine/input/tests/output/sdl_binder_test.out b/libs/openengine/input/tests/output/sdl_binder_test.out new file mode 100644 index 000000000..fd4eb90e3 --- /dev/null +++ b/libs/openengine/input/tests/output/sdl_binder_test.out @@ -0,0 +1,4 @@ +Hold the Q key to quit: +You are running in script mode, aborting. Run this test with a parameter (any at all) to test the input loop properly + +Bye bye! diff --git a/libs/openengine/input/tests/output/sdl_driver_test.out b/libs/openengine/input/tests/output/sdl_driver_test.out new file mode 100644 index 000000000..fd4eb90e3 --- /dev/null +++ b/libs/openengine/input/tests/output/sdl_driver_test.out @@ -0,0 +1,4 @@ +Hold the Q key to quit: +You are running in script mode, aborting. Run this test with a parameter (any at all) to test the input loop properly + +Bye bye! diff --git a/libs/openengine/input/tests/sdl_binder_test.cpp b/libs/openengine/input/tests/sdl_binder_test.cpp new file mode 100644 index 000000000..7de5f5d4f --- /dev/null +++ b/libs/openengine/input/tests/sdl_binder_test.cpp @@ -0,0 +1,71 @@ +#include +#include +#include +#include "../dispatcher.hpp" +#include "../poller.hpp" + +using namespace std; +using namespace Mangle::Input; +using namespace OEngine::Input; + +enum Actions + { + A_Quit, + A_Left, + A_Right, + + A_LAST + }; + +bool quit=false; + +void doExit(int,const void*) +{ + quit = true; +} + +void goLeft(int,const void*) +{ + cout << "Going left\n"; +} + +int main(int argc, char** argv) +{ + SDL_Init(SDL_INIT_VIDEO); + SDL_SetVideoMode(640, 480, 0, SDL_SWSURFACE); + SDLDriver input; + Dispatcher *disp = new Dispatcher(A_LAST); + Poller poll(input); + + input.setEvent(EventPtr(disp)); + + disp->funcs.bind(A_Quit, &doExit); + disp->funcs.bind(A_Left, &goLeft); + + disp->bind(A_Quit, SDLK_q); + disp->bind(A_Left, SDLK_a); + disp->bind(A_Left, SDLK_LEFT); + + poll.bind(A_Right, SDLK_d); + poll.bind(A_Right, SDLK_RIGHT); + + cout << "Hold the Q key to quit:\n"; + //input->setEvent(&mycb); + while(!quit) + { + input.capture(); + if(poll.isDown(A_Right)) + cout << "We're going right!\n"; + SDL_Delay(20); + + if(argc == 1) + { + cout << "You are running in script mode, aborting. Run this test with a parameter (any at all) to test the input loop properly\n"; + break; + } + } + cout << "\nBye bye!\n"; + + SDL_Quit(); + return 0; +} diff --git a/libs/openengine/input/tests/sdl_driver_test.cpp b/libs/openengine/input/tests/sdl_driver_test.cpp new file mode 100644 index 000000000..1771bcfe4 --- /dev/null +++ b/libs/openengine/input/tests/sdl_driver_test.cpp @@ -0,0 +1,31 @@ +#include +#include +#include + +using namespace std; +using namespace Mangle::Input; + +int main(int argc, char** argv) +{ + SDL_Init(SDL_INIT_VIDEO); + SDL_SetVideoMode(640, 480, 0, SDL_SWSURFACE); + SDLDriver input; + + cout << "Hold the Q key to quit:\n"; + //input->setEvent(&mycb); + while(!input.isDown(SDLK_q)) + { + input.capture(); + SDL_Delay(20); + + if(argc == 1) + { + cout << "You are running in script mode, aborting. Run this test with a parameter (any at all) to test the input loop properly\n"; + break; + } + } + cout << "\nBye bye!\n"; + + SDL_Quit(); + return 0; +} diff --git a/libs/openengine/input/tests/test.sh b/libs/openengine/input/tests/test.sh new file mode 100755 index 000000000..2d07708ad --- /dev/null +++ b/libs/openengine/input/tests/test.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +make || exit + +mkdir -p output + +PROGS=*_test + +for a in $PROGS; do + if [ -f "output/$a.out" ]; then + echo "Running $a:" + ./$a | diff output/$a.out - + else + echo "Creating $a.out" + ./$a > "output/$a.out" + git add "output/$a.out" + fi +done diff --git a/libs/openengine/misc/list.hpp b/libs/openengine/misc/list.hpp new file mode 100644 index 000000000..b08b57494 --- /dev/null +++ b/libs/openengine/misc/list.hpp @@ -0,0 +1,178 @@ +#ifndef MISC_LIST_H +#define MISC_LIST_H + +#include + +namespace Misc{ + +/* + A simple and completely allocation-less doubly linked list. The + class only manages pointers to and between elements. It leaving all + memory management to the user. +*/ +template +struct List +{ + List() : head(0), tail(0), totalNum(0) {} + + // Empty the list. + void reset() + { + head = 0; + tail = 0; + totalNum = 0; + } + + // Insert an element at the end of the list. The element cannot be + // part of any other list when this is called. + void insert(Elem *p) + { + if(tail) + { + // There are existing elements. Insert the node at the end of + // the list. + assert(head && totalNum > 0); + tail->next = p; + } + else + { + // This is the first element + assert(head == 0 && totalNum == 0); + head = p; + } + + // These have to be done in either case + p->prev = tail; + p->next = 0; + tail = p; + + totalNum++; + } + + // Remove element from the list. The element MUST be part of the + // list when this is called. + void remove(Elem *p) + { + assert(totalNum > 0); + + if(p->next) + { + // There's an element following us. Set it up correctly. + p->next->prev = p->prev; + assert(tail && tail != p); + } + else + { + // We're the tail + assert(tail == p); + tail = p->prev; + } + + // Now do exactly the same for the previous element + if(p->prev) + { + p->prev->next = p->next; + assert(head && head != p); + } + else + { + assert(head == p); + head = p->next; + } + + totalNum--; + } + + // Pop the first element off the list + Elem *pop() + { + Elem *res = getHead(); + if(res) remove(res); + return res; + } + + // Swap the contents of this list with another of the same type + void swap(List &other) + { + Elem *tmp; + + tmp = head; + head = other.head; + other.head = tmp; + + tmp = tail; + tail = other.tail; + other.tail = tmp; + + unsigned int tmp2 = totalNum; + totalNum = other.totalNum; + other.totalNum = tmp2; + } + + /* Absorb the contents of another list. All the elements from the + list are moved to the end of this list, and the other list is + cleared. + */ + void absorb(List &other) + { + assert(&other != this); + if(other.totalNum) + { + absorb(other.head, other.tail, other.totalNum); + other.reset(); + } + assert(other.totalNum == 0); + } + + /* Absorb a range of elements, endpoints included. The elements are + assumed NOT to belong to any list, but they ARE assumed to be + connected with a chain between them. + + The connection MUST run all the way from 'first' to 'last' + through the ->next pointers, and vice versa through ->prev + pointers. + + The parameter 'num' must give the exact number of elements in the + chain. + + Passing first == last, num == 1 is allowed and is equivalent to + calling insert(). + */ + void absorb(Elem* first, Elem *last, int num) + { + assert(first && last && num>=1); + if(tail) + { + // There are existing elements. Insert the first node at the + // end of the list. + assert(head && totalNum > 0); + tail->next = first; + } + else + { + // This is the first element + assert(head == 0 && totalNum == 0); + head = first; + } + + // These have to be done in either case + first->prev = tail; + last->next = 0; + tail = last; + + totalNum += num; + } + + Elem* getHead() const { return head; } + Elem* getTail() const { return tail; } + unsigned int getNum() const { return totalNum; } + +private: + + Elem *head; + Elem *tail; + unsigned int totalNum; +}; + +} +#endif diff --git a/libs/openengine/ogre/.gitignore b/libs/openengine/ogre/.gitignore new file mode 100644 index 000000000..3367afdbb --- /dev/null +++ b/libs/openengine/ogre/.gitignore @@ -0,0 +1 @@ +old diff --git a/libs/openengine/ogre/exitlistener.hpp b/libs/openengine/ogre/exitlistener.hpp new file mode 100644 index 000000000..5a9d1ff68 --- /dev/null +++ b/libs/openengine/ogre/exitlistener.hpp @@ -0,0 +1,37 @@ +#ifndef OENGINE_OGRE_EXITLISTEN_H +#define OENGINE_OGRE_EXITLISTEN_H + +/* + This FrameListener simply exits the rendering loop when the window + is closed. You can also tell it to exit manually by setting the exit + member to true; + */ + +#include +#include + +namespace OEngine { +namespace Render +{ + struct ExitListener : Ogre::FrameListener + { + Ogre::RenderWindow *window; + bool exit; + + ExitListener(Ogre::RenderWindow *wnd) + : window(wnd), exit(false) {} + + bool frameStarted(const Ogre::FrameEvent &evt) + { + if(window->isClosed()) + exit = true; + + return !exit; + } + + // Function equivalent of setting exit=true. Handy when you need a + // delegate to bind to an event. + void exitNow() { exit = true; } + }; +}} +#endif diff --git a/libs/openengine/ogre/fader.cpp b/libs/openengine/ogre/fader.cpp new file mode 100644 index 000000000..062559e00 --- /dev/null +++ b/libs/openengine/ogre/fader.cpp @@ -0,0 +1,136 @@ +#include "fader.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#define FADE_OVERLAY_NAME "FadeInOutOverlay" +#define FADE_OVERLAY_PANEL_NAME "FadeInOutOverlayPanel" +#define FADE_MATERIAL_NAME "FadeInOutMaterial" + +using namespace Ogre; +using namespace OEngine::Render; + +Fader::Fader() : + mMode(FadingMode_In), + mRemainingTime(0.f), + mTargetTime(0.f), + mTargetAlpha(0.f), + mCurrentAlpha(0.f), + mStartAlpha(0.f) +{ + + // Create the fading material + MaterialPtr material = MaterialManager::getSingleton().create( FADE_MATERIAL_NAME, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME ); + Pass* pass = material->getTechnique(0)->getPass(0); + pass->setSceneBlending(SBT_TRANSPARENT_ALPHA); + mFadeTextureUnit = pass->createTextureUnitState(); + mFadeTextureUnit->setColourOperationEx(LBX_SOURCE1, LBS_MANUAL, LBS_CURRENT, ColourValue(0.f, 0.f, 0.f)); // always black colour + + // Create the overlay + OverlayManager& ovm = OverlayManager::getSingleton(); + + mOverlay = ovm.create( FADE_OVERLAY_NAME ); + + OverlayContainer* overlay_panel; + overlay_panel = (OverlayContainer*)ovm.createOverlayElement("Panel", FADE_OVERLAY_PANEL_NAME); + + // position it over the whole screen + overlay_panel->_setPosition(0, 0); + overlay_panel->_setDimensions(1, 1); + + overlay_panel->setMaterialName( FADE_MATERIAL_NAME ); + overlay_panel->show(); + mOverlay->add2D(overlay_panel); + mOverlay->hide(); +} + +void Fader::update(float dt) +{ + if (mRemainingTime > 0) + { + if (mMode == FadingMode_In) + { + mCurrentAlpha -= dt/mTargetTime * (mStartAlpha-mTargetAlpha); + if (mCurrentAlpha < mTargetAlpha) mCurrentAlpha = mTargetAlpha; + } + else if (mMode == FadingMode_Out) + { + mCurrentAlpha += dt/mTargetTime * (mTargetAlpha-mStartAlpha); + if (mCurrentAlpha > mTargetAlpha) mCurrentAlpha = mTargetAlpha; + } + + applyAlpha(); + + mRemainingTime -= dt; + } + + if (mCurrentAlpha == 0.f) mOverlay->hide(); +} + +void Fader::applyAlpha() +{ + mOverlay->show(); + mFadeTextureUnit->setAlphaOperation(LBX_SOURCE1, LBS_MANUAL, LBS_CURRENT, mCurrentAlpha); +} + +void Fader::fadeIn(float time) +{ + if (time<0.f) return; + if (time==0.f) + { + mCurrentAlpha = 0.f; + applyAlpha(); + return; + } + + mStartAlpha = mCurrentAlpha; + mTargetAlpha = 0.f; + mMode = FadingMode_In; + mTargetTime = time; + mRemainingTime = time; +} + +void Fader::fadeOut(const float time) +{ + if (time<0.f) return; + if (time==0.f) + { + mCurrentAlpha = 1.f; + applyAlpha(); + return; + } + + mStartAlpha = mCurrentAlpha; + mTargetAlpha = 1.f; + mMode = FadingMode_Out; + mTargetTime = time; + mRemainingTime = time; +} + +void Fader::fadeTo(const int percent, const float time) +{ + if (time<0.f) return; + if (time==0.f) + { + mCurrentAlpha = percent/100.f; + applyAlpha(); + return; + } + + mStartAlpha = mCurrentAlpha; + mTargetAlpha = percent/100.f; + + if (mTargetAlpha == mStartAlpha) return; + else if (mTargetAlpha > mStartAlpha) mMode = FadingMode_Out; + else mMode = FadingMode_In; + + mTargetTime = time; + mRemainingTime = time; +} diff --git a/libs/openengine/ogre/fader.hpp b/libs/openengine/ogre/fader.hpp new file mode 100644 index 000000000..f76ac51ef --- /dev/null +++ b/libs/openengine/ogre/fader.hpp @@ -0,0 +1,57 @@ +#ifndef OENGINE_OGRE_FADE_H +#define OENGINE_OGRE_FADE_H + +/* + A class that handles fading in the screen from black or fading it out to black. + + To achieve this, it uses a full-screen Ogre::Overlay + + inspired by http://www.ogre3d.org/tikiwiki/FadeEffectOverlay (heavily adjusted) + */ + +#include + +namespace Ogre +{ + class TextureUnitState; + class Overlay; +} + +namespace OEngine { +namespace Render +{ + class Fader + { + public: + Fader(); + + void update(float dt); + + void fadeIn(const float time); + void fadeOut(const float time); + void fadeTo(const int percent, const float time); + + private: + enum FadingMode + { + FadingMode_In, + FadingMode_Out + }; + + void applyAlpha(); + + Ogre::TextureUnitState* mFadeTextureUnit; + Ogre::Overlay* mOverlay; + + FadingMode mMode; + + float mRemainingTime; + float mTargetTime; + float mTargetAlpha; + float mCurrentAlpha; + float mStartAlpha; + + protected: + }; +}} +#endif diff --git a/libs/openengine/ogre/mouselook.cpp b/libs/openengine/ogre/mouselook.cpp new file mode 100644 index 000000000..841bab603 --- /dev/null +++ b/libs/openengine/ogre/mouselook.cpp @@ -0,0 +1,57 @@ +#include "mouselook.hpp" + +#include +#include +#include + +using namespace OIS; +using namespace Ogre; +using namespace OEngine::Render; + +void MouseLookEvent::event(Type type, int index, const void *p) +{ + if(type != EV_MouseMove || camera == NULL) return; + + MouseEvent *arg = (MouseEvent*)(p); + + float x = arg->state.X.rel * sensX; + float y = arg->state.Y.rel * sensY; + + camera->getParentSceneNode()->getParentSceneNode()->yaw(Degree(-x)); + camera->getParentSceneNode()->pitch(Degree(-y)); + if(flipProt) + { + // The camera before pitching + /*Quaternion nopitch = camera->getParentSceneNode()->getOrientation(); + + camera->getParentSceneNode()->pitch(Degree(-y)); + + // Apply some failsafe measures against the camera flipping + // upside down. Is the camera close to pointing straight up or + // down? + if(Ogre::Vector3(camera->getParentSceneNode()->getOrientation()*Ogre::Vector3::UNIT_Y)[1] <= 0.1) + // If so, undo the last pitch + camera->getParentSceneNode()->setOrientation(nopitch);*/ + //camera->getU + + // Angle of rotation around the X-axis. + float pitchAngle = (2 * Ogre::Degree(Ogre::Math::ACos(camera->getParentSceneNode()->getOrientation().w)).valueDegrees()); + + // Just to determine the sign of the angle we pick up above, the + // value itself does not interest us. + float pitchAngleSign = camera->getParentSceneNode()->getOrientation().x; + + // Limit the pitch between -90 degress and +90 degrees, Quake3-style. + if (pitchAngle > 90.0f) + { + if (pitchAngleSign > 0) + // Set orientation to 90 degrees on X-axis. + camera->getParentSceneNode()->setOrientation(Ogre::Quaternion(Ogre::Math::Sqrt(0.5f), + Ogre::Math::Sqrt(0.5f), 0, 0)); + else if (pitchAngleSign < 0) + // Sets orientation to -90 degrees on X-axis. + camera->getParentSceneNode()->setOrientation(Ogre::Quaternion(Ogre::Math::Sqrt(0.5f), + -Ogre::Math::Sqrt(0.5f), 0, 0)); + } + } +} diff --git a/libs/openengine/ogre/mouselook.hpp b/libs/openengine/ogre/mouselook.hpp new file mode 100644 index 000000000..6e09ff4a1 --- /dev/null +++ b/libs/openengine/ogre/mouselook.hpp @@ -0,0 +1,56 @@ +#ifndef OENGINE_OGRE_MOUSELOOK_H +#define OENGINE_OGRE_MOUSELOOK_H + +/* + A mouse-look class for Ogre. Accepts input events from Mangle::Input + and translates them. + + You can adjust the mouse sensibility and switch to a different + camera. The mouselook class also has an optional wrap protection + that keeps the camera from flipping upside down. + + You can disable the mouse looker at any time by calling + setCamera(NULL), and reenable it by setting the camera back. + + NOTE: The current implementation will ONLY work for native OIS + events. + */ + +#include + +namespace Ogre +{ + class Camera; +} + +namespace OEngine { +namespace Render +{ + class MouseLookEvent : public Mangle::Input::Event + { + Ogre::Camera* camera; + float sensX, sensY; // Mouse sensibility + bool flipProt; // Flip protection + + public: + MouseLookEvent(Ogre::Camera *cam=NULL, + float sX=0.2, float sY=0.2, + bool prot=true) + : camera(cam) + , sensX(sX) + , sensY(sY) + , flipProt(prot) + {} + + void setCamera(Ogre::Camera *cam) + { camera = cam; } + void setSens(float sX, float sY) + { sensX = sX; sensY = sY; } + void setProt(bool p) { flipProt = p; } + + void event(Type type, int index, const void *p); + }; + + typedef boost::shared_ptr MouseLookEventPtr; +}} +#endif diff --git a/libs/openengine/ogre/renderer.cpp b/libs/openengine/ogre/renderer.cpp new file mode 100644 index 000000000..ecded08d8 --- /dev/null +++ b/libs/openengine/ogre/renderer.cpp @@ -0,0 +1,118 @@ +#include "renderer.hpp" +#include "fader.hpp" + +#include "OgreRoot.h" +#include "OgreRenderWindow.h" +#include "OgreLogManager.h" +#include "OgreLog.h" + +#include + +using namespace Ogre; +using namespace OEngine::Render; + +void OgreRenderer::cleanup() +{ + if (mFader) + delete mFader; + + if(mRoot) + delete mRoot; + mRoot = NULL; +} + +void OgreRenderer::start() +{ + mRoot->startRendering(); +} + +void OgreRenderer::update(float dt) +{ + mFader->update(dt); +} + +void OgreRenderer::screenshot(const std::string &file) +{ + mWindow->writeContentsToFile(file); +} + +float OgreRenderer::getFPS() +{ + return mWindow->getLastFPS(); +} + +bool OgreRenderer::configure(bool showConfig, + const std::string &cfgPath, + const std::string &logPath, + const std::string &pluginCfg, + bool _logging) +{ + // Set up logging first + new LogManager; + Log *log = LogManager::getSingleton().createLog(logPath + std::string("Ogre.log")); + logging = _logging; + + if(logging) + // Full log detail + log->setLogDetail(LL_BOREME); + else + // Disable logging + log->setDebugOutputEnabled(false); + + mRoot = new Root(pluginCfg, cfgPath, ""); + + // Show the configuration dialog and initialise the system, if the + // showConfig parameter is specified. The settings are stored in + // ogre.cfg. If showConfig is false, the settings are assumed to + // already exist in ogre.cfg. + int result; + if(showConfig) + result = mRoot->showConfigDialog(); + else + result = mRoot->restoreConfig(); + + return !result; +} + +bool OgreRenderer::configure(bool showConfig, + const std::string &cfgPath, + const std::string &pluginCfg, + bool _logging) +{ + return configure(showConfig, cfgPath, cfgPath, pluginCfg, _logging); +} + +bool OgreRenderer::configure(bool showConfig, + const std::string &pluginCfg, + bool _logging) +{ + return configure(showConfig, "", pluginCfg, _logging); +} + +void OgreRenderer::createWindow(const std::string &title) +{ + assert(mRoot); + // Initialize OGRE window + mWindow = mRoot->initialise(true, title, ""); +} + +void OgreRenderer::createScene(const std::string camName, float fov, float nearClip) +{ + assert(mRoot); + assert(mWindow); + // Get the SceneManager, in this case a generic one + mScene = mRoot->createSceneManager(ST_GENERIC); + + // Create the camera + mCamera = mScene->createCamera(camName); + mCamera->setNearClipDistance(nearClip); + mCamera->setFOVy(Degree(fov)); + + // Create one viewport, entire window + mView = mWindow->addViewport(mCamera); + + // Alter the camera aspect ratio to match the viewport + mCamera->setAspectRatio(Real(mView->getActualWidth()) / Real(mView->getActualHeight())); + + mFader = new Fader(); +} diff --git a/libs/openengine/ogre/renderer.hpp b/libs/openengine/ogre/renderer.hpp new file mode 100644 index 000000000..f48383cbc --- /dev/null +++ b/libs/openengine/ogre/renderer.hpp @@ -0,0 +1,98 @@ +#ifndef OENGINE_OGRE_RENDERER_H +#define OENGINE_OGRE_RENDERER_H + +/* + Ogre renderer class + */ + +#include + +namespace Ogre +{ + class Root; + class RenderWindow; + class SceneManager; + class Camera; + class Viewport; +} + +namespace OEngine { +namespace Render +{ + class Fader; + class OgreRenderer + { + Ogre::Root *mRoot; + Ogre::RenderWindow *mWindow; + Ogre::SceneManager *mScene; + Ogre::Camera *mCamera; + Ogre::Viewport *mView; + Fader* mFader; + bool logging; + + public: + OgreRenderer() + : mRoot(NULL), mWindow(NULL), mScene(NULL), mFader(NULL) {} + ~OgreRenderer() { cleanup(); } + + /** Configure the renderer. This will load configuration files and + set up the Root and logging classes. */ + bool configure(bool showConfig, // Show config dialog box? + const std::string &cfgPath, // Path to directory where to store config files + const std::string &logPath, // Path to directory where to store log files + const std::string &pluginCfg, // plugin.cfg file + bool _logging); // Enable or disable logging + + bool configure(bool showConfig, // Show config dialog box? + const std::string &cfgPath, // Path to directory where to store config files + const std::string &pluginCfg, // plugin.cfg file + bool _logging); // Enable or disable logging + + /** Configure the renderer. This will load configuration files and + set up the Root and logging classes. */ + bool configure(bool showConfig, // Show config dialog box? + const std::string &pluginCfg, // plugin.cfg file + bool _logging); // Enable or disable logging + + /// Create a window with the given title + void createWindow(const std::string &title); + + /// Set up the scene manager, camera and viewport + void createScene(const std::string camName="Camera",// Camera name + float fov=55, // Field of view angle + float nearClip=5 // Near clip distance + ); + + /// Kill the renderer. + void cleanup(); + + /// Start the main rendering loop + void start(); + + void update(float dt); + + /// Write a screenshot to file + void screenshot(const std::string &file); + + float getFPS(); + + /// Get the Root + Ogre::Root *getRoot() { return mRoot; } + + /// Get the rendering window + Ogre::RenderWindow *getWindow() { return mWindow; } + + /// Get the scene manager + Ogre::SceneManager *getScene() { return mScene; } + + /// Get the screen colour fader + Fader *getFader() { return mFader; } + + /// Camera + Ogre::Camera *getCamera() { return mCamera; } + + /// Viewport + Ogre::Viewport *getViewport() { return mView; } + }; +}} +#endif diff --git a/libs/openengine/sound/sndmanager.cpp b/libs/openengine/sound/sndmanager.cpp new file mode 100644 index 000000000..02c6ba1e7 --- /dev/null +++ b/libs/openengine/sound/sndmanager.cpp @@ -0,0 +1,219 @@ +#include "sndmanager.hpp" + +#include "../misc/list.hpp" +#include + +using namespace OEngine::Sound; +using namespace Mangle::Sound; + +/** This is our own internal implementation of the + Mangle::Sound::Sound interface. This class links a SoundPtr to + itself and prevents itself from being deleted as long as the sound + is playing. + */ +struct OEngine::Sound::ManagedSound : SoundFilter +{ +private: + /** Who's your daddy? This is set if and only if we are listed + internally in the given SoundManager. + + It may be NULL if the manager has been deleted but the user + keeps their own SoundPtrs to the object. + */ + SoundManager *mgr; + + /** Keep a weak pointer to ourselves, which we convert into a + 'strong' pointer when we are playing. When 'self' is pointing to + ourselves, the object will never be deleted. + + This is used to make sure the sound is not deleted while + playing, unless it is explicitly ordered to do so by the + manager. + + TODO: This kind of construct is useful. If we need it elsewhere + later, template it. It would be generally useful in any system + where we poll to check if a resource is still needed, but where + manual references are allowed. + */ + WSoundPtr weak; + SoundPtr self; + + // Keep this object from being deleted + void lock() + { + self = SoundPtr(weak); + } + + // Release the lock. This may or may not delete the object. Never do + // anything after calling unlock()! + void unlock() + { + self.reset(); + } + +public: + // Used for putting ourselves in linked lists + ManagedSound *next, *prev; + + /** Detach this sound from its manager. This means that the manager + will no longer know we exist. Typically only called when either + the sound or the manager is about to get deleted. + + Since this means update() will no longer be called, we also have + to unlock the sound manually since it will no longer be able to + do that itself. This means that the sound may be deleted, even + if it is still playing, when the manager is deleted. + + However, you are still allowed to keep and manage your own + SoundPtr references, but the lock/unlock system is disabled + after the manager is gone. + */ + void detach() + { + if(mgr) + { + mgr->detach(this); + mgr = NULL; + } + + // Unlock must be last command. Object may get deleted at this + // point. + unlock(); + } + + ManagedSound(SoundPtr snd, SoundManager *mg) + : SoundFilter(snd), mgr(mg) + {} + ~ManagedSound() { detach(); } + + // Needed to set up the weak pointer + void setup(SoundPtr self) + { + weak = WSoundPtr(self); + } + + // Override play() to mark the object as locked + void play() + { + SoundFilter::play(); + + // Lock the object so that it is not deleted while playing. Only + // do this if we have a manager, otherwise the object will never + // get unlocked. + if(mgr) lock(); + } + + // Called regularly by the manager + void update() + { + // If we're no longer playing, don't force object retention. + if(!isPlaying()) + unlock(); + + // unlock() may delete the object, so don't do anything below this + // point. + } + + SoundPtr clone() + { + // Cloning only works when we have a manager. + assert(mgr); + return mgr->wrap(client->clone()); + } +}; + +struct SoundManager::SoundManagerList +{ +private: + // A linked list of ManagedSound objects. + typedef Misc::List SoundList; + SoundList list; + +public: + // Add a new sound to the list + void addNew(ManagedSound* snd) + { + list.insert(snd); + } + + // Remove a sound from the list + void remove(ManagedSound *snd) + { + list.remove(snd); + } + + // Number of sounds in the list + int numSounds() { return list.getNum(); } + + // Update all sounds + void updateAll() + { + ManagedSound *s = list.getHead(); + while(s) + { + ManagedSound *cur = s; + // Propagate first, since update() may delete object + s = s->next; + cur->update(); + } + } + + // Detach and unlock all sounds + void detachAll() + { + ManagedSound *s = list.getHead(); + while(s) + { + ManagedSound *cur = s; + s = s->next; + cur->detach(); + } + } +}; + +SoundManager::SoundManager(SoundFactoryPtr fact) + : FactoryFilter(fact) +{ + needsUpdate = true; + list = new SoundManagerList; +} + +SoundManager::~SoundManager() +{ + // Detach all sounds + list->detachAll(); +} + +SoundPtr SoundManager::wrap(SoundPtr client) +{ + // Create and set up the sound wrapper + ManagedSound *snd = new ManagedSound(client,this); + SoundPtr ptr(snd); + snd->setup(ptr); + + // Add ourselves to the list of all sounds + list->addNew(snd); + + return ptr; +} + +// Remove the sound from this manager. +void SoundManager::detach(ManagedSound *sound) +{ + list->remove(sound); +} + +int SoundManager::numSounds() +{ + return list->numSounds(); +} + +void SoundManager::update() +{ + // Update all the sounds we own + list->updateAll(); + + // Update the source if it needs it + if(client->needsUpdate) + client->update(); +} diff --git a/libs/openengine/sound/sndmanager.hpp b/libs/openengine/sound/sndmanager.hpp new file mode 100644 index 000000000..5ea0c4fc3 --- /dev/null +++ b/libs/openengine/sound/sndmanager.hpp @@ -0,0 +1,93 @@ +#ifndef OENGINE_SOUND_MANAGER_H +#define OENGINE_SOUND_MANAGER_H + +#include + +namespace OEngine +{ + namespace Sound + { + using namespace Mangle::Sound; + + class ManagedSound; + + /** A manager of Mangle::Sounds. + + The sound manager is a wrapper around the more low-level + SoundFactory - although it is also itself an implementation of + SoundFactory. It will: + - keep a list of all created sounds + - let you iterate the list + - keep references to playing sounds so you don't have to + - auto-release references to sounds that are finished playing + (ie. deleting them if you're not referencing them) + */ + class SoundManager : public FactoryFilter + { + // Shove the implementation details into the cpp file. + struct SoundManagerList; + SoundManagerList *list; + + // Create a new sound wrapper based on the given source sound. + SoundPtr wrap(SoundPtr snd); + + /** Internal function. Will completely disconnect the given + sound from this manager. Called from ManagedSound. + */ + friend class ManagedSound; + void detach(ManagedSound *sound); + public: + SoundManager(SoundFactoryPtr fact); + ~SoundManager(); + void update(); + + /// Get number of sounds currently managed by this manager. + int numSounds(); + + SoundPtr loadRaw(SampleSourcePtr input) + { return wrap(client->loadRaw(input)); } + + SoundPtr load(Mangle::Stream::StreamPtr input) + { return wrap(client->load(input)); } + + SoundPtr load(const std::string &file) + { return wrap(client->load(file)); } + + // Play a sound immediately, and release when done unless you + // keep the returned SoundPtr. + SoundPtr play(Mangle::Stream::StreamPtr sound) + { + SoundPtr snd = load(sound); + snd->play(); + return snd; + } + + SoundPtr play(const std::string &sound) + { + SoundPtr snd = load(sound); + snd->play(); + return snd; + } + + // Ditto for 3D sounds + SoundPtr play3D(Mangle::Stream::StreamPtr sound, float x, float y, float z) + { + SoundPtr snd = load(sound); + snd->setPos(x,y,z); + snd->play(); + return snd; + } + + SoundPtr play3D(const std::string &sound, float x, float y, float z) + { + SoundPtr snd = load(sound); + snd->setPos(x,y,z); + snd->play(); + return snd; + } + }; + + typedef boost::shared_ptr SoundManagerPtr; + } +} +#endif diff --git a/libs/openengine/sound/tests/Makefile b/libs/openengine/sound/tests/Makefile new file mode 100644 index 000000000..04952167f --- /dev/null +++ b/libs/openengine/sound/tests/Makefile @@ -0,0 +1,16 @@ +GCC=g++ -I../ + +all: sound_manager_test sound_3d_test + +L_FFMPEG=$(shell pkg-config --libs libavcodec libavformat) +L_OPENAL=$(shell pkg-config --libs openal) +L_AUDIERE=-laudiere + +sound_manager_test: sound_manager_test.cpp ../../mangle/sound/sources/audiere_source.cpp ../../mangle/sound/outputs/openal_out.cpp ../../mangle/stream/clients/audiere_file.cpp ../sndmanager.cpp + $(GCC) $^ -o $@ $(L_AUDIERE) $(L_OPENAL) -I../.. + +sound_3d_test: sound_3d_test.cpp ../../mangle/sound/sources/audiere_source.cpp ../../mangle/sound/outputs/openal_out.cpp ../../mangle/stream/clients/audiere_file.cpp ../sndmanager.cpp + $(GCC) $^ -o $@ $(L_AUDIERE) $(L_OPENAL) -I../.. + +clean: + rm *_test diff --git a/libs/openengine/sound/tests/output/sound_3d_test.out b/libs/openengine/sound/tests/output/sound_3d_test.out new file mode 100644 index 000000000..a443c84f0 --- /dev/null +++ b/libs/openengine/sound/tests/output/sound_3d_test.out @@ -0,0 +1,3 @@ +Playing at 0,0,0 +Playing at 1,1,0 +Playing at -1,0,0 diff --git a/libs/openengine/sound/tests/output/sound_manager_test.out b/libs/openengine/sound/tests/output/sound_manager_test.out new file mode 100644 index 000000000..2b458493d --- /dev/null +++ b/libs/openengine/sound/tests/output/sound_manager_test.out @@ -0,0 +1,5 @@ +Playing ../../mangle/sound/tests/cow.wav +Replaying +pause +restart +Done playing. diff --git a/libs/openengine/sound/tests/sound_3d_test.cpp b/libs/openengine/sound/tests/sound_3d_test.cpp new file mode 100644 index 000000000..f5b197fd0 --- /dev/null +++ b/libs/openengine/sound/tests/sound_3d_test.cpp @@ -0,0 +1,46 @@ +#include +#include +#include + +#include +#include + +#include + +using namespace std; +using namespace Mangle::Stream; +using namespace Mangle::Sound; +using namespace OEngine::Sound; + +const std::string sound = "../../mangle/sound/tests/cow.wav"; + +SoundManagerPtr m; + +// Play and wait for finish +void play(float x, float y, float z) +{ + cout << "Playing at " << x << "," << y << "," << z << endl; + + SoundPtr snd = m->play3D(sound,x,y,z); + + while(snd->isPlaying()) + { + usleep(10000); + m->update(); + } +} + +int main() +{ + SoundFactoryPtr oaf(new OpenAL_Audiere_Factory); + SoundManagerPtr mg(new SoundManager(oaf)); + m = mg; + + mg->setListenerPos(0,0,0,0,1,0,0,0,1); + + play(0,0,0); + play(1,1,0); + play(-1,0,0); + + return 0; +} diff --git a/libs/openengine/sound/tests/sound_manager_test.cpp b/libs/openengine/sound/tests/sound_manager_test.cpp new file mode 100644 index 000000000..3794c4a3c --- /dev/null +++ b/libs/openengine/sound/tests/sound_manager_test.cpp @@ -0,0 +1,73 @@ +#include +#include +#include + +#include +#include + +#include + +using namespace std; +using namespace Mangle::Stream; +using namespace Mangle::Sound; +using namespace OEngine::Sound; + +const std::string sound = "../../mangle/sound/tests/cow.wav"; + +int main() +{ + SoundFactoryPtr oaf(new OpenAL_Audiere_Factory); + SoundManagerPtr mg(new SoundManager(oaf)); + + cout << "Playing " << sound << "\n"; + + assert(mg->numSounds() == 0); + + /** Start the sound playing, and then let the pointer go out of + scope. Lower-level players (like 'oaf' above) will immediately + delete the sound. SoundManager OTOH will keep it until it's + finished. + */ + mg->play(sound); + + assert(mg->numSounds() == 1); + + // Loop while there are still sounds to manage + while(mg->numSounds() != 0) + { + assert(mg->numSounds() == 1); + usleep(10000); + if(mg->needsUpdate) + mg->update(); + } + + SoundPtr snd = mg->play(sound); + cout << "Replaying\n"; + int i = 0; + while(mg->numSounds() != 0) + { + assert(mg->numSounds() == 1); + usleep(10000); + if(mg->needsUpdate) + mg->update(); + + if(i++ == 70) + { + cout << "pause\n"; + snd->pause(); + } + if(i == 130) + { + cout << "restart\n"; + snd->play(); + // Let the sound go out of scope + snd.reset(); + } + } + + cout << "Done playing.\n"; + + assert(mg->numSounds() == 0); + + return 0; +} diff --git a/libs/openengine/sound/tests/test.sh b/libs/openengine/sound/tests/test.sh new file mode 100755 index 000000000..2d07708ad --- /dev/null +++ b/libs/openengine/sound/tests/test.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +make || exit + +mkdir -p output + +PROGS=*_test + +for a in $PROGS; do + if [ -f "output/$a.out" ]; then + echo "Running $a:" + ./$a | diff output/$a.out - + else + echo "Creating $a.out" + ./$a > "output/$a.out" + git add "output/$a.out" + fi +done diff --git a/libs/openengine/testall.sh b/libs/openengine/testall.sh new file mode 100755 index 000000000..097fdabd5 --- /dev/null +++ b/libs/openengine/testall.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +function run() +{ + echo "TESTING $1" + cd "$1/tests/" + ./test.sh + cd ../../ +} + +run input diff --git a/libs/platform/string.h b/libs/platform/string.h index d4302ae78..5368d757c 100644 --- a/libs/platform/string.h +++ b/libs/platform/string.h @@ -2,13 +2,33 @@ #ifndef _STRING_WRAPPER_H #define _STRING_WRAPPER_H +#ifdef __APPLE__ +#include +#endif + #include -#if defined(__APPLE__) || defined(__MINGW32__) +#if (defined(__APPLE__) && __MAC_OS_X_VERSION_MIN_REQUIRED < 1070) || defined(__MINGW32__) // need our own implementation of strnlen +#ifdef __MINGW32__ static size_t strnlen(const char *s, size_t n) { - const char *p = (const char *)memchr(s, 0, n); - return(p ? p-s : n); + const char *p = (const char *)memchr(s, 0, n); + return(p ? p-s : n); +} +#elif (defined(__APPLE__) && __MAC_OS_X_VERSION_MIN_REQUIRED < 1070) +static size_t mw_strnlen(const char *s, size_t n) +{ + if (strnlen != NULL) { + return strnlen(s, n); + } + else { + const char *p = (const char *)memchr(s, 0, n); + return(p ? p-s : n); + } } +#define strnlen mw_strnlen #endif + +#endif + #endif diff --git a/readme.txt b/readme.txt index cfddd9a1f..d9e6cbfa4 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ OpenMW: A reimplementation of The Elder Scrolls III: Morrowind OpenMW is an attempt at recreating the engine for the popular role-playing game Morrowind by Bethesda Softworks. You need to own and install the original game for OpenMW to work. -Version: 0.11.1 +Version: 0.12.0 License: GPL (see GPL3.txt for more information) Website: http://www.openmw.org @@ -14,7 +14,7 @@ THIS IS A WORK IN PROGRESS INSTALLATION Windows: -Just unpack to a location of your choice. Currently there is no installer. +Run the installer. Linux: Ubuntu (and most others) @@ -35,25 +35,87 @@ TODO add description here THE DATA PATH -After the installation OpenMW needs to be told where to find the Morrowind data directory. Create a text file named openmw.cfg (location depends on platform) and enter the following line: +The data path tells OpenMW where to find your Morrowind files. From 0.12.0 on OpenMW should be able to +pick up the location of these files on its own, if both Morrowind and OpenMW are installed properly +(installing Morrowind under WINE is considered a proper install). -data=path to your data directory +If that does not work for you, please check if you have any leftover openmw.cfg files from versions earlier than 0.12.0. These can interfere with the configuration process, so try to remove then. -(where you replace "path to your data directory" with the actual location of your data directory) +If you are running OpenMW without installing it, you still need to manually adjust the data path. Create a text file named openmw.cfg in the location of the binary and enter the following line: -On Windows a suitable location for the cfg file is alongside the binary. Currently the binary release comes with such a file pre-generated, but you still need to adjust the data setting. +data=path to your data directory -On Linux and Mac the default location will be ~/.config/openmw/openmw.cfg. +(where you replace "path to your data directory" with the actual location of your data directory) COMMAND LINE OPTIONS -TODO add description of command line options + +Syntax: openmw +Allowed options: + --help print help message + --version print version information and quit + --data arg (=data) set data directories (later directories have + higher priority) + --data-local arg set local data directory (highest priority) + --resources arg (=resources) set resources directory + --start arg (=Beshara) set initial cell + --master arg master file(s) + --plugin arg plugin file(s) + --fps [=arg(=1)] (=0) fps counter detail (0 = off, 1 = fps counter + , 2 = full detail) + --anim-verbose [=arg(=1)] (=0) output animation indices files + --debug [=arg(=1)] (=0) debug mode + --nosound [=arg(=1)] (=0) disable all sounds + --script-verbose [=arg(=1)] (=0) verbose script output + --new-game [=arg(=1)] (=0) activate char gen/new game mechanics + --script-all [=arg(=1)] (=0) compile all scripts (excluding dialogue scri + pts) at startup + --fs-strict [=arg(=1)] (=0) strict file system handling (no case folding + ) + --encoding arg (=win1252) Character encoding used in OpenMW game messa + ges: + + win1250 - Central and Eastern European such + as Polish, Czech, Slovak, Hungarian, Slovene + , Bosnian, Croatian, Serbian (Latin script), + Romanian and Albanian languages + + win1251 - Cyrillic alphabet such as Russian, + Bulgarian, Serbian Cyrillic and other langua + ges + + win1252 - Western European (Latin) alphabet, + used by default + --report-focus [=arg(=1)] (=0) write name of focussed object to cout CREDITS -Developers: -TODO add list of developers +Current Developers: +Alexander “Ace” Olofsson +athile +Cris “Mirceam” Mihalache +gugus / gus +Jacob “Yacoby” Essex +Jason “jhooks” Hooks +Lukasz “lgro” Gromanowski +Marc “Zini” Zinnschlag +Nikolay “corristo” Kasyanov +Pieter “pvdk” van der Kloet +Sebastian “swick” Wick + +Retired Developers: +Ardekantur +Armin Preiml +Diggory Hardy +Jan Borsodi +Jan-Peter “peppe” Nilsson +Josua Grawitter +Karl-Felix “k1ll” Glatzer +Nicolay Korslund +sergoz +Star-Demon +Yuri Krupenin OpenMW: Thanks to DokterDume for kindly providing us with the Moon and Star logo used as the application icon and project logo. @@ -64,6 +126,31 @@ Thanks to Kevin Ryan for kindly providing us with the icon used for the Data Fil CHANGELOG +0.12.0 + +Bug #154: FPS Drop +Bug #169: Local scripts continue running if associated object is deleted +Bug #174: OpenMW fails to start if the config directory doesn't exist +Bug #187: Missing lighting +Bug #188: Lights without a mesh are not rendered +Bug #191: Taking screenshot causes crash when running installed +Feature #28: Sort out the cell load problem +Feature #31: Allow the player to move away from pre-defined cells +Feature #35: Use alternate storage location for modified object position +Feature #45: NPC animations +Feature #46: Creature Animation +Feature #89: Basic Journal Window +Feature #110: Automatically pick up the path of existing MW-installations +Feature #183: More FPS display settings +Task #19: Refactor engine class +Task #109/Feature #162: Automate Packaging +Task #112: Catch exceptions thrown in input handling functions +Task #128/#168: Cleanup Configuration File Handling +Task #131: NPC Activation doesn't work properly +Task #144: MWRender cleanup +Task #155: cmake cleanup + + 0.11.1 Bug #2: Resources loading doesn't work outside of bsa files @@ -90,4 +177,4 @@ Task #14: Replace tabs with 4 spaces Task #18: Move components from global namespace into their own namespace Task #123: refactor header files in components/esm -TODO add old changelog (take pre 0.11.0 changelog from wiki) +TODO add old changelog (take pre 0.11.1 changelog from wiki)